Files

115 lines
3.5 KiB
JavaScript

const { SMTPServer } = require("smtp-server");
const { simpleParser } = require("mailparser");
const express = require("express");
const SMTP_PORT = parseInt(process.env.SMTP_PORT || "25", 10);
const API_PORT = parseInt(process.env.API_PORT || "3025", 10);
const API_KEY = process.env.API_KEY || "";
const MAX_AGE_MS = 3600_000; // 1 saat
const store = new Map();
function extractCode(text) {
if (!text) return null;
const m = text.match(/\b(\d{4,8})\b/);
return m ? m[1] : null;
}
function cleanup() {
const now = Date.now();
for (const [addr, mails] of store) {
const fresh = mails.filter((m) => now - m.ts < MAX_AGE_MS);
if (fresh.length) store.set(addr, fresh);
else store.delete(addr);
}
}
setInterval(cleanup, 60_000);
const smtp = new SMTPServer({
secure: false,
authOptional: true,
disabledCommands: ["STARTTLS"],
onData(stream, session, cb) {
const chunks = [];
stream.on("data", (c) => chunks.push(c));
stream.on("end", async () => {
try {
const raw = Buffer.concat(chunks);
const parsed = await simpleParser(raw);
const recipients = session.envelope.rcptTo.map((r) =>
r.address.toLowerCase()
);
const entry = {
ts: Date.now(),
from: parsed.from?.text || "",
subject: parsed.subject || "",
text: parsed.text || "",
html: parsed.html || "",
code: extractCode(parsed.text) || extractCode(parsed.subject),
};
for (const addr of recipients) {
if (!store.has(addr)) store.set(addr, []);
store.get(addr).push(entry);
if (store.get(addr).length > 50) store.get(addr).shift();
}
console.log(
`[MAIL] ${entry.from} -> ${recipients.join(",")} subj="${entry.subject}" code=${entry.code || "-"}`
);
} catch (e) {
console.error("[MAIL] parse error:", e.message);
}
cb();
});
},
});
const app = express();
app.get("/health", (_req, res) => {
res.json({ status: "ok", stored: store.size });
});
app.use((req, res, next) => {
if (API_KEY && req.headers["x-api-key"] !== API_KEY) {
return res.status(401).json({ error: "unauthorized" });
}
next();
});
app.get("/mail/:address", (req, res) => {
const addr = req.params.address.toLowerCase();
const mails = store.get(addr) || [];
const limit = parseInt(req.query.limit || "10", 10);
res.json({ address: addr, count: mails.length, mails: mails.slice(-limit) });
});
app.get("/code/:address", (req, res) => {
const addr = req.params.address.toLowerCase();
const mails = store.get(addr) || [];
const last = [...mails].reverse().find((m) => m.code);
if (last) {
res.json({ address: addr, code: last.code, subject: last.subject, age_s: Math.round((Date.now() - last.ts) / 1000) });
} else {
res.json({ address: addr, code: null, message: "no code found" });
}
});
app.get("/codes", (_req, res) => {
const result = {};
for (const [addr, mails] of store) {
const last = [...mails].reverse().find((m) => m.code);
if (last) result[addr] = { code: last.code, age_s: Math.round((Date.now() - last.ts) / 1000) };
}
res.json(result);
});
app.delete("/mail/:address", (req, res) => {
store.delete(req.params.address.toLowerCase());
res.json({ ok: true });
});
smtp.listen(SMTP_PORT, () => console.log(`[SMTP] listening on :${SMTP_PORT}`));
app.listen(API_PORT, () => console.log(`[API] listening on :${API_PORT}`));
smtp.on("error", (e) => console.error("[SMTP] error:", e.message));