my website at ewancroft.uk
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add Atom feed endpoint for status updates

Introduces a new GET endpoint at /now/atom that generates an Atom XML feed of user status updates. The feed is built from records fetched via the user's PDS, includes error handling, and escapes XML content for safety.

Ewan Croft 77844a2a 9a09074a

+95
+95
src/routes/now/atom/+server.ts
··· 1 + import type { RequestHandler } from "@sveltejs/kit"; 2 + import type { URL } from "url"; 3 + import { dev } from "$app/environment"; 4 + import { getProfile } from "$components/profile/profile"; 5 + import { formatDate } from "$utils/formatters"; 6 + import type { StatusUpdate } from "$components/shared"; 7 + 8 + export const GET: RequestHandler = async ({ url, fetch }: { url: URL, fetch: typeof globalThis.fetch }) => { 9 + let baseUrl: string; 10 + baseUrl = dev ? url.origin : "https://ewancroft.uk"; 11 + try { 12 + const profileData = await getProfile(fetch); 13 + const did = profileData.did; 14 + const pdsUrl = profileData.pds; 15 + if (!pdsUrl) throw new Error("Could not find PDS URL"); 16 + const statusResponse = await fetch( 17 + `${pdsUrl}/xrpc/com.atproto.repo.listRecords?repo=${did}&collection=uk.ewancroft.now` 18 + ); 19 + if (!statusResponse.ok) 20 + throw new Error(`Status fetch failed: ${statusResponse.status}`); 21 + const statusData = await statusResponse.json(); 22 + const statusUpdates: StatusUpdate[] = []; 23 + for (const data of statusData.records) { 24 + const matches = data.uri.split("/"); 25 + const tid = matches[matches.length - 1]; 26 + const record = data.value; 27 + if (matches && matches.length === 5 && record) { 28 + statusUpdates.push({ 29 + text: record.text, 30 + createdAt: new Date(record.createdAt), 31 + tid, 32 + }); 33 + } 34 + } 35 + const sortedUpdates = statusUpdates.sort( 36 + (a, b) => b.createdAt.getTime() - a.createdAt.getTime() 37 + ); 38 + const atomXml = `<?xml version="1.0" encoding="utf-8"?> 39 + <feed xmlns="http://www.w3.org/2005/Atom"> 40 + <title>Now - ${profileData.displayName || profileData.handle}'s Status Updates</title> 41 + <subtitle>Short status updates showing what ${profileData.displayName || profileData.handle} is currently doing.</subtitle> 42 + <link href="${baseUrl}/now" /> 43 + <link href="${baseUrl}/now/atom" rel="self" /> 44 + <updated>${new Date().toISOString()}</updated> 45 + <id>${baseUrl}/now</id> 46 + <author> 47 + <name>${profileData.displayName || profileData.handle}</name> 48 + <uri>${baseUrl}/now</uri> 49 + </author> 50 + ${sortedUpdates 51 + .map( 52 + (status) => ` 53 + <entry> 54 + <title>Status update from ${formatDate(status.createdAt)}</title> 55 + <link href="${baseUrl}/now#${status.tid}" /> 56 + <id>${did}/uk.ewancroft.now/${status.tid}</id> 57 + <updated>${status.createdAt.toISOString()}</updated> 58 + <summary type="html"><![CDATA[${escapeXml(status.text)}]]></summary> 59 + <content type="html"><![CDATA[${escapeXml(status.text)}]]></content> 60 + <author> 61 + <name>${profileData.displayName || profileData.handle}</name> 62 + </author> 63 + </entry>` 64 + ) 65 + .join("")} 66 + </feed>`; 67 + return new Response(atomXml, { 68 + headers: { 69 + "Content-Type": "application/atom+xml", 70 + "Cache-Control": "max-age=0, s-maxage=600", 71 + }, 72 + }); 73 + } catch (error) { 74 + console.error("Error generating Atom feed:", error); 75 + return new Response( 76 + `<?xml version="1.0" encoding="utf-8"?>\n<feed xmlns="http://www.w3.org/2005/Atom">\n <title>Now - Status Updates</title>\n <subtitle>Short status updates showing what I'm currently doing.</subtitle>\n <link href="${url.origin}/now" />\n <link href="${url.origin}/now/atom" rel="self" />\n <updated>${new Date().toISOString()}</updated>\n <!-- Error occurred while generating feed entries -->\n</feed>`, 77 + { 78 + headers: { 79 + "Content-Type": "application/atom+xml", 80 + "Cache-Control": "no-cache", 81 + }, 82 + } 83 + ); 84 + } 85 + }; 86 + 87 + function escapeXml(unsafe: string): string { 88 + if (!unsafe) return ""; 89 + return unsafe 90 + .replace(/&/g, "&amp;") 91 + .replace(/</g, "&lt;") 92 + .replace(/>/g, "&gt;") 93 + .replace(/"/g, "&quot;") 94 + .replace(/'/g, "&apos;"); 95 + }