Files
kill-the-news/src/domain/native-feed.ts
T

55 lines
1.8 KiB
TypeScript

/**
* Pure detection of a newsletter's own syndication feed. No DOM, no I/O — it
* receives already-extracted <link> tuples (infra parses the HTML) and decides
* which ones are real feeds. This module owns the business knowledge: the strict
* set of recognized feed MIME types.
*/
import { NativeFeed } from "../types";
// MIME type → feed kind. Strict: only the three canonical syndication types.
// `application/json` is deliberately excluded — too broad, captures non-feeds.
const MIME_TO_KIND: Record<string, NativeFeed["type"]> = {
"application/atom+xml": "atom",
"application/rss+xml": "rss",
"application/feed+json": "json",
};
// Drop MIME parameters ("; charset=…"), trim, lowercase.
function normalizeMime(type: string): string {
return type.split(";")[0].trim().toLowerCase();
}
/** Map raw <link> tuples to recognized native feeds, deduped by URL. */
export function detectNativeFeeds(
links: { href: string; type: string }[],
): NativeFeed[] {
const out: NativeFeed[] = [];
const seen = new Set<string>();
for (const link of links) {
const kind = MIME_TO_KIND[normalizeMime(link.type)];
if (!kind) continue;
const url = link.href.trim();
if (!url || seen.has(url)) continue;
seen.add(url);
out.push({ url, type: kind });
}
return out;
}
/** Flatten per-sender native feeds into one list, deduped by URL (first wins). */
export function unionNativeFeeds(
bySender: Record<string, NativeFeed[]> | undefined,
): NativeFeed[] {
if (!bySender) return [];
const out: NativeFeed[] = [];
const seen = new Set<string>();
for (const feeds of Object.values(bySender)) {
for (const feed of feeds) {
if (seen.has(feed.url)) continue;
seen.add(feed.url);
out.push({ ...feed });
}
}
return out;
}