import path from "path"; import express from "express"; import { SMTPServer } from "smtp-server"; import mailparser from "mailparser"; import escapeStringRegexp from "escape-string-regexp"; import fs from "fs-extra"; import cryptoRandomString from "crypto-random-string"; const VERSION = require("../package.json").version; export default function killTheNewsletter( rootDirectory: string ): { webApplication: express.Express; emailApplication: SMTPServer } { const webApplication = express(); webApplication.set("url", "http://localhost:4000"); webApplication.set("email port", 2525); webApplication.set("email host", "localhost"); webApplication.set("administrator", "kill-the-newsletter@leafac.com"); const emailApplication = new SMTPServer(); return { webApplication, emailApplication }; } if (require.main === module) { console.log(`Kill the Newsletter!/${VERSION}`); const configurationFile = path.resolve( process.argv[2] ?? path.join(process.cwd(), "configuration.js") ); require(configurationFile)(require); console.log(`Configuration file loaded from ‘${configurationFile}’.`); } /* export const webServer = express() .use(["/feeds", "/alternate"], (req, res, next) => { res.header("X-Robots-Tag", "noindex"); next(); }) .use(express.static("static")) .use(express.urlencoded({ extended: true })) .get("/", (req, res) => res.send(layout(newInbox()))) .post("/", async (req, res, next) => { try { const { name } = req.body; const identifier = createIdentifier(); const renderedCreated = created(identifier); await writeFileAtomic( feedFilePath(identifier), feed( identifier, X(name), entry( identifier, createIdentifier(), `“${X(name)}” Inbox Created`, "Kill the Newsletter!", X(renderedCreated) ) ) ); res.send( layout(html`
“${H(name)}” Inbox Created
${renderedCreated} `) ); } catch (error) { console.error(error); next(error); } }) .get( alternatePath(":feedIdentifier", ":entryIdentifier"), async (req, res, next) => { try { const { feedIdentifier, entryIdentifier } = req.params; const path = feedFilePath(feedIdentifier); let text; try { text = await fs.readFile(path, "utf8"); } catch { return res.sendStatus(404); } const feed = new JSDOM(text, { contentType: "text/xml" }); const document = feed.window.document; const link = document.querySelector( `link[href="${alternateURL(feedIdentifier, entryIdentifier)}"]` ); if (link === null) return res.sendStatus(404); res.send( entities.decodeXML( link.parentElement!.querySelector("content")!.textContent! ) ); } catch (error) { console.error(error); next(error); } } ) .listen(WEB_PORT, () => console.log(`Server started: ${BASE_URL}`)); export const emailServer = new SMTPServer({ disabledCommands: ["AUTH", "STARTTLS"], async onData(stream, session, callback) { try { const email = await mailparser.simpleParser(stream); const content = typeof email.html === "string" ? email.html : email.textAsHtml ?? ""; for (const address of new Set( session.envelope.rcptTo.map(({ address }) => address) )) { const match = address.match( new RegExp( `^(?Convert email newsletters into Atom feeds
Sign up for the newsletter with${feedEmail(identifier)}
Subscribe to the Atom feed at${feedURL(identifier)}
Don’t share these addresses.
They contain an identifier that other
people could use
to send you spam and to control your newsletter
subscriptions.
Enjoy your readings!
`.trim(); } function feed(identifier: string, name: string, initialEntry: string): string { return html`