rsshub
Version:
Make RSS Great Again!
142 lines (127 loc) • 5.8 kB
text/typescript
import { Route } from '@/types';
import cache from '@/utils/cache';
import ofetch from '@/utils/ofetch';
import parser from '@/utils/rss-parser';
import { load } from 'cheerio';
import path from 'node:path';
import { art } from '@/utils/render';
export const route: Route = {
path: '/:hub?',
categories: ['new-media'],
example: '/theverge',
parameters: { hub: 'Hub, see below, All Posts by default' },
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
radar: [
{
source: ['theverge.com/:hub', 'theverge.com/'],
},
],
name: 'Category',
maintainers: ['HenryQW', 'vbali'],
handler,
description: `| Hub | Hub name |
| ----------- | ------------------- |
| | All Posts |
| android | Android |
| apple | Apple |
| apps | Apps & Software |
| blackberry | BlackBerry |
| culture | Culture |
| gaming | Gaming |
| hd | HD & Home |
| microsoft | Microsoft |
| photography | Photography & Video |
| policy | Policy & Law |
| web | Web & Social |
Provides a better reading experience (full text articles) over the official one.`,
};
const renderBlock = (b) => {
switch (b.__typename) {
case 'CoreEmbedBlockType':
return b.embedHtml;
case 'CoreGalleryBlockType':
return b.images.map((i) => `<figure><img src="${i.image.thumbnails.horizontal.url.split('?')[0]}" alt="${i.alt}" /><figcaption>${i.caption.html}</figcaption></figure>`).join('');
case 'CoreHeadingBlockType':
return `<h${b.level}>${b.contents.html}</h${b.level}>`;
case 'CoreHTMLBlockType':
return b.markup;
case 'CoreImageBlockType':
return `<figure><img src="${b.thumbnail.url.split('?')[0]}" alt="${b.alt}" /><figcaption>${b.caption.html}</figcaption></figure>`;
case 'CoreListBlockType':
return `${b.ordered ? '<ol>' : '<ul>'}${b.items.map((i) => `<li>${i.contents.html}</li>`).join('')}${b.ordered ? '</ol>' : '</ul>'}`;
case 'CoreParagraphBlockType':
return b.contents.html;
case 'CorePullquoteBlockType':
return `<blockquote>${b.contents.html}</blockquote>`;
case 'CoreQuoteBlockType':
return `<blockquote>${b.children.map((child) => renderBlock(child)).join('')}</blockquote>`;
case 'CoreSeparatorBlockType':
return '<hr>';
case 'HighlightBlockType':
return b.children.map((c) => renderBlock(c)).join('');
case 'MethodologyAccordionBlockType':
return `<h2>${b.heading.html}</h2>${b.sections.map((s) => `<h3>${s.heading.html}</h3>${s.content.html}`).join('')}`;
default:
throw new Error(`Unsupported block type: ${b.__typename}`);
}
};
async function handler(ctx) {
const link = ctx.req.param('hub') ? `https://www.theverge.com/${ctx.req.param('hub')}/rss/index.xml` : 'https://www.theverge.com/rss/index.xml';
const feed = await parser.parseURL(link);
const items = await Promise.all(
feed.items.map((item) =>
cache.tryGet(item.link, async () => {
const response = await ofetch(item.link);
const $ = load(response);
const nextData = JSON.parse($('script#__NEXT_DATA__').text());
const node = nextData.props.pageProps.hydration.responses.find((x) => x.operationName === 'PostLayoutQuery' || x.operationName === 'StreamLayoutQuery').data.node;
let description = art(path.join(__dirname, 'templates/header.art'), {
featuredImage: node.featuredImage,
ledeMediaData: node.ledeMediaData,
});
description += node.blocks
.filter((b) => b.__typename !== 'NewsletterBlockType' && b.__typename !== 'RelatedPostsBlockType' && b.__typename !== 'ProductBlockType' && b.__typename !== 'TableOfContentsBlockType')
.map((b) => renderBlock(b))
.join('<br><br>');
if (node.__typename === 'StreamResourceType') {
description += node.posts.edges
.map(({ node: n }) => {
let d =
`<h2><a href="${n.permalink}">${n.promo.headline || n.title}</a></h2>` +
art(path.join(__dirname, 'templates/header.art'), {
ledeMediaData: n.ledeMediaData,
});
switch (n.__typename) {
case 'PostResourceType':
d += n.excerpt.map((e) => e.contents.html).join('<br>');
break;
case 'QuickPostResourceType':
d += n.blocks.map((b) => renderBlock(b)).join('<br>');
break;
default:
break;
}
return d;
})
.join('<br>');
}
item.description = description;
item.category = node.categories;
return item;
})
)
);
return {
title: feed.title,
link: feed.link,
description: feed.description,
item: items,
};
}