SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
2.2 KB · 39 lines typescript
Raw Blame History
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