rss

tcmts.content.rss · #ioyr5fkv · function · runs anywhere

An RSS 2.0 feed: its items in the order given (newest first, as readers expect).

import type { FeedItem } from "#codebase/tcmts/content";

export function rss(feed: {
  title: string;
  /** the site's address, in full: the items' relative links are from it */
  link: string;
  description: string;
  /** its self link, for readers: the feed's own address */
  self?: string;
  language?: string;
  items: FeedItem[];
}): string {
  const esc = (s: string) =>
    s.replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]!);
  const day = (date: string) =>
    new Date(/^\d{4}-\d{2}-\d{2}$/.test(date) ? `${date}T00:00:00Z` : date).toUTCString();
  const absolute = (html: string) =>
    html.replace(
      /(href|src)="(?!#|[a-z][a-z0-9+.-]*:|\/\/)([^"]*)"/gi,
      (_, attr: string, to: string) => `${attr}="${new URL(to, feed.link).href}"`,
    );
  const items = feed.items.map((item) =>
    [
      "<item>",
      `<title>${esc(item.title)}</title>`,
      `<link>${esc(item.link)}</link>`,
      `<guid isPermaLink="true">${esc(item.link)}</guid>`,
      `<pubDate>${day(item.date)}</pubDate>`,
      ...(item.author ? [`<dc:creator>${esc(item.author)}</dc:creator>`] : []),
      ...(item.html !== undefined
        ? [`<description>${esc(absolute(item.html))}</description>`]
        : []),
      "</item>",
    ].join("\n"),
  );
  return [
    '<?xml version="1.0" encoding="utf-8"?>',
    '<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">',
    "<channel>",
    `<title>${esc(feed.title)}</title>`,
    `<link>${esc(feed.link)}</link>`,
    ...(feed.self
      ? [`<atom:link href="${esc(feed.self)}" rel="self" type="application/rss+xml"/>`]
      : []),
    `<description>${esc(feed.description)}</description>`,
    ...(feed.language ? [`<language>${esc(feed.language)}</language>`] : []),
    ...(feed.items[0] ? [`<lastBuildDate>${day(feed.items[0].date)}</lastBuildDate>`] : []),
    ...items,
    "</channel>",
    "</rss>",
    "",
  ].join("\n");
}

uses

used by