feat(route): add TCTMD conference news (#21072)

* feat(route): add TCTMD conference news

Add RSS route for TCTMD (tctmd.com) Conference News section,
covering interventional cardiology conference coverage.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(route): address review feedback for TCTMD conference news

- Use www.tctmd.com instead of tctmd.com in url and radar source
- Fetch from search page (SEE ALL) to get 20 articles instead of 6
- Remove unused selector fallbacks (.field--name-body, .field--name-field-body, .field--name-field-subhead)
- Remove try-catch wrapper, let errors propagate properly
- Delete redundant radar.ts (radar rules already in route definition)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: ChuYinan2023 <ChuYinan2023@users.noreply.github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
ChuYinan2023 2026-02-07 22:46:23 +08:00 committed by GitHub
parent a0b3c9dc6f
commit 86a0015096
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 115 additions and 0 deletions

View File

@ -0,0 +1,107 @@
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';
export const route: Route = {
path: '/conference-news',
categories: ['journal'],
example: '/tctmd/conference-news',
parameters: {},
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
radar: [
{
source: ['www.tctmd.com/news/conference-news'],
},
],
name: 'Conference News',
maintainers: ['ChuYinan2023'],
handler,
url: 'www.tctmd.com/news/conference-news',
};
async function handler() {
const rootUrl = 'https://www.tctmd.com';
const currentUrl = `${rootUrl}/search?keyword=&f%5B0%5D=news_subtype%3AConference%20News&f%5B1%5D=type%3Anews`;
const response = await got({
method: 'get',
url: currentUrl,
});
const $ = load(response.data);
const items = $('.views-row article.news-teaser')
.toArray()
.map((el) => {
const $el = $(el);
const $link = $el.find('h2.algolia-search--title a').first();
const href = $link.attr('href');
if (!href) {
return null;
}
return {
title: $link.text().trim(),
link: href.startsWith('http') ? href : `${rootUrl}${href}`,
pubDate: $el.find('time.datetime').attr('datetime') ? parseDate($el.find('time.datetime').attr('datetime')!) : undefined,
author: $el.find('.author__container--name').text().trim() || undefined,
image: $el.find('.field--name-field-teaser-image img').attr('src') || undefined,
};
})
.filter(Boolean);
const fullItems = await Promise.all(
items.map((item) =>
cache.tryGet(item.link, async () => {
const detailResponse = await got({
method: 'get',
url: item.link,
});
const content = load(detailResponse.data);
const articleBody = content('.field--name-field-body-content').html() || '';
const introText = content('.field--name-field-intro-text').text().trim();
if (articleBody) {
item.description = (introText ? `<p><strong>${introText}</strong></p>` : '') + articleBody;
} else if (introText) {
item.description = introText;
}
if (!item.pubDate) {
const detailTime = content('time').first().attr('datetime');
if (detailTime) {
item.pubDate = parseDate(detailTime);
}
}
if (!item.author) {
const detailAuthor = content('.node-meta__text a').first().text().trim();
if (detailAuthor) {
item.author = detailAuthor;
}
}
return item;
})
)
);
return {
title: 'TCTMD - Conference News',
description: 'Latest conference news coverage from TCTMD, the leading source for interventional cardiology news',
link: `${rootUrl}/news/conference-news`,
image: 'https://www.tctmd.com/themes/tctmd/logo.svg',
item: fullItems,
};
}

View File

@ -0,0 +1,8 @@
import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'TCTMD',
url: 'www.tctmd.com',
description: 'Cardiovascular news, education, and clinical trial coverage from the Cardiovascular Research Foundation',
lang: 'en',
};