Project Files
imap.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.listFolders = listFolders;
exports.searchEmails = searchEmails;
exports.moveToTrash = moveToTrash;
exports.readEmail = readEmail;
const imapflow_1 = require("imapflow");
const mailparser_1 = require("mailparser");
function makeClient(cfg) {
return new imapflow_1.ImapFlow({
host: cfg.host,
port: cfg.port,
secure: cfg.port === 993,
auth: { user: cfg.user, pass: cfg.password },
logger: false,
});
}
async function listFolders(cfg) {
const client = makeClient(cfg);
await client.connect();
try {
const list = await client.list();
return list.map(f => f.path).sort();
}
finally {
await client.logout();
}
}
async function searchEmails(cfg, folder, options) {
const client = makeClient(cfg);
await client.connect();
try {
await client.mailboxOpen(folder);
const criteria = [];
if (options.query)
criteria.push(["TEXT", options.query]);
if (options.from)
criteria.push(["FROM", options.from]);
if (options.to)
criteria.push(["TO", options.to]);
if (options.subject)
criteria.push(["SUBJECT", options.subject]);
if (options.since)
criteria.push(["SINCE", options.since]);
if (options.before)
criteria.push(["BEFORE", options.before]);
if (options.unseen)
criteria.push("UNSEEN");
const searchCriteria = criteria.length > 0
? (criteria.length === 1 ? criteria[0] : ["AND", ...criteria])
: "ALL";
const uids = await client.search(searchCriteria, { uid: true });
// Newest first, then slice the requested page
const allNewestFirst = [...uids].reverse();
const total = allNewestFirst.length;
const offset = options.offset ?? 0;
const limited = allNewestFirst.slice(offset, offset + options.limit);
const summaries = [];
for await (const msg of client.fetch(limited, {
uid: true,
envelope: true,
bodyStructure: true,
bodyParts: ["1"],
}, { uid: true })) {
const env = msg.envelope;
if (!env)
continue;
const fromAddr = env.from?.[0];
const toAddr = env.to?.[0];
const hasAtt = !!(msg.bodyStructure && JSON.stringify(msg.bodyStructure).includes("attachment"));
let snippet = "";
try {
const partRaw = msg.bodyParts?.get("1");
if (partRaw) {
const chunks = [];
for await (const chunk of partRaw)
chunks.push(chunk);
snippet = Buffer.concat(chunks).toString("utf8").replace(/\s+/g, " ").slice(0, 200);
}
}
catch { /* snippet is optional */ }
summaries.push({
uid: msg.uid,
messageId: env.messageId ?? "",
subject: env.subject ?? "(no subject)",
from: fromAddr ? `${fromAddr.name ?? ""} <${fromAddr.address ?? ""}>`.trim() : "",
to: toAddr ? `${toAddr.name ?? ""} <${toAddr.address ?? ""}>`.trim() : "",
date: env.date?.toISOString() ?? "",
snippet,
hasAttachments: hasAtt,
folder,
});
}
return {
emails: summaries,
total,
offset: options.offset ?? 0,
hasMore: (options.offset ?? 0) + options.limit < total,
};
}
finally {
await client.logout();
}
}
// Common Trash folder names across providers
const TRASH_CANDIDATES = ["Trash", "[Gmail]/Trash", "Deleted Items", "Deleted Messages", "INBOX.Trash"];
async function moveToTrash(cfg, folder, uids, trashFolder) {
const client = makeClient(cfg);
await client.connect();
try {
// Discover trash folder if not specified
let trash = trashFolder?.trim() || "";
if (!trash) {
const allFolders = await client.list();
const paths = allFolders.map(f => f.path);
// Prefer folder with \Trash special-use attribute
const special = allFolders.find(f => f.specialUse === "\\Trash");
if (special) {
trash = special.path;
}
else {
trash = TRASH_CANDIDATES.find(c => paths.includes(c)) ?? "Trash";
}
}
await client.mailboxOpen(folder);
await client.messageMove(uids, trash, { uid: true });
return { moved: uids.length, trashFolder: trash };
}
finally {
await client.logout();
}
}
async function readEmail(cfg, folder, uid) {
const client = makeClient(cfg);
await client.connect();
try {
await client.mailboxOpen(folder);
let rawBuffer = Buffer.alloc(0);
for await (const msg of client.fetch([uid], { source: true }, { uid: true })) {
const chunks = [];
for await (const chunk of msg.source)
chunks.push(chunk);
rawBuffer = Buffer.concat(chunks);
}
const parsed = await (0, mailparser_1.simpleParser)(rawBuffer);
const from = parsed.from?.text ?? "";
const to = Array.isArray(parsed.to) ? parsed.to.map((a) => a.text).join(", ") : (parsed.to?.text ?? "");
const cc = Array.isArray(parsed.cc) ? parsed.cc.map((a) => a.text).join(", ") : (parsed.cc?.text ?? "");
return {
uid,
messageId: parsed.messageId ?? "",
subject: parsed.subject ?? "(no subject)",
from,
to,
cc,
replyTo: parsed.replyTo?.text ?? "",
date: parsed.date?.toISOString() ?? "",
body: parsed.text ?? "",
html: parsed.html || "",
snippet: (parsed.text ?? "").replace(/\s+/g, " ").slice(0, 200),
hasAttachments: (parsed.attachments?.length ?? 0) > 0,
attachments: (parsed.attachments ?? []).map((a) => `${a.filename ?? "unnamed"} (${a.contentType}, ${Math.round(a.size / 1024)}KB)`),
folder,
references: (Array.isArray(parsed.references) ? parsed.references.join(" ") : parsed.references) ?? "",
inReplyTo: parsed.inReplyTo ?? "",
};
}
finally {
await client.logout();
}
}