feat(route): add Meritalk latest articles (#21282)

* feat: add route and namespace for MeriTalk articles

* feat: update articles route to support pagination and add description rendering

* feat: enhance articles route with improved description and error handling

* feat(meritalk): add articles route with pagination limit

- Add latest articles route for Meritalk
- Update route example and parameter description
- Implement pagination limit to avoid Cloudflare anti-bot

* fix(meritalk): remove nums param and fetch only first page

* fix(meritalk): update route category to new-media

Co-authored-by: Tony <TonyRL@users.noreply.github.com>

* fix(meritalk): remove the post-processing of items and modify some of the logic in the handler

- Remove unnecessary sorting.
- Remove custom limit logic.

---------
This commit is contained in:
Lipeng 2026-03-05 11:41:55 +08:00 committed by GitHub
parent c09dacac5b
commit 6e2ca63189
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 107 additions and 0 deletions

View File

@ -0,0 +1,80 @@
import { load } from 'cheerio';
import type { Route } from '@/types';
import cache from '@/utils/cache';
import got from '@/utils/got';
import { parseDate } from '@/utils/parse-date';
import { renderDescription } from './templates/description';
export const route: Route = {
path: '/articles',
categories: ['new-media'],
example: '/meritalk/articles',
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
radar: [
{
source: ['meritalk.com/articles/'],
target: '/articles',
},
],
name: 'Latest Articles',
maintainers: ['superguyDiluc'],
handler,
};
async function handler() {
const baseUrl = 'https://www.meritalk.com/articles';
const { data: response } = await got(baseUrl);
const $ = load(response);
const list = $('div.news-block-sm')
.toArray()
.map((item) => {
const $item = $(item);
const a = $item.find('.news-block-title a');
const link = a.attr('href');
return {
title: a.text().trim(),
link: link as string,
pubDate: parseDate($item.find('time[datetime]').attr('datetime') as string),
category: $item
.find('.category-header-name a')
.toArray()
.map((elem) => $(elem).text()),
description: '',
};
});
const items = await Promise.all(
list.map((item) =>
cache.tryGet(item.link, async () => {
const { data: response } = await got(item.link);
const $ = load(response);
const featuredImage = $('.single-featured-image').first().html() || '';
const fullContent = $('.single-body').first().html() || '';
item!.description = renderDescription({
featuredImage,
fullContent,
});
return item;
})
)
);
return {
title: 'News MeriTalk',
link: 'https://www.meritalk.com/articles/',
item: items,
};
}

View File

@ -0,0 +1,6 @@
import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'MeriTalk',
url: 'meritalk.com',
};

View File

@ -0,0 +1,21 @@
import { raw } from 'hono/html';
import { renderToString } from 'hono/jsx/dom/server';
type DescriptionParams = {
featuredImage?: string;
fullContent?: string;
};
export const renderDescription = ({ featuredImage, fullContent }: DescriptionParams) =>
renderToString(
<>
{featuredImage ? (
<>
{raw(featuredImage)}
<br />
</>
) : null}
{fullContent ? raw(fullContent) : null}
</>
);