From 4428f35dd47d03cdea43605d92dc02af19a66b90 Mon Sep 17 00:00:00 2001 From: Julien Herr Date: Fri, 22 May 2026 18:41:21 +0200 Subject: [PATCH] fix(feed): add Atom link in emails page, fix HTML stripping, use request URL for self-link - Add Atom Feed URL to the Feed Details card in the emails page - Fix extractBodyContent to handle emails without a closing tag (regex now falls back to capturing everything after the opening ) - Use the actual request URL origin for atom:link rel="self" in RSS/Atom feeds, guaranteeing it always matches the document location regardless of how DOMAIN is configured Co-Authored-By: Claude Sonnet 4.6 --- src/routes/admin/emails.tsx | 3 ++- src/routes/atom.ts | 2 ++ src/routes/rss.ts | 2 ++ src/utils/feed-generator.ts | 31 +++++++++++++++++++++++++------ 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/routes/admin/emails.tsx b/src/routes/admin/emails.tsx index b1fbb94..f182d74 100644 --- a/src/routes/admin/emails.tsx +++ b/src/routes/admin/emails.tsx @@ -93,7 +93,7 @@ emailsRouter.get("/feeds/:feedId/emails", async (c) => { const emailAddress = `${feedId}@${env.DOMAIN}`; const rssUrl = `https://${env.DOMAIN}/rss/${feedId}`; - + const atomUrl = `https://${env.DOMAIN}/atom/${feedId}`; return c.html( @@ -114,6 +114,7 @@ emailsRouter.get("/feeds/:feedId/emails", async (c) => {
+
diff --git a/src/routes/atom.ts b/src/routes/atom.ts index f0ae451..dcde4be 100644 --- a/src/routes/atom.ts +++ b/src/routes/atom.ts @@ -16,11 +16,13 @@ export async function handle(c: Context<{ Bindings: Env }>): Promise { } const baseUrl = `https://${c.env.DOMAIN}`; + const selfUrl = new URL(c.req.url).origin + `/atom/${feedId}`; const atomXml = generateAtomFeed( feedData.feedConfig, feedData.emails, baseUrl, feedId, + selfUrl, ); const linkHeader = [ `<${baseUrl}/hub>; rel="hub"`, diff --git a/src/routes/rss.ts b/src/routes/rss.ts index 12ed6ee..87a0acf 100644 --- a/src/routes/rss.ts +++ b/src/routes/rss.ts @@ -16,11 +16,13 @@ export async function handle(c: Context<{ Bindings: Env }>): Promise { } const baseUrl = `https://${c.env.DOMAIN}`; + const selfUrl = new URL(c.req.url).origin + `/rss/${feedId}`; const rssXml = generateRssFeed( feedData.feedConfig, feedData.emails, baseUrl, feedId, + selfUrl, ); const linkHeader = [ `<${baseUrl}/hub>; rel="hub"`, diff --git a/src/utils/feed-generator.ts b/src/utils/feed-generator.ts index a4d466e..e6c8ef9 100644 --- a/src/utils/feed-generator.ts +++ b/src/utils/feed-generator.ts @@ -16,8 +16,12 @@ function parseFromAddress(from: string): { name: string; email?: string } { // Email content is stored as a full HTML document. Feed readers expect only // the body fragment in /, not a full document. export function extractBodyContent(html: string): string { - const match = html.match(/]*>([\s\S]*?)<\/body>/i); - return match ? match[1] : html; + const withClose = html.match(/]*>([\s\S]*?)<\/body>/i); + if (withClose) return withClose[1]; + // Some HTML emails omit ; capture everything after the opening tag + const withoutClose = html.match(/]*>([\s\S]*)/i); + if (withoutClose) return withoutClose[1].replace(/<\/html>\s*$/i, ""); + return html; } function buildFeed( @@ -25,6 +29,7 @@ function buildFeed( emails: EmailData[], baseUrl: string, feedId: string, + selfUrl?: { rss?: string; atom?: string }, ): Feed { const feed = new Feed({ title: feedConfig.title, @@ -39,8 +44,8 @@ function buildFeed( generator: "kill-the-news", copyright: `Copyright © ${new Date().getFullYear()} ${feedConfig.title}`, feedLinks: { - rss: `${baseUrl}/rss/${feedId}`, - atom: `${baseUrl}/atom/${feedId}`, + rss: selfUrl?.rss ?? `${baseUrl}/rss/${feedId}`, + atom: selfUrl?.atom ?? `${baseUrl}/atom/${feedId}`, }, author: feedConfig.author ? { @@ -80,8 +85,15 @@ export function generateRssFeed( emails: EmailData[], baseUrl: string, feedId: string, + selfUrl?: string, ): string { - return buildFeed(feedConfig, emails, baseUrl, feedId).rss2(); + return buildFeed( + feedConfig, + emails, + baseUrl, + feedId, + selfUrl ? { rss: selfUrl } : undefined, + ).rss2(); } export function generateAtomFeed( @@ -89,6 +101,13 @@ export function generateAtomFeed( emails: EmailData[], baseUrl: string, feedId: string, + selfUrl?: string, ): string { - return buildFeed(feedConfig, emails, baseUrl, feedId).atom1(); + return buildFeed( + feedConfig, + emails, + baseUrl, + feedId, + selfUrl ? { atom: selfUrl } : undefined, + ).atom1(); }