const Imap = require('imap'); const nodemailer = require('nodemailer'); const { simpleParser } = require('mailparser'); const { getDb } = require('./db'); const util = require('util'); function getAccount(accountId) { const db = getDb(); return db.prepare('SELECT * FROM email_accounts WHERE id = ?').get(accountId); } function getUserAccounts(userId) { const db = getDb(); return db.prepare('SELECT id, email, name, imap_host, smtp_host, active FROM email_accounts WHERE user_id = ?').all(userId); } function createImapConnection(account) { return new Promise((resolve, reject) => { const imap = new Imap({ user: account.imap_user, password: account.imap_pass, host: account.imap_host, port: account.imap_port || 993, tls: account.imap_secure !== 0, tlsOptions: { rejectUnauthorized: false }, connTimeout: 10000, authTimeout: 5000 }); imap.once('ready', () => resolve(imap)); imap.once('error', (err) => reject(err)); imap.connect(); }); } function openMailbox(imap, folder) { return new Promise((resolve, reject) => { imap.openBox(folder || 'INBOX', true, (err, box) => { if (err) return reject(err); resolve(box); }); }); } function searchMessages(imap, criteria) { return new Promise((resolve, reject) => { imap.search(criteria, (err, uids) => { if (err) return reject(err); resolve(uids || []); }); }); } function fetchMessages(imap, uids) { return new Promise((resolve, reject) => { if (!uids || uids.length === 0) return resolve([]); const f = imap.fetch(uids, { bodies: '', struct: true }); const messages = []; f.on('message', (msg, seqno) => { const message = { uid: null, seqno, body: '' }; msg.on('body', (stream, info) => { let buffer = ''; stream.on('data', chunk => buffer += chunk.toString('utf8')); stream.on('end', () => { message.body = buffer; }); }); msg.on('attributes', attrs => { message.uid = attrs.uid; message.flags = attrs.flags || []; message.date = attrs.date; message.size = attrs.size; }); msg.on('end', () => { if (message.uid) messages.push(message); }); }); f.once('error', reject); f.once('end', () => resolve(messages)); }); } async function fetchAndStoreMessages(accountId, folder, limit) { const account = getAccount(accountId); if (!account) throw new Error('Account not found'); const db = getDb(); const imap = await createImapConnection(account); const box = await openMailbox(imap, folder || 'INBOX'); const total = Math.min(box.messages.total, limit || 50); const start = Math.max(1, box.messages.total - total + 1); const uids = []; for (let i = start; i <= box.messages.total; i++) uids.push(i); if (uids.length === 0) { imap.end(); return []; } const messages = await fetchMessages(imap, uids); imap.end(); const results = []; for (const msg of messages) { try { const parsed = await simpleParser(msg.body); const seen = msg.flags.includes('\\Seen'); const flagged = msg.flags.includes('\\Flagged'); const existing = db.prepare('SELECT id FROM email_messages WHERE account_id = ? AND uid = ?').get(accountId, msg.uid); if (!existing) { db.prepare(`INSERT INTO email_messages (account_id, uid, folder, subject, from_name, from_addr, to_addr, date, body_text, body_html, attachments, seen, flagged, size) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run( accountId, msg.uid, folder || 'INBOX', (parsed.subject || '').substring(0, 500), (parsed.from && parsed.from.text || '').substring(0, 200), (parsed.from && parsed.from.value && parsed.from.value[0] && parsed.from.value[0].address || ''), (parsed.to && parsed.to.text || '').substring(0, 500), (parsed.date && parsed.date.toISOString() || msg.date || ''), (parsed.text || '').substring(0, 100000), (parsed.html || '').substring(0, 100000), (parsed.attachments && parsed.attachments.length) || 0, seen ? 1 : 0, flagged ? 1 : 0, msg.size || 0 ); } else { db.prepare('UPDATE email_messages SET seen=?, flagged=? WHERE id=?').run(seen ? 1 : 0, flagged ? 1 : 0, existing.id); } results.push({ uid: msg.uid, subject: parsed.subject || '', from: parsed.from ? parsed.from.text : '', date: parsed.date || msg.date, seen, flagged, size: msg.size }); } catch (e) { console.error('Error parsing message:', e.message); } } return results; } async function sendEmail(accountId, to, subject, text, html, cc, bcc) { const account = getAccount(accountId); if (!account) throw new Error('Account not found'); const transporter = nodemailer.createTransport({ host: account.smtp_host, port: account.smtp_port || 587, secure: account.smtp_secure === 1, auth: { user: account.smtp_user || account.imap_user, pass: account.smtp_pass || account.imap_pass } }); const info = await transporter.sendMail({ from: `"${account.name || account.email}" <${account.email}>`, to, subject, text, html, cc, bcc }); return info; } async function getMailboxes(accountId) { const account = getAccount(accountId); if (!account) return []; const imap = await createImapConnection(account); const list = await new Promise((resolve, reject) => { imap.getBoxes((err, boxes) => { if (err) return reject(err); resolve(boxes); }); }); imap.end(); function flatten(boxes, path) { const result = []; for (const [name, box] of Object.entries(boxes)) { const fullPath = path ? `${path}${box.delimiter}${name}` : name; result.push({ name: fullPath, delimiter: box.delimiter }); if (box.children) result.push(...flatten(box.children, fullPath)); } return result; } return flatten(list, ''); } async function getMessageBody(accountId, uid) { const account = getAccount(accountId); if (!account) return null; const imap = await createImapConnection(account); await openMailbox(imap, 'INBOX'); const messages = await fetchMessages(imap, [uid]); imap.end(); if (messages.length === 0) return null; const parsed = await simpleParser(messages[0].body); return parsed; } module.exports = { getAccount, getUserAccounts, createImapConnection, openMailbox, searchMessages, fetchMessages, fetchAndStoreMessages, sendEmail, getMailboxes, getMessageBody };