chore: fixed rss for /now a06d18ce
Steve · 2026-01-11 11:58 1 file(s) · +42 −76
packages/client/src/pages/now/rss.xml.ts +42 −76
1 -
import type { APIRoute } from "astro";
1 +
import rss from "@astrojs/rss";
2 +
import sanitizeHtml from "sanitize-html";
3 +
import MarkdownIt from "markdown-it";
2 4
3 5
export const prerender = false;
4 6
5 7
const DID = "did:plc:ia2zdnhjaokf5lazhxrmj6eu";
6 8
const PDS_URL = "https://polybius.social";
7 9
10 +
const md = new MarkdownIt({
11 +
	html: true,
12 +
	linkify: true,
13 +
	typographer: true,
14 +
});
15 +
8 16
interface DocumentRecord {
9 17
	uri: string;
10 18
	cid: string;
27 35
	cursor?: string;
28 36
}
29 37
30 -
export const GET: APIRoute = async () => {
38 +
export async function GET() {
31 39
	try {
32 40
		const response = await fetch(
33 41
			`${PDS_URL}/xrpc/com.atproto.repo.listRecords?` +
52 60
			return dateB.getTime() - dateA.getTime();
53 61
		});
54 62
55 -
		// Build RSS XML manually to avoid dependencies
56 -
		const items = documents
57 -
			.map((record) => {
58 -
				const doc = record.value;
59 -
				const rkey = record.uri.split("/").pop();
63 +
		const items = documents.map((record) => {
64 +
			const doc = record.value;
65 +
			const rkey = record.uri.split("/").pop();
60 66
61 -
				// Use custom path if available, otherwise use rkey
62 -
				const urlPath = doc.path || `/${rkey}`;
63 -
				const fullUrl = `https://stevedylan.dev/now${urlPath}`;
67 +
			// Use custom path if available, otherwise use rkey
68 +
			const urlPath = doc.path || `/${rkey}`;
64 69
65 -
				let content = doc.title;
66 -
				let description = doc.title;
67 -
68 -
				if (doc.content && doc.content.markdown) {
69 -
					content = doc.content.markdown;
70 -
					description = doc.textContent || doc.title;
71 -
				} else if (doc.textContent) {
72 -
					content = doc.textContent;
73 -
					description = doc.textContent;
74 -
				}
75 -
76 -
				// Escape XML entities
77 -
				const escapeXml = (str: string) =>
78 -
					str
79 -
						.replace(/&/g, "&")
80 -
						.replace(/</g, "&lt;")
81 -
						.replace(/>/g, "&gt;")
82 -
						.replace(/"/g, "&quot;")
83 -
						.replace(/'/g, "&apos;");
84 -
85 -
				const pubDate = new Date(doc.publishedAt).toUTCString();
86 -
87 -
				return `    <item>
88 -
      <title>${escapeXml(doc.title)}</title>
89 -
      <link>${fullUrl}</link>
90 -
      <guid>${fullUrl}</guid>
91 -
      <description>${escapeXml(description)}</description>
92 -
      <content:encoded><![CDATA[${content}]]></content:encoded>
93 -
      <pubDate>${pubDate}</pubDate>
94 -
    </item>`;
95 -
			})
96 -
			.join("\n");
70 +
			// Always treat content as markdown and render to HTML
71 +
			const markdownContent = doc.content?.markdown || doc.title;
72 +
			const htmlContent = md.render(markdownContent);
73 +
			const description = doc.textContent || doc.title;
97 74
98 -
		const rssXml = `<?xml version="1.0" encoding="UTF-8"?>
99 -
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
100 -
  <channel>
101 -
    <title>Steve Dylan - Updates</title>
102 -
    <description>Small updates from my life that don't quite fit into a blog</description>
103 -
    <link>https://stevedylan.dev/now</link>
104 -
    <atom:link href="https://stevedylan.dev/now/rss.xml" rel="self" type="application/rss+xml"/>
105 -
    <language>en</language>
106 -
    <lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
107 -
${items}
108 -
  </channel>
109 -
</rss>`;
75 +
			return {
76 +
				title: doc.title,
77 +
				description: description,
78 +
				pubDate: new Date(doc.publishedAt),
79 +
				link: `/now${urlPath}`,
80 +
				content: sanitizeHtml(htmlContent, {
81 +
					allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"]),
82 +
				}),
83 +
			};
84 +
		});
110 85
111 -
		return new Response(rssXml, {
112 -
			status: 200,
113 -
			headers: {
114 -
				"Content-Type": "application/xml",
115 -
				"Cache-Control": "public, max-age=3600",
116 -
			},
86 +
		return rss({
87 +
			title: "Steve Dylan - Updates",
88 +
			description:
89 +
				"Small updates from my life that don't quite fit into a blog",
90 +
			site: process.env.SITE_URL || "https://stevedylan.dev",
91 +
			items: items,
117 92
		});
118 93
	} catch (error) {
119 94
		console.error("Error generating RSS feed:", error);
120 95
121 96
		// Return an empty feed on error
122 -
		const errorRss = `<?xml version="1.0" encoding="UTF-8"?>
123 -
<rss version="2.0">
124 -
  <channel>
125 -
    <title>Steve Dylan - Updates</title>
126 -
    <description>Small updates from my life that don't quite fit into a blog</description>
127 -
    <link>https://stevedylan.dev/now</link>
128 -
    <language>en</language>
129 -
  </channel>
130 -
</rss>`;
131 -
132 -
		return new Response(errorRss, {
133 -
			status: 200,
134 -
			headers: {
135 -
				"Content-Type": "application/xml",
136 -
			},
97 +
		return rss({
98 +
			title: "Steve Dylan - Updates",
99 +
			description:
100 +
				"Small updates from my life that don't quite fit into a blog",
101 +
			site: process.env.SITE_URL || "https://stevedylan.dev",
102 +
			items: [],
137 103
		});
138 104
	}
139 -
};
105 +
}