Add feed discovery plugin

This commit is contained in:
Anthony Ettinger 2026-06-09 09:34:31 +00:00
parent e47616bf9d
commit 5cfeea6b57
37 changed files with 2432 additions and 5 deletions

View file

@ -0,0 +1,26 @@
import type { FeedDiscoveryResponse } from "../types.js";
import { escapeXml } from "./opml.js";
export function renderAtom(response: FeedDiscoveryResponse) {
const updated = new Date().toISOString();
const entries = response.results
.map((result) => {
const id = result.canonicalFeedUrl ?? result.feedUrl;
return ` <entry>
<id>${escapeXml(id)}</id>
<title>${escapeXml(result.title)}</title>
<link href="${escapeXml(result.homepageUrl ?? result.feedUrl)}" />
<updated>${escapeXml(result.lastPublishedAt ?? updated)}</updated>
<summary>${escapeXml(result.description ?? `Discovered ${result.kind} feed from ${result.provider}`)}</summary>
</entry>`;
})
.join("\n");
return `<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id>logicsrc:feed-discovery:${escapeXml(response.normalizedQuery)}</id>
<title>LogicSRC feed discovery: ${escapeXml(response.query)}</title>
<updated>${updated}</updated>
${entries}
</feed>`;
}

View file

@ -0,0 +1,23 @@
import type { FeedDiscoveryResponse, FeedOutputFormat } from "../types.js";
import { renderAtom } from "./atom.js";
import { renderJsonFeed } from "./json-feed.js";
import { renderOpml } from "./opml.js";
import { renderRss } from "./rss.js";
export function renderDiscoveryOutput(response: FeedDiscoveryResponse, format: FeedOutputFormat) {
if (format === "opml") {
return renderOpml(response);
}
if (format === "rss") {
return renderRss(response);
}
if (format === "atom") {
return renderAtom(response);
}
if (format === "json-feed") {
return renderJsonFeed(response);
}
return JSON.stringify(response, null, 2);
}
export { renderAtom, renderJsonFeed, renderOpml, renderRss };

View file

@ -0,0 +1,22 @@
import type { FeedDiscoveryResponse } from "../types.js";
export function renderJsonFeed(response: FeedDiscoveryResponse) {
return JSON.stringify(
{
version: "https://jsonfeed.org/version/1.1",
title: `LogicSRC feed discovery: ${response.query}`,
home_page_url: "https://bittorrented.com/rss",
feed_url: `https://bittorrented.com/json-feed/discover/${encodeURIComponent(response.normalizedQuery)}.json`,
items: response.results.map((result) => ({
id: result.canonicalFeedUrl ?? result.feedUrl,
url: result.homepageUrl ?? result.feedUrl,
title: result.title,
summary: result.description ?? `Discovered ${result.kind} feed from ${result.provider}`,
date_published: result.lastPublishedAt,
tags: [result.kind, result.provider, ...result.tags]
}))
},
null,
2
);
}

View file

@ -0,0 +1,27 @@
import type { DiscoveredFeed, FeedDiscoveryResponse } from "../types.js";
export function renderOpml(response: FeedDiscoveryResponse) {
const outlines = response.results.map(renderOutline).join("\n");
return `<?xml version="1.0" encoding="UTF-8"?>
<opml version="2.0">
<head>
<title>LogicSRC feed discovery: ${escapeXml(response.query)}</title>
</head>
<body>
${outlines}
</body>
</opml>`;
}
function renderOutline(feed: DiscoveredFeed) {
return ` <outline text="${escapeXml(feed.title)}" title="${escapeXml(feed.title)}" type="rss" xmlUrl="${escapeXml(feed.canonicalFeedUrl ?? feed.feedUrl)}"${feed.homepageUrl ? ` htmlUrl="${escapeXml(feed.homepageUrl)}"` : ""} category="${escapeXml(feed.kind)}" />`;
}
export function escapeXml(value: string) {
return value
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&apos;");
}

View file

@ -0,0 +1,25 @@
import RSS from "rss";
import type { FeedDiscoveryResponse } from "../types.js";
export function renderRss(response: FeedDiscoveryResponse) {
const feed = new RSS({
title: `LogicSRC feed discovery: ${response.query}`,
description: `Discovered feed sources for ${response.query}`,
feed_url: `https://bittorrented.com/rss/discover/${encodeURIComponent(response.normalizedQuery)}.xml`,
site_url: "https://bittorrented.com/rss",
language: "en"
});
for (const result of response.results) {
feed.item({
title: result.title,
description: result.description ?? `Discovered ${result.kind} feed from ${result.provider}`,
url: result.homepageUrl ?? result.feedUrl,
guid: result.canonicalFeedUrl ?? result.feedUrl,
categories: [result.kind, result.provider, ...result.tags],
date: result.lastPublishedAt ? new Date(result.lastPublishedAt) : new Date()
});
}
return feed.xml({ indent: true });
}