TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import * as cheerio from 'cheerio';23/** RSS 2.0 / Atom feed parsing (SPEC §1) — used for auction calendars, new-arrival feeds and news. */4export interface FeedItem {5 id: string | null;6 title: string;7 link: string | null;8 publishedAt: Date | null;9 summary: string | null;10 content: string | null;11 categories: string[];12 imageUrl: string | null;13}1415export function parseFeed(xml: string): { title: string | null; items: FeedItem[] } {16 const $ = cheerio.load(xml, { xml: true });17 const items: FeedItem[] = [];18 const date = (s: string | undefined) => {19 if (!s) return null;20 const d = new Date(s.trim());21 return Number.isNaN(d.getTime()) ? null : d;22 };23 if ($('feed').length) {24 $('feed > entry').each((_, el) => {25 const e = $(el);26 const link = e.find('link[rel="alternate"]').attr('href') ?? e.find('link').first().attr('href') ?? null;27 const img = e.find('media\\:content, media\\:thumbnail, enclosure').first().attr('url') ?? null;28 items.push({ id: e.find('id').first().text().trim() || null, title: e.find('title').first().text().trim(), link, publishedAt: date(e.find('published').first().text() || e.find('updated').first().text()), summary: e.find('summary').first().text().trim() || null, content: e.find('content').first().text().trim() || null, categories: e.find('category').map((_, c) => $(c).attr('term') ?? $(c).text()).get().filter(Boolean), imageUrl: img });29 });30 return { title: $('feed > title').first().text().trim() || null, items };31 }32 $('item').each((_, el) => {33 const e = $(el);34 const img = e.find('media\\:content, media\\:thumbnail, enclosure').first().attr('url') ?? null;35 items.push({ id: e.find('guid').first().text().trim() || null, title: e.find('title').first().text().trim(), link: e.find('link').first().text().trim() || null, publishedAt: date(e.find('pubDate').first().text() || e.find('dc\\:date').first().text()), summary: e.find('description').first().text().trim() || null, content: e.find('content\\:encoded').first().text().trim() || null, categories: e.find('category').map((_, c) => $(c).text()).get().filter(Boolean), imageUrl: img });36 });37 return { title: $('channel > title').first().text().trim() || null, items };38}39