chore: fixed rss for /now
a06d18ce
1 file(s) · +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, "<") |
|
| 81 | - | .replace(/>/g, ">") |
|
| 82 | - | .replace(/"/g, """) |
|
| 83 | - | .replace(/'/g, "'"); |
|
| 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 | + | } |
|