rsshub
Version:
Make RSS Great Again!
177 lines (160 loc) • 5.85 kB
text/typescript
import { Route } from '@/types';
import cache from '@/utils/cache';
import ofetch from '@/utils/ofetch';
import parser from '@/utils/rss-parser';
import * as cheerio from 'cheerio';
import { parseDate } from '@/utils/parse-date';
import { art } from '@/utils/render';
import path from 'node:path';
const renderFanBox = (media) =>
art(path.join(__dirname, 'templates/fancybox.art'), {
media,
});
const renderDesc = (media, desc) =>
art(path.join(__dirname, 'templates/description.art'), {
media: renderFanBox(media),
desc,
});
const fixFancybox = (element, $) => {
const $e = $(element);
const url = new URL($e.attr('href'));
let video;
if (url.hostname === 'videop.mingpao.com') {
video = new URL(url.searchParams.get('file'));
video.hostname = 'cfrvideo.mingpao.com'; // use cloudflare cdn
video = video.href;
}
return {
href: url.href,
title: $e.attr('title'),
video,
};
};
export const route: Route = {
path: '/:type?/:category?',
name: '新聞',
example: '/mingpao/ins/all',
parameters: {
type: {
description: '新聞類型',
default: 'ins',
options: [
{ value: 'ins', label: '即時新聞' },
{ value: 'pns', label: '每日明報' },
],
},
category: '頻道,見下表',
},
radar: [
{
title: '即時新聞',
source: ['news.mingpao.com/ins/:categoryName/section/:date/:category'],
target: '/mingpao/ins/:category',
},
{
title: '每日明報',
source: ['news.mingpao.com/pns/:categoryName/section/:date/:category'],
target: '/mingpao/pns/:category',
},
],
maintainers: ['TonyRL'],
handler,
description: `| category | 即時新聞頻道 |
| -------- | ------------ |
| all | 總目錄 |
| s00001 | 港聞 |
| s00002 | 經濟 |
| s00003 | 地產 |
| s00004 | 兩岸 |
| s00005 | 國際 |
| s00006 | 體育 |
| s00007 | 娛樂 |
| s00022 | 文摘 |
| s00024 | 熱點 |
| category | 每日明報頻道 |
| -------- | ------------ |
| s00001 | 要聞 |
| s00002 | 港聞 |
| s00003 | 社評 |
| s00004 | 經濟 |
| s00005 | 副刊 |
| s00011 | 教育 |
| s00012 | 觀點 |
| s00013 | 中國 |
| s00014 | 國際 |
| s00015 | 體育 |
| s00016 | 娛樂 |
| s00017 | English |
| s00018 | 作家專欄 |`,
};
async function handler(ctx) {
const type = ctx.req.param('type') ?? 'ins';
const category = ctx.req.param('category') ?? (type === 'ins' ? 'all' : 's00001');
const link = `https://news.mingpao.com/rss/${type}/${category}.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, {
headers: {
Referer: 'https://news.mingpao.com/',
},
});
const $ = cheerio.load(response);
const topVideo = $('#topvideo').length
? $('#topvideo iframe')
.toArray()
.map((e) => $(e).attr('href', $(e).attr('src')))
.map((e) => fixFancybox(e, $))
: [];
const fancyboxImg = $('a.fancybox').length ? $('a.fancybox') : $('a.fancybox-buttons');
// remove unwanted elements
$('div.ad300ins_m').remove();
$('div.clear, div.inReadLrecGroup, div.clr').remove();
$('div#ssm2').remove();
$('iframe').remove();
$('p[dir=ltr]').remove();
// extract categories
item.category = item.categories;
// fix fancybox image
let fancybox = [...topVideo, ...fancyboxImg.toArray().map((e) => fixFancybox(e, $))];
const script = $('script')
.toArray()
.find((e) => $(e).text()?.includes("$('#lower').prepend('"));
const lowerContent = script
? $(script)
.text()
?.match(/\$\('#lower'\)\.prepend\('(.*)'\);/)?.[1]
?.replaceAll(/\\"/g, '"')
: '';
if (lowerContent) {
const $ = cheerio.load(lowerContent, null, false);
fancybox = [
...fancybox,
...$('a.fancybox')
.toArray()
.map((e) => fixFancybox(e, $)),
];
}
// remove unwanted key value
delete item.categories;
delete item.content;
delete item.contentSnippet;
delete item.creator;
delete item.isoDate;
item.description = renderDesc(fancybox, $('.txt4').html() ?? $('.article_content.line_1_5em').html() ?? $('.txt3').html());
item.pubDate = parseDate(item.pubDate);
item.guid = item.link.includes('?') ? item.link : item.link.slice(0, item.link.lastIndexOf('/'));
return item;
})
)
);
return {
title: feed.title,
link: feed.link,
description: feed.description,
item: items,
image: feed.image.url,
language: feed.language,
};
}