fix(route/rfa): use embedded data (#22479)

This commit is contained in:
Tony 2026-07-09 04:00:41 +08:00 committed by GitHub
parent cb0791cb41
commit 5a50c8fa0e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 74 additions and 33 deletions

View File

@ -1,8 +1,9 @@
import { load } from 'cheerio';
import type { Context } from 'hono';
import type { Route } from '@/types';
import cache from '@/utils/cache';
import got from '@/utils/got';
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
export const route: Route = {
@ -25,56 +26,96 @@ export const route: Route = {
Parameters can be obtained from the official website, for instance:
\`https://www.rfa.org/cantonese/news\` corresponds to \`/rfa/cantonese/news\`
\`https://www.rfa.org/cantonese\` corresponds to \`/rfa/cantonese\`
\`https://www.rfa.org/cantonese/news/htm\` corresponds to \`/rfa/cantonese/news/htm\``,
\`https://www.rfa.org/cantonese/htm\` corresponds to \`/rfa/cantonese/htm\``,
};
async function handler(ctx) {
let url = 'https://www.rfa.org/' + (ctx.req.param('language') ?? 'english');
if (ctx.req.param('channel')) {
url += '/' + ctx.req.param('channel');
}
if (ctx.req.param('subChannel')) {
url += '/' + ctx.req.param('subChannel');
const renderElement = (element: any) => {
switch (element.type) {
case 'text':
return `<p>${element.content}</p>`;
case 'header':
return `<h${element.level ?? 2}>${element.content}</h${element.level ?? 2}>`;
case 'divider':
return '<hr>';
case 'image':
return `<figure><img src="${element.url}" alt="${element.alt_text ?? ''}">${element.caption ? `<figcaption>${element.caption}</figcaption>` : ''}</figure>`;
case 'video': {
const stream = element.streams?.find((s) => s.stream_type === 'mp4');
return stream ? `<figure><video controls src="${stream.url}" poster="${element.promo_image?.url ?? ''}"></video>${element.headlines?.basic ? `<figcaption>${element.headlines.basic}</figcaption>` : ''}</figure>` : '';
}
case 'oembed_response':
return element.raw_oembed?.html ?? '';
case 'list': {
const tag = element.list_type === 'ordered' ? 'ol' : 'ul';
return `<${tag}>${element.items.map((el) => `<li>${el.content}</li>`).join('')}</${tag}>`;
}
case 'gallery':
return element.content_elements.map((el) => renderElement(el)).join('');
case 'quote':
return `<blockquote>${element.content_elements.map((el) => renderElement(el)).join('')}${element.citation?.content ? `<cite>${element.citation.content}</cite>` : ''}</blockquote>`;
case 'raw_html':
return element.content;
case 'custom_embed':
return '';
default:
throw new Error(`Unsupported element type: ${element.type}`);
}
};
const response = await got(url);
const $ = load(response.data);
async function handler(ctx: Context) {
const { language = 'english', channel, subChannel } = ctx.req.param();
const baseUrl = 'https://www.rfa.org';
const link = `${baseUrl}/${[language, channel, subChannel].filter(Boolean).join('/')}/`;
const selectors = ['div[id=topstorywidefull]', 'div.two_featured', 'div.three_featured', 'div.single_column_teaser', 'div.sectionteaser', 'div.specialwrap'];
const list = [];
for (const selector of selectors) {
$(selector).each((_, e) => {
const item = {
title: $(e).find('h2 a span').first().text(),
link: $(e).find('h2 a').first().attr('href'),
const response = await ofetch(link);
const $ = load(response);
const contentCache = JSON.parse(response.match(/Fusion\.contentCache=(\{.*?\});Fusion\.layout/)?.[1] ?? '{}');
const list = new Map<string, any>(
Object.values<any>(contentCache['content-api-collections'] ?? {})
.flatMap((entry) => entry.data?.content_elements ?? [])
.map((story) => [story._id, story])
)
.values()
.toArray()
.map((story) => {
const website = Object.values<any>(story.websites)[0];
return {
title: story.headlines.basic,
description: story.description.basic,
link: new URL(website.website_url, baseUrl).href,
pubDate: parseDate(story.display_date),
author: story.credits?.by?.map((author) => author.name).join(', '),
category: website.website_section?.name,
image: story.promo_items.basic.url,
};
list.push(item);
});
}
const result = await Promise.all(
const items = await Promise.all(
list.map((item) =>
cache.tryGet(item.link, async () => {
const content = await got(item.link);
const response = await ofetch(item.link);
const globalContent = JSON.parse(response.match(/Fusion\.globalContent=(\{.*?\});Fusion\.globalContentConfig/)?.[1] ?? '{}');
const description = load(content.data);
item.description = description('#headerimg').html() + description('div[id=storytext]').html();
item.pubDate = parseDate(description('span[id=story_date]').text());
if (description('meta[property=og:audio]').attr('content') !== undefined) {
item.enclosure_url = description('meta[property=og:audio]').attr('content');
item.enclosure_type = description('meta[property=og:audio:type]').attr('content');
if (globalContent.content_elements) {
item.description = [globalContent.promo_items?.basic, ...globalContent.content_elements]
.filter(Boolean)
.map((element) => renderElement(element))
.join('');
}
item.category = [...new Set([item.category, ...(globalContent.taxonomy?.sections?.map((section) => section.name) ?? []), ...(globalContent.taxonomy?.tags?.map((tag) => tag.text) ?? [])].filter(Boolean))];
return item;
})
)
);
return {
title: 'RFA',
link: 'https://www.rfa.org/',
item: result,
title: $('head title').text(),
link,
image: $('meta[name="og:image"]').attr('content'),
language: $('html').attr('lang'),
item: items,
};
}