Mail: unified inbox, snooze, templates, and saving attachments to Files

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-27 10:25:48 -04:00
1 parent 54d18594a3
commit 808274b4fe
7 files changed
+101 -14

No files matched your search

+5 -1
View File
@@ -6,7 +6,7 @@ const settings = require('./settings'), users = require('./users'), books = requ
const SEAFILE = () => (process.env.SEAFILE_URL || 'https://seafile.necloud.us').replace(/\/$/, '');
const MAX_FILE = 500 * 1024 * 1024;
const TABS = { 'vbv-notes': 'podcast', 'vbv-lesson': 'podcast', 'vbv-study': 'podcast', book: 'read', 'librivox-book': 'audio', 'librivox-chapter': 'audio' };
const TABS = { 'vbv-notes': 'podcast', 'vbv-lesson': 'podcast', 'vbv-study': 'podcast', book: 'read', 'mail-attachment': 'mail', 'librivox-book': 'audio', 'librivox-chapter': 'audio' };
const json = (res, code, o) => { res.writeHead(code, { 'content-type': 'application/json', 'cache-control': 'no-store' }); res.end(JSON.stringify(o)); };
const fail = (code, message) => Object.assign(new Error(message), { code });
const safe = (s, max = 100) => String(s || '').replace(/[\u0000-\u001f\\/:*?"<>|]+/g, ' ').replace(/\s+/g, ' ').replace(/^[. ]+|[. ]+$/g, '').slice(0, max).trim() || 'Untitled';
@@ -44,6 +44,10 @@ async function plan(me, b) {
out.push({ folder: ['Books'], name: safe(f.author ? `${f.author} - ${f.title}` : f.title, 120) + '.' + f.ext, load: async () => fs.promises.readFile(f.path) });
return { label: f.title, out };
}
if (kind === 'mail-attachment') {
const f = await require('./mail').attachmentOf(String(b.id || ''), String(b.aid || '')); if (f.buffer.length > MAX_FILE) throw fail(413, 'That attachment is too large to save');
out.push({ folder: ['Mail attachments'], name: safe(f.name, 120), load: async () => f.buffer }); return { label: f.name, out };
}
if (kind === 'librivox-book' || kind === 'librivox-chapter') {
const bk = await librivox.book(String(b.id || '')), only = kind === 'librivox-chapter' ? +b.n : -1;
bk.chapters.forEach(c => { if (only >= 0 && c.n !== only) return; out.push({ folder: ['Audiobooks', safe(bk.title)], name: `${num(c.n, bk.chapters.length)} - ${safe(c.title, 80)}.mp3`, load: () => download(c.url, librivox.allowed, 'NE-Hub/1.0') }); });