178 lines
6.4 KiB
JavaScript
178 lines
6.4 KiB
JavaScript
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 };
|