- - - -
+ +' + author.name + author.authorBio.replaceAll(/^
/g, ' ')).join(''); + const authorsBio = data.authors.map((author) => '
' + author.name + author.authorBio.replaceAll(/^
/g, ' ')).join(''); item.description = art(path.join(__dirname, 'templates/essay.art'), { banner, authorsBio, content: capture.html() }); } diff --git a/lib/routes/afdian/dynamic.ts b/lib/routes/afdian/dynamic.ts index 2819a48a9e5c65..539279154e22d1 100644 --- a/lib/routes/afdian/dynamic.ts +++ b/lib/routes/afdian/dynamic.ts @@ -13,7 +13,7 @@ export const route: Route = { async function handler(ctx) { const url_slug = ctx.req.param('uid').replace('@', ''); - const baseUrl = 'https://afdian.net'; + const baseUrl = 'https://afdian.com'; const userInfoRes = await got(`${baseUrl}/api/user/get-profile-by-slug`, { searchParams: { url_slug, diff --git a/lib/routes/afdian/explore.ts b/lib/routes/afdian/explore.ts index 14437242a98ea8..1f51c1d57cd6e0 100644 --- a/lib/routes/afdian/explore.ts +++ b/lib/routes/afdian/explore.ts @@ -35,21 +35,21 @@ export const route: Route = { maintainers: ['sanmmm'], description: `分类 - | 推荐 | 最热 | - | ---- | ---- | - | rec | hot | - - 目录类型 - - | 所有 | 绘画 | 视频 | 写作 | 游戏 | 音乐 | 播客 | 摄影 | 技术 | Vtuber | 舞蹈 | 体育 | 旅游 | 美食 | 时尚 | 数码 | 动画 | 其他 | - | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ------ | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | - | 所有 | 绘画 | 视频 | 写作 | 游戏 | 音乐 | 播客 | 摄影 | 技术 | Vtuber | 舞蹈 | 体育 | 旅游 | 美食 | 时尚 | 数码 | 动画 | 其他 |`, + | 推荐 | 最热 | + | ---- | ---- | + | rec | hot | + + 目录类型 + + | 所有 | 绘画 | 视频 | 写作 | 游戏 | 音乐 | 播客 | 摄影 | 技术 | Vtuber | 舞蹈 | 体育 | 旅游 | 美食 | 时尚 | 数码 | 动画 | 其他 | + | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ------ | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | + | 所有 | 绘画 | 视频 | 写作 | 游戏 | 音乐 | 播客 | 摄影 | 技术 | Vtuber | 舞蹈 | 体育 | 旅游 | 美食 | 时尚 | 数码 | 动画 | 其他 |`, handler, }; async function handler(ctx) { const { type = 'rec', category = '所有' } = ctx.req.param(); - const baseUrl = 'https://afdian.net'; + const baseUrl = 'https://afdian.com'; const link = `${baseUrl}/api/creator/list`; const res = await got(link, { searchParams: { diff --git a/lib/routes/afdian/namespace.ts b/lib/routes/afdian/namespace.ts index 3c4d181d72f71c..43af5fb4c42cd4 100644 --- a/lib/routes/afdian/namespace.ts +++ b/lib/routes/afdian/namespace.ts @@ -3,4 +3,5 @@ import type { Namespace } from '@/types'; export const namespace: Namespace = { name: '爱发电', url: 'afdian.net', + lang: 'zh-CN', }; diff --git a/lib/routes/afr/latest.ts b/lib/routes/afr/latest.ts new file mode 100644 index 00000000000000..b656755ceccfc8 --- /dev/null +++ b/lib/routes/afr/latest.ts @@ -0,0 +1,69 @@ +import { Route } from '@/types'; +import type { Context } from 'hono'; + +import cache from '@/utils/cache'; +import ofetch from '@/utils/ofetch'; +import { parseDate } from '@/utils/parse-date'; +import { assetsConnectionByCriteriaQuery } from './query'; +import { getItem } from './utils'; + +export const route: Route = { + path: '/latest', + categories: ['traditional-media'], + example: '/afr/latest', + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['www.afr.com/latest', 'www.afr.com/'], + }, + ], + name: 'Latest', + maintainers: ['TonyRL'], + handler, + url: 'www.afr.com/latest', +}; + +async function handler(ctx: Context) { + const limit = Number.parseInt(ctx.req.query('limit') ?? '10'); + const response = await ofetch('https://api.afr.com/graphql', { + query: { + query: assetsConnectionByCriteriaQuery, + operationName: 'assetsConnectionByCriteria', + variables: { + brand: 'afr', + first: limit, + render: 'web', + types: ['article', 'bespoke', 'featureArticle', 'liveArticle', 'video'], + after: '', + }, + }, + }); + + const list = response.data.assetsConnectionByCriteria.edges.map(({ node }) => ({ + title: node.asset.headlines.headline, + description: node.asset.about, + link: `https://www.afr.com${node.urls.published.afr.path}`, + pubDate: parseDate(node.dates.firstPublished), + updated: parseDate(node.dates.modified), + author: node.asset.byline, + category: [node.tags.primary.displayName, ...node.tags.secondary.map((tag) => tag.displayName)], + image: node.featuredImages && `https://static.ffx.io/images/${node.featuredImages.landscape16x9.data.id}`, + })); + + const items = await Promise.all(list.map((item) => cache.tryGet(item.link, () => getItem(item)))); + + return { + title: 'Latest | The Australian Financial Review | AFR', + description: 'The latest news, events, analysis and opinion from The Australian Financial Review', + image: 'https://www.afr.com/apple-touch-icon-1024x1024.png', + link: 'https://www.afr.com/latest', + item: items, + }; +} diff --git a/lib/routes/afr/namespace.ts b/lib/routes/afr/namespace.ts new file mode 100644 index 00000000000000..d6fd9b647e2165 --- /dev/null +++ b/lib/routes/afr/namespace.ts @@ -0,0 +1,7 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: 'The Australian Financial Review', + url: 'afr.com', + lang: 'en', +}; diff --git a/lib/routes/afr/navigation.ts b/lib/routes/afr/navigation.ts new file mode 100644 index 00000000000000..cbe7421a296b16 --- /dev/null +++ b/lib/routes/afr/navigation.ts @@ -0,0 +1,75 @@ +import { Route } from '@/types'; +import type { Context } from 'hono'; + +import cache from '@/utils/cache'; +import ofetch from '@/utils/ofetch'; +import { parseDate } from '@/utils/parse-date'; +import { pageByNavigationPathQuery } from './query'; +import { getItem } from './utils'; + +export const route: Route = { + path: '/navigation/:path{.+}', + categories: ['traditional-media'], + example: '/afr/navigation/markets', + parameters: { + path: 'Navigation path, can be found in the URL of the page', + }, + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['www.afr.com/path*'], + }, + ], + name: 'Navigation', + maintainers: ['TonyRL'], + handler, + url: 'www.afr.com', +}; + +async function handler(ctx: Context) { + const { path } = ctx.req.param(); + const limit = Number.parseInt(ctx.req.query('limit') ?? '10'); + + const response = await ofetch('https://api.afr.com/api/content-audience/afr/graphql', { + query: { + query: pageByNavigationPathQuery, + operationName: 'pageByNavigationPath', + variables: { + input: { brandKey: 'afr', navigationPath: `/${path}`, renderName: 'web' }, + firstStories: limit, + afterStories: '', + }, + }, + }); + + const list = response.data.pageByNavigationPath.page.latestStoriesConnection.edges.map(({ node }) => ({ + title: node.headlines.headline, + description: node.overview.about, + link: `https://www.afr.com${node.urls.canonical.path}`, + pubDate: parseDate(node.dates.firstPublished), + updated: parseDate(node.dates.modified), + author: node.byline + .filter((byline) => byline.type === 'AUTHOR') + .map((byline) => byline.author.name) + .join(', '), + category: [node.tags.primary.displayName, ...node.tags.secondary.map((tag) => tag.displayName)], + image: node.images && `https://static.ffx.io/images/${node.images.landscape16x9.mediaId}`, + })); + + const items = await Promise.all(list.map((item) => cache.tryGet(item.link, () => getItem(item)))); + + return { + title: response.data.pageByNavigationPath.page.seo.title, + description: response.data.pageByNavigationPath.page.seo.description, + image: 'https://www.afr.com/apple-touch-icon-1024x1024.png', + link: `https://www.afr.com/${path}`, + item: items, + }; +} diff --git a/lib/routes/afr/query.ts b/lib/routes/afr/query.ts new file mode 100644 index 00000000000000..a596f8fc4f70dd --- /dev/null +++ b/lib/routes/afr/query.ts @@ -0,0 +1,349 @@ +export const pageByNavigationPathQuery = `query pageByNavigationPath( + $input: PageByNavigationPathInput! + $firstStories: Int + $afterStories: Cursor + ) { + pageByNavigationPath(input: $input) { + error { + message + type { + class + ... on ErrorTypeInvalidRequest { + fields { + field + message + } + } + } + } + page { + ads { + suppress + } + description + id + latestStoriesConnection(first: $firstStories, after: $afterStories) { + edges { + node { + byline { + ...AssetBylineFragment + } + headlines { + headline + } + ads { + sponsor { + name + } + } + overview { + about + label + } + type + dates { + firstPublished + published + } + id + publicId + images { + ...AssetImagesFragmentAudience + } + tags { + primary { + ...TagFragmentAudience + } + secondary { + ...TagFragmentAudience + } + } + urls { + ...AssetUrlsAudienceFragment + } + } + } + pageInfo { + endCursor + hasNextPage + } + } + name + seo { + canonical { + brand { + key + } + } + description + title + } + social { + image { + height + url + width + } + } + } + redirect + } + } + fragment AssetBylineFragment on AssetByline { + type + ... on AssetBylineAuthor { + author { + name + publicId + profile { + avatar + bio + body + canonical { + brand { + key + } + } + email + socials { + facebook { + publicId + } + twitter { + publicId + } + } + title + } + } + } + ... on AssetBylineName { + name + } + } + fragment AssetImagesFragmentAudience on ImageRenditions { + landscape16x9 { + ...ImageFragmentAudience + } + landscape3x2 { + ...ImageFragmentAudience + } + portrait2x3 { + ...ImageFragmentAudience + } + square1x1 { + ...ImageFragmentAudience + } + } + fragment ImageFragmentAudience on ImageRendition { + altText + animated + caption + credit + crop { + offsetX + offsetY + width + zoom + } + mediaId + mimeType + source + type + } + fragment AssetUrlsAudienceFragment on AssetURLs { + canonical { + brand { + key + } + path + } + external { + url + } + published { + brand { + key + } + path + } + } + fragment TagFragmentAudience on Tag { + company { + exchangeCode + stockCode + } + context { + name + } + description + displayName + externalEntities { + google { + placeId + } + wikipedia { + publicId + url + } + } + id + location { + latitude + longitude + postalCode + state + } + name + publicId + seo { + description + title + } + urls { + canonical { + brand { + key + } + path + } + published { + brand { + key + } + path + } + } + }`; + +export const assetsConnectionByCriteriaQuery = `query assetsConnectionByCriteria( + $after: ID + $brand: Brand! + $categories: [Int!] + $first: Int! + $render: Render! + $types: [AssetType!]! + ) { + assetsConnectionByCriteria( + after: $after + brand: $brand + categories: $categories + first: $first + render: $render + types: $types + ) { + edges { + cursor + node { + ...AssetFragment + sponsor { + name + } + } + } + error { + message + type { + class + } + } + pageInfo { + endCursor + hasNextPage + } + } + } + fragment AssetFragment on Asset { + asset { + about + byline + duration + headlines { + headline + } + live + } + assetType + dates { + firstPublished + modified + published + } + id + featuredImages { + landscape16x9 { + ...ImageFragment + } + landscape3x2 { + ...ImageFragment + } + portrait2x3 { + ...ImageFragment + } + square1x1 { + ...ImageFragment + } + } + label + tags { + primary: primaryTag { + ...AssetTag + } + secondary { + ...AssetTag + } + } + urls { + ...AssetURLs + } + } + fragment AssetTag on AssetTagDetails { + ...AssetTagAudience + shortID + slug + } + fragment AssetTagAudience on AssetTagDetails { + company { + exchangeCode + stockCode + } + context + displayName + id + name + urls { + canonical { + brand + path + } + published { + afr { + path + } + } + } + } + fragment AssetURLs on AssetURLs { + canonical { + brand + path + } + published { + afr { + path + } + } + } + fragment ImageFragment on Image { + data { + altText + aspect + autocrop + caption + cropWidth + id + offsetX + offsetY + zoom + } + }`; diff --git a/lib/routes/afr/utils.ts b/lib/routes/afr/utils.ts new file mode 100644 index 00000000000000..c055ae9e70d29a --- /dev/null +++ b/lib/routes/afr/utils.ts @@ -0,0 +1,80 @@ +import * as cheerio from 'cheerio'; +import ofetch from '@/utils/ofetch'; + +export const getItem = async (item) => { + const response = await ofetch(item.link); + const $ = cheerio.load(response); + + const reduxState = JSON.parse($('script#__REDUX_STATE__').text().replaceAll(':undefined', ':null').match('__REDUX_STATE__=(.*);')?.[1] || '{}'); + + const content = reduxState.page.content; + const asset = content.asset; + + switch (content.assetType) { + case 'liveArticle': + item.description = asset.posts.map((post) => `
{{ intro }}+{{ /if }} + +{{ if description }} + {{@ description }} +{{ /if }} \ No newline at end of file diff --git a/lib/routes/ahjzu/namespace.ts b/lib/routes/ahjzu/namespace.ts index 2efc11070943a0..98bf403952ec20 100644 --- a/lib/routes/ahjzu/namespace.ts +++ b/lib/routes/ahjzu/namespace.ts @@ -3,4 +3,5 @@ import type { Namespace } from '@/types'; export const namespace: Namespace = { name: '安徽建筑大学', url: 'news.ahjzu.edu.cn', + lang: 'zh-CN', }; diff --git a/lib/routes/aibase/discover.ts b/lib/routes/aibase/discover.ts new file mode 100644 index 00000000000000..ac70b06cfb3567 --- /dev/null +++ b/lib/routes/aibase/discover.ts @@ -0,0 +1,388 @@ +import { Route } from '@/types'; + +import ofetch from '@/utils/ofetch'; +import { load } from 'cheerio'; + +import { rootUrl, buildApiUrl, processItems } from './util'; + +export const handler = async (ctx) => { + const { id } = ctx.req.param(); + + const [pid, sid] = id?.split(/-/) ?? [undefined, undefined]; + + const limit = Number.parseInt(ctx.req.query('limit') ?? '30', 10); + + const currentUrl = new URL(`discover${id ? `/${id}` : ''}`, rootUrl).href; + + const currentHtml = await ofetch(currentUrl); + + const $ = load(currentHtml); + + const { apiRecommListUrl, apiRecommProcUrl, apiTagProcUrl } = await buildApiUrl($); + + let ptag, stag; + let isTag = !!(pid && sid); + + if (isTag) { + const apiRecommList = await ofetch(apiRecommListUrl); + + const recommList = apiRecommList?.data?.results ?? []; + + const parentTag = recommList.find((t) => String(t.Id) === pid); + const subTag = parentTag ? parentTag.sublist.find((t) => String(t.Id) === sid) : undefined; + + ptag = parentTag?.tag ?? parentTag?.alias ?? undefined; + stag = subTag?.tag ?? subTag?.alias ?? undefined; + + isTag = !!(ptag && stag); + } + + const query = { + page: 1, + pagesize: limit, + ticket: '', + }; + + const { + data: { results: apiProcs }, + } = await (isTag + ? ofetch(apiRecommProcUrl, { + query: { + ...query, + ptag, + stag, + }, + }) + : ofetch(apiTagProcUrl, { + query: { + ...query, + f: 'id', + o: 'desc', + }, + })); + + const items = processItems(apiProcs?.slice(0, limit) ?? []); + + const image = new URL($('img.logo').prop('src'), rootUrl).href; + + const author = $('title').text().split(/_/).pop(); + + return { + title: `${author}${isTag ? ` | ${ptag} - ${stag}` : ''}`, + description: $('meta[property="og:description"]').prop('content'), + link: currentUrl, + item: items, + allowEmpty: true, + image, + author, + }; +}; + +export const route: Route = { + path: '/discover/:id?', + name: '发现', + url: 'top.aibase.com', + maintainers: ['nczitzk'], + handler, + example: '/aibase/discover', + parameters: { id: '发现分类,默认为空,即全部产品,可在对应发现分类页 URL 中找到' }, + description: `:::tip + 若订阅 [图片背景移除](https://top.aibase.com/discover/37-49),网址为 \`https://top.aibase.com/discover/37-49\`。截取 \`https://top.aibase.com/discover/\` 到末尾的部分 \`37-49\` 作为参数填入,此时路由为 [\`/aibase/discover/37-49\`](https://rsshub.app/aibase/discover/37-49)。 + ::: + +
名称 | +{{ item.name }} | +
---|---|
标签 | ++ {{ each strToArray(item.tags) t }} + {{ t }}  + {{ /each }} + | +
类型 | ++ {{ if item.proctypename }} + {{ item.proctypename }} + {{ else }} + 无 + {{ /if }} + | +描述 | + {{ if item.desc }} + {{ item.desc }} + {{ else }} + 无 + {{ /if }} + +
需求人群 | +
+ {{ set list = strToArray(item.use) }}
+ {{ if list.length === 1 }}
+ {{ list[0] }}
+ {{ else }}
+ {{ each list l }}
+ |
+
使用场景示例 | +
+ {{ set list = strToArray(item.example) }}
+ {{ if list.length === 1 }}
+ {{ list[0] }}
+ {{ else }}
+ {{ each list l }}
+ |
+
产品特色 | +
+ {{ set list = strToArray(item.functions) }}
+ {{ if list.length === 1 }}
+ {{ list[0] }}
+ {{ else }}
+ {{ each list l }}
+ |
+
站点 | ++ {{ if item.url }} + + {{ item.url }} + + {{ else }} + 无 + {{ /if }} + | +
{{ intro }}+{{ /if }} + +{{ if description }} + {{@ description }} +{{ /if }} \ No newline at end of file diff --git a/lib/routes/alicesoft/infomation.ts b/lib/routes/alicesoft/infomation.ts new file mode 100644 index 00000000000000..dc855add300248 --- /dev/null +++ b/lib/routes/alicesoft/infomation.ts @@ -0,0 +1,88 @@ +import { Route } from '@/types'; +import got from '@/utils/got'; +import cache from '@/utils/cache'; +import { load } from 'cheerio'; + +const baseUrl = 'https://www.alicesoft.com'; + +export const route: Route = { + url: 'www.alicesoft.com/information', + path: '/information/:category?/:game?', + categories: ['game'], + example: '/alicesoft/information/game/cat377', + parameters: { + category: 'Category in the URL, which can be accessed under カテゴリ一覧 on the website.', + game: 'Game-specific subcategory in the URL, which can be accessed under カテゴリ一覧 on the website. In this case, the category value should be `game`.', + }, + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['www.alicesoft.com/information', 'www.alicesoft.com/information/:category', 'www.alicesoft.com/information/:category/:game'], + target: '/information/:category/:game', + }, + ], + name: 'ニュース', + maintainers: ['keocheung'], + handler, +}; + +async function handler(ctx) { + const { category, game } = ctx.req.param(); + const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 10; + + let url = `${baseUrl}/information`; + if (category) { + url += `/${category}`; + if (game) { + url += `/${game}`; + } + } + + const response = await got(url); + const $ = load(response.data); + + let items = $('div.cont-main li') + .slice(0, limit) + .toArray() + .map((item) => { + item = $(item); + return { + title: item.find('p.txt').text(), + link: item.find('a').attr('href'), + pubDate: new Date(item.find('time').attr('datetime')), + }; + }); + + items = await Promise.all( + items.map((item) => { + if (!item.link.startsWith(`${baseUrl}/information/`)) { + return item; + } + return cache.tryGet(item.link, async () => { + const contentResponse = await got(item.link); + + const content = load(contentResponse.data); + content('iframe[src^="https://www.youtube.com/"]').removeAttr('height').removeAttr('width'); + item.description = `
(.+?)<\/p>/g, '
(.+?)<\/p>/g, '
${item.review}
+ `, + pubDate: new Date(item.date).toUTCString(), + })); + + const link = `https://appstare.net/data/app/comment/${appid}/${country}`; + + return { + title: 'App Comments', + appID: appid, + country, + item: items, + link, + allowEmpty: true, + }; +}; + +export const route: Route = { + path: '/comments/:country/:appid', + name: 'Comments', + url: 'appstare.net/', + example: '/appstare/comments/cn/989673964', + maintainers: ['zhixideyu'], + handler, + parameters: { + country: 'App Store country code, e.g., US, CN', + appid: 'Unique App Store application identifier (app id)', + }, + categories: ['program-update'], + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['appstare.net/'], + }, + ], + description: 'Retrieve only the comments of the app from the past 7 days.', +}; diff --git a/lib/routes/appstare/namespace.ts b/lib/routes/appstare/namespace.ts new file mode 100644 index 00000000000000..3d2809e68a9fe9 --- /dev/null +++ b/lib/routes/appstare/namespace.ts @@ -0,0 +1,7 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: 'AppStare', + url: 'appstare.net', + lang: 'zh-CN', +}; diff --git a/lib/routes/appstore/namespace.ts b/lib/routes/appstore/namespace.ts index 4561b783496be9..cc4f08444621c0 100644 --- a/lib/routes/appstore/namespace.ts +++ b/lib/routes/appstore/namespace.ts @@ -3,4 +3,5 @@ import type { Namespace } from '@/types'; export const namespace: Namespace = { name: 'App Store/Mac App Store', url: 'apps.apple.com', + lang: 'en', }; diff --git a/lib/routes/appstorrent/namespace.ts b/lib/routes/appstorrent/namespace.ts index 3b5b12a004499b..f95fc97cb516bc 100644 --- a/lib/routes/appstorrent/namespace.ts +++ b/lib/routes/appstorrent/namespace.ts @@ -3,4 +3,5 @@ import type { Namespace } from '@/types'; export const namespace: Namespace = { name: 'AppsTorrent', url: 'appstorrent.ru', + lang: 'ru', }; diff --git a/lib/routes/aqara/namespace.ts b/lib/routes/aqara/namespace.ts index e14ff72843e459..d343eaea253e9f 100644 --- a/lib/routes/aqara/namespace.ts +++ b/lib/routes/aqara/namespace.ts @@ -3,4 +3,5 @@ import type { Namespace } from '@/types'; export const namespace: Namespace = { name: 'Aqara', url: 'aqara.com', + lang: 'zh-CN', }; diff --git a/lib/routes/aqara/region.ts b/lib/routes/aqara/region.ts index e3c20484d217d8..48174a0834bec3 100644 --- a/lib/routes/aqara/region.ts +++ b/lib/routes/aqara/region.ts @@ -14,5 +14,5 @@ function handler(ctx) { const { region = 'en', type = 'news' } = ctx.req.param(); const redirectTo = `/aqara/${region}/category/${types[type]}`; - ctx.redirect(redirectTo); + ctx.set('redirect', redirectTo); } diff --git a/lib/routes/aqicn/namespace.ts b/lib/routes/aqicn/namespace.ts index e370267b48c4ee..a0c29c99d6d400 100644 --- a/lib/routes/aqicn/namespace.ts +++ b/lib/routes/aqicn/namespace.ts @@ -3,4 +3,5 @@ import type { Namespace } from '@/types'; export const namespace: Namespace = { name: '空气质量', url: 'aqicn.org', + lang: 'zh-CN', }; diff --git a/lib/routes/arcteryx/namespace.ts b/lib/routes/arcteryx/namespace.ts index 0ada8bdaa86502..cc97c54bf81889 100644 --- a/lib/routes/arcteryx/namespace.ts +++ b/lib/routes/arcteryx/namespace.ts @@ -3,4 +3,5 @@ import type { Namespace } from '@/types'; export const namespace: Namespace = { name: 'Arcteryx', url: 'arcteryx.com', + lang: 'zh-CN', }; diff --git a/lib/routes/artstation/namespace.ts b/lib/routes/artstation/namespace.ts index 48d714be2142b2..6967b625032b3b 100644 --- a/lib/routes/artstation/namespace.ts +++ b/lib/routes/artstation/namespace.ts @@ -3,4 +3,5 @@ import type { Namespace } from '@/types'; export const namespace: Namespace = { name: 'ArtStation', url: 'www.artstation.com', + lang: 'en', }; diff --git a/lib/routes/asiantolick/namespace.ts b/lib/routes/asiantolick/namespace.ts index 277a3e0829dd54..21d7619ddb8cc9 100644 --- a/lib/routes/asiantolick/namespace.ts +++ b/lib/routes/asiantolick/namespace.ts @@ -3,4 +3,5 @@ import type { Namespace } from '@/types'; export const namespace: Namespace = { name: 'Asian to lick', url: 'asiantolick.com', + lang: 'zh-CN', }; diff --git a/lib/routes/asmr-200/index.ts b/lib/routes/asmr-200/index.ts new file mode 100644 index 00000000000000..a26b6762c45910 --- /dev/null +++ b/lib/routes/asmr-200/index.ts @@ -0,0 +1,66 @@ +import { Result, Work } from '@/routes/asmr-200/type'; +import { DataItem, Route } from '@/types'; +import ofetch from '@/utils/ofetch'; +import path from 'node:path'; +import { parseDate } from '@/utils/parse-date'; +import { art } from '@/utils/render'; +import timezone from '@/utils/timezone'; +import { getCurrentPath } from '@/utils/helpers'; + +const render = (work: Work, link: string) => art(path.join(getCurrentPath(import.meta.url), 'templates', 'work.art'), { work, link }); + +export const route: Route = { + path: '/works/:order?/:subtitle?/:sort?', + categories: ['multimedia'], + example: '/asmr-200/works', + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: true, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + parameters: { + order: '排序字段,默认按照资源的收录日期来排序,详见下表', + sort: '排序方式,可选 `asc` 和 `desc` ,默认倒序', + subtitle: '筛选带字幕音频,可选 `0` 和 `1` ,默认关闭', + }, + radar: [ + { + source: ['asmr-200.com'], + target: 'asmr-200/works', + }, + ], + name: '最新收录', + maintainers: ['hualiong'], + url: 'asmr-200.com', + description: `| 发售日期 | 收录日期 | 销量 | 价格 | 评价 | 随机 | RJ号 | +| ---- | ---- | ---- | ---- | ---- | ---- | ---- | +| release | create_date | dl_count | price | rate_average_2dp | random | id |`, + handler: async (ctx) => { + const { order = 'create_date', sort = 'desc', subtitle = '0' } = ctx.req.param(); + const res = await ofetch发布者:{{ work.name }}
+评分:{{ work.rate_average_2dp }} | 评论数:{{ work.review_count }} | 总时长:{{ work.duration }} | 音频来源:{{ work.source_type }}
+价格:{{ work.price }} JPY | 销量:{{ work.dl_count }}
+分类:{{ work.category }}
+声优:{{ work.cv }}
\ No newline at end of file diff --git a/lib/routes/asmr-200/type.ts b/lib/routes/asmr-200/type.ts new file mode 100644 index 00000000000000..8036204afd1222 --- /dev/null +++ b/lib/routes/asmr-200/type.ts @@ -0,0 +1,96 @@ +export interface Result { + pagination: { + currentPage: number; + pageSize: number; + totalCount: number; + }; + works: Work[]; +} + +export interface Work { + age_category_string: string; + circle: { + id: number; + name: string; + source_id: string; + source_type: string; + }; + circle_id: number; + create_date: string; + dl_count: number; + duration: number; + has_subtitle: boolean; + id: number; + language_editions: { + display_order: number; + edition_id: number; + edition_type: string; + label: string; + lang: string; + workno: string; + }[]; + mainCoverUrl: string; + name: string; + nsfw: boolean; + original_workno: null | string; + other_language_editions_in_db: { + id: number; + is_original: boolean; + lang: string; + source_id: string; + source_type: string; + title: string; + }[]; + playlistStatus: any; + price: number; + rank: + | { + category: string; + rank: number; + rank_date: string; + term: string; + }[] + | null; + rate_average_2dp: number | number; + rate_count: number; + rate_count_detail: { + count: number; + ratio: number; + review_point: number; + }[]; + release: string; + review_count: number; + samCoverUrl: string; + source_id: string; + source_type: string; + source_url: string; + tags: { + i18n: any; + id: number; + name: string; + }[]; + category: string; + thumbnailCoverUrl: string; + title: string; + translation_info: { + child_worknos: string[]; + is_child: boolean; + is_original: boolean; + is_parent: boolean; + is_translation_agree: boolean; + is_translation_bonus_child: boolean; + is_volunteer: boolean; + lang: null | string; + original_workno: null | string; + parent_workno: null | string; + production_trade_price_rate: number; + translation_bonus_langs: string[]; + }; + userRating: null; + vas: { + id: string; + name: string; + }[]; + cv: string; + work_attributes: string; +} diff --git a/lib/routes/asus/bios.ts b/lib/routes/asus/bios.ts index e82587ce2dd4e3..2bd81a185d5012 100644 --- a/lib/routes/asus/bios.ts +++ b/lib/routes/asus/bios.ts @@ -2,26 +2,76 @@ import { Route } from '@/types'; import { getCurrentPath } from '@/utils/helpers'; const __dirname = getCurrentPath(import.meta.url); -import got from '@/utils/got'; +import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; import { art } from '@/utils/render'; import path from 'node:path'; +import cache from '@/utils/cache'; -const getProductID = async (model) => { - const searchAPI = `https://odinapi.asus.com.cn/recent-data/apiv2/SearchSuggestion?SystemCode=asus&WebsiteCode=cn&SearchKey=${model}&SearchType=ProductsAll&RowLimit=4&sitelang=cn`; - const response = await got(searchAPI); +const endPoints = { + zh: { + url: 'https://odinapi.asus.com.cn/', + lang: 'cn', + websiteCode: 'cn', + }, + en: { + url: 'https://odinapi.asus.com/', + lang: 'en', + websiteCode: 'global', + }, +}; - return { - productID: response.data.Result[0].Content[0].DataId, - url: response.data.Result[0].Content[0].Url, - }; +const getProductInfo = (model, language) => { + const currentEndpoint = endPoints[language] ?? endPoints.zh; + const { url, lang, websiteCode } = currentEndpoint; + + const searchAPI = `${url}recent-data/apiv2/SearchSuggestion?SystemCode=asus&WebsiteCode=${websiteCode}&SearchKey=${model}&SearchType=ProductsAll&RowLimit=4&sitelang=${lang}`; + + return cache.tryGet(`asus:bios:${model}:${language}`, async () => { + const response = await ofetch(searchAPI); + const product = response.Result[0].Content[0]; + + return { + productID: product.DataId, + hashId: product.HashId, + url: product.Url, + title: product.Title, + image: product.ImageURL, + m1Id: product.M1Id, + productLine: product.ProductLine, + }; + }) as Promise<{ + productID: string; + hashId: string; + url: string; + title: string; + image: string; + m1Id: string; + productLine: string; + }>; }; export const route: Route = { - path: '/bios/:model', + path: '/bios/:model/:lang?', categories: ['program-update'], - example: '/asus/bios/RT-AX88U', - parameters: { model: 'Model, can be found in product page' }, + example: '/asus/bios/RT-AX88U/zh', + parameters: { + model: 'Model, can be found in product page', + lang: { + description: 'Language, provide access routes for other parts of the world', + options: [ + { + label: 'Chinese', + value: 'zh', + }, + { + label: 'Global', + value: 'en', + }, + ], + default: 'en', + }, + }, features: { requireConfig: false, requirePuppeteer: false, @@ -32,36 +82,52 @@ export const route: Route = { }, radar: [ { - source: ['asus.com.cn/'], + source: [ + 'www.asus.com/displays-desktops/:productLine/:series/:model', + 'www.asus.com/laptops/:productLine/:series/:model', + 'www.asus.com/motherboards-components/:productLine/:series/:model', + 'www.asus.com/networking-iot-servers/:productLine/:series/:model', + 'www.asus.com/:region/displays-desktops/:productLine/:series/:model', + 'www.asus.com/:region/laptops/:productLine/:series/:model', + 'www.asus.com/:region/motherboards-components/:productLine/:series/:model', + 'www.asus.com/:region/networking-iot-servers/:productLine/:series/:model', + ], + target: '/bios/:model', }, ], name: 'BIOS', maintainers: ['Fatpandac'], handler, - url: 'asus.com.cn/', + url: 'www.asus.com', }; async function handler(ctx) { const model = ctx.req.param('model'); - const { productID, url } = await getProductID(model); - const biosAPI = `https://www.asus.com.cn/support/api/product.asmx/GetPDBIOS?website=cn&model=${model}&pdid=${productID}&sitelang=cn`; + const language = ctx.req.param('lang') ?? 'en'; + const productInfo = await getProductInfo(model, language); + const biosAPI = + language === 'zh' + ? `https://www.asus.com.cn/support/api/product.asmx/GetPDBIOS?website=cn&model=${model}&pdid=${productInfo.productID}&sitelang=cn` + : `https://www.asus.com/support/api/product.asmx/GetPDBIOS?website=global&model=${model}&pdid=${productInfo.productID}&sitelang=en`; - const response = await got(biosAPI); - const biosList = response.data.Result.Obj[0].Files; + const response = await ofetch(biosAPI); + const biosList = response.Result.Obj[0].Files; const items = biosList.map((item) => ({ title: item.Title, description: art(path.join(__dirname, 'templates/bios.art'), { item, + language, }), - guid: url + item.Version, + guid: productInfo.url + item.Version, pubDate: parseDate(item.ReleaseDate, 'YYYY/MM/DD'), - link: url, + link: productInfo.url, })); return { - title: `${model} BIOS`, - link: url, + title: `${productInfo.title} BIOS`, + link: productInfo.url, + image: productInfo.image, item: items, }; } diff --git a/lib/routes/asus/namespace.ts b/lib/routes/asus/namespace.ts index a8df4f68690cb2..5bfdf756afa789 100644 --- a/lib/routes/asus/namespace.ts +++ b/lib/routes/asus/namespace.ts @@ -3,4 +3,5 @@ import type { Namespace } from '@/types'; export const namespace: Namespace = { name: 'ASUS', url: 'asus.com.cn', + lang: 'zh-CN', }; diff --git a/lib/routes/asus/templates/bios.art b/lib/routes/asus/templates/bios.art index 08bcee2ea332c9..559dcc7571a330 100644 --- a/lib/routes/asus/templates/bios.art +++ b/lib/routes/asus/templates/bios.art @@ -1,6 +1,13 @@ -更新信息:
-{{@ item.Description}} -版本: {{item.Version}}
-大小: {{item.FileSize}}
-更新日期: {{item.ReleaseDate}}
- +{{ if language !== 'zh' }} +Changes:
+ {{@ item.Description}} +Version: {{item.Version}}
+Size: {{item.FileSize}}
+Download: {{ item.DownloadUrl.Global.split('/').pop().split('?')[0] }}
+{{ else }} +更新信息:
+ {{@ item.Description}} +版本: {{item.Version}}
+大小: {{item.FileSize}}
+ +{{ /if }} diff --git a/lib/routes/atcoder/namespace.ts b/lib/routes/atcoder/namespace.ts index 0f4fa427f724df..cb177cc58be66f 100644 --- a/lib/routes/atcoder/namespace.ts +++ b/lib/routes/atcoder/namespace.ts @@ -3,4 +3,5 @@ import type { Namespace } from '@/types'; export const namespace: Namespace = { name: 'AtCoder', url: 'atcoder.jp', + lang: 'en', }; diff --git a/lib/routes/atptour/namespace.ts b/lib/routes/atptour/namespace.ts index 5a0805bd67a7d5..0916b893b623c5 100644 --- a/lib/routes/atptour/namespace.ts +++ b/lib/routes/atptour/namespace.ts @@ -4,4 +4,5 @@ export const namespace: Namespace = { name: 'ATP Tour', url: 'www.atptour.com', description: "News from the official site of men's professional tennis.", + lang: 'en', }; diff --git a/lib/routes/auto-stats/namespace.ts b/lib/routes/auto-stats/namespace.ts index 54adee1c7e09a9..247b60e68d57c0 100644 --- a/lib/routes/auto-stats/namespace.ts +++ b/lib/routes/auto-stats/namespace.ts @@ -3,4 +3,5 @@ import type { Namespace } from '@/types'; export const namespace: Namespace = { name: '中国汽车工业协会统计信息网', url: 'auto-stats.org.cn', + lang: 'zh-CN', }; diff --git a/lib/routes/autocentre/index.ts b/lib/routes/autocentre/index.ts new file mode 100644 index 00000000000000..7a434f5a7f0fbb --- /dev/null +++ b/lib/routes/autocentre/index.ts @@ -0,0 +1,29 @@ +import { Data, Route } from '@/types'; +import parser from '@/utils/rss-parser'; + +export const route: Route = { + path: '/', + name: 'Автомобільний сайт N1 в Україні', + categories: ['new-media'], + maintainers: ['driversti'], + example: '/autocentre', + handler, +}; + +const createItem = (item) => ({ + title: item.title, + link: item.link, + description: item.contentSnippet, +}); + +async function handler(): Promise { + const feed = await parser.parseURL('https://www.autocentre.ua/rss'); + + return { + title: feed.title as string, + link: feed.link, + description: feed.description, + language: 'uk', + item: await Promise.all(feed.items.map((item) => createItem(item))), + }; +} diff --git a/lib/routes/autocentre/namespace.ts b/lib/routes/autocentre/namespace.ts new file mode 100644 index 00000000000000..b9db3ac3a9c2c3 --- /dev/null +++ b/lib/routes/autocentre/namespace.ts @@ -0,0 +1,8 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: 'Автоцентр.ua', + url: 'autocentre.ua', + description: 'Автоцентр.ua: автоновини - Автомобільний сайт N1 в Україні', + lang: 'ru', +}; diff --git a/lib/routes/baai/namespace.ts b/lib/routes/baai/namespace.ts index 409fd300b0aaba..3b7c640abe59ba 100644 --- a/lib/routes/baai/namespace.ts +++ b/lib/routes/baai/namespace.ts @@ -3,4 +3,5 @@ import type { Namespace } from '@/types'; export const namespace: Namespace = { name: '北京智源人工智能研究院', url: 'hub.baai.ac.cn', + lang: 'zh-CN', }; diff --git a/lib/routes/backlinko/namespace.ts b/lib/routes/backlinko/namespace.ts index 8ad09707cafa59..ec3016624fae29 100644 --- a/lib/routes/backlinko/namespace.ts +++ b/lib/routes/backlinko/namespace.ts @@ -3,4 +3,5 @@ import type { Namespace } from '@/types'; export const namespace: Namespace = { name: 'Backlinko', url: 'backlinko.com', + lang: 'en', }; diff --git a/lib/routes/bad/namespace.ts b/lib/routes/bad/namespace.ts index 56c2be9f943430..9338a21f419430 100644 --- a/lib/routes/bad/namespace.ts +++ b/lib/routes/bad/namespace.ts @@ -3,4 +3,5 @@ import type { Namespace } from '@/types'; export const namespace: Namespace = { name: 'Bad.news', url: 'bad.news', + lang: 'zh-CN', }; diff --git a/lib/routes/baidu/gushitong/index.ts b/lib/routes/baidu/gushitong/index.ts index c8bcd175e0a737..452131d24e17d8 100644 --- a/lib/routes/baidu/gushitong/index.ts +++ b/lib/routes/baidu/gushitong/index.ts @@ -1,4 +1,4 @@ -import { Route } from '@/types'; +import { Route, ViewType } from '@/types'; import { getCurrentPath } from '@/utils/helpers'; const __dirname = getCurrentPath(import.meta.url); @@ -13,7 +13,8 @@ const STATUS_MAP = { export const route: Route = { path: '/gushitong/index', - categories: ['finance'], + categories: ['finance', 'popular'], + view: ViewType.Notifications, example: '/baidu/gushitong/index', parameters: {}, features: { diff --git a/lib/routes/baidu/namespace.ts b/lib/routes/baidu/namespace.ts index 262deb052ecc02..d7447282ed7e8f 100644 --- a/lib/routes/baidu/namespace.ts +++ b/lib/routes/baidu/namespace.ts @@ -3,4 +3,5 @@ import type { Namespace } from '@/types'; export const namespace: Namespace = { name: '百度', url: 'www.baidu.com', + lang: 'zh-CN', }; diff --git a/lib/routes/baidu/search.ts b/lib/routes/baidu/search.ts index caf46980d62ddb..1d9bbdeb1478d2 100644 --- a/lib/routes/baidu/search.ts +++ b/lib/routes/baidu/search.ts @@ -42,15 +42,16 @@ async function handler(ctx) { const contentLeft = $('#content_left'); const containers = contentLeft.find('.c-container'); return containers - .map((i, el) => { + .toArray() + .map((el) => { const element = $(el); const link = element.find('h3 a').first().attr('href'); if (link && !visitedLinks.has(link)) { visitedLinks.add(link); const imgs = element .find('img') - .map((_j, _el) => $(_el).attr('src')) - .toArray(); + .toArray() + .map((_el) => $(_el).attr('src')); const description = element.find('.c-gap-top-small [class^="content-right_"]').first().text() || element.find('.c-row').first().text() || element.find('.cos-row').first().text(); return { title: element.find('h3').first().text(), @@ -61,7 +62,6 @@ async function handler(ctx) { } return null; }) - .toArray() .filter((e) => e?.link); }, config.cache.routeExpire, diff --git a/lib/routes/baijing/index.ts b/lib/routes/baijing/index.ts new file mode 100644 index 00000000000000..79b2bda6b4ae4e --- /dev/null +++ b/lib/routes/baijing/index.ts @@ -0,0 +1,48 @@ +import { Route } from '@/types'; +import cache from '@/utils/cache'; +import { load } from 'cheerio'; +import { parseDate } from '@/utils/parse-date'; +import ofetch from '@/utils/ofetch'; + +export const route: Route = { + path: '/article', + categories: ['new-media'], + example: '/baijing/article', + url: 'www.baijing.cn/article/', + name: '资讯', + maintainers: ['p3psi-boo'], + handler, +}; + +async function handler() { + const apiUrl = 'https://www.baijing.cn/index/ajax/get_article/'; + const response = await ofetch(apiUrl); + const data = response.data.article_list; + + const list = data.map((item) => ({ + title: item.title, + link: `https://www.baijing.cn/article/${item.id}`, + author: item.user_info.user_name, + category: item.topic?.map((t) => t.title), + })); + + const items = await Promise.all( + list.map((item) => + cache.tryGet(item.link, async () => { + const response = await ofetch(item.link); + + const $ = load(response); + item.description = $('.content').html(); + item.pubDate = parseDate($('.timeago').text()); + + return item; + }) + ) + ); + + return { + title: '白鲸出海 - 资讯', + link: 'https://www.baijing.cn/article/', + item: items, + }; +} diff --git a/lib/routes/baijing/namespace.ts b/lib/routes/baijing/namespace.ts new file mode 100644 index 00000000000000..57d69294cd4383 --- /dev/null +++ b/lib/routes/baijing/namespace.ts @@ -0,0 +1,8 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: '白鲸出海', + url: 'baijing.cn', + description: '白鲸出海', + lang: 'zh-CN', +}; diff --git a/lib/routes/bandcamp/namespace.ts b/lib/routes/bandcamp/namespace.ts index 70a481f475a0fb..dc244d34eb8966 100644 --- a/lib/routes/bandcamp/namespace.ts +++ b/lib/routes/bandcamp/namespace.ts @@ -3,4 +3,5 @@ import type { Namespace } from '@/types'; export const namespace: Namespace = { name: 'Bandcamp', url: 'bandcamp.com', + lang: 'en', }; diff --git a/lib/routes/bangumi/moe/index.ts b/lib/routes/bangumi.moe/index.ts similarity index 94% rename from lib/routes/bangumi/moe/index.ts rename to lib/routes/bangumi.moe/index.ts index 1dea1500b281dc..e8a3667a8a576a 100644 --- a/lib/routes/bangumi/moe/index.ts +++ b/lib/routes/bangumi.moe/index.ts @@ -5,21 +5,22 @@ import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; export const route: Route = { - path: '/moe/*', + path: '/*', + categories: ['anime'], radar: [ { source: ['bangumi.moe/'], - target: '/moe', }, ], - name: 'Unknown', - maintainers: [], + name: 'Latest', + example: '/bangumi.moe', + maintainers: ['nczitzk'], handler, url: 'bangumi.moe/', }; async function handler(ctx) { - const isLatest = getSubPath(ctx) === '/moe'; + const isLatest = getSubPath(ctx) === '/'; const rootUrl = 'https://bangumi.moe'; let response; diff --git a/lib/routes/bangumi.moe/namespace.ts b/lib/routes/bangumi.moe/namespace.ts new file mode 100644 index 00000000000000..697c3a2b4f4b4b --- /dev/null +++ b/lib/routes/bangumi.moe/namespace.ts @@ -0,0 +1,7 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: '萌番组', + url: 'bangumi.online', + lang: 'zh-CN', +}; diff --git a/lib/routes/bangumi/namespace.ts b/lib/routes/bangumi.online/namespace.ts similarity index 72% rename from lib/routes/bangumi/namespace.ts rename to lib/routes/bangumi.online/namespace.ts index c2670f8bca306f..fd31a2bab5074f 100644 --- a/lib/routes/bangumi/namespace.ts +++ b/lib/routes/bangumi.online/namespace.ts @@ -2,5 +2,6 @@ import type { Namespace } from '@/types'; export const namespace: Namespace = { name: 'アニメ新番組', - url: 'bangumi.moe', + url: 'bangumi.online', + lang: 'ja', }; diff --git a/lib/routes/bangumi/online/online.ts b/lib/routes/bangumi.online/online.ts similarity index 91% rename from lib/routes/bangumi/online/online.ts rename to lib/routes/bangumi.online/online.ts index 02dcc058b404f9..9312fc8b5c5f8d 100644 --- a/lib/routes/bangumi/online/online.ts +++ b/lib/routes/bangumi.online/online.ts @@ -8,9 +8,9 @@ import { parseDate } from '@/utils/parse-date'; import path from 'node:path'; export const route: Route = { - path: '/online', + path: '/', categories: ['anime'], - example: '/bangumi/online', + example: '/bangumi.online', parameters: {}, features: { requireConfig: false, @@ -40,7 +40,7 @@ async function handler() { const items = list.map((item) => ({ title: `${item.title.zh ?? item.title.ja} - 第 ${item.volume} 集`, - description: art(path.join(__dirname, '../templates/online/image.art'), { + description: art(path.join(__dirname, 'templates/image.art'), { src: `https:${item.cover}`, alt: `${item.title_zh} - 第 ${item.volume} 集`, }), diff --git a/lib/routes/bangumi/templates/online/image.art b/lib/routes/bangumi.online/templates/image.art similarity index 100% rename from lib/routes/bangumi/templates/online/image.art rename to lib/routes/bangumi.online/templates/image.art diff --git a/lib/routes/bangumi/tv/calendar/_base.ts b/lib/routes/bangumi.tv/calendar/_base.ts similarity index 100% rename from lib/routes/bangumi/tv/calendar/_base.ts rename to lib/routes/bangumi.tv/calendar/_base.ts diff --git a/lib/routes/bangumi/tv/calendar/today.ts b/lib/routes/bangumi.tv/calendar/today.ts similarity index 89% rename from lib/routes/bangumi/tv/calendar/today.ts rename to lib/routes/bangumi.tv/calendar/today.ts index a82807c2a95169..2f12eaa9cae9b6 100644 --- a/lib/routes/bangumi/tv/calendar/today.ts +++ b/lib/routes/bangumi.tv/calendar/today.ts @@ -8,9 +8,9 @@ import { art } from '@/utils/render'; import path from 'node:path'; export const route: Route = { - path: '/tv/calendar/today', + path: '/calendar/today', categories: ['anime'], - example: '/bangumi/tv/calendar/today', + example: '/bangumi.tv/calendar/today', parameters: {}, features: { requireConfig: false, @@ -42,10 +42,10 @@ async function handler() { const todayList = list.find((l) => l.weekday.id % 7 === day); const todayBgmId = new Set(todayList.items.map((t) => t.id.toString())); - const images = todayList.items.reduce((p, c) => { - p[c.id] = (c.images || {}).large; - return p; - }, {}); + const images: { [key: string]: string } = {}; + for (const item of todayList.items) { + images[item.id] = (item.images || {}).large; + } const todayBgm = data.items.filter((d) => todayBgmId.has(d.bgmId)); for (const bgm of todayBgm) { bgm.image = images[bgm.bgmId]; @@ -65,7 +65,7 @@ async function handler() { const link = `https://bangumi.tv/subject/${bgm.bgmId}`; const id = `${link}#${new Intl.DateTimeFormat('zh-CN').format(updated)}`; - const html = art(path.resolve(__dirname, '../../templates/tv/today.art'), { + const html = art(path.join(__dirname, '../templates/today.art'), { bgm, siteMeta, }); diff --git a/lib/routes/bangumi/tv/group/reply.ts b/lib/routes/bangumi.tv/group/reply.ts similarity index 94% rename from lib/routes/bangumi/tv/group/reply.ts rename to lib/routes/bangumi.tv/group/reply.ts index 6e4cd264f509cc..33a25c0dab1a4c 100644 --- a/lib/routes/bangumi/tv/group/reply.ts +++ b/lib/routes/bangumi.tv/group/reply.ts @@ -1,13 +1,13 @@ import { Route } from '@/types'; -import got from '@/utils/got'; +import ofetch from '@/utils/ofetch'; import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; export const route: Route = { - path: '/tv/topic/:id', + path: '/topic/:id', categories: ['anime'], - example: '/bangumi/tv/topic/367032', + example: '/bangumi.tv/topic/367032', parameters: { id: '话题 id, 在话题页面地址栏查看' }, features: { requireConfig: false, @@ -31,7 +31,7 @@ async function handler(ctx) { // bangumi.tv未提供获取小组话题的API,因此仍需要通过抓取网页来获取 const topicID = ctx.req.param('id'); const link = `https://bgm.tv/group/topic/${topicID}`; - const { data: html } = await got(link); + const html = await ofetch(link); const $ = load(html); const title = $('#pageHeader h1').text(); const latestReplies = $('.row_reply') diff --git a/lib/routes/bangumi/tv/group/topic.ts b/lib/routes/bangumi.tv/group/topic.ts similarity index 54% rename from lib/routes/bangumi/tv/group/topic.ts rename to lib/routes/bangumi.tv/group/topic.ts index 95f9b37aada29d..2ba94387fc73be 100644 --- a/lib/routes/bangumi/tv/group/topic.ts +++ b/lib/routes/bangumi.tv/group/topic.ts @@ -1,14 +1,14 @@ import { Route } from '@/types'; import cache from '@/utils/cache'; -import got from '@/utils/got'; +import ofetch from '@/utils/ofetch'; import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; -const base_url = 'https://bgm.tv'; +const baseUrl = 'https://bgm.tv'; export const route: Route = { - path: '/tv/group/:id', + path: '/group/:id', categories: ['anime'], - example: '/bangumi/tv/group/boring', + example: '/bangumi.tv/group/boring', parameters: { id: '小组 id, 在小组页面地址栏查看' }, features: { requireConfig: false, @@ -30,29 +30,29 @@ export const route: Route = { async function handler(ctx) { const groupID = ctx.req.param('id'); - const link = `${base_url}/group/${groupID}/forum`; - const { data: html } = await got(link); + const link = `${baseUrl}/group/${groupID}/forum`; + const html = await ofetch(link); const $ = load(html); const title = 'Bangumi - ' + $('.SecondaryNavTitle').text(); const items = await Promise.all( $('.topic_list .topic') .toArray() - .map(async (elem) => { - const link = new URL($('.subject a', elem).attr('href'), base_url).href; - const fullText = await cache.tryGet(link, async () => { - const { data: html } = await got(link); + .map((elem) => { + const link = new URL($('.subject a', elem).attr('href'), baseUrl).href; + return cache.tryGet(link, async () => { + const html = await ofetch(link); const $ = load(html); - return $('.postTopic .topic_content').html(); + const fullText = $('.postTopic .topic_content').html(); + const summary = 'Reply: ' + $('.posts', elem).text(); + return { + link, + title: $('.subject a', elem).attr('title'), + pubDate: parseDate($('.lastpost .time', elem).text()), + description: fullText ? summary + '${item.ImageContent.Description}
`;
+ }
+ return {
+ title: item.ImageContent.Title,
+ description,
+ link: `${apiUrl}${item.ImageContent.BackstageUrl}`,
+ author: item.ImageContent.Copyright,
+ pubDate: timezone(parseDate(ssd, 'YYYYMMDD_HHmm'), 0),
+ };
+ });
return {
title: 'Bing每日壁纸',
- link: 'https://cn.bing.com/',
- item: data.images.map((item) => ({
- title: item.copyright,
- description: ``,
- link: item.copyrightlink,
- pubDate: timezone(parseDate(item.fullstartdate), 0),
- })),
+ link: apiUrl,
+ description: 'Bing每日壁纸',
+ item: items,
};
}
diff --git a/lib/routes/bing/namespace.ts b/lib/routes/bing/namespace.ts
index 173fa4a65cca81..abaf432718e02f 100644
--- a/lib/routes/bing/namespace.ts
+++ b/lib/routes/bing/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'Bing',
url: 'cn.bing.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/biodiscover/index.ts b/lib/routes/biodiscover/index.ts
index afd6e69cf50338..0b71e5ef4de1b6 100644
--- a/lib/routes/biodiscover/index.ts
+++ b/lib/routes/biodiscover/index.ts
@@ -24,11 +24,11 @@ async function handler(ctx) {
const $ = load(response.data);
const items = $('.new_list .newList_box')
- .map((_, item) => ({
+ .toArray()
+ .map((item) => ({
pubDate: parseDate($(item).find('.news_flow_tag .times').text().trim()),
link: 'http://www.biodiscover.com' + $(item).find('h2 a').attr('href'),
- }))
- .toArray();
+ }));
return {
title: '生物探索 - ' + $('.header li.sel a').text(),
diff --git a/lib/routes/biodiscover/namespace.ts b/lib/routes/biodiscover/namespace.ts
index c40450888e5840..e81481e842ebeb 100644
--- a/lib/routes/biodiscover/namespace.ts
+++ b/lib/routes/biodiscover/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'biodiscover.com 生物探索',
url: 'www.biodiscover.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/bioone/namespace.ts b/lib/routes/bioone/namespace.ts
index 2b4d0a772a3f47..f2a193208ed7b8 100644
--- a/lib/routes/bioone/namespace.ts
+++ b/lib/routes/bioone/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'BioOne',
url: 'bioone.org',
+ lang: 'en',
};
diff --git a/lib/routes/biquge/namespace.ts b/lib/routes/biquge/namespace.ts
index 215a860e0d3906..f98c1b3feb32a7 100644
--- a/lib/routes/biquge/namespace.ts
+++ b/lib/routes/biquge/namespace.ts
@@ -24,4 +24,5 @@ export const namespace: Namespace = {
| [https://www.ibiquge.info](https://www.ibiquge.info) | 爱笔楼 |
| [https://www.ishuquge.com](https://www.ishuquge.com) | 书趣阁 |
| [https://www.mayiwxw.com](https://www.mayiwxw.com) | 蚂蚁文学 |`,
+ lang: 'zh-CN',
};
diff --git a/lib/routes/bit/namespace.ts b/lib/routes/bit/namespace.ts
index 3879be0523e216..0566caa96cb222 100644
--- a/lib/routes/bit/namespace.ts
+++ b/lib/routes/bit/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '北京理工大学',
url: 'cs.bit.edu.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/bitbucket/namespace.ts b/lib/routes/bitbucket/namespace.ts
index f414ea13c0ca27..a5607f530fa05a 100644
--- a/lib/routes/bitbucket/namespace.ts
+++ b/lib/routes/bitbucket/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'Bitbucket',
url: 'bitbucket.com',
+ lang: 'en',
};
diff --git a/lib/routes/bitget/announcement.ts b/lib/routes/bitget/announcement.ts
new file mode 100644
index 00000000000000..499816a48e6da9
--- /dev/null
+++ b/lib/routes/bitget/announcement.ts
@@ -0,0 +1,201 @@
+import { DataItem, Route, ViewType } from '@/types';
+import ofetch from '@/utils/ofetch';
+import { load } from 'cheerio';
+import cache from '@/utils/cache';
+import { BitgetResponse } from './type';
+import { parseDate } from '@/utils/parse-date';
+import { config } from '@/config';
+
+const handler: Route['handler'] = async (ctx) => {
+ const baseUrl = 'https://www.bitget.com';
+ const announcementApiUrl = `${baseUrl}/v1/msg/push/stationLetterNew`;
+ const { type, lang = 'zh-CN' } = ctx.req.param<'/bitget/announcement/:type/:lang?'>();
+ const languageCode = lang.replace('-', '_');
+ const headers = {
+ Referer: baseUrl,
+ accept: 'application/json, text/plain, */*',
+ 'content-type': 'application/json;charset=UTF-8',
+ language: languageCode,
+ locale: languageCode,
+ };
+ const pageSize = ctx.req.query('limit') ?? '10';
+
+ // stationLetterType: 0 表示全部通知,02 表示新币上线,01 表示最新活动,06 表示最新公告
+ const reqBody: {
+ pageSize: string;
+ openUnread: number;
+ stationLetterType: string;
+ isPre: boolean;
+ lastEndId: null;
+ languageType: number;
+ excludeStationLetterType?: string;
+ } = {
+ pageSize,
+ openUnread: 0,
+ stationLetterType: '0',
+ isPre: false,
+ lastEndId: null,
+ languageType: 1,
+ };
+
+ // 根据 type 判断 reqBody 的 stationLetterType 的值
+ switch (type) {
+ case 'new-listing':
+ reqBody.stationLetterType = '02';
+ break;
+
+ case 'latest-activities':
+ reqBody.stationLetterType = '01';
+ break;
+
+ case 'new-announcement':
+ reqBody.stationLetterType = '06';
+ break;
+
+ case 'all':
+ reqBody.stationLetterType = '0';
+ reqBody.excludeStationLetterType = '00';
+ break;
+
+ default:
+ throw new Error('Invalid type');
+ }
+
+ const response = (await cache.tryGet(
+ `bitget:announcement:${type}:${pageSize}:${lang}`,
+ async () => {
+ const result = await ofetch 和 中无用的内容
+ newsContent.find('p, span, strong').each(function () {
+ const element = content(this);
+ const text = element.text().trim();
+
+ // 删除没有有用文本的元素,防止空元素被保留
+ if (text === '') {
+ element.remove();
+ } else {
+ // 去除多余的嵌套标签,但保留其内容
+ element.replaceWith(text);
+ }
+ });
+
+ // 清理后的内容转换为文本
+ const cleanedDescription = newsContent.text().trim();
+
+ // 提取并格式化发布时间
+ item.description = cleanedDescription;
+ item.pubDate = timezone(parseDate(content('.info').text().replace('发布时间:', '').trim()), +8);
+
+ return item;
+ })
+ )
+ );
+
+ return {
+ title: `北京邮电大学教务处 - ${pageTitle}`,
+ link: currentUrl,
+ item: items,
+ };
+}
diff --git a/lib/routes/bupt/namespace.ts b/lib/routes/bupt/namespace.ts
index 8d7475341a2aa6..6a0dca8e8498db 100644
--- a/lib/routes/bupt/namespace.ts
+++ b/lib/routes/bupt/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '北京邮电大学',
url: 'bupt.edu.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/byau/namespace.ts b/lib/routes/byau/namespace.ts
index 9b05692137cca1..e256a5556e3bee 100644
--- a/lib/routes/byau/namespace.ts
+++ b/lib/routes/byau/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '黑龙江八一农垦大学',
url: 'byau.edu.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/byteclicks/namespace.ts b/lib/routes/byteclicks/namespace.ts
index f6932376ef668c..159f94cf99d87a 100644
--- a/lib/routes/byteclicks/namespace.ts
+++ b/lib/routes/byteclicks/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '字节点击',
url: 'byteclicks.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/bytes/namespace.ts b/lib/routes/bytes/namespace.ts
index 99b9f6beb30dfb..7b040e171a07eb 100644
--- a/lib/routes/bytes/namespace.ts
+++ b/lib/routes/bytes/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'ui.dev',
url: 'bytes.dev',
+ lang: 'en',
};
diff --git a/lib/routes/c114/namespace.ts b/lib/routes/c114/namespace.ts
index 3c44a5c483146f..dd5c3be2afd3ae 100644
--- a/lib/routes/c114/namespace.ts
+++ b/lib/routes/c114/namespace.ts
@@ -3,4 +3,7 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'C114 通信网',
url: 'c114.com.cn',
+ categories: ['new-media'],
+ description: '',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/c114/roll.ts b/lib/routes/c114/roll.ts
index ae22d3c79884fa..2e4835cad7a7b1 100644
--- a/lib/routes/c114/roll.ts
+++ b/lib/routes/c114/roll.ts
@@ -1,4 +1,5 @@
import { Route } from '@/types';
+
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
@@ -6,78 +7,106 @@ import timezone from '@/utils/timezone';
import { parseDate } from '@/utils/parse-date';
import iconv from 'iconv-lite';
-export const route: Route = {
- path: '/roll',
- categories: ['new-media'],
- example: '/c114/roll',
- parameters: {},
- features: {
- requireConfig: false,
- requirePuppeteer: false,
- antiCrawler: false,
- supportBT: false,
- supportPodcast: false,
- supportScihub: false,
- },
- radar: [
- {
- source: ['c114.com.cn/news/roll.asp', 'c114.com.cn/'],
- },
- ],
- name: '滚动新闻',
- maintainers: ['nczitzk'],
- handler,
- url: 'c114.com.cn/news/roll.asp',
-};
+export const handler = async (ctx) => {
+ const { original = 'false' } = ctx.req.param();
+ const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 15;
-async function handler(ctx) {
const rootUrl = 'https://www.c114.com.cn';
- const currentUrl = `${rootUrl}/news/roll.asp`;
+ const currentUrl = new URL(`news/roll.asp${original === 'true' ? `?o=true` : ''}`, rootUrl).href;
- const response = await got({
- method: 'get',
- url: currentUrl,
+ const { data: response } = await got(currentUrl, {
responseType: 'buffer',
});
- const $ = load(iconv.decode(response.data, 'gbk'));
+ const $ = load(iconv.decode(response, 'gbk'));
+
+ const language = $('html').prop('lang');
- let items = $('.new_list_c h6 a')
- .slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 50)
+ let items = $('div.new_list_c')
+ .slice(0, limit)
.toArray()
.map((item) => {
item = $(item);
return {
- title: item.text(),
- link: item.attr('href'),
+ title: item.find('h6 a').text(),
+ pubDate: timezone(parseDate(item.find('div.new_list_time').text(), ['HH:mm', 'M/D']), +8),
+ link: new URL(item.find('h6 a').prop('href'), rootUrl).href,
+ author: item.find('div.new_list_author').text().trim(),
+ language,
};
});
items = await Promise.all(
items.map((item) =>
cache.tryGet(item.link, async () => {
- const detailResponse = await got({
- method: 'get',
- url: item.link,
+ const { data: detailResponse } = await got(item.link, {
responseType: 'buffer',
});
- const content = load(iconv.decode(detailResponse.data, 'gbk'));
+ const $$ = load(iconv.decode(detailResponse, 'gbk'));
- item.description = content('.text').html();
- item.author = content('.author').first().text().replace('C114通信网 ', '');
- item.pubDate = timezone(parseDate(content('.r_time').text()), +8);
- item.category = content('meta[name="keywords"]').attr('content').split(',');
+ const title = $$('h1').text();
+ const description = $$('div.text').html();
+
+ item.title = title;
+ item.description = description;
+ item.pubDate = timezone(parseDate($$('div.r_time').text(), 'YYYY/M/D HH:mm'), +8);
+ item.author = $$('div.author').first().text().trim();
+ item.content = {
+ html: description,
+ text: $$('.text').text(),
+ };
+ item.language = language;
return item;
})
)
);
+ const image = new URL($('div.top2-1 a img').prop('src'), rootUrl).href;
+
return {
title: $('title').text(),
+ description: $('meta[name="description"]').prop('content'),
link: currentUrl,
item: items,
+ allowEmpty: true,
+ image,
+ author: $('p.top1-1-1 a').first().text(),
+ language,
};
-}
+};
+
+export const route: Route = {
+ path: '/roll/:original?',
+ name: '滚动资讯',
+ url: 'c114.com.cn',
+ maintainers: ['nczitzk'],
+ handler,
+ example: '/c114/roll',
+ parameters: { original: '只看原创,可选 true 和 false,默认为 false' },
+ description: '',
+ categories: ['new-media'],
+
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: false,
+ supportRadar: true,
+ supportBT: false,
+ supportPodcast: false,
+ supportScihub: false,
+ },
+ radar: [
+ {
+ source: ['c114.com.cn/news/roll.asp'],
+ target: (_, url) => {
+ url = new URL(url);
+ const original = url.searchParams.get('o');
+
+ return `/roll${original ? `/${original}` : ''}`;
+ },
+ },
+ ],
+};
diff --git a/lib/routes/caai/namespace.ts b/lib/routes/caai/namespace.ts
index aa50eac2ef5bec..bf586f31ff981f 100644
--- a/lib/routes/caai/namespace.ts
+++ b/lib/routes/caai/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '中国人工智能学会',
url: 'caai.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/caam/namespace.ts b/lib/routes/caam/namespace.ts
index 237b6afc0eb3b3..fe756ac0a46881 100644
--- a/lib/routes/caam/namespace.ts
+++ b/lib/routes/caam/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '中国汽车工业协会',
url: 'caam.org.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/caareviews/namespace.ts b/lib/routes/caareviews/namespace.ts
index 8aa1f4c722e75a..5ebfc5382e2d82 100644
--- a/lib/routes/caareviews/namespace.ts
+++ b/lib/routes/caareviews/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'caa.reviews',
url: 'caareviews.org',
+ lang: 'en',
};
diff --git a/lib/routes/cags/edu/index.ts b/lib/routes/cags/edu/index.ts
new file mode 100644
index 00000000000000..f5e71ed6d6fb7e
--- /dev/null
+++ b/lib/routes/cags/edu/index.ts
@@ -0,0 +1,84 @@
+import ofetch from '@/utils/ofetch';
+import { Route } from '@/types';
+import { parseDate } from '@/utils/parse-date';
+import timezone from '@/utils/timezone';
+
+const host = 'https://edu.cags.ac.cn';
+
+const titles = {
+ tzgg: '通知公告',
+ ywjx: '要闻简讯',
+ zs_bss: '博士生招生',
+ zs_sss: '硕士生招生',
+ zs_dxsxly: '大学生夏令营',
+};
+
+export const route: Route = {
+ path: '/edu/:category',
+ categories: ['university'],
+ example: '/cags/edu/tzgg',
+ parameters: {
+ category: '通知频道,可选 tzgg/ywjx/zs_bss/zs_sss/zs_dxsxly',
+ },
+ features: {
+ antiCrawler: false,
+ requireConfig: false,
+ requirePuppeteer: false,
+ supportBT: false,
+ supportPodcast: false,
+ supportScihub: false,
+ },
+ name: '研究生院',
+ maintainers: ['Chikit-L'],
+ radar: [
+ {
+ source: ['edu.cags.ac.cn/'],
+ },
+ ],
+ handler,
+ description: `
+| 通知公告 | 要闻简讯 | 博士生招生 | 硕士生招生 | 大学生夏令营 |
+| -------- | -------- | ---------- | ---------- | ------------ |
+| tzgg | ywjx | zs_bss | zs_sss | zs_dxsxly |
+`,
+};
+
+async function handler(ctx) {
+ const category = ctx.req.param('category');
+ const title = titles[category];
+
+ if (!title) {
+ throw new Error(`Invalid category: ${category}`);
+ }
+
+ const API_URL = `${host}/api/cms/cmsNews/pageByCmsNavBarId/${category}/1/10/0`;
+ const response = await ofetch(API_URL);
+ const data = response.data;
+
+ const items = data.map((item) => {
+ const id = item.id;
+ const title = item.title;
+
+ let pubDate = null;
+ if (item.publishDate) {
+ pubDate = parseDate(item.publishDate, 'YYYY-MM-DD');
+ pubDate = timezone(pubDate, 8);
+ }
+
+ const link = `${host}/#/dky/view/id=${id}/barId=${category}`;
+
+ return {
+ title,
+ description: item.introduction,
+ link,
+ guid: link,
+ pubDate,
+ };
+ });
+
+ return {
+ title,
+ link: `${host}/#/dky/list/barId=${category}/cmsNavCategory=1`,
+ item: items,
+ };
+}
diff --git a/lib/routes/cags/namespace.ts b/lib/routes/cags/namespace.ts
new file mode 100644
index 00000000000000..abb34c06bfc9fb
--- /dev/null
+++ b/lib/routes/cags/namespace.ts
@@ -0,0 +1,9 @@
+import type { Namespace } from '@/types';
+
+export const namespace: Namespace = {
+ name: 'Chinese Academy of Geological Sciences',
+ url: 'cags.cgs.gov.cn',
+ zh: {
+ name: '中国地质科学院',
+ },
+};
diff --git a/lib/routes/cahkms/namespace.ts b/lib/routes/cahkms/namespace.ts
index 8d51e2b858146e..941ab11a9aca19 100644
--- a/lib/routes/cahkms/namespace.ts
+++ b/lib/routes/cahkms/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '全国港澳研究会',
url: 'cahkms.org',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/caijing/namespace.ts b/lib/routes/caijing/namespace.ts
index 6f2acf580b363c..4483b85f997c7c 100644
--- a/lib/routes/caijing/namespace.ts
+++ b/lib/routes/caijing/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '财经网',
url: 'roll.caijing.com.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/caixin/article.ts b/lib/routes/caixin/article.ts
index 9ab71ea06953fb..1fd50c39203a2d 100644
--- a/lib/routes/caixin/article.ts
+++ b/lib/routes/caixin/article.ts
@@ -42,7 +42,7 @@ async function handler() {
audio_image_url: item.audio_image_url,
}));
- const items = await Promise.all(list.map((item) => parseArticle(item, cache.tryGet)));
+ const items = await Promise.all(list.map((item) => cache.tryGet(item.link, () => parseArticle(item))));
return {
title: '财新网 - 首页',
diff --git a/lib/routes/caixin/blog.ts b/lib/routes/caixin/blog.ts
index 4c5b6849ef0795..90997606aca5bc 100644
--- a/lib/routes/caixin/blog.ts
+++ b/lib/routes/caixin/blog.ts
@@ -67,8 +67,7 @@ async function handler(ctx) {
pubDate: parseDate(item.publishTime, 'x'),
}));
- const items = await Promise.all(posts.map((item) => parseBlogArticle(item, cache.tryGet)));
-
+ const items = await Promise.all(posts.map((item) => cache.tryGet(item.link, () => parseBlogArticle(item))));
return {
title: `财新博客 - ${authorName}`,
link,
@@ -90,7 +89,7 @@ async function handler(ctx) {
link: item.postUrl.replace('http://', 'https://'),
pubDate: parseDate(item.publishTime, 'x'),
}));
- const items = await Promise.all(posts.map((item) => parseBlogArticle(item, cache.tryGet)));
+ const items = await Promise.all(posts.map((item) => cache.tryGet(item.link, () => parseBlogArticle(item))));
return {
title: `财新博客 - 全部`,
diff --git a/lib/routes/caixin/category.ts b/lib/routes/caixin/category.ts
index 60f17b21aad69f..7dc456c37c1145 100644
--- a/lib/routes/caixin/category.ts
+++ b/lib/routes/caixin/category.ts
@@ -83,7 +83,7 @@ async function handler(ctx) {
audio_image_url: item.pict.imgs[0].url,
}));
- const items = await Promise.all(list.map((item) => parseArticle(item, cache.tryGet)));
+ const items = await Promise.all(list.map((item) => cache.tryGet(item.link, () => parseArticle(item))));
return {
title,
diff --git a/lib/routes/caixin/latest.ts b/lib/routes/caixin/latest.ts
index 599e0d8ad2198f..da585b75c2e293 100644
--- a/lib/routes/caixin/latest.ts
+++ b/lib/routes/caixin/latest.ts
@@ -1,16 +1,14 @@
-import { Route } from '@/types';
-import { getCurrentPath } from '@/utils/helpers';
-const __dirname = getCurrentPath(import.meta.url);
+import { Route, ViewType } from '@/types';
+import { getFulltext } from './utils-fulltext';
import cache from '@/utils/cache';
import got from '@/utils/got';
-import { load } from 'cheerio';
-import { art } from '@/utils/render';
-import path from 'node:path';
+import { parseArticle } from './utils';
export const route: Route = {
path: '/latest',
- categories: ['traditional-media'],
+ categories: ['traditional-media', 'popular'],
+ view: ViewType.Articles,
example: '/caixin/latest',
parameters: {},
features: {
@@ -30,10 +28,10 @@ export const route: Route = {
maintainers: ['tpnonthealps'],
handler,
url: 'caixin.com/',
- description: `说明:此 RSS feed 会自动抓取财新网的最新文章,但不包含 FM 及视频内容。`,
+ description: `说明:此 RSS feed 会自动抓取财新网的最新文章,但不包含 FM 及视频内容。订阅用户可根据文档设置环境变量后,在url传入\`fulltext=\`以解锁全文。`,
};
-async function handler() {
+async function handler(ctx) {
const { data } = await got('https://gateway.caixin.com/api/dataplatform/scroll/index');
const list = data.data.articleList
@@ -48,21 +46,20 @@ async function handler() {
const rss = await Promise.all(
list.map((item) =>
cache.tryGet(`caixin:latest:${item.link}`, async () => {
- const entry_r = await got(item.link);
- const $ = load(entry_r.data);
-
// desc
- const desc = art(path.join(__dirname, 'templates/article.art'), {
- item,
- $,
- });
+ const desc = await parseArticle(item);
- item.description = desc;
+ if (ctx.req.query('fulltext') === 'true') {
+ const authorizedFullText = await getFulltext(item.link);
+ item.description = authorizedFullText === '' ? desc.description : authorizedFullText;
+ } else {
+ item.description = desc.description;
+ }
// prevent cache coliision with /caixin/article and /caixin/:column/:category
// since those have podcasts
item.guid = `caixin:latest:${item.link}`;
- return item;
+ return { ...desc, ...item };
})
)
);
diff --git a/lib/routes/caixin/namespace.ts b/lib/routes/caixin/namespace.ts
index 8099f11d8d4ef5..b00547f4e02ca1 100644
--- a/lib/routes/caixin/namespace.ts
+++ b/lib/routes/caixin/namespace.ts
@@ -3,5 +3,6 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '财新博客',
url: 'caixin.com',
- description: `> 网站部分内容需要付费订阅,RSS 仅做更新提醒,不含付费内容。`,
+ description: `> 网站部分内容需要付费订阅,RSS 仅做更新提醒,不含付费内容。若需要得到付费内容全文,请使用订阅账户在手机网页版登录,然后设置\`CAIXIN_COOKIE\`为至少包含cookie中的以下字段: \`SA_USER_UID\`, \`SA_USER_UNIT\`, \`SA_USER_DEVICE_TYPE\`, \`USER_LOGIN_CODE\``,
+ lang: 'zh-CN',
};
diff --git a/lib/routes/caixin/utils-fulltext.ts b/lib/routes/caixin/utils-fulltext.ts
new file mode 100644
index 00000000000000..c866784508e529
--- /dev/null
+++ b/lib/routes/caixin/utils-fulltext.ts
@@ -0,0 +1,51 @@
+import crypto from 'crypto';
+import { hextob64, KJUR } from 'jsrsasign';
+import ofetch from '@/utils/ofetch';
+import { config } from '@/config';
+
+// The following constant is extracted from this script: https://file.caixin.com/pkg/cx-pay-layer/js/wap.js?v=5.15.421933 . It is believed to contain no sensitive information.
+// Refer to this discussion for further explanation: https://github.com/DIYgod/RSSHub/pull/17231
+const rsaPrivateKey =
+ '-----BEGIN PRIVATE KEY-----MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCLci8q2u3NGFyFlMUwjCP91PsvGjHdRAq9fmqZLxvue+n+RhzNxnKKYOv35pLgFKWXsGq2TV+5Xrv6xZgNx36IUkqbmrO+eCa8NFmti04wvMfG3DCNdKA7Lue880daNiK3BOhlQlZPykUXt1NftMNS/z+e70W+Vpv1ZxCx5BipqZkdoceM3uin0vUQmqmHqjxi5qKUuov90dXLaMxypCA0TDsIDnX8RPvPtqKff1p2TMW2a0XYe7CPYhRggaQMpmo0TcFutgrM1Vywyr2TPxYR+H/tpuuWRET7tUIQykBYoO1WKfL2dX6cxarjAJfnYnod3sMzppHouyp8Pt7gHVG7AgMBAAECggEAEFshSy6IrADKgWSUyH/3jMNZfwnchW6Ar/9O847CAPQJ2yhQIpa/Qpnhs58Y5S2myqcHrUBgFPcWp3BbyGn43naAh8XahWHEcVjWl/N6BV9vM1UKYN0oGikDR3dljCBDbCIoPBBO3WcFOaXoIpaqPmbwCG1aSdwQyPUA0UzG08eDbuHK6L5jvbe3xv5kLpWTVddrocW+SakbZRAX1Ykp7IujOce235nM7GOfoq4b8jmK5CLg6VIZGQV20wnn9YxuFOndRSjneFberzfzBMhVLpPsQ16M2xDLpZaDTggZnq2L6nZygds8Hda++ga3WbD3TcgjJNYuENu1S88IowYhSQKBgQDFqRA+38mo6KsxVDCNWcuEk2hSq8NEUzRHJpS7/QjZmEIYpFzDXgSGwhZJ0WNsQtaxJeBbc7B/OOqh8TL1reLl5AdTimS1OLHWVf/MUsLVS7Y82hx/hpYWxZnRSq41oI3P8FO/53FiQMYo2wbwqF6uQjB1y8h58aqL3OYpTH/5xQKBgQC0mobALJ+bU4nCPzkVDZuD6RyNWPwS1aE3+925wDSN2rJ0iLIb4N5czWZmHb66VlAtfGbp2q+amsCV4r6UR19A/y8k9SFB0mdtxix6mjEfaGhVJm4B1mkvsn0OHMAanKkohUvCjROQc3sziyp2gqSEQ98G7//VMPx/3dhgyQpVfwKBgQCycsqu6N0n+D6t/0MCKiJaI7bYhCd7JN8aqVM4UN5PjG2Hz8PLwbK2cr0qkbaAA+vN7NMb3Vtn0FvMLnUCZqVlRTP0EQqQrYmoZuXUcpdhd8QkNgnqe/g+wND4qcKTucquA1uo8mtj9/Su5+bhGDC6hBk6D+uDZFHDiX/loyIavQKBgQCXF6AcLjjpDZ52b8Yloti0JtXIOuXILAlQeNoqiG5vLsOVUrcPM7VUFlLQo5no8kTpiOXgRyAaS9VKkAO4sW0zR0n9tUY5dvkokV6sw0rNZ9/BPQFTcDlXug99OvhMSzwJtlqHTNdNRg+QM6E2vF0+ejmf6DEz/mN/5e0cK5UFqQKBgCR2hVfbRtDz9Cm/P8chPqaWFkH5ulUxBpc704Igc6bVH5DrEoWo6akbeJixV2obAZO3sFyeJqBUqaCvqG17Xei6jn3Hc3WMz9nLrAJEI9BTCfwvuxCOyY0IxqAAYT28xYv42I4+ADT/PpCq2Dj5u43X0dapAjZBZDfVVis7q1Bw-----END PRIVATE KEY-----';
+
+export async function getFulltext(url: string) {
+ if (!config.caixin.cookie) {
+ return;
+ }
+ if (!/(\d+)\.html/.test(url)) {
+ return;
+ }
+ const articleID = url.match(/(\d+)\.html/)[1];
+
+ const nonce = crypto.randomUUID().replaceAll('-', '').toUpperCase();
+
+ const userID = config.caixin.cookie
+ .split(';')
+ .find((e) => e.includes('SA_USER_UID'))
+ ?.split('=')[1]; //
+
+ const rawString = `id=${articleID}&uid=${userID}&${nonce}=nonce`;
+
+ const sig = new KJUR.crypto.Signature({ alg: 'SHA256withRSA' });
+ sig.init(rsaPrivateKey);
+ sig.updateString(rawString);
+ const sigValueHex = hextob64(sig.sign());
+
+ const isWeekly = url.includes('weekly');
+ const res = await ofetch(`https://gateway.caixin.com/api/newauth/checkAuthByIdJsonp`, {
+ params: {
+ type: 1,
+ page: isWeekly ? 0 : 1,
+ rand: Math.random(),
+ id: articleID,
+ },
+ headers: {
+ 'X-Sign': encodeURIComponent(sigValueHex),
+ 'X-Nonce': encodeURIComponent(nonce),
+ Cookie: config.caixin.cookie,
+ },
+ });
+
+ const { content = '', pictureList } = JSON.parse(res.data.match(/resetContentInfo\((.*)\)/)[1]);
+ return content + (pictureList ? pictureList.map((e) => ` {{ content }} ${source} ';
+ if (data.contents && Array.isArray(data.contents)) {
+ html += data.contents.map((data) => extractArticleContent(data)).join('');
+ }
+ html += '
'),
embed: post.embed,
- // embed.$type "app.bsky.embed.record#view" and "app.bsky.embed.recordWithMedia#view"
- // are not handled
+ // embed.$type "app.bsky.embed.record#view" and "app.bsky.embed.recordWithMedia#view" are not handled
}),
author: post.author.displayName,
pubDate: parseDate(post.record.createdAt),
@@ -62,9 +81,10 @@ async function handler(ctx) {
title: `${profile.displayName} (@${profile.handle}) — Bluesky`,
description: profile.description?.replaceAll('\n', ' '),
link: `https://bsky.app/profile/${profile.handle}`,
- image: profile.banner,
+ image: profile.avatar,
icon: profile.avatar,
logo: profile.avatar,
item: items,
+ allowEmpty: true,
};
}
diff --git a/lib/routes/bsky/templates/post.art b/lib/routes/bsky/templates/post.art
index 06b42960a4de92..80d41fea1844ca 100644
--- a/lib/routes/bsky/templates/post.art
+++ b/lib/routes/bsky/templates/post.art
@@ -3,11 +3,20 @@
{{ /if }}
{{ if embed }}
- {{ if embed.$type == 'app.bsky.embed.images#view'}}
+ {{ if embed.$type === 'app.bsky.embed.images#view' }}
{{ each embed.images i }}
{{ /each }}
- {{ else if embed.$type == 'app.bsky.embed.external#view' }}
+ {{ else if embed.$type === 'app.bsky.embed.video#view' }}
+
+ {{ else if embed.$type === 'app.bsky.embed.external#view' }}
{{ embed.external.title }}
{{ embed.external.description }}
diff --git a/lib/routes/bsky/utils.ts b/lib/routes/bsky/utils.ts
index 3ffe67f638bf3f..6aff1ab5eca50c 100644
--- a/lib/routes/bsky/utils.ts
+++ b/lib/routes/bsky/utils.ts
@@ -28,14 +28,14 @@ const getProfile = (did, tryGet) =>
});
// https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/feed/getAuthorFeed.json
-const getAuthorFeed = (did, tryGet) =>
+const getAuthorFeed = (did, filter, tryGet) =>
tryGet(
- `bsky:authorFeed:${did}`,
+ `bsky:authorFeed:${did}:${filter}`,
async () => {
const { data } = await got('https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed', {
searchParams: {
actor: did,
- filter: 'posts_and_author_threads',
+ filter,
limit: 30,
},
});
diff --git a/lib/routes/bt0/mv.ts b/lib/routes/bt0/mv.ts
new file mode 100644
index 00000000000000..36ef8d33e95c3c
--- /dev/null
+++ b/lib/routes/bt0/mv.ts
@@ -0,0 +1,65 @@
+import { Route } from '@/types';
+import InvalidParameterError from '@/errors/types/invalid-parameter';
+import { doGot, genSize } from './util';
+
+export const route: Route = {
+ path: '/mv/:number/:domain?',
+ categories: ['multimedia'],
+ example: '/bt0/mv/35575567/2',
+ parameters: { number: '影视详情id, 网页路径为`/mv/{id}.html`其中的id部分, 一般为8位纯数字', domain: '数字1-9, 比如1表示请求域名为 1bt0.com, 默认为 2' },
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: false,
+ supportBT: true,
+ supportPodcast: false,
+ supportScihub: false,
+ },
+ radar: [
+ {
+ source: ['2bt0.com/mv/'],
+ },
+ ],
+ name: '影视资源下载列表',
+ maintainers: ['miemieYaho'],
+ handler,
+};
+
+async function handler(ctx) {
+ const domain = ctx.req.param('domain') ?? '2';
+ const number = ctx.req.param('number');
+ if (!/^[1-9]$/.test(domain)) {
+ throw new InvalidParameterError('Invalid domain');
+ }
+ const regex = /^\d{6,}$/;
+ if (!regex.test(number)) {
+ throw new InvalidParameterError('Invalid number');
+ }
+
+ const host = `https://www.${domain}bt0.com`;
+ const _link = `${host}/prod/core/system/getVideoDetail/${number}`;
+
+ const data = (await doGot(0, host, _link)).data;
+ const items = Object.values(data.ecca).flatMap((item) =>
+ item.map((i) => ({
+ title: i.zname,
+ guid: i.zname,
+ description: `${i.zname}[${i.zsize}]`,
+ link: `${host}/tr/${i.id}.html`,
+ pubDate: i.ezt,
+ enclosure_type: 'application/x-bittorrent',
+ enclosure_url: i.zlink,
+ enclosure_length: genSize(i.zsize),
+ category: strsJoin(i.zqxd, i.text_html, i.audio_html, i.new === 1 ? '新' : ''),
+ }))
+ );
+ return {
+ title: data.title,
+ link: `${host}/mv/${number}.html`,
+ item: items,
+ };
+}
+
+function strsJoin(...strings) {
+ return strings.filter((str) => str !== '').join(',');
+}
diff --git a/lib/routes/bt0/namespace.ts b/lib/routes/bt0/namespace.ts
new file mode 100644
index 00000000000000..b1d66d4447f9fe
--- /dev/null
+++ b/lib/routes/bt0/namespace.ts
@@ -0,0 +1,10 @@
+import type { Namespace } from '@/types';
+
+export const namespace: Namespace = {
+ name: '不太灵影视',
+ url: '2bt0.com',
+ description: `:::tip
+ (1-9)bt0.com 都指向同一个
+ :::`,
+ lang: 'zh-CN',
+};
diff --git a/lib/routes/bt0/tlist.ts b/lib/routes/bt0/tlist.ts
new file mode 100644
index 00000000000000..481eab960d8b4b
--- /dev/null
+++ b/lib/routes/bt0/tlist.ts
@@ -0,0 +1,67 @@
+import { Route } from '@/types';
+import InvalidParameterError from '@/errors/types/invalid-parameter';
+import { doGot, genSize } from './util';
+import { parseRelativeDate } from '@/utils/parse-date';
+
+const categoryDict = {
+ 1: '电影',
+ 2: '电视剧',
+ 3: '近日热门',
+ 4: '本周热门',
+ 5: '本月热门',
+};
+
+export const route: Route = {
+ path: '/tlist/:sc/:domain?',
+ categories: ['multimedia'],
+ example: '/bt0/tlist/1',
+ parameters: { sc: '分类(1-5), 1:电影, 2:电视剧, 3:近日热门, 4:本周热门, 5:本月热门', domain: '数字1-9, 比如1表示请求域名为 1bt0.com, 默认为 2' },
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: false,
+ supportBT: true,
+ supportPodcast: false,
+ supportScihub: false,
+ },
+ radar: [
+ {
+ source: ['2bt0.com/tlist/'],
+ },
+ ],
+ name: '最新资源列表',
+ maintainers: ['miemieYaho'],
+ handler,
+};
+
+async function handler(ctx) {
+ const domain = ctx.req.param('domain') ?? '2';
+ const sc = ctx.req.param('sc');
+ if (!/^[1-9]$/.test(domain)) {
+ throw new InvalidParameterError('Invalid domain');
+ }
+ if (!/^[1-5]$/.test(sc)) {
+ throw new InvalidParameterError('Invalid sc');
+ }
+
+ const host = `https://www.${domain}bt0.com`;
+ const _link = `${host}/prod/core/system/getTList?sc=${sc}`;
+
+ const data = await doGot(0, host, _link);
+ const items = data.data.list.map((item) => ({
+ title: item.zname,
+ guid: item.zname,
+ description: `《${item.title}》 导演: ${item.daoyan}
编剧: ${item.bianji}
演员: ${item.yanyuan}
简介: ${item.conta.trim()}`,
+ link: host + item.aurl,
+ pubDate: item.eztime.endsWith('前') ? parseRelativeDate(item.eztime) : item.eztime,
+ enclosure_type: 'application/x-bittorrent',
+ enclosure_url: item.zlink,
+ enclosure_length: genSize(item.zsize),
+ itunes_item_image: item.epic,
+ }));
+ return {
+ title: `不太灵-最新资源列表-${categoryDict[sc]}`,
+ link: `${host}/tlist/${sc}_1.html`,
+ item: items,
+ };
+}
diff --git a/lib/routes/bt0/util.ts b/lib/routes/bt0/util.ts
new file mode 100644
index 00000000000000..1cb34774af4275
--- /dev/null
+++ b/lib/routes/bt0/util.ts
@@ -0,0 +1,51 @@
+import { CookieJar } from 'tough-cookie';
+import got from '@/utils/got';
+const cookieJar = new CookieJar();
+
+async function doGot(num, host, link) {
+ if (num > 4) {
+ throw new Error('The number of attempts has exceeded 5 times');
+ }
+ const response = await got.get(link, {
+ cookieJar,
+ });
+ const data = response.data;
+ if (typeof data === 'string') {
+ const regex = /document\.cookie\s*=\s*"([^"]*)"/;
+ const match = data.match(regex);
+ if (!match) {
+ throw new Error('api error');
+ }
+ cookieJar.setCookieSync(match[1], host);
+ return doGot(++num, host, link);
+ }
+ return data;
+}
+
+const genSize = (sizeStr) => {
+ // 正则表达式,用于匹配数字和单位 GB 或 MB
+ const regex = /^(\d+(\.\d+)?)\s*(gb|mb)$/i;
+ const match = sizeStr.match(regex);
+
+ if (!match) {
+ return 0;
+ }
+
+ const value = Number.parseFloat(match[1]);
+ const unit = match[3].toUpperCase();
+
+ let bytes;
+ switch (unit) {
+ case 'GB':
+ bytes = Math.floor(value * 1024 * 1024 * 1024);
+ break;
+ case 'MB':
+ bytes = Math.floor(value * 1024 * 1024);
+ break;
+ default:
+ bytes = 0;
+ }
+ return bytes;
+};
+
+export { doGot, genSize };
diff --git a/lib/routes/btzj/namespace.ts b/lib/routes/btzj/namespace.ts
index f5847215f8b37d..f6fea9869ef79c 100644
--- a/lib/routes/btzj/namespace.ts
+++ b/lib/routes/btzj/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'BT 之家',
url: 'btbtt20.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/buaa/jiaowu.ts b/lib/routes/buaa/jiaowu.ts
new file mode 100644
index 00000000000000..91890bfe466f9f
--- /dev/null
+++ b/lib/routes/buaa/jiaowu.ts
@@ -0,0 +1,124 @@
+import { Data, Route } from '@/types';
+import { Context } from 'hono';
+import cache from '@/utils/cache';
+import got from '@/utils/got';
+import { load } from 'cheerio';
+import { parseDate } from '@/utils/parse-date';
+import timezone from '@/utils/timezone';
+
+const BASE_URL = 'https://jiaowu.buaa.edu.cn/bhjwc2.0/index/newsList.do';
+
+export const route: Route = {
+ path: '/jiaowu/:cddm?',
+ name: '教务部',
+ url: 'jiaowu.buaa.edu.cn',
+ maintainers: ['OverflowCat'],
+ handler,
+ example: '/buaa/jiaowu/02',
+ parameters: {
+ cddm: '菜单代码,可以是 2 位或者 4 位,默认为 `02`(通知公告)',
+ },
+ description: `:::tip
+
+菜单代码(\`cddm\`)应填写链接中调用的 newsList 接口的参数,可以是 2 位或者 4 位数字。若为 2 位,则为 \`fcd\`(父菜单);若为 4 位,则为 \`cddm\`(菜单代码),其中前 2 位为 \`fcd\`。
+示例:
+
+1. 新闻快讯页面的链接中 \`onclick="javascript:onNewsList('03');return false;"\`,对应的路径参数为 \`03\`,完整路由为 \`/buaa/jiaowu/03\`;
+2. 通知公告 > 公示专区页面的链接中 \`onclick="javascript:onNewsList2('0203','2');return false;"\`,对应的路径参数为 \`0203\`,完整路由为 \`/buaa/jiaowu/0203\`。
+:::`,
+ categories: ['university'],
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: false,
+ supportRadar: false,
+ supportBT: false,
+ supportPodcast: false,
+ supportScihub: false,
+ },
+};
+
+async function handler(ctx: Context): Promise {
+ let cddm = ctx.req.param('cddm');
+ if (!cddm) {
+ cddm = '02';
+ }
+ if (cddm.length !== 2 && cddm.length !== 4) {
+ throw new Error('cddm should be 2 or 4 digits');
+ }
+
+ const { title, list } = await getList(BASE_URL, {
+ id: '',
+ fcdTab: cddm.slice(0, 2),
+ cddmTab: cddm,
+ xsfsTab: '2',
+ tplbid: '',
+ xwid: '',
+ zydm: '',
+ zymc: '',
+ yxdm: '',
+ pyzy: '',
+ szzqdm: '',
+ });
+ const item = await getItems(list);
+
+ return {
+ title,
+ item,
+ link: BASE_URL,
+ author: '北航教务部',
+ language: 'zh-CN',
+ };
+}
+
+function getArticleUrl(onclick?: string) {
+ if (!onclick) {
+ return null;
+ }
+ const xwid = onclick.match(/'(\d+)'/)?.at(1);
+ if (!xwid) {
+ return null;
+ }
+ return `http://jiaowu.buaa.edu.cn/bhjwc2.0/index/newsView.do?xwid=${xwid}`;
+}
+
+async function getList(url: string | URL, form: Record
');
+ item.author = $descrption('#main > div.content > div.search_height > span.search_con').text().split('发布者:').at(-1) || '教务部';
+ return item;
+ })
+ )
+ );
+}
diff --git a/lib/routes/buaa/lib/space/newbook.ts b/lib/routes/buaa/lib/space/newbook.ts
new file mode 100644
index 00000000000000..810b8ef3cf5882
--- /dev/null
+++ b/lib/routes/buaa/lib/space/newbook.ts
@@ -0,0 +1,171 @@
+import { Data, DataItem, Route } from '@/types';
+import { Context } from 'hono';
+import got from '@/utils/got';
+import { parseDate } from '@/utils/parse-date';
+import timezone from '@/utils/timezone';
+import cache from '@/utils/cache';
+import { art } from '@/utils/render';
+import path from 'node:path';
+import { getCurrentPath } from '@/utils/helpers';
+const __dirname = getCurrentPath(import.meta.url);
+
+interface Book {
+ bibId: string;
+ inBooklist: number;
+ thumb: string;
+ holdingTypes: string[];
+ author: string;
+ callno: string[];
+ docType: string;
+ onSelfDate: string;
+ groupId: string;
+ isbn: string;
+ inDate: number;
+ language: string;
+ bibNo: string;
+ abstract: string;
+ docTypeDesc: string;
+ title: string;
+ itemCount: number;
+ tags: string[];
+ circCount: number;
+ pub_year: string;
+ classno: string;
+ publisher: string;
+ holdings: string;
+}
+
+interface Holding {
+ classMethod: string;
+ callNo: string;
+ inDate: number;
+ shelfMark: string;
+ itemsCount: number;
+ barCode: string;
+ tempLocation: string;
+ circStatus: number;
+ itemId: number;
+ vol: string;
+ library: string;
+ itemStatus: string;
+ itemsAvailable: number;
+ location: string;
+ extenStatus: number;
+ donatorId: null;
+ status: string;
+ locationName: string;
+}
+
+interface Info {
+ _id: string;
+ imageUrl: string | null;
+ authorInfo: string;
+ catalog: string | null;
+ content: string;
+ title: string;
+}
+
+export const route: Route = {
+ path: String.raw`/lib/space/:path{newbook.*}`,
+ name: '图书馆 - 新书速递',
+ url: 'space.lib.buaa.edu.cn/mspace/newBook',
+ maintainers: ['OverflowCat'],
+ example: '/buaa/lib/space/newbook/',
+ handler,
+ description: `可通过参数进行筛选:\`/buaa/lib/space/newbook/key1=value1&key2=value2...\`
+- \`dcpCode\`:学科分类代码
+ - 例:
+ - 工学:\`08\`
+ - 工学 > 计算机 > 计算机科学与技术:\`080901\`
+ - 默认值:\`nolimit\`
+ - 注意事项:不可与 \`clsNo\` 同时使用。
+- \`clsNo\`:中图分类号
+ - 例:
+ - 计算机科学:\`TP3\`
+ - 默认值:无
+ - 注意事项
+ - 不可与 \`dcpCode\` 同时使用。
+ - 此模式下获取不到上架日期。
+- \`libCode\`:图书馆代码
+ - 例:
+ - 本馆:\`00000\`
+ - 默认值:无
+ - 注意事项:只有本馆一个可选值。
+- \`locaCode\`:馆藏地代码
+ - 例:
+ - 五层西-中文新书借阅室(A-Z类):\`02503\`
+ - 默认值:无
+ - 注意事项:必须与 \`libCode\` 同时使用。
+
+示例:
+- \`buaa/lib/space/newbook\` 为所有新书
+- \`buaa/lib/space/newbook/clsNo=U&libCode=00000&locaCode=60001\` 为沙河教2图书馆所有中图分类号为 U(交通运输)的书籍
+`,
+ categories: ['university'],
+
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: false,
+ supportRadar: false,
+ supportBT: false,
+ supportPodcast: false,
+ supportScihub: false,
+ },
+};
+
+async function handler(ctx: Context): Promise {
+ const path = ctx.req.param('path');
+ const i = path.indexOf('/');
+ const params = i === -1 ? '' : path.slice(i + 1);
+ const searchParams = new URLSearchParams(params);
+ const dcpCode = searchParams.get('dcpCode'); // Filter by subject (discipline code)
+ const clsNo = searchParams.get('clsNo'); // Filter by class (Chinese Library Classification)
+ if (dcpCode && clsNo) {
+ throw new Error('dcpCode and clsNo cannot be used at the same time');
+ }
+ searchParams.set('pageSize', '100'); // Max page size. Any larger value will be ignored
+ searchParams.set('page', '1');
+ !dcpCode && !clsNo && searchParams.set('dcpCode', 'nolimit'); // No classification filter
+ const url = `https://space.lib.buaa.edu.cn/meta-local/opac/new/100/${clsNo ? 'byclass' : 'bysubject'}?${searchParams.toString()}`;
+ const { data } = await got(url);
+ const list = (data?.data?.dataList || []) as Book[];
+ const item = await Promise.all(list.map(async (item: Book) => await getItem(item)));
+ const res: Data = {
+ title: '北航图书馆 - 新书速递',
+ item,
+ description: '北京航空航天大学图书馆新书速递',
+ language: 'zh-CN',
+ link: 'https://space.lib.buaa.edu.cn/space/newBook',
+ author: '北京航空航天大学图书馆',
+ allowEmpty: true,
+ image: 'https://lib.buaa.edu.cn/apple-touch-icon.png',
+ };
+ return res;
+}
+
+async function getItem(item: Book): Promise书籍信息
+简介
+
+
+{{if info.authorInfo}}
+
+ ISBN {{item.isbn}}
+ 语言 {{item.language}}
+类型 {{item.docTypeDesc}} 作者简介
+馆藏信息
+{{if item.onSelfDate}}
+上架时间:
+
+馆藏地点
+
+ {{each holdings holding}}
+
+{{if info.catalog}}
+
+ 所属馆藏地 {{holding.location}}
+ 索书号 {{holding.callNo}}
+ 条码号 {{holding.barCode}}
+ 编号 {{holding.itemId}}
+
+ {{/each}}
+书刊状态
+ {{holding.status}}
+ 目录
+
`).join('') : '');
+}
diff --git a/lib/routes/caixin/utils.ts b/lib/routes/caixin/utils.ts
index fca14c8761199c..0bd0334e3bb421 100644
--- a/lib/routes/caixin/utils.ts
+++ b/lib/routes/caixin/utils.ts
@@ -6,44 +6,44 @@ import { load } from 'cheerio';
import { art } from '@/utils/render';
import path from 'node:path';
-const parseArticle = (item, tryGet) =>
- /\.blog\.caixin\.com$/.test(new URL(item.link).hostname)
- ? parseBlogArticle(item, tryGet)
- : tryGet(item.link, async () => {
- const { data: response } = await got(item.link);
-
- const $ = load(response);
-
- item.description = art(path.join(__dirname, 'templates/article.art'), {
- item,
- $,
- });
-
- if (item.audio) {
- item.itunes_item_image = item.audio_image_url;
- item.enclosure_url = item.audio;
- item.enclosure_type = 'audio/mpeg';
- }
-
- return item;
- });
-
-const parseBlogArticle = (item, tryGet) =>
- tryGet(item.link, async () => {
- const response = await got(item.link);
- const $ = load(response.data);
- const article = $('#the_content').removeAttr('style');
- article.find('img').removeAttr('style');
- article
- .find('p')
- // Non-breaking space U+00A0, ` ` in html
- // element.children[0].data === $(element, article).text()
- .filter((_, element) => element.children[0].data === String.fromCharCode(160))
- .remove();
-
- item.description = article.html();
+const parseArticle = async (item) => {
+ if (/\.blog\.caixin\.com$/.test(new URL(item.link).hostname)) {
+ return parseBlogArticle(item);
+ } else {
+ const { data: response } = await got(item.link);
+
+ const $ = load(response);
+
+ item.description = art(path.join(__dirname, 'templates/article.art'), {
+ item,
+ $,
+ });
+
+ if (item.audio) {
+ item.itunes_item_image = item.audio_image_url;
+ item.enclosure_url = item.audio;
+ item.enclosure_type = 'audio/mpeg';
+ }
return item;
- });
+ }
+};
+
+const parseBlogArticle = async (item) => {
+ const response = await got(item.link);
+ const $ = load(response.data);
+ const article = $('#the_content').removeAttr('style');
+ article.find('img').removeAttr('style');
+ article
+ .find('p')
+ // Non-breaking space U+00A0, ` ` in html
+ // element.children[0].data === $(element, article).text()
+ .filter((_, element) => element.children[0].data === String.fromCodePoint(160))
+ .remove();
+
+ item.description = article.html();
+
+ return item;
+};
export { parseArticle, parseBlogArticle };
diff --git a/lib/routes/caixinglobal/namespace.ts b/lib/routes/caixinglobal/namespace.ts
index bf1e6f1aed92b6..8d4880dd8533cc 100644
--- a/lib/routes/caixinglobal/namespace.ts
+++ b/lib/routes/caixinglobal/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'Caixin Global',
url: 'caixinglobal.com',
+ lang: 'en',
};
diff --git a/lib/routes/camchina/namespace.ts b/lib/routes/camchina/namespace.ts
index d2b1d471d88720..468a798bd240ff 100644
--- a/lib/routes/camchina/namespace.ts
+++ b/lib/routes/camchina/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '中国管理现代化研究会',
url: 'cste.org.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cankaoxiaoxi/namespace.ts b/lib/routes/cankaoxiaoxi/namespace.ts
index 13e98f6d4ecffc..8520e948b2e095 100644
--- a/lib/routes/cankaoxiaoxi/namespace.ts
+++ b/lib/routes/cankaoxiaoxi/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '参考消息',
url: 'cankaoxiaoxi.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cara/constant.ts b/lib/routes/cara/constant.ts
new file mode 100644
index 00000000000000..4f1b7bec4fcdd1
--- /dev/null
+++ b/lib/routes/cara/constant.ts
@@ -0,0 +1,5 @@
+export const HOST = 'https://cara.app';
+
+export const API_HOST = `${HOST}/api`;
+
+export const CDN_HOST = 'https://cdn.cara.app';
diff --git a/lib/routes/cara/likes.ts b/lib/routes/cara/likes.ts
new file mode 100644
index 00000000000000..bee254f5a6730d
--- /dev/null
+++ b/lib/routes/cara/likes.ts
@@ -0,0 +1,56 @@
+import type { Data, DataItem, Route } from '@/types';
+import type { PostsResponse } from './types';
+import { customFetch, parseUserData } from './utils';
+import { API_HOST, CDN_HOST, HOST } from './constant';
+import { getCurrentPath } from '@/utils/helpers';
+import { art } from '@/utils/render';
+import { parseDate } from '@/utils/parse-date';
+import path from 'node:path';
+
+const __dirname = getCurrentPath(import.meta.url);
+
+export const route: Route = {
+ path: ['/likes/:user'],
+ categories: ['social-media', 'popular'],
+ example: '/cara/likes/fengz',
+ parameters: { user: 'username' },
+ name: 'Likes',
+ maintainers: ['KarasuShin'],
+ handler,
+ radar: [
+ {
+ source: ['cara.app/:user', 'cara.app/:user/*'],
+ target: '/likes/:user',
+ },
+ ],
+};
+
+async function handler(ctx): Promise {
+ const user = ctx.req.param('user');
+ const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 15;
+ const userInfo = await parseUserData(user);
+
+ const api = `${API_HOST}/posts/getAllLikesByUser?slug=${userInfo.slug}&take=${limit}`;
+
+ const timelineResponse = await customFetch
');
+
+ const dataItem: DataItem = {
+ title: res.data.title || res.data.content,
+ pubDate: parseDate(res.data.createdAt),
+ link: `${HOST}/post/${item.postId}`,
+ description,
+ };
+
+ return dataItem;
+}
diff --git a/lib/routes/cartoonmad/namespace.ts b/lib/routes/cartoonmad/namespace.ts
index 3768ff781d78ac..d267db9e5f53a4 100644
--- a/lib/routes/cartoonmad/namespace.ts
+++ b/lib/routes/cartoonmad/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '動漫狂',
url: 'cartoonmad.com',
+ lang: 'zh-TW',
};
diff --git a/lib/routes/cas/namespace.ts b/lib/routes/cas/namespace.ts
index 68c7a05f8bd51b..5cd8916c10051b 100644
--- a/lib/routes/cas/namespace.ts
+++ b/lib/routes/cas/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '中国科学院',
url: 'www.cas.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/casssp/namespace.ts b/lib/routes/casssp/namespace.ts
index c20708fdc076f7..1ba8855ad0f181 100644
--- a/lib/routes/casssp/namespace.ts
+++ b/lib/routes/casssp/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '中国科学学与科技政策研究会',
url: 'casssp.org.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cast/namespace.ts b/lib/routes/cast/namespace.ts
index 38c0e21b5aa509..8c1d0f7eb9a53e 100644
--- a/lib/routes/cast/namespace.ts
+++ b/lib/routes/cast/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '中国科学技术协会',
url: 'cast.org.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cau/namespace.ts b/lib/routes/cau/namespace.ts
index eb1d7960a4e418..6d069cf3c41888 100644
--- a/lib/routes/cau/namespace.ts
+++ b/lib/routes/cau/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '中国农业大学',
url: 'ciee.cau.edu.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/caus/namespace.ts b/lib/routes/caus/namespace.ts
index 0f48c051073d3a..52f68de483d495 100644
--- a/lib/routes/caus/namespace.ts
+++ b/lib/routes/caus/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '加美财经',
url: 'caus.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cbaigui/namespace.ts b/lib/routes/cbaigui/namespace.ts
index 47cb2b970082be..5095c30cb37728 100644
--- a/lib/routes/cbaigui/namespace.ts
+++ b/lib/routes/cbaigui/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '纪妖',
url: 'cbaigui.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cbaigui/utils.ts b/lib/routes/cbaigui/utils.ts
index 713d0d67622558..eb43835f155aa9 100644
--- a/lib/routes/cbaigui/utils.ts
+++ b/lib/routes/cbaigui/utils.ts
@@ -8,7 +8,7 @@ const GetFilterId = async (type, name) => {
const { data: filterResponse } = await got(filterApiUrl);
- return filterResponse.filter((f) => f.name === name).pop()?.id ?? undefined;
+ return filterResponse.findLast((f) => f.name === name)?.id ?? undefined;
};
export { rootUrl, apiSlug, GetFilterId };
diff --git a/lib/routes/cbc/namespace.ts b/lib/routes/cbc/namespace.ts
index 7d44268c5054fa..198963f7c307d5 100644
--- a/lib/routes/cbc/namespace.ts
+++ b/lib/routes/cbc/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'Canadian Broadcasting Corporation',
url: 'cbc.ca',
+ lang: 'en',
};
diff --git a/lib/routes/cbirc/namespace.ts b/lib/routes/cbirc/namespace.ts
index 3ccd490487bff4..c21982ff11e9d2 100644
--- a/lib/routes/cbirc/namespace.ts
+++ b/lib/routes/cbirc/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '中国银行保险监督管理委员会',
url: 'cbirc.gov.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cbnweek/namespace.ts b/lib/routes/cbnweek/namespace.ts
index d122276f65f6f5..8f78fc29e263af 100644
--- a/lib/routes/cbnweek/namespace.ts
+++ b/lib/routes/cbnweek/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '第一财经杂志',
url: 'cbnweek.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cbpanet/index.ts b/lib/routes/cbpanet/index.ts
new file mode 100644
index 00000000000000..c0eb76f92caa9e
--- /dev/null
+++ b/lib/routes/cbpanet/index.ts
@@ -0,0 +1,380 @@
+import { Route } from '@/types';
+
+import cache from '@/utils/cache';
+import got from '@/utils/got';
+import { load } from 'cheerio';
+import timezone from '@/utils/timezone';
+import { parseDate } from '@/utils/parse-date';
+
+export const handler = async (ctx) => {
+ const { bigId = '2', smallId = '11' } = ctx.req.param();
+ const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 15;
+
+ const rootUrl = 'http://www.cbpanet.com';
+ const currentUrl = new URL(`dzp_news.aspx?bigid=${bigId}&smallid=${smallId}`, rootUrl).href;
+
+ const { data: response } = await got(currentUrl);
+
+ const $ = load(response);
+
+ const language = $('html').prop('lang');
+
+ let items = $('div.divmore ul li')
+ .slice(0, limit)
+ .toArray()
+ .map((item) => {
+ item = $(item);
+
+ const a = item.find('div.zxcont1 a');
+
+ return {
+ title: a.text(),
+ pubDate: parseDate(item.find('div.zxtime1').text(), 'YY/MM/DD'),
+ link: new URL(a.prop('href'), rootUrl).href,
+ };
+ });
+
+ items = await Promise.all(
+ items.map((item) =>
+ cache.tryGet(item.link, async () => {
+ const { data: detailResponse } = await got(item.link);
+
+ const $$ = load(detailResponse);
+
+ const description = $$('div.newscont').html();
+
+ item.title = $$('div.newstlt').text();
+ item.description = description;
+ item.pubDate = timezone(
+ parseDate(
+ $$('div.newstime')
+ .text()
+ .replace(/发布时间:/, ''),
+ 'YYYY/M/D HH:mm:ss'
+ ),
+ +8
+ );
+ item.content = {
+ html: description,
+ text: $$('div.newscont').text(),
+ };
+ return item;
+ })
+ )
+ );
+
+ const title = $('title').text();
+ const image = new URL($('div#logo img').prop('src'), rootUrl).href;
+
+ return {
+ title,
+ description: title.split(/-/).pop(),
+ link: currentUrl,
+ item: items,
+ allowEmpty: true,
+ image,
+ author: title.split(/-/)[0],
+ language,
+ };
+};
+
+export const route: Route = {
+ path: '/dzp_news/:bigId?/:smallId?',
+ name: '资讯',
+ url: 'cbpanet.com',
+ maintainers: ['nczitzk'],
+ handler,
+ example: '/cbpanet/dzp_news/2/11',
+ parameters: {
+ bigId: '分类 id,默认为 `2`,即行业资讯,可在对应分类页 URL 中找到',
+ smallId: '子分类 id,默认为 `11`,即行业资讯,可在对应分类页 URL 中找到',
+ },
+ description: `:::tip
+ 若订阅 [行业资讯](http://www.cbpanet.com/dzp_news.aspx?bigid=2&smallid=11),网址为 \`http://www.cbpanet.com/dzp_news.aspx?bigid=2&smallid=11\`。截取 \`https://www.cbpanet.com/\` 的 \`bigid\` 和 \`smallid\` 的部分作为参数填入,此时路由为 [\`/cbpanet/dzp_news/4/15\`](https://rsshub.app/cbpanet/dzp_news/4/15)。
+ :::
+
+ 更多分类
+
+ #### [协会](http://www.cbpanet.com/dzp_xiehui.aspx)
+
+ | [协会介绍](http://www.cbpanet.com/dzp_news.aspx?bigid=1&smallid=1) | [协会章程](http://www.cbpanet.com/dzp_news.aspx?bigid=1&smallid=2) | [理事会](http://www.cbpanet.com/dzp_news.aspx?bigid=1&smallid=3) | [内设机构](http://www.cbpanet.com/dzp_news.aspx?bigid=1&smallid=4) | [协会通知](http://www.cbpanet.com/dzp_news.aspx?bigid=1&smallid=5) | [协会活动](http://www.cbpanet.com/dzp_news.aspx?bigid=1&smallid=6) |
+ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ---------------------------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ |
+ | [1/1](https://rsshub.app/cbpanet/dzp_news/1/1) | [1/2](https://rsshub.app/cbpanet/dzp_news/1/2) | [1/3](https://rsshub.app/cbpanet/dzp_news/1/3) | [1/4](https://rsshub.app/cbpanet/dzp_news/1/4) | [1/5](https://rsshub.app/cbpanet/dzp_news/1/5) | [1/6](https://rsshub.app/cbpanet/dzp_news/1/6) |
+
+ | [出版物](http://www.cbpanet.com/dzp_news.aspx?bigid=1&smallid=7) | [会员权利与义务](http://www.cbpanet.com/dzp_news.aspx?bigid=1&smallid=30) |
+ | ---------------------------------------------------------------- | ------------------------------------------------------------------------- |
+ | [1/7](https://rsshub.app/cbpanet/dzp_news/1/7) | [1/30](https://rsshub.app/cbpanet/dzp_news/1/30) |
+
+ #### [行业资讯](http://www.cbpanet.com/dzp_news_list.aspx)
+
+ | [国内资讯](http://www.cbpanet.com/dzp_news.aspx?bigid=2&smallid=8) | [海外资讯](http://www.cbpanet.com/dzp_news.aspx?bigid=2&smallid=9) | [企业新闻](http://www.cbpanet.com/dzp_news.aspx?bigid=2&smallid=10) | [行业资讯](http://www.cbpanet.com/dzp_news.aspx?bigid=2&smallid=11) | [热点聚焦](http://www.cbpanet.com/dzp_news.aspx?bigid=2&smallid=43) | [今日推荐](http://www.cbpanet.com/dzp_news.aspx?bigid=2&smallid=44) |
+ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- |
+ | [2/8](https://rsshub.app/cbpanet/dzp_news/2/8) | [2/9](https://rsshub.app/cbpanet/dzp_news/2/9) | [2/10](https://rsshub.app/cbpanet/dzp_news/2/10) | [2/11](https://rsshub.app/cbpanet/dzp_news/2/11) | [2/43](https://rsshub.app/cbpanet/dzp_news/2/43) | [2/44](https://rsshub.app/cbpanet/dzp_news/2/44) |
+
+ #### [原料信息](http://www.cbpanet.com/dzp_yuanliao.aspx)
+
+ | [价格行情](http://www.cbpanet.com/dzp_news.aspx?bigid=3&smallid=12) | [分析预测](http://www.cbpanet.com/dzp_news.aspx?bigid=3&smallid=13) | [原料信息](http://www.cbpanet.com/dzp_news.aspx?bigid=3&smallid=40) | [热点聚焦](http://www.cbpanet.com/dzp_news.aspx?bigid=3&smallid=45) |
+ | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- |
+ | [3/12](https://rsshub.app/cbpanet/dzp_news/3/12) | [3/13](https://rsshub.app/cbpanet/dzp_news/3/13) | [3/40](https://rsshub.app/cbpanet/dzp_news/3/40) | [3/45](https://rsshub.app/cbpanet/dzp_news/3/45) |
+
+ #### [法规标准](http://www.cbpanet.com/dzp_fagui.aspx)
+
+ | [法规资讯](http://www.cbpanet.com/dzp_news.aspx?bigid=4&smallid=15) | [法律法规](http://www.cbpanet.com/dzp_news.aspx?bigid=4&smallid=16) | [国内标准](http://www.cbpanet.com/dzp_news.aspx?bigid=4&smallid=14) | [国外标准](http://www.cbpanet.com/dzp_news.aspx?bigid=4&smallid=17) | [法规聚焦](http://www.cbpanet.com/dzp_news.aspx?bigid=4&smallid=46) | [今日推荐](http://www.cbpanet.com/dzp_news.aspx?bigid=4&smallid=47) |
+ | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- |
+ | [4/15](https://rsshub.app/cbpanet/dzp_news/4/15) | [4/16](https://rsshub.app/cbpanet/dzp_news/4/16) | [4/14](https://rsshub.app/cbpanet/dzp_news/4/14) | [4/17](https://rsshub.app/cbpanet/dzp_news/4/17) | [4/46](https://rsshub.app/cbpanet/dzp_news/4/46) | [4/47](https://rsshub.app/cbpanet/dzp_news/4/47) |
+
+ #### [技术专区](http://www.cbpanet.com/dzp_jishu.aspx)
+
+ | [产品介绍](http://www.cbpanet.com/dzp_news.aspx?bigid=5&smallid=18) | [科技成果](http://www.cbpanet.com/dzp_news.aspx?bigid=5&smallid=19) | [学术论文](http://www.cbpanet.com/dzp_news.aspx?bigid=5&smallid=20) | [资料下载](http://www.cbpanet.com/dzp_news.aspx?bigid=5&smallid=21) | [专家](http://www.cbpanet.com/dzp_news.aspx?bigid=5&smallid=50) | [民间智库](http://www.cbpanet.com/dzp_news.aspx?bigid=5&smallid=57) |
+ | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------- |
+ | [5/18](https://rsshub.app/cbpanet/dzp_news/5/18) | [5/19](https://rsshub.app/cbpanet/dzp_news/5/19) | [5/20](https://rsshub.app/cbpanet/dzp_news/5/20) | [5/21](https://rsshub.app/cbpanet/dzp_news/5/21) | [5/50](https://rsshub.app/cbpanet/dzp_news/5/50) | [5/57](https://rsshub.app/cbpanet/dzp_news/5/57) |
+
+ #### [豆制品消费指南](http://www.cbpanet.com/dzp_zhinan.aspx)
+
+ | [膳食指南](http://www.cbpanet.com/dzp_news.aspx?bigid=6&smallid=22) | [营养成分](http://www.cbpanet.com/dzp_news.aspx?bigid=6&smallid=23) | [豆食菜谱](http://www.cbpanet.com/dzp_news.aspx?bigid=6&smallid=24) | [问与答](http://www.cbpanet.com/dzp_news.aspx?bigid=6&smallid=31) | [今日推荐](http://www.cbpanet.com/dzp_news.aspx?bigid=6&smallid=48) | [消费热点](http://www.cbpanet.com/dzp_news.aspx?bigid=6&smallid=53) |
+ | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- |
+ | [6/22](https://rsshub.app/cbpanet/dzp_news/6/22) | [6/23](https://rsshub.app/cbpanet/dzp_news/6/23) | [6/24](https://rsshub.app/cbpanet/dzp_news/6/24) | [6/31](https://rsshub.app/cbpanet/dzp_news/6/31) | [6/48](https://rsshub.app/cbpanet/dzp_news/6/48) | [6/53](https://rsshub.app/cbpanet/dzp_news/6/53) |
+
+ #### [营养与健康](http://www.cbpanet.com/dzp_yingyang.aspx)
+
+ | [大豆营养概况](http://www.cbpanet.com/dzp_news.aspx?bigid=7&smallid=25) | [大豆食品和人类健康](http://www.cbpanet.com/dzp_news.aspx?bigid=7&smallid=26) | [世界豆类日,爱豆大行动](http://www.cbpanet.com/dzp_news.aspx?bigid=7&smallid=27) | [谣言粉碎机](http://www.cbpanet.com/dzp_news.aspx?bigid=7&smallid=29) | [最新资讯](http://www.cbpanet.com/dzp_news.aspx?bigid=7&smallid=41) | [专家视点](http://www.cbpanet.com/dzp_news.aspx?bigid=7&smallid=49) |
+ | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- |
+ | [7/25](https://rsshub.app/cbpanet/dzp_news/7/25) | [7/26](https://rsshub.app/cbpanet/dzp_news/7/26) | [7/27](https://rsshub.app/cbpanet/dzp_news/7/27) | [7/29](https://rsshub.app/cbpanet/dzp_news/7/29) | [7/41](https://rsshub.app/cbpanet/dzp_news/7/41) | [7/49](https://rsshub.app/cbpanet/dzp_news/7/49) |
+
+ {{ intro }}
+{{ /if }}
+
+{{ if description }}
+ {{@ description }}
+{{ /if }}
\ No newline at end of file
diff --git a/lib/routes/ccnu/namespace.ts b/lib/routes/ccnu/namespace.ts
index dd46468ac4b5e6..e13e8112acadd8 100644
--- a/lib/routes/ccnu/namespace.ts
+++ b/lib/routes/ccnu/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '华中师范大学',
url: 'ccnu.91wllm.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/ccreports/namespace.ts b/lib/routes/ccreports/namespace.ts
index 309fd8cbe28d2f..19ebbfbf5fc26d 100644
--- a/lib/routes/ccreports/namespace.ts
+++ b/lib/routes/ccreports/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '消费者报道',
url: 'www.ccreports.com.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cctv/category.ts b/lib/routes/cctv/category.ts
index 8c416634ff375c..15eaebd67130f2 100644
--- a/lib/routes/cctv/category.ts
+++ b/lib/routes/cctv/category.ts
@@ -2,6 +2,7 @@ import { Route } from '@/types';
import getMzzlbg from './utils/mzzlbg';
import xinwen1j1 from './utils/xinwen1j1';
import getNews from './utils/news';
+import getXWLB from './xwlb';
export const route: Route = {
path: '/:category',
@@ -31,18 +32,21 @@ export const route: Route = {
async function handler(ctx) {
const category = ctx.req.param('category');
- let responseData;
- if (category === 'mzzlbg') {
- // 每周质量报告
- responseData = await getMzzlbg();
- } else if (category === 'xinwen1j1') {
- // 新闻1+1
- responseData = await xinwen1j1();
- } else {
- // 央视新闻
- responseData = await getNews(category);
- }
+ switch (category) {
+ case 'mzzlbg':
+ // 每周质量报告
+ return await getMzzlbg();
+
+ case 'xinwen1j1':
+ // 新闻1+1
+ return await xinwen1j1();
- return responseData;
+ case 'xwlb':
+ return await getXWLB();
+
+ default:
+ // 央视新闻
+ return await getNews(category);
+ }
}
diff --git a/lib/routes/cctv/namespace.ts b/lib/routes/cctv/namespace.ts
index 766fb0b88da296..1c85ef01b5780b 100644
--- a/lib/routes/cctv/namespace.ts
+++ b/lib/routes/cctv/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '央视新闻',
url: 'news.cctv.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cctv/xwlb.ts b/lib/routes/cctv/xwlb.ts
index d0218dbf37887b..121f94b6816628 100644
--- a/lib/routes/cctv/xwlb.ts
+++ b/lib/routes/cctv/xwlb.ts
@@ -9,10 +9,22 @@ import customParseFormat from 'dayjs/plugin/customParseFormat';
dayjs.extend(customParseFormat);
export const route: Route = {
- path: '/xwlb',
+ path: '/:site/:category/:name',
categories: ['traditional-media'],
- example: '/cctv/xwlb',
- parameters: {},
+ example: '/cctv/tv/lm/xwlb',
+ parameters: {
+ site: "站点, 可选值如'tv', 既'央视节目'",
+ category: "分类名, 官网对应分类, 当前可选值'lm', 既'栏目大全'",
+ name: {
+ description: "栏目名称, 可在对应栏目页面 URL 中找到, 可选值如'xwlb',既'新闻联播'",
+ options: [
+ {
+ value: 'xwlb',
+ label: '新闻联播',
+ },
+ ],
+ },
+ },
features: {
requireConfig: false,
requirePuppeteer: false,
@@ -33,12 +45,21 @@ export const route: Route = {
description: `新闻联播内容摘要。`,
};
-async function handler() {
+async function handler(ctx) {
+ const { site, category, name } = ctx.req.param();
+ let responseData;
+ if (site === 'tv' && category === 'lm' && name === 'xwlb') {
+ responseData = await getXWLB();
+ }
+ return responseData;
+}
+
+const getXWLB = async () => {
const res = await got({ method: 'get', url: 'https://tv.cctv.com/lm/xwlb/' });
const $ = load(res.data);
// 解析最新一期新闻联播的日期
const latestDate = dayjs($('.rilititle p').text(), 'YYYY-MM-DD');
- const count = [];
+ const count: number[] = [];
for (let i = 0; i < 20; i++) {
count.push(i);
}
@@ -49,13 +70,13 @@ async function handler() {
const item = {
title: `新闻联播 ${newsDate.format('YYYY/MM/DD')}`,
link: url,
- pubDate: timezone(parseDate(newsDate), +8),
+ pubDate: timezone(parseDate(newsDate.format()), +8),
description: await cache.tryGet(url, async () => {
const res = await got(url);
const content = load(res.data);
- const list = [];
- content('body li').map((i, e) => {
- e = content(e);
+ const list: string[] = [];
+ content('body li').map((i, elem) => {
+ const e = content(elem);
const href = e.find('a').attr('href');
const title = e.find('a').attr('title');
const dur = e.find('span').text();
@@ -74,4 +95,5 @@ async function handler() {
link: 'http://tv.cctv.com/lm/xwlb/',
item: resultItems,
};
-}
+};
+export default getXWLB;
diff --git a/lib/routes/cde/namespace.ts b/lib/routes/cde/namespace.ts
index 24497b5e7034df..074888104d2e82 100644
--- a/lib/routes/cde/namespace.ts
+++ b/lib/routes/cde/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '国家药品审评网站',
url: 'www.cde.org.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cdi/namespace.ts b/lib/routes/cdi/namespace.ts
index f976a667f7e950..93130b4bdcafc8 100644
--- a/lib/routes/cdi/namespace.ts
+++ b/lib/routes/cdi/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '国家高端智库 / 综合开发研究院',
url: 'cdi.com.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cdu/jwgg.ts b/lib/routes/cdu/jwgg.ts
new file mode 100644
index 00000000000000..dcd2f2be0fa2bd
--- /dev/null
+++ b/lib/routes/cdu/jwgg.ts
@@ -0,0 +1,79 @@
+import { Route } from '@/types';
+import cache from '@/utils/cache';
+import got from '@/utils/got';
+import { load } from 'cheerio';
+import { parseDate } from '@/utils/parse-date';
+import timezone from '@/utils/timezone';
+
+export const route: Route = {
+ path: '/jwgg',
+ categories: ['university'],
+ example: '/cdu/jwgg',
+ parameters: {},
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: false,
+ supportBT: false,
+ supportPodcast: false,
+ supportScihub: false,
+ },
+ radar: [
+ {
+ source: ['jw.cdu.edu.cn/'],
+ },
+ ],
+ name: '教务处通知公告',
+ maintainers: ['uuwor'],
+ handler,
+ url: 'jw.cdu.edu.cn/',
+};
+
+async function handler() {
+ const url = 'https://jw.cdu.edu.cn/jwgg.htm'; // 数据来源网页(待提取网页)
+ const response = await got.get(url);
+ const data = response.data;
+ const $ = load(data);
+ const list = $('.ListTable.dataTable.no-footer tbody tr[role="row"].odd')
+ .slice(0, 10)
+ .toArray()
+ .map((e) => {
+ const element = $(e);
+ const title = element.find('tr.odd a').text().trim(); /* 1.选择器 tr.odd a:这个选择器查找具有 class="odd" 的 元素下的 标签。
+ 2..text():该方法获取选中元素的文本内容。
+ 3..trim():用于去掉字符串前后的空格,确保得到干净的文本。*/
+ const link = element.find('tr.odd a').attr('href');
+ const date = element
+ .find('tr.odd td.columnDate')
+ .text()
+ .match(/\d{4}-\d{2}-\d{2}/);
+ const pubDate = timezone(parseDate(date), 8);
+
+ return {
+ title,
+ link: 'https://jw.cdu.edu.cn/' + link,
+ author: '成都大学教务处通知公告',
+ pubDate,
+ };
+ });
+
+ const result = await Promise.all(
+ list.map((item) =>
+ cache.tryGet(item.link, async () => {
+ const itemReponse = await got.get(item.link);
+ const data = itemReponse.data;
+ const itemElement = load(data);
+
+ item.description = itemElement('.v_news_content').html();
+
+ return item;
+ })
+ )
+ );
+
+ return {
+ title: '成大教务处通知公告',
+ link: url,
+ item: result,
+ };
+}
diff --git a/lib/routes/cdu/namespace.ts b/lib/routes/cdu/namespace.ts
new file mode 100644
index 00000000000000..cefdf80b32d652
--- /dev/null
+++ b/lib/routes/cdu/namespace.ts
@@ -0,0 +1,7 @@
+import type { Namespace } from '@/types';
+
+export const namespace: Namespace = {
+ name: '成都大学',
+ url: 'www.cdu.edu.cn',
+ lang: 'zh-CN',
+};
diff --git a/lib/routes/cdzjryb/namespace.ts b/lib/routes/cdzjryb/namespace.ts
index 26c405bc0113b5..7245dd0ffb3526 100644
--- a/lib/routes/cdzjryb/namespace.ts
+++ b/lib/routes/cdzjryb/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '成都住建蓉 e 办',
url: 'zw.cdzjryb.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cebbank/namespace.ts b/lib/routes/cebbank/namespace.ts
index 2bafdede77b80c..360b3424ee677c 100644
--- a/lib/routes/cebbank/namespace.ts
+++ b/lib/routes/cebbank/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '中国光大银行',
url: 'cebbank.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/ceph/blog.ts b/lib/routes/ceph/blog.ts
new file mode 100644
index 00000000000000..7c5e1dc9a2c2dd
--- /dev/null
+++ b/lib/routes/ceph/blog.ts
@@ -0,0 +1,72 @@
+import { Data, Route } from '@/types';
+import cache from '@/utils/cache';
+import got from '@/utils/got';
+import { load } from 'cheerio';
+import { parseDate } from '@/utils/parse-date';
+import { Context } from 'hono';
+
+export const route: Route = {
+ path: '/blog/:topic?',
+ categories: ['blog'],
+ example: '/ceph/blog/a11y',
+ parameters: {
+ category: 'filter blog post by category, return all posts if not specified',
+ },
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: false,
+ supportBT: false,
+ supportPodcast: false,
+ supportScihub: false,
+ },
+ radar: [
+ {
+ source: ['ceph.io/'],
+ },
+ ],
+ name: 'Blog',
+ maintainers: ['pandada8'],
+ handler,
+ url: 'ceph.io',
+};
+
+async function handler(ctx: Context): Promise {
+ const { category } = ctx.req.param();
+ const url = category ? `https://ceph.io/en/news/blog/category/${category}/` : 'https://ceph.io/en/news/blog/';
+ const response = await got.get(url);
+ const data = response.data;
+ const $ = load(data);
+ const list = $('#main .section li')
+ .toArray()
+ .map((e) => {
+ const element = $(e);
+ const title = element.find('a').text().trim();
+ const pubDate = parseDate(element.find('time').attr('datetime'));
+ return {
+ title,
+ link: new URL(element.find('a').attr('href'), 'https://ceph.io').href,
+ pubDate,
+ };
+ });
+
+ const result = await Promise.all(
+ list.map((item) =>
+ cache.tryGet(item.link, async () => {
+ const itemReponse = await got.get(item.link);
+ const data = itemReponse.data;
+ const item$ = load(data);
+
+ item.author = item$('#main section > div:nth-child(1) span').text().trim();
+ item.description = item$('#main section > div:nth-child(2) > div').html();
+ return item;
+ })
+ )
+ );
+
+ return {
+ title: 'Ceph Blog',
+ link: url,
+ item: result,
+ };
+}
diff --git a/lib/routes/ceph/namespace.ts b/lib/routes/ceph/namespace.ts
new file mode 100644
index 00000000000000..0313877bf90be7
--- /dev/null
+++ b/lib/routes/ceph/namespace.ts
@@ -0,0 +1,8 @@
+import type { Namespace } from '@/types';
+
+export const namespace: Namespace = {
+ name: 'Ceph',
+ url: 'ceph.io',
+ description: 'Ceph is an open source distributed storage system designed to evolve with data.',
+ lang: 'en',
+};
diff --git a/lib/routes/cfachina/namespace.ts b/lib/routes/cfachina/namespace.ts
index 28aa653fc42a02..74b9dc44765640 100644
--- a/lib/routes/cfachina/namespace.ts
+++ b/lib/routes/cfachina/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '中国期货业协会',
url: 'cfachina.org',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cffex/announcement.ts b/lib/routes/cffex/announcement.ts
new file mode 100644
index 00000000000000..f9d449335ed79b
--- /dev/null
+++ b/lib/routes/cffex/announcement.ts
@@ -0,0 +1,73 @@
+import { DataItem, Route } from '@/types';
+import ofetch from '@/utils/ofetch';
+import { load } from 'cheerio';
+import { parseDate } from '@/utils/parse-date';
+import timezone from '@/utils/timezone';
+import cache from '@/utils/cache';
+
+export const route: Route = {
+ path: '/announcement',
+ name: '交易所公告',
+ url: 'www.cffex.com.cn',
+ maintainers: ['ChenXiangcheng1'],
+ example: '/cffex/announcement',
+ parameters: {},
+ description: '',
+ categories: ['government'],
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: false,
+ supportBT: false,
+ supportPodcast: false,
+ supportScihub: false,
+ },
+ radar: [
+ {
+ source: ['cffex.com.cn'],
+ target: '/announcement',
+ },
+ ],
+ handler,
+};
+
+async function handler(): Promise<{ title: string; link: string; item: DataItem[] }> {
+ const baseUrl = 'http://www.cffex.com.cn';
+ const homeUrl = `${baseUrl}/jystz`;
+ const response = await ofetch(homeUrl);
+
+ // 使用 Cheerio 选择器解析 HTML
+ const $ = load(response);
+ const list = $('div.notice_list li')
+ .toArray()
+ .map((item) => {
+ item = $(item); // (Element) -> LoadedCheerio
+ const titleEle = $(item).find('a').first();
+ const dateEle = $(item).find('a').eq(1);
+
+ return {
+ title: titleEle.text().trim(),
+ link: `${baseUrl}${titleEle.attr('href')}`,
+ pubDate: timezone(parseDate(dateEle.text(), 'YYYY-MM-DD'), +8),
+ };
+ });
+
+ // (Promise) -> Promise
+ const items = await Promise.all(
+ // (Promise|null) -> Promise|null
+ list.map((item) =>
+ cache.tryGet(item.link, async () => {
+ const response = await ofetch(item.link);
+ const $ = load(response);
+ item.description = $('div.jysggnr div.nan p').eq(1)?.html();
+ return item;
+ })
+ )
+ );
+
+ return {
+ title: '中国金融期货交易所 - 交易所公告',
+ link: homeUrl,
+ item: items,
+ };
+}
diff --git a/lib/routes/cffex/namespace.ts b/lib/routes/cffex/namespace.ts
new file mode 100644
index 00000000000000..1c51fc9df2659f
--- /dev/null
+++ b/lib/routes/cffex/namespace.ts
@@ -0,0 +1,7 @@
+import type { Namespace } from '@/types';
+
+export const namespace: Namespace = {
+ name: '中国金融期货交易所',
+ url: 'cffex.com.cn',
+ lang: 'zh-CN',
+};
diff --git a/lib/routes/cfmmc/namespace.ts b/lib/routes/cfmmc/namespace.ts
index b99decd30df5a1..8091132c2806da 100644
--- a/lib/routes/cfmmc/namespace.ts
+++ b/lib/routes/cfmmc/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '中国期货市场监控中心',
url: 'cfmmc.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cfr/namespace.ts b/lib/routes/cfr/namespace.ts
index 46065157deaf08..939154e0cb5a5f 100644
--- a/lib/routes/cfr/namespace.ts
+++ b/lib/routes/cfr/namespace.ts
@@ -3,4 +3,5 @@ import { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'Council on Foreign Relations',
url: 'www.cfr.org',
+ lang: 'en',
};
diff --git a/lib/routes/cgtn/namespace.ts b/lib/routes/cgtn/namespace.ts
index 61c80e9b34a63f..3d5cfe086ee5b5 100644
--- a/lib/routes/cgtn/namespace.ts
+++ b/lib/routes/cgtn/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '中国环球电视网',
url: 'cgtn.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/chaincatcher/namespace.ts b/lib/routes/chaincatcher/namespace.ts
index e3c714a0f6f93b..babd6f0982137f 100644
--- a/lib/routes/chaincatcher/namespace.ts
+++ b/lib/routes/chaincatcher/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '链捕手 ChainCatcher',
url: 'chaincatcher.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/changba/namespace.ts b/lib/routes/changba/namespace.ts
index 44b2ca3e138db8..cbaf6b7c995a55 100644
--- a/lib/routes/changba/namespace.ts
+++ b/lib/routes/changba/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '唱吧',
url: 'changba.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/changba/user.ts b/lib/routes/changba/user.ts
index db74c3e405fcbf..0443cd036909d6 100644
--- a/lib/routes/changba/user.ts
+++ b/lib/routes/changba/user.ts
@@ -1,4 +1,4 @@
-import { Route } from '@/types';
+import { Route, ViewType } from '@/types';
import { getCurrentPath } from '@/utils/helpers';
const __dirname = getCurrentPath(import.meta.url);
@@ -11,7 +11,8 @@ const headers = { 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Ma
export const route: Route = {
path: '/:userid',
- categories: ['social-media'],
+ categories: ['social-media', 'popular'],
+ view: ViewType.Audios,
example: '/changba/skp6hhF59n48R-UpqO3izw',
parameters: { userid: '用户ID, 可在对应分享页面的 URL 中找到' },
features: {
@@ -28,7 +29,7 @@ export const route: Route = {
},
],
name: '用户',
- maintainers: [],
+ maintainers: ['kt286', 'xizeyoupan', 'pseudoyu'],
handler,
};
@@ -101,9 +102,9 @@ async function handler(ctx) {
items = items.filter(Boolean);
return {
- title: $('title').text(),
+ title: author + ' - 唱吧',
link: url,
- description: $('meta[name="description"]').attr('content') || $('title').text(),
+ description: $('meta[name="description"]').attr('content') || author + ' - 唱吧',
item: items,
image: authorimg,
itunes_author: author,
diff --git a/lib/routes/chaoxing/namespace.ts b/lib/routes/chaoxing/namespace.ts
index e054c9f6680c9a..53af60795d994b 100644
--- a/lib/routes/chaoxing/namespace.ts
+++ b/lib/routes/chaoxing/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '超星',
url: 'chaoxing.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/chaping/banner.ts b/lib/routes/chaping/banner.ts
index 359f30701dd447..5cf19fce6b1bbe 100644
--- a/lib/routes/chaping/banner.ts
+++ b/lib/routes/chaping/banner.ts
@@ -5,7 +5,7 @@ import { parseDate } from '@/utils/parse-date';
export const route: Route = {
path: '/banner',
- categories: ['new-media'],
+ categories: ['new-media', 'popular'],
example: '/chaping/banner',
parameters: {},
features: {
diff --git a/lib/routes/chaping/namespace.ts b/lib/routes/chaping/namespace.ts
index c01167a7fbbbe9..696bef8a0083f3 100644
--- a/lib/routes/chaping/namespace.ts
+++ b/lib/routes/chaping/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '差评',
url: 'chaping.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/chaping/news.ts b/lib/routes/chaping/news.ts
index 8dcbc3d5694411..7977b387300ae4 100644
--- a/lib/routes/chaping/news.ts
+++ b/lib/routes/chaping/news.ts
@@ -17,7 +17,7 @@ const titles = {
export const route: Route = {
path: '/news/:caty?',
- categories: ['new-media'],
+ categories: ['new-media', 'popular'],
example: '/chaping/news/15',
parameters: { caty: '分类,默认为全部资讯' },
features: {
diff --git a/lib/routes/chaping/newsflash.ts b/lib/routes/chaping/newsflash.ts
index 91868481e9e5ba..2f45d044f2698f 100644
--- a/lib/routes/chaping/newsflash.ts
+++ b/lib/routes/chaping/newsflash.ts
@@ -1,12 +1,12 @@
import { Route } from '@/types';
-import got from '@/utils/got';
+import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
const host = 'https://chaping.cn';
export const route: Route = {
path: '/newsflash',
- categories: ['new-media'],
+ categories: ['new-media', 'popular'],
example: '/chaping/newsflash',
parameters: {},
features: {
@@ -30,7 +30,7 @@ export const route: Route = {
async function handler() {
const newflashAPI = `${host}/api/official/information/newsflash?page=1&limit=21`;
- const response = await got(newflashAPI).json();
+ const response = await ofetch(newflashAPI);
const data = response.data;
return {
diff --git a/lib/routes/chiculture/namespace.ts b/lib/routes/chiculture/namespace.ts
index 521bad6fcee6a5..40c02be395bd62 100644
--- a/lib/routes/chiculture/namespace.ts
+++ b/lib/routes/chiculture/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '通識・現代中國',
url: 'chiculture.org.hk',
+ lang: 'zh-HK',
};
diff --git a/lib/routes/chikubi/category.ts b/lib/routes/chikubi/category.ts
new file mode 100644
index 00000000000000..b640b94f121159
--- /dev/null
+++ b/lib/routes/chikubi/category.ts
@@ -0,0 +1,41 @@
+import { Route, Data } from '@/types';
+import { getBySlug, getPostsBy } from './utils';
+
+export const route: Route = {
+ path: '/category/:keyword',
+ categories: ['multimedia'],
+ example: '/chikubi/category/nipple-lesbian',
+ parameters: { keyword: 'Keyword' },
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: false,
+ supportBT: false,
+ supportPodcast: false,
+ supportScihub: false,
+ },
+ name: 'Category',
+ maintainers: ['SnowAgar25'],
+ handler,
+ radar: [
+ {
+ title: 'Category',
+ source: ['chikubi.jp/category/:keyword'],
+ target: '/category/:keyword',
+ },
+ ],
+};
+
+async function handler(ctx): Promise {
+ const baseUrl = 'https://chikubi.jp';
+ const { keyword } = ctx.req.param();
+ const { id, name } = await getBySlug('category', keyword);
+
+ const items = await getPostsBy('category', id);
+
+ return {
+ title: `Category: ${name} - chikubi.jp`,
+ link: `${baseUrl}/category/${keyword}`,
+ item: items,
+ };
+}
diff --git a/lib/routes/chikubi/index.ts b/lib/routes/chikubi/index.ts
new file mode 100644
index 00000000000000..444d2fe0da288e
--- /dev/null
+++ b/lib/routes/chikubi/index.ts
@@ -0,0 +1,66 @@
+import { Route, Data } from '@/types';
+import { getPosts } from './utils';
+
+export const route: Route = {
+ path: '/',
+ categories: ['multimedia'],
+ example: '/chikubi',
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: false,
+ supportBT: false,
+ supportPodcast: false,
+ supportScihub: false,
+ },
+ name: '最新記事',
+ maintainers: ['SnowAgar25'],
+ handler,
+ radar: [
+ {
+ title: '最新記事',
+ source: ['chikubi.jp/'],
+ target: '/',
+ },
+ {
+ title: '殿堂',
+ source: ['chikubi.jp/best-nipple-article'],
+ target: '/best',
+ },
+ {
+ title: '動畫',
+ source: ['chikubi.jp/nipple-video'],
+ target: '/video',
+ },
+ {
+ title: 'VR',
+ source: ['chikubi.jp/nipple-video-category/cat-nipple-video-vr'],
+ target: '/vr',
+ },
+ {
+ title: '漫畫',
+ source: ['chikubi.jp/comic'],
+ target: '/comic',
+ },
+ {
+ title: '音聲',
+ source: ['chikubi.jp/voice'],
+ target: '/voice',
+ },
+ {
+ title: 'CG・イラスト',
+ source: ['chikubi.jp/cg'],
+ target: '/cg',
+ },
+ ],
+};
+
+async function handler(): Promise {
+ const items = await getPosts();
+
+ return {
+ title: '最新記事 - chikubi.jp',
+ link: 'https://chikubi.jp',
+ item: items,
+ };
+}
diff --git a/lib/routes/chikubi/namespace.ts b/lib/routes/chikubi/namespace.ts
new file mode 100644
index 00000000000000..f723c05ee7090b
--- /dev/null
+++ b/lib/routes/chikubi/namespace.ts
@@ -0,0 +1,16 @@
+import type { Namespace } from '@/types';
+
+export const namespace: Namespace = {
+ name: '乳首ふぇち',
+ url: 'chikubi.jp',
+ description: `:::tip
+The content of 乳首ふぇち is divided into two parts:
+
+Works: Only reposts official product descriptions.
+Posts: Contains the website author's thoughts and additional information.
+
+Sometimes a product may exist in both posts and works.
+Sometimes there might be only a single post without any reposted work, and vice versa.
+:::`,
+ lang: 'ja',
+};
diff --git a/lib/routes/chikubi/navigation.ts b/lib/routes/chikubi/navigation.ts
new file mode 100644
index 00000000000000..09015ea56837d1
--- /dev/null
+++ b/lib/routes/chikubi/navigation.ts
@@ -0,0 +1,66 @@
+import { Route, Data } from '@/types';
+import { getBySlug, getPostsBy, processItems } from './utils';
+import parser from '@/utils/rss-parser';
+
+export const route: Route = {
+ path: '/:keyword',
+ categories: ['multimedia'],
+ example: '/chikubi',
+ parameters: { keyword: '導覽列,見下表,默認爲最新' },
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: false,
+ supportBT: false,
+ supportPodcast: false,
+ supportScihub: false,
+ },
+ name: 'Navigation',
+ maintainers: ['SnowAgar25'],
+ handler,
+ description: `| 殿堂 | 動畫 | VR | 漫畫 | 音聲 | CG・イラスト |
+ | ---- | ----- | -- | ----- | ----- | -- |
+ | best | video | vr | comic | voice | cg |`,
+};
+
+const navigationItems = {
+ video: { url: '/nipple-video', title: '動畫' },
+ vr: { url: '/nipple-video-category/cat-nipple-video-vr', title: 'VR' },
+ comic: { url: '/comic', title: '漫畫' },
+ voice: { url: '/voice', title: '音聲' },
+ cg: { url: '/cg', title: 'CG' },
+};
+
+async function handler(ctx): Promise {
+ const keyword = ctx.req.param('keyword') ?? '';
+ const baseUrl = 'https://chikubi.jp';
+
+ if (keyword === 'best') {
+ const { id } = await getBySlug('category', 'nipple-best');
+ const items = await getPostsBy('category', id);
+
+ return {
+ title: '殿堂 - chikubi.jp',
+ link: `${baseUrl}/best-nipple-article`,
+ item: items,
+ };
+ } else {
+ const { url, title } = navigationItems[keyword];
+
+ const feed = await parser.parseURL(`${baseUrl}${url}/feed`);
+
+ const list = feed.items.map((item) => ({
+ title: item.title,
+ link: item.link,
+ }));
+
+ // 獲取內文
+ const items = await processItems(list);
+
+ return {
+ title: `${title} - chikubi.jp`,
+ link: `${baseUrl}${url}`,
+ item: items,
+ };
+ }
+}
diff --git a/lib/routes/chikubi/nipple-video-category.ts b/lib/routes/chikubi/nipple-video-category.ts
new file mode 100644
index 00000000000000..02042a34ad2212
--- /dev/null
+++ b/lib/routes/chikubi/nipple-video-category.ts
@@ -0,0 +1,49 @@
+import { Route, Data } from '@/types';
+import { processItems } from './utils';
+import parser from '@/utils/rss-parser';
+
+export const route: Route = {
+ path: '/nipple-video-category/:keyword',
+ categories: ['multimedia'],
+ example: '/chikubi/nipple-video-category/cat-nipple-video-god',
+ parameters: { keyword: 'Keyword' },
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: false,
+ supportBT: false,
+ supportPodcast: false,
+ supportScihub: false,
+ },
+ name: '動画カテゴリー',
+ maintainers: ['SnowAgar25'],
+ handler,
+ radar: [
+ {
+ title: '動画カテゴリー',
+ source: ['chikubi.jp/nipple-video-category/:keyword'],
+ target: '/nipple-video-category/:keyword',
+ },
+ ],
+};
+
+async function handler(ctx): Promise {
+ const { keyword } = ctx.req.param();
+ const baseUrl = 'https://chikubi.jp';
+ const url = `/nipple-video-category/${encodeURIComponent(keyword)}`;
+
+ const feed = await parser.parseURL(`${baseUrl}${url}/feed`);
+
+ const list = feed.items.map((item) => ({
+ title: item.title,
+ link: item.link,
+ }));
+
+ const items = await processItems(list);
+
+ return {
+ title: `動画カテゴリー: ${feed.title?.split('-')[0]} - chikubi.jp`,
+ link: `${baseUrl}${url}`,
+ item: items,
+ };
+}
diff --git a/lib/routes/chikubi/nipple-video-maker.ts b/lib/routes/chikubi/nipple-video-maker.ts
new file mode 100644
index 00000000000000..d563c8cad8e58b
--- /dev/null
+++ b/lib/routes/chikubi/nipple-video-maker.ts
@@ -0,0 +1,49 @@
+import { Route, Data } from '@/types';
+import { processItems } from './utils';
+import parser from '@/utils/rss-parser';
+
+export const route: Route = {
+ path: '/nipple-video-maker/:keyword',
+ categories: ['multimedia'],
+ example: '/chikubi/nipple-video-maker/nipple-video-maker-nh',
+ parameters: { keyword: 'Keyword' },
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: false,
+ supportBT: false,
+ supportPodcast: false,
+ supportScihub: false,
+ },
+ name: 'AVメーカー',
+ maintainers: ['SnowAgar25'],
+ handler,
+ radar: [
+ {
+ title: 'AVメーカー',
+ source: ['chikubi.jp/nipple-video-maker/:keyword'],
+ target: '/nipple-video-maker/:keyword',
+ },
+ ],
+};
+
+async function handler(ctx): Promise {
+ const { keyword } = ctx.req.param();
+ const baseUrl = 'https://chikubi.jp';
+ const url = `/nipple-video-maker/${encodeURIComponent(keyword)}`;
+
+ const feed = await parser.parseURL(`${baseUrl}${url}/feed`);
+
+ const list = feed.items.map((item) => ({
+ title: item.title,
+ link: item.link,
+ }));
+
+ const items = await processItems(list);
+
+ return {
+ title: `AVメーカー: ${feed.title?.split('-')[0]} - chikubi.jp`,
+ link: `${baseUrl}${url}`,
+ item: items,
+ };
+}
diff --git a/lib/routes/chikubi/search.ts b/lib/routes/chikubi/search.ts
new file mode 100644
index 00000000000000..57519077bca051
--- /dev/null
+++ b/lib/routes/chikubi/search.ts
@@ -0,0 +1,39 @@
+import { Route, Data } from '@/types';
+import { getPosts } from './utils';
+import got from '@/utils/got';
+
+export const route: Route = {
+ path: '/search/:keyword',
+ categories: ['multimedia'],
+ example: '/chikubi/search/ギャップ',
+ parameters: { keyword: 'Keyword' },
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: false,
+ supportBT: false,
+ supportPodcast: false,
+ supportScihub: false,
+ },
+ name: 'Search',
+ maintainers: ['SnowAgar25'],
+ handler,
+};
+
+async function handler(ctx): Promise {
+ const { keyword } = ctx.req.param();
+ const baseUrl = 'https://chikubi.jp';
+ const searchUrl = `${baseUrl}/wp-json/wp/v2/search?search=${keyword}`;
+
+ const response = await got.get(searchUrl);
+ const searchResults = response.data;
+
+ const postIds = searchResults.map((item) => item.id.toString());
+ const items = await getPosts(postIds);
+
+ return {
+ title: `Search: ${keyword} - chikubi.jp`,
+ link: `${baseUrl}/search/${encodeURIComponent(keyword)}`,
+ item: items,
+ };
+}
diff --git a/lib/routes/chikubi/tag.ts b/lib/routes/chikubi/tag.ts
new file mode 100644
index 00000000000000..148514eb2f3e48
--- /dev/null
+++ b/lib/routes/chikubi/tag.ts
@@ -0,0 +1,41 @@
+import { Route, Data } from '@/types';
+import { getBySlug, getPostsBy } from './utils';
+
+export const route: Route = {
+ path: '/tag/:keyword',
+ categories: ['multimedia'],
+ example: '/chikubi/tag/ドリームチケット',
+ parameters: { keyword: 'Keyword' },
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: false,
+ supportBT: false,
+ supportPodcast: false,
+ supportScihub: false,
+ },
+ name: 'Tag',
+ maintainers: ['SnowAgar25'],
+ handler,
+ radar: [
+ {
+ title: 'Tag',
+ source: ['chikubi.jp/tag/:keyword'],
+ target: '/tag/:keyword',
+ },
+ ],
+};
+
+async function handler(ctx): Promise {
+ const baseUrl = 'https://chikubi.jp';
+ const { keyword } = ctx.req.param();
+ const { id, name } = await getBySlug('tag', keyword);
+
+ const items = await getPostsBy('tag', id);
+
+ return {
+ title: `Tag: ${name} - chikubi.jp`,
+ link: `${baseUrl}/category/${keyword}`,
+ item: items,
+ };
+}
diff --git a/lib/routes/chikubi/utils.ts b/lib/routes/chikubi/utils.ts
new file mode 100644
index 00000000000000..2edbdea745f69a
--- /dev/null
+++ b/lib/routes/chikubi/utils.ts
@@ -0,0 +1,135 @@
+import { DataItem } from '@/types';
+import cache from '@/utils/cache';
+import got from '@/utils/got';
+import { load } from 'cheerio';
+import { parseDate } from '@/utils/parse-date';
+
+const CONTENT_TYPES = {
+ doujin: {
+ title: '.doujin-title',
+ description: ['.doujin-detail', '.section', '.area-buy > a.btn'],
+ },
+ video: {
+ title: '.video-title',
+ description: ['.video-data', '.section', '.lp-samplearea a.btn'],
+ },
+ article: {
+ title: '.article_title',
+ description: ['.article_icatch', '.article_contents'],
+ },
+};
+
+function getContentType(link: string): keyof typeof CONTENT_TYPES {
+ const typePatterns = {
+ doujin: ['/cg/', '/comic/', '/voice/'],
+ video: ['/nipple-video/'],
+ article: ['/post-'],
+ };
+
+ for (const [type, patterns] of Object.entries(typePatterns)) {
+ if (patterns.some((pattern) => link.includes(pattern))) {
+ return type as keyof typeof CONTENT_TYPES;
+ }
+ }
+
+ throw new Error(`Unknown content type for link: ${link}`);
+}
+
+export async function processItems(list): Promise 更多分类
+
+#### [党建园地](https://www.chinacdc.cn/dqgz/djgz/)
+
+| [党建工作](https://www.chinacdc.cn/dqgz/djgz/) | [廉政文化](https://www.chinacdc.cn/djgz_13611/) | [工会工作](https://www.chinacdc.cn/ghgz/) | [团青工作](https://www.chinacdc.cn/tqgz/) | [理论学习](https://www.chinacdc.cn/tqgz_13618/) |
+| -------------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------------------- |
+| [dqgz/djgz](https://rsshub.app/chinacdc/dqgz/djgz) | [dqgz/djgz_13611](https://rsshub.app/chinacdc/dqgz/djgz_13611) | [dqgz/ghgz](https://rsshub.app/chinacdc/dqgz/ghgz) | [dqgz/tqgz](https://rsshub.app/chinacdc/dqgz/tqgz) | [dqgz/tqgz_13618](https://rsshub.app/chinacdc/dqgz/tqgz_13618) |
+
+#### [疾控应急](https://www.chinacdc.cn/jkyj/)
+
+| [传染病](https://www.chinacdc.cn/jkyj/crb2/) | [突发公共卫生事件](https://www.chinacdc.cn/jkyj/tfggws/) | [慢性病与伤害防控](https://www.chinacdc.cn/jkyj/mxfcrxjb2/) | [烟草控制](https://www.chinacdc.cn/jkyj/yckz/) | [营养与健康](https://www.chinacdc.cn/jkyj/yyyjk2/) |
+| -------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------ | -------------------------------------------------- | ------------------------------------------------------ |
+| [jkyj/crb2](https://rsshub.app/chinacdc/jkyj/crb2) | [jkyj/tfggws](https://rsshub.app/chinacdc/jkyj/tfggws) | [jkyj/mxfcrxjb2](https://rsshub.app/chinacdc/jkyj/mxfcrxjb2) | [jkyj/yckz](https://rsshub.app/chinacdc/jkyj/yckz) | [jkyj/yyyjk2](https://rsshub.app/chinacdc/jkyj/yyyjk2) |
+
+| [环境与健康](https://www.chinacdc.cn/jkyj/hjyjk/) | [职业卫生与中毒控制](https://www.chinacdc.cn/jkyj/hjwsyzdkz/) | [放射卫生](https://www.chinacdc.cn/jkyj/fsws/) | [免疫规划](https://www.chinacdc.cn/jkyj/mygh02/) | [结核病防控](https://www.chinacdc.cn/jkyj/jhbfk/) |
+| ---------------------------------------------------- | ------------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------ | ---------------------------------------------------- |
+| [jkyj/hjyjk](https://rsshub.app/chinacdc/jkyj/hjyjk) | [jkyj/hjwsyzdkz](https://rsshub.app/chinacdc/jkyj/hjwsyzdkz) | [jkyj/fsws](https://rsshub.app/chinacdc/jkyj/fsws) | [jkyj/mygh02](https://rsshub.app/chinacdc/jkyj/mygh02) | [jkyj/jhbfk](https://rsshub.app/chinacdc/jkyj/jhbfk) |
+
+| [寄生虫病](https://www.chinacdc.cn/jkyj/jscb/) |
+| -------------------------------------------------- |
+| [jkyj/jscb](https://rsshub.app/chinacdc/jkyj/jscb) |
+
+#### [科学研究](https://www.chinacdc.cn/kxyj/)
+
+| [科技进展](https://www.chinacdc.cn/kxyj/kjjz/) | [学术动态](https://www.chinacdc.cn/kxyj/xsdt/) | [科研平台](https://www.chinacdc.cn/kxyj/xsjl/) | [科研亮点](https://www.chinacdc.cn/kxyj/kyld/) | [科技政策](https://www.chinacdc.cn/kxyj/kjzc/) |
+| -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- |
+| [kxyj/kjjz](https://rsshub.app/chinacdc/kxyj/kjjz) | [kxyj/xsdt](https://rsshub.app/chinacdc/kxyj/xsdt) | [kxyj/xsjl](https://rsshub.app/chinacdc/kxyj/xsjl) | [kxyj/kyld](https://rsshub.app/chinacdc/kxyj/kyld) | [kxyj/kjzc](https://rsshub.app/chinacdc/kxyj/kjzc) |
+
+#### [教育培训](https://www.chinacdc.cn/jypx/)
+
+| [研究生院](https://www.chinacdc.cn/jypx/yjsy/) | [继续教育](https://www.chinacdc.cn/jypx/jxjy/) | [博士后](https://www.chinacdc.cn/jypx/bsh/) | [中国现场流行病学培训项目(CFETP)](https://www.chinacdc.cn/jypx/CFETP/) |
+| -------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------ |
+| [jypx/yjsy](https://rsshub.app/chinacdc/jypx/yjsy) | [jypx/jxjy](https://rsshub.app/chinacdc/jypx/jxjy) | [jypx/bsh](https://rsshub.app/chinacdc/jypx/bsh) | [jypx/CFETP](https://rsshub.app/chinacdc/jypx/CFETP) |
+
+#### [全球公卫](https://www.chinacdc.cn/qqgw/)
+
+| [合作伙伴](https://www.chinacdc.cn/qqgw/hzhb/) | [世界卫生组织合作中心和参比实验室](https://www.chinacdc.cn/qqgw/wszz/) | [国际交流(港澳台交流)](https://www.chinacdc.cn/qqgw/gjjl/) | [公共卫生援外与合作](https://www.chinacdc.cn/qqgw/ggws/) |
+| -------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------- | -------------------------------------------------------- |
+| [qqgw/hzhb](https://rsshub.app/chinacdc/qqgw/hzhb) | [qqgw/wszz](https://rsshub.app/chinacdc/qqgw/wszz) | [qqgw/gjjl](https://rsshub.app/chinacdc/qqgw/gjjl) | [qqgw/ggws](https://rsshub.app/chinacdc/qqgw/ggws) |
+
+#### [人才建设](https://www.chinacdc.cn/rcjs/)
+
+| [院士风采](https://www.chinacdc.cn/rcjs/ysfc/) | [首席专家](https://www.chinacdc.cn/rcjs/sxzj/) | [人才队伍](https://www.chinacdc.cn/rcjs/rcdw/) | [人才招聘](https://www.chinacdc.cn/rcjs/rczp/) |
+| -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- |
+| [rcjs/ysfc](https://rsshub.app/chinacdc/rcjs/ysfc) | [rcjs/sxzj](https://rsshub.app/chinacdc/rcjs/sxzj) | [rcjs/rcdw](https://rsshub.app/chinacdc/rcjs/rcdw) | [rcjs/rczp](https://rsshub.app/chinacdc/rcjs/rczp) |
+
+#### [健康数据](https://www.chinacdc.cn/jksj/)
+
+| [全国法定传染病疫情情况](https://www.chinacdc.cn/jksj/jksj01/) | [全国新型冠状病毒感染疫情情况](https://www.chinacdc.cn/jksj/xgbdyq/) | [重点传染病和突发公共卫生事件风险评估报告](https://www.chinacdc.cn/jksj/jksj02/) | [全球传染病事件风险评估报告](https://www.chinacdc.cn/jksj/jksj03/) | [全国预防接种异常反应监测信息概况](https://www.chinacdc.cn/jksj/jksj04_14209/) |
+| -------------------------------------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------ |
+| [jksj/jksj01](https://rsshub.app/chinacdc/jksj/jksj01) | [jksj/xgbdyq](https://rsshub.app/chinacdc/jksj/xgbdyq) | [jksj/jksj02](https://rsshub.app/chinacdc/jksj/jksj02) | [jksj/jksj03](https://rsshub.app/chinacdc/jksj/jksj03) | [jksj/jksj04_14209](https://rsshub.app/chinacdc/jksj/jksj04_14209) |
+
+| [流感监测周报](https://www.chinacdc.cn/jksj/jksj04_14249/) | [全国急性呼吸道传染病哨点监测情况](https://www.chinacdc.cn/jksj/jksj04_14275/) | [健康报告](https://www.chinacdc.cn/jksj/jksj04/) |
+| ------------------------------------------------------------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------ |
+| [jksj/jksj04_14249](https://rsshub.app/chinacdc/jksj/jksj04_14249) | [jksj/jksj04_14275](https://rsshub.app/chinacdc/jksj/jksj04_14275) | [jksj/jksj04](https://rsshub.app/chinacdc/jksj/jksj04) |
+
+#### [健康科普](https://www.chinacdc.cn/jkkp/)
+
+| [传染病](https://www.chinacdc.cn/jkkp/crb/) | [慢性非传染性疾病](https://www.chinacdc.cn/jkkp/mxfcrb/) | [免疫规划](https://www.chinacdc.cn/jkkp/mygh/) | [公共卫生事件](https://www.chinacdc.cn/jkkp/ggws/) | [烟草控制](https://www.chinacdc.cn/jkkp/yckz/) |
+| ------------------------------------------------ | -------------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- |
+| [jkkp/crb](https://rsshub.app/chinacdc/jkkp/crb) | [jkkp/mxfcrb](https://rsshub.app/chinacdc/jkkp/mxfcrb) | [jkkp/mygh](https://rsshub.app/chinacdc/jkkp/mygh) | [jkkp/ggws](https://rsshub.app/chinacdc/jkkp/ggws) | [jkkp/yckz](https://rsshub.app/chinacdc/jkkp/yckz) |
+
+| [营养与健康](https://www.chinacdc.cn/jkkp/yyjk/) | [环境健康](https://www.chinacdc.cn/jkkp/hjjk/) | [职业健康与中毒控制](https://www.chinacdc.cn/jkkp/zyjk/) | [放射卫生](https://www.chinacdc.cn/jkkp/fsws/) |
+| -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------- |
+| [jkkp/yyjk](https://rsshub.app/chinacdc/jkkp/yyjk) | [jkkp/hjjk](https://rsshub.app/chinacdc/jkkp/hjjk) | [jkkp/zyjk](https://rsshub.app/chinacdc/jkkp/zyjk) | [jkkp/fsws](https://rsshub.app/chinacdc/jkkp/fsws) |
+
+{{ intro }}
+{{ /if }}
+
+{{ if description }}
+ {{@ description }}
+{{ /if }}
\ No newline at end of file
diff --git a/lib/routes/chinadegrees/namespace.ts b/lib/routes/chinadegrees/namespace.ts
index 7773eec5214541..e6f755a7b73103 100644
--- a/lib/routes/chinadegrees/namespace.ts
+++ b/lib/routes/chinadegrees/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '中华人民共和国学位证书查询',
url: 'chinadegrees.com.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/chinadegrees/province.ts b/lib/routes/chinadegrees/province.ts
index f37fb760d3024b..418cc8596acbd1 100644
--- a/lib/routes/chinadegrees/province.ts
+++ b/lib/routes/chinadegrees/province.ts
@@ -26,39 +26,39 @@ export const route: Route = {
},
name: '各学位授予单位学位证书上网进度',
description: `| 省市 | 代号 |
- | ---------------- | ---- |
- | 北京市 | 11 |
- | 天津市 | 12 |
- | 河北省 | 13 |
- | 山西省 | 14 |
- | 内蒙古自治区 | 15 |
- | 辽宁省 | 21 |
- | 吉林省 | 22 |
- | 黑龙江省 | 23 |
- | 上海市 | 31 |
- | 江苏省 | 32 |
- | 浙江省 | 33 |
- | 安徽省 | 34 |
- | 福建省 | 35 |
- | 江西省 | 36 |
- | 山东省 | 37 |
- | 河南省 | 41 |
- | 湖北省 | 42 |
- | 湖南省 | 43 |
- | 广东省 | 44 |
- | 广西壮族自治区 | 45 |
- | 海南省 | 46 |
- | 重庆市 | 50 |
- | 四川省 | 51 |
- | 贵州省 | 52 |
- | 云南省 | 53 |
- | 西藏自治区 | 54 |
- | 陕西省 | 61 |
- | 甘肃省 | 62 |
- | 青海省 | 63 |
- | 宁夏回族自治区 | 64 |
- | 新疆维吾尔自治区 | 65 |
- | 台湾 | 71 |`,
+ | ---------------- | ---- |
+ | 北京市 | 11 |
+ | 天津市 | 12 |
+ | 河北省 | 13 |
+ | 山西省 | 14 |
+ | 内蒙古自治区 | 15 |
+ | 辽宁省 | 21 |
+ | 吉林省 | 22 |
+ | 黑龙江省 | 23 |
+ | 上海市 | 31 |
+ | 江苏省 | 32 |
+ | 浙江省 | 33 |
+ | 安徽省 | 34 |
+ | 福建省 | 35 |
+ | 江西省 | 36 |
+ | 山东省 | 37 |
+ | 河南省 | 41 |
+ | 湖北省 | 42 |
+ | 湖南省 | 43 |
+ | 广东省 | 44 |
+ | 广西壮族自治区 | 45 |
+ | 海南省 | 46 |
+ | 重庆市 | 50 |
+ | 四川省 | 51 |
+ | 贵州省 | 52 |
+ | 云南省 | 53 |
+ | 西藏自治区 | 54 |
+ | 陕西省 | 61 |
+ | 甘肃省 | 62 |
+ | 青海省 | 63 |
+ | 宁夏回族自治区 | 64 |
+ | 新疆维吾尔自治区 | 65 |
+ | 台湾 | 71 |`,
maintainers: ['TonyRL'],
handler,
};
diff --git a/lib/routes/chinafactcheck/namespace.ts b/lib/routes/chinafactcheck/namespace.ts
index ccd631dad68f33..94165553f96908 100644
--- a/lib/routes/chinafactcheck/namespace.ts
+++ b/lib/routes/chinafactcheck/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '有据',
url: 'chinafactcheck.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/chinaisa/index.ts b/lib/routes/chinaisa/index.ts
index 7d54d7eb07179a..5de50965fc8b63 100644
--- a/lib/routes/chinaisa/index.ts
+++ b/lib/routes/chinaisa/index.ts
@@ -20,144 +20,144 @@ export const route: Route = {
name: '栏目',
maintainers: ['nczitzk'],
handler,
- description: `| 栏目 | id |
- | -------- | ---------------------------------------------------------------- |
+ description: `| 栏目 | id |
+ | -------- | --------------------------------------------------------------- |
| 钢协动态 | 58af05dfb6b4300151760176d2aad0a04c275aaadbb1315039263f021f920dcd |
| 钢协要闻 | 67ea4f106bd8f0843c0538d43833c463a0cd411fc35642cbd555a5f39fcf352b |
| 会议报道 | e5070694f299a43b20d990e53b6a69dc02e755fef644ae667cf75deaff80407a |
| 领导讲话 | a873c2e67b26b4a2d8313da769f6e106abc9a1ff04b7f1a50674dfa47cf91a7b |
| 图片新闻 | 806254321b2459bddb3c2cb5590fef6332bd849079d3082daf6153d7f8d62e1e |
- 更多栏目
-
- #### 党建工作
-
- | 栏目 | id |
- | ---------------------------------------------------- | ---------------------------------------------------------------- |
- | 党建工作 | 10e8911e0c852d91f08e173c768700da608abfb4e7b0540cb49fa5498f33522b |
- | 学习贯彻习近平新时代中国特色社会主义思想主题教育专栏 | b7a7ad4b5d8ffaca4b29f3538fd289da9d07f827f89e6ea57ef07257498aacf9 |
- | 党史学习教育专栏 | 4d8e7dec1b672704916331431156ea7628a598c191d751e4fc28408ccbd4e0c4 |
- | 不忘初心、牢记使命 | 427f7c28c90ec9db1aab78db8156a63ff2e23f6a0cea693e3847fe6d595753db |
- | 两学一做 | 5b0609fedc9052bb44f1cfe9acf5ec8c9fe960f22a07be69636f2cf1cacaa8f7 |
- | 钢协党代会 | beaaa0314f0f532d4b18244cd70df614a4af97465d974401b1f5b3349d78144b |
- | 创先争优 | e7ea82c886ba18691210aaf48b3582a92dca9c4f2aab912757cedafb066ff8a6 |
- | 青年工作 | 2706ee3a4a4c3c23e90e13c8fdc3002855d1dba394b61626562a97b33af3dbd0 |
- | 日常动态 | e21157a082fc0ab0d7062c8755e91472ee0d23de6ccc5c2a44b62e54062cf1e4 |
-
- #### 要闻
-
- | 栏目 | id |
- | ------------ | ---------------------------------------------------------------- |
- | 要闻 | c42511ce3f868a515b49668dd250290c80d4dc8930c7e455d0e6e14b8033eae2 |
- | 会员动态 | 268f86fdf61ac8614f09db38a2d0295253043b03e092c7ff48ab94290296125c |
- | 疫情应对专栏 | a83c48faeb34065fd9b33d3c84957a152675141458aedc0ec454b760c9fcad65 |
-
- #### 统计发布
-
- | 栏目 | id |
- | -------- | ---------------------------------------------------------------- |
- | 统计发布 | 2e3c87064bdfc0e43d542d87fce8bcbc8fe0463d5a3da04d7e11b4c7d692194b |
- | 生产经营 | 3238889ba0fa3aabcf28f40e537d440916a361c9170a4054f9fc43517cb58c1e |
- | 进出口 | 95ef75c752af3b6c8be479479d8b931de7418c00150720280d78c8f0da0a438c |
- | 环保统计 | 619ce7b53a4291d47c19d0ee0765098ca435e252576fbe921280a63fba4bc712 |
-
- #### 行业分析
-
- | 栏目 | id |
- | -------- | ---------------------------------------------------------------- |
- | 行业分析 | 1b4316d9238e09c735365896c8e4f677a3234e8363e5622ae6e79a5900a76f56 |
- | 市场分析 | a44207e193a5caa5e64102604b6933896a0025eb85c57c583b39626f33d4dafd |
- | 板带材 | 05d0e136828584d2cd6e45bdc3270372764781b98546cce122d9974489b1e2f2 |
- | 社会库存 | 197422a82d9a09b9cc86188444574816e93186f2fde87474f8b028fc61472d35 |
-
- #### 钢材价格指数
-
- | 栏目 | id |
- | ------------ | ---------------------------------------------------------------- |
- | 钢材价格指数 | 17b6a9a214c94ccc28e56d4d1a2dbb5acef3e73da431ddc0a849a4dcfc487d04 |
- | 综合价格指数 | 63913b906a7a663f7f71961952b1ddfa845714b5982655b773a62b85dd3b064e |
- | 地区价格 | fc816c75aed82b9bc25563edc9cf0a0488a2012da38cbef5258da614d6e51ba9 |
-
- #### 宏观经济信息
-
- | 栏目 | id |
- | ------------ | ---------------------------------------------------------------- |
- | 宏观经济信息 | 5d77b433182404193834120ceed16fe0625860fafd5fd9e71d0800c4df227060 |
- | 相关行业信息 | ae2a3c0fd4936acf75f4aab6fadd08bc6371aa65bdd50419e74b70d6f043c473 |
- | 国际动态 | 1bad7c56af746a666e4a4e56e54a9508d344d7bc1498360580613590c16b6c41 |
-
- #### 专题报道
-
- | 栏目 | id |
- | -------------------- | ---------------------------------------------------------------- |
- | 专题报道 | 50e7242bfd78b4395f3338df7699a0ff8847b886c4c3a55bd7c102a2cfe32fe9 |
- | 钢协理事会 | 40c6404418699f0f8cb4e513013bb110ef250c782f0959852601e7c75e1afcd8 |
- | 钢协新闻发布会 | 11ea370f565c6c141b1a4dac60aa00c4331bd442382a5dd476a5e73e001b773c |
- | 劳模表彰 | 907e4ae217bf9c981a132051572103f9c87cccb7f00caf5a1770078829e6bcb3 |
- | 钢铁行业职业技能竞赛 | 563c15270a691e3c7cb9cd9ba457c5af392eb4630fa833fc1a55c8e2afbc28a9 |
-
- #### 成果奖励
-
- | 栏目 | id |
- | ---------------------- | ---------------------------------------------------------------- |
- | 成果奖励 | a6c30053b66356b4d77fbf6668bda69f7e782b2ae08a21d5db171d50a504bd40 |
- | 冶金科学技术奖 | 50fe0c63f657ee48e49cb13fe7f7c5502046acdb05e2ee8a317f907af4191683 |
- | 企业管理现代化创新成果 | b5607d3b73c2c3a3b069a97b9dbfd59af64aea27bafd5eb87ba44d1b07a33b66 |
- | 清洁生产环境友好企业 | 4475c8e21374d063a22f95939a2909837e78fab1832dc97bf64f09fa01c0c5f7 |
- | 产品开发市场开拓奖 | 169e34d7b29e3deaf4d4496da594d3bbde2eb0a40f7244b54dbfb9cc89a37296 |
- | 质量金杯奖 | 68029784be6d9a7bf9cb8cace5b8a5ce5d2d871e9a0cbcbf84eeae0ea2746311 |
-
- #### 节能减排
-
- | 栏目 | id |
- | ------------------------------------------ | ---------------------------------------------------------------- |
- | 节能减排 | 08895f1681c198fdf297ab38e33e1f428f6ccf2add382f3844a52e410f10e5a0 |
- | 先进节能环保技术 | 6e639343a517fd08e5860fba581d41940da523753956ada973b6952fc05ef94f |
- | 钢铁企业超低排放改造和评估监测进展情况公示 | 50d99531d5dee68346653ca9548f308764ad38410a091e662834a5ed66770174 |
-
- #### 国际交流
-
- | 栏目 | id |
- | -------- | ---------------------------------------------------------------- |
- | 国际交流 | 4753eef81b4019369d4751413d852ab9027944b84c612b5a08614e046d169e81 |
- | 外事动态 | aa590ec6f835136a9ce8c9f3d0c3b194beb6b78037466ab40bb4aacc32adfcc9 |
- | 国际会展 | 05ac1f2971bc375d25c9112e399f9c3cbb237809684ebc5b0ca4a68a1fcb971c |
-
- #### 政策法规
-
- | 栏目 | id |
- | -------- | ---------------------------------------------------------------- |
- | 政策法规 | 63a69eb0087f1984c0b269a1541905f19a56e117d56b3f51dfae0e6c1d436533 |
- | 政策法规 | a214b2e71c3c79fa4a36ff382ee5f822b9603634626f7e320f91ed696b3666f2 |
- | 贸易规则 | 5988b2380d04d3efde8cc247377d19530c17904ec0b5decdd00f9b3e026e3715 |
-
- #### 分会园地
-
- | 栏目 | id |
- | ------------ | ---------------------------------------------------------------- |
- | 分会园地 | d059d6751dcaae94e31a795072267f7959c35d012eebb9858b3ede2990e82ea9 |
- | 法律分会 | 96000647f18ea78fa134a3932563e7d27c68d0482de498f179b44846234567a9 |
- | 设备分会 | c8e1e3f52406115c2c03928271bbe883c0875b7c9f2f67492395685a62a1a2d8 |
- | 国际产能合作 | 4fb8cc4b0d6f905a969ac3375f6d17b34df4dcae69d798d2a4616daa80af020c |
- | 绿化分会 | ad55a0fbc1a44e94fb60e21b98cf967aca17ecf1450bdfb3699468fe8235103b |
-
- #### 钢铁知识
-
- | 栏目 | id |
- | ------------ | ---------------------------------------------------------------- |
- | 钢铁知识 | 7f7509ff045023015e0d6c1ba22c32734b673be2ec14eae730a99c08e3badb3f |
- | 钢铁材料使用 | 7e319d71258ed6bb663cf59b4cf67fe97894e60aa5520f3d2cf966f82f9b89ac |
- | 钢铁标准 | fae0c4dd27f8fe4759941e78c9dc1dfe0088ce30d1b684d12be4c8172d2c08e1 |
-
- #### 钢协刊物
-
- | 栏目 | id |
- | ---------- | ---------------------------------------------------------------- |
- | 钢协刊物 | ed51af486f6d4b313b3aaf8fea0b32a4a2d4a89714c61992caf01942eb61831b |
- | 中国钢铁业 | 6440bdfccadf87908b13d8bbd9a66bb89bbd60cc5e175c018ca1c62c7d55e61f |
- | 钢铁信息 | 2b66af0b2cda9b420739e55e255a6f72f277557670ef861c9956da8fde25da05 |
- 更多栏目
+
+ #### 党建工作
+
+ | 栏目 | id |
+ | ---------------------------------------------------- | ---------------------------------------------------------------- |
+ | 党建工作 | 10e8911e0c852d91f08e173c768700da608abfb4e7b0540cb49fa5498f33522b |
+ | 学习贯彻习近平新时代中国特色社会主义思想主题教育专栏 | b7a7ad4b5d8ffaca4b29f3538fd289da9d07f827f89e6ea57ef07257498aacf9 |
+ | 党史学习教育专栏 | 4d8e7dec1b672704916331431156ea7628a598c191d751e4fc28408ccbd4e0c4 |
+ | 不忘初心、牢记使命 | 427f7c28c90ec9db1aab78db8156a63ff2e23f6a0cea693e3847fe6d595753db |
+ | 两学一做 | 5b0609fedc9052bb44f1cfe9acf5ec8c9fe960f22a07be69636f2cf1cacaa8f7 |
+ | 钢协党代会 | beaaa0314f0f532d4b18244cd70df614a4af97465d974401b1f5b3349d78144b |
+ | 创先争优 | e7ea82c886ba18691210aaf48b3582a92dca9c4f2aab912757cedafb066ff8a6 |
+ | 青年工作 | 2706ee3a4a4c3c23e90e13c8fdc3002855d1dba394b61626562a97b33af3dbd0 |
+ | 日常动态 | e21157a082fc0ab0d7062c8755e91472ee0d23de6ccc5c2a44b62e54062cf1e4 |
+
+ #### 要闻
+
+ | 栏目 | id |
+ | ------------ | ---------------------------------------------------------------- |
+ | 要闻 | c42511ce3f868a515b49668dd250290c80d4dc8930c7e455d0e6e14b8033eae2 |
+ | 会员动态 | 268f86fdf61ac8614f09db38a2d0295253043b03e092c7ff48ab94290296125c |
+ | 疫情应对专栏 | a83c48faeb34065fd9b33d3c84957a152675141458aedc0ec454b760c9fcad65 |
+
+ #### 统计发布
+
+ | 栏目 | id |
+ | -------- | ---------------------------------------------------------------- |
+ | 统计发布 | 2e3c87064bdfc0e43d542d87fce8bcbc8fe0463d5a3da04d7e11b4c7d692194b |
+ | 生产经营 | 3238889ba0fa3aabcf28f40e537d440916a361c9170a4054f9fc43517cb58c1e |
+ | 进出口 | 95ef75c752af3b6c8be479479d8b931de7418c00150720280d78c8f0da0a438c |
+ | 环保统计 | 619ce7b53a4291d47c19d0ee0765098ca435e252576fbe921280a63fba4bc712 |
+
+ #### 行业分析
+
+ | 栏目 | id |
+ | -------- | ---------------------------------------------------------------- |
+ | 行业分析 | 1b4316d9238e09c735365896c8e4f677a3234e8363e5622ae6e79a5900a76f56 |
+ | 市场分析 | a44207e193a5caa5e64102604b6933896a0025eb85c57c583b39626f33d4dafd |
+ | 板带材 | 05d0e136828584d2cd6e45bdc3270372764781b98546cce122d9974489b1e2f2 |
+ | 社会库存 | 197422a82d9a09b9cc86188444574816e93186f2fde87474f8b028fc61472d35 |
+
+ #### 钢材价格指数
+
+ | 栏目 | id |
+ | ------------ | ---------------------------------------------------------------- |
+ | 钢材价格指数 | 17b6a9a214c94ccc28e56d4d1a2dbb5acef3e73da431ddc0a849a4dcfc487d04 |
+ | 综合价格指数 | 63913b906a7a663f7f71961952b1ddfa845714b5982655b773a62b85dd3b064e |
+ | 地区价格 | fc816c75aed82b9bc25563edc9cf0a0488a2012da38cbef5258da614d6e51ba9 |
+
+ #### 宏观经济信息
+
+ | 栏目 | id |
+ | ------------ | ---------------------------------------------------------------- |
+ | 宏观经济信息 | 5d77b433182404193834120ceed16fe0625860fafd5fd9e71d0800c4df227060 |
+ | 相关行业信息 | ae2a3c0fd4936acf75f4aab6fadd08bc6371aa65bdd50419e74b70d6f043c473 |
+ | 国际动态 | 1bad7c56af746a666e4a4e56e54a9508d344d7bc1498360580613590c16b6c41 |
+
+ #### 专题报道
+
+ | 栏目 | id |
+ | -------------------- | ---------------------------------------------------------------- |
+ | 专题报道 | 50e7242bfd78b4395f3338df7699a0ff8847b886c4c3a55bd7c102a2cfe32fe9 |
+ | 钢协理事会 | 40c6404418699f0f8cb4e513013bb110ef250c782f0959852601e7c75e1afcd8 |
+ | 钢协新闻发布会 | 11ea370f565c6c141b1a4dac60aa00c4331bd442382a5dd476a5e73e001b773c |
+ | 劳模表彰 | 907e4ae217bf9c981a132051572103f9c87cccb7f00caf5a1770078829e6bcb3 |
+ | 钢铁行业职业技能竞赛 | 563c15270a691e3c7cb9cd9ba457c5af392eb4630fa833fc1a55c8e2afbc28a9 |
+
+ #### 成果奖励
+
+ | 栏目 | id |
+ | ---------------------- | ---------------------------------------------------------------- |
+ | 成果奖励 | a6c30053b66356b4d77fbf6668bda69f7e782b2ae08a21d5db171d50a504bd40 |
+ | 冶金科学技术奖 | 50fe0c63f657ee48e49cb13fe7f7c5502046acdb05e2ee8a317f907af4191683 |
+ | 企业管理现代化创新成果 | b5607d3b73c2c3a3b069a97b9dbfd59af64aea27bafd5eb87ba44d1b07a33b66 |
+ | 清洁生产环境友好企业 | 4475c8e21374d063a22f95939a2909837e78fab1832dc97bf64f09fa01c0c5f7 |
+ | 产品开发市场开拓奖 | 169e34d7b29e3deaf4d4496da594d3bbde2eb0a40f7244b54dbfb9cc89a37296 |
+ | 质量金杯奖 | 68029784be6d9a7bf9cb8cace5b8a5ce5d2d871e9a0cbcbf84eeae0ea2746311 |
+
+ #### 节能减排
+
+ | 栏目 | id |
+ | ------------------------------------------ | ---------------------------------------------------------------- |
+ | 节能减排 | 08895f1681c198fdf297ab38e33e1f428f6ccf2add382f3844a52e410f10e5a0 |
+ | 先进节能环保技术 | 6e639343a517fd08e5860fba581d41940da523753956ada973b6952fc05ef94f |
+ | 钢铁企业超低排放改造和评估监测进展情况公示 | 50d99531d5dee68346653ca9548f308764ad38410a091e662834a5ed66770174 |
+
+ #### 国际交流
+
+ | 栏目 | id |
+ | -------- | ---------------------------------------------------------------- |
+ | 国际交流 | 4753eef81b4019369d4751413d852ab9027944b84c612b5a08614e046d169e81 |
+ | 外事动态 | aa590ec6f835136a9ce8c9f3d0c3b194beb6b78037466ab40bb4aacc32adfcc9 |
+ | 国际会展 | 05ac1f2971bc375d25c9112e399f9c3cbb237809684ebc5b0ca4a68a1fcb971c |
+
+ #### 政策法规
+
+ | 栏目 | id |
+ | -------- | ---------------------------------------------------------------- |
+ | 政策法规 | 63a69eb0087f1984c0b269a1541905f19a56e117d56b3f51dfae0e6c1d436533 |
+ | 政策法规 | a214b2e71c3c79fa4a36ff382ee5f822b9603634626f7e320f91ed696b3666f2 |
+ | 贸易规则 | 5988b2380d04d3efde8cc247377d19530c17904ec0b5decdd00f9b3e026e3715 |
+
+ #### 分会园地
+
+ | 栏目 | id |
+ | ------------ | ---------------------------------------------------------------- |
+ | 分会园地 | d059d6751dcaae94e31a795072267f7959c35d012eebb9858b3ede2990e82ea9 |
+ | 法律分会 | 96000647f18ea78fa134a3932563e7d27c68d0482de498f179b44846234567a9 |
+ | 设备分会 | c8e1e3f52406115c2c03928271bbe883c0875b7c9f2f67492395685a62a1a2d8 |
+ | 国际产能合作 | 4fb8cc4b0d6f905a969ac3375f6d17b34df4dcae69d798d2a4616daa80af020c |
+ | 绿化分会 | ad55a0fbc1a44e94fb60e21b98cf967aca17ecf1450bdfb3699468fe8235103b |
+
+ #### 钢铁知识
+
+ | 栏目 | id |
+ | ------------ | ---------------------------------------------------------------- |
+ | 钢铁知识 | 7f7509ff045023015e0d6c1ba22c32734b673be2ec14eae730a99c08e3badb3f |
+ | 钢铁材料使用 | 7e319d71258ed6bb663cf59b4cf67fe97894e60aa5520f3d2cf966f82f9b89ac |
+ | 钢铁标准 | fae0c4dd27f8fe4759941e78c9dc1dfe0088ce30d1b684d12be4c8172d2c08e1 |
+
+ #### 钢协刊物
+
+ | 栏目 | id |
+ | ---------- | ---------------------------------------------------------------- |
+ | 钢协刊物 | ed51af486f6d4b313b3aaf8fea0b32a4a2d4a89714c61992caf01942eb61831b |
+ | 中国钢铁业 | 6440bdfccadf87908b13d8bbd9a66bb89bbd60cc5e175c018ca1c62c7d55e61f |
+ | 钢铁信息 | 2b66af0b2cda9b420739e55e255a6f72f277557670ef861c9956da8fde25da05 |
+更多分类
+
+ #### [协会动态](https://www.chinania.org.cn/html/xiehuidongtai/)
+
+ | [协会动态](https://www.chinania.org.cn/html/xiehuidongtai/xiehuidongtai/) | [协会通知](https://www.chinania.org.cn/html/xiehuidongtai/xiehuitongzhi/) | [有色企业50强](https://www.chinania.org.cn/html/xiehuidongtai/youseqiye50qiang/) |
+ | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
+ | [xiehuidongtai/xiehuidongtai](https://rsshub.app/chinania/xiehuidongtai/xiehuidongtai) | [xiehuidongtai/xiehuitongzhi](https://rsshub.app/chinania/xiehuidongtai/xiehuitongzhi) | [xiehuidongtai/youseqiye50qiang](https://rsshub.app/chinania/xiehuidongtai/youseqiye50qiang) |
+
+ #### [党建工作](https://www.chinania.org.cn/html/djgz/)
+
+ | [协会党建](https://www.chinania.org.cn/html/djgz/xiehuidangjian/) | [行业党建](https://www.chinania.org.cn/html/djgz/hangyedangjian/) |
+ | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- |
+ | [djgz/xiehuidangjian](https://rsshub.app/chinania/djgz/xiehuidangjian) | [djgz/hangyedangjian](https://rsshub.app/chinania/djgz/hangyedangjian) |
+
+ #### [行业新闻](https://www.chinania.org.cn/html/hangyexinwen/)
+
+ | [时政要闻](https://www.chinania.org.cn/html/hangyexinwen/shizhengyaowen/) | [要闻](https://www.chinania.org.cn/html/hangyexinwen/yaowen/) | [行业新闻](https://www.chinania.org.cn/html/hangyexinwen/guoneixinwen/) | [资讯](https://www.chinania.org.cn/html/hangyexinwen/zixun/) |
+ | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
+ | [hangyexinwen/shizhengyaowen](https://rsshub.app/chinania/hangyexinwen/shizhengyaowen) | [hangyexinwen/yaowen](https://rsshub.app/chinania/hangyexinwen/yaowen) | [hangyexinwen/guoneixinwen](https://rsshub.app/chinania/hangyexinwen/guoneixinwen) | [hangyexinwen/zixun](https://rsshub.app/chinania/hangyexinwen/zixun) |
+
+ #### [人力资源](https://www.chinania.org.cn/html/renliziyuan/)
+
+ | [相关通知](https://www.chinania.org.cn/html/renliziyuan/xiangguantongzhi/) | [人事招聘](https://www.chinania.org.cn/html/renliziyuan/renshizhaopin/) |
+ | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
+ | [renliziyuan/xiangguantongzhi](https://rsshub.app/chinania/renliziyuan/xiangguantongzhi) | [renliziyuan/renshizhaopin](https://rsshub.app/chinania/renliziyuan/renshizhaopin) |
+
+ #### [行业统计](https://www.chinania.org.cn/html/hangyetongji/jqzs/)
+
+ | [行业分析](https://www.chinania.org.cn/html/hangyetongji/tongji/) | [数据统计](https://www.chinania.org.cn/html/hangyetongji/chanyeshuju/) | [景气指数](https://www.chinania.org.cn/html/hangyetongji/jqzs/) |
+ | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
+ | [hangyetongji/tongji](https://rsshub.app/chinania/hangyetongji/tongji) | [hangyetongji/chanyeshuju](https://rsshub.app/chinania/hangyetongji/chanyeshuju) | [hangyetongji/jqzs](https://rsshub.app/chinania/hangyetongji/jqzs) |
+
+ #### [政策法规](https://www.chinania.org.cn/html/zcfg/zhengcefagui/)
+
+ | [政策法规](https://www.chinania.org.cn/html/zcfg/zhengcefagui/) |
+ | ------------------------------------------------------------------ |
+ | [zcfg/zhengcefagui](https://rsshub.app/chinania/zcfg/zhengcefagui) |
+
+ #### [会议展览](https://www.chinania.org.cn/html/hyzl/huiyizhanlan/)
+
+ | [会展通知](https://www.chinania.org.cn/html/hyzl/huiyizhanlan/) | [会展报道](https://www.chinania.org.cn/html/hyzl/huizhanbaodao/) |
+ | ------------------------------------------------------------------ | -------------------------------------------------------------------- |
+ | [hyzl/huiyizhanlan](https://rsshub.app/chinania/hyzl/huiyizhanlan) | [hyzl/huizhanbaodao](https://rsshub.app/chinania/hyzl/huizhanbaodao) |
+
+ 更多分类
+
+ #### [分支机构信息](http://www.cisia.org/site/term/14.html)
+
+ | [企业动态](http://www.cisia.org/site/term/17.html) | [产品展示](http://www.cisia.org/site/term/18.html) |
+ | -------------------------------------------------- | -------------------------------------------------- |
+ | [17](https://rsshub.app/cisia/17) | [18](https://rsshub.app/cisia/18) |
+
+ #### [新闻中心](http://www.cisia.org/site/term/8.html)
+
+ | [协会动态](http://www.cisia.org/site/term/9.html) | [行业新闻](http://www.cisia.org/site/term/10.html) | [通知公告](http://www.cisia.org/site/term/11.html) | [市场信息](http://www.cisia.org/site/term/12.html) |
+ | ------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- |
+ | [9](https://rsshub.app/cisia/9) | [10](https://rsshub.app/cisia/10) | [11](https://rsshub.app/cisia/11) | [12](https://rsshub.app/cisia/12) |
+
+ #### [政策法规](http://www.cisia.org/site/term/19.html)
+
+ | [宏观聚焦](http://www.cisia.org/site/term/20.html) | [技术园区](http://www.cisia.org/site/term/396.html) |
+ | -------------------------------------------------- | --------------------------------------------------- |
+ | [20](https://rsshub.app/cisia/20) | [396](https://rsshub.app/cisia/396) |
+
+ #### [合作交流](http://www.cisia.org/site/term/22.html)
+
+ | [国际交流](http://www.cisia.org/site/term/23.html) | [行业交流](http://www.cisia.org/site/term/24.html) | [企业调研](http://www.cisia.org/site/term/25.html) | [会展信息](http://www.cisia.org/site/term/84.html) | [宣传专题](http://www.cisia.org/site/term/430.html) |
+ | -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | --------------------------------------------------- |
+ | [23](https://rsshub.app/cisia/23) | [24](https://rsshub.app/cisia/24) | [25](https://rsshub.app/cisia/25) | [84](https://rsshub.app/cisia/84) | [430](https://rsshub.app/cisia/430) |
+
+ #### [党建工作](http://www.cisia.org/site/term/26.html)
+
+ | [党委文件](http://www.cisia.org/site/term/27.html) | [学习园地](http://www.cisia.org/site/term/28.html) | [两会专题](http://www.cisia.org/site/term/443.html) |
+ | -------------------------------------------------- | -------------------------------------------------- | --------------------------------------------------- |
+ | [27](https://rsshub.app/cisia/27) | [28](https://rsshub.app/cisia/28) | [443](https://rsshub.app/cisia/443) |
+
+ #### [网上服务平台](http://www.cisia.org/site/term/29.html)
+
+ | [前沿科技](http://www.cisia.org/site/term/31.html) | [新材料新技术](http://www.cisia.org/site/term/133.html) | [文件共享](http://www.cisia.org/site/term/30.html) |
+ | -------------------------------------------------- | ------------------------------------------------------- | -------------------------------------------------- |
+ | [31](https://rsshub.app/cisia/31) | [133](https://rsshub.app/cisia/133) | [30](https://rsshub.app/cisia/30) |
+
+ #### [会员社区](http://www.cisia.org/site/term/34.html)
+
+ | [会员分布](http://www.cisia.org/site/term/35.html) | [会员风采](http://www.cisia.org/site/term/68.html) |
+ | -------------------------------------------------- | -------------------------------------------------- |
+ | [35](https://rsshub.app/cisia/35) | [68](https://rsshub.app/cisia/68) |
+
+
${attachments}`;
+ return item;
+ })
+ )
+ );
+
+ return {
+ title: titleMap.get(cate),
+ link: `https://yjsy.cjlu.edu.cn/index/${cate}.htm`,
+ item: items,
+ };
+}
diff --git a/lib/routes/clickme/namespace.ts b/lib/routes/clickme/namespace.ts
index 2db57538e94147..089498bec5e808 100644
--- a/lib/routes/clickme/namespace.ts
+++ b/lib/routes/clickme/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'ClickMe',
url: 'clickme.net',
+ lang: 'en',
};
diff --git a/lib/routes/cloudnative/namespace.ts b/lib/routes/cloudnative/namespace.ts
index a5e84bcd4f286e..73fa96fdb8df36 100644
--- a/lib/routes/cloudnative/namespace.ts
+++ b/lib/routes/cloudnative/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '云原生社区',
url: 'cloudnative.to',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cls/namespace.ts b/lib/routes/cls/namespace.ts
index 25ebea7ffbb88f..399323e79488c6 100644
--- a/lib/routes/cls/namespace.ts
+++ b/lib/routes/cls/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '财联社',
url: 'cls.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cls/subject.ts b/lib/routes/cls/subject.ts
new file mode 100644
index 00000000000000..d91efef8d549e5
--- /dev/null
+++ b/lib/routes/cls/subject.ts
@@ -0,0 +1,153 @@
+import { Route } from '@/types';
+import { getCurrentPath } from '@/utils/helpers';
+const __dirname = getCurrentPath(import.meta.url);
+
+import cache from '@/utils/cache';
+import got from '@/utils/got';
+import { load } from 'cheerio';
+import { parseDate } from '@/utils/parse-date';
+import { art } from '@/utils/render';
+import path from 'node:path';
+
+import { rootUrl, getSearchParams } from './utils';
+
+export const handler = async (ctx) => {
+ const { id = '1103' } = ctx.req.param();
+ const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20;
+
+ const currentUrl = new URL(`subject/${id}`, rootUrl).href;
+ const apiUrl = new URL(`api/subject/${id}/article`, rootUrl).href;
+
+ const { data: response } = await got(apiUrl, {
+ searchParams: getSearchParams({
+ Subject_Id: id,
+ }),
+ });
+
+ let items = response.data.slice(0, limit).map((item) => {
+ const title = item.article_title;
+ const description = art(path.join(__dirname, 'templates/description.art'), {
+ intro: item.article_brief,
+ });
+ const guid = `cls-${item.article_id}`;
+ const image = item.article_img;
+
+ return {
+ title,
+ description,
+ pubDate: parseDate(item.article_time, 'X'),
+ link: new URL(`detail/${item.article_id}`, rootUrl).href,
+ category: item.subjects.map((s) => s.subject_name),
+ author: item.article_author,
+ guid,
+ id: guid,
+ content: {
+ html: description,
+ text: item.article_brief,
+ },
+ image,
+ banner: image,
+ };
+ });
+
+ items = await Promise.all(
+ items.map((item) =>
+ cache.tryGet(item.link, async () => {
+ const { data: detailResponse } = await got(item.link);
+
+ const $$ = load(detailResponse);
+
+ const data = JSON.parse($$('script#__NEXT_DATA__').text())?.props?.initialState?.detail?.articleDetail ?? undefined;
+
+ if (!data) {
+ return item;
+ }
+
+ const title = data.title;
+ const description = art(path.join(__dirname, 'templates/description.art'), {
+ images: data.images.map((i) => ({
+ src: i,
+ alt: title,
+ })),
+ intro: data.brief,
+ description: data.content,
+ });
+ const guid = `cls-${data.id}`;
+ const image = data.images?.[0] ?? undefined;
+
+ item.title = title;
+ item.description = description;
+ item.pubDate = parseDate(data.ctime, 'X');
+ item.category = [...new Set(data.subject?.flatMap((s) => [s.name, ...(s.subjectCategory?.flatMap((c) => [c.columnName || [], c.name || []]) ?? [])]) ?? [])].filter(Boolean);
+ item.author = data.author?.name ?? item.author;
+ item.guid = guid;
+ item.id = guid;
+ item.content = {
+ html: description,
+ text: data.content,
+ };
+ item.image = image;
+ item.banner = image;
+ item.enclosure_url = data.audioUrl;
+ item.enclosure_type = item.enclosure_url ? `audio/${item.enclosure_url.split(/\./).pop()}` : undefined;
+ item.enclosure_title = title;
+
+ return item;
+ })
+ )
+ );
+
+ const { data: currentResponse } = await got(currentUrl);
+
+ const $ = load(currentResponse);
+
+ const data = JSON.parse($('script#__NEXT_DATA__').text())?.props?.initialProps?.pageProps?.subjectDetail ?? undefined;
+
+ const author = '财联社';
+ const image = data?.img ?? undefined;
+
+ return {
+ title: `${author} - ${data?.name ?? $('title').text()}`,
+ description: data?.description ?? undefined,
+ link: currentUrl,
+ item: items,
+ allowEmpty: true,
+ image,
+ author,
+ };
+};
+
+export const route: Route = {
+ path: '/subject/:id?',
+ name: '话题',
+ url: 'www.cls.cn',
+ maintainers: ['nczitzk'],
+ handler,
+ example: '/cls/subject/1103',
+ parameters: { category: '分类,默认为 1103,即A股盘面直播,可在对应话题页 URL 中找到' },
+ description: `:::tip
+ 若订阅 [有声早报](https://www.cls.cn/subject/1151),网址为 \`https://www.cls.cn/subject/1151\`。截取 \`https://www.cls.cn/subject/\` 到末尾的部分 \`1151\` 作为参数填入,此时路由为 [\`/cls/subject/1151\`](https://rsshub.app/cls/subject/1151)。
+ :::
+ `,
+ categories: ['finance'],
+
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: false,
+ supportRadar: true,
+ supportBT: false,
+ supportPodcast: false,
+ supportScihub: false,
+ },
+ radar: [
+ {
+ source: ['www.cls.cn/subject/:id'],
+ target: (params) => {
+ const id = params.id;
+
+ return `/subject${id ? `/${id}` : ''}`;
+ },
+ },
+ ],
+};
diff --git a/lib/routes/cls/templates/description.art b/lib/routes/cls/templates/description.art
new file mode 100644
index 00000000000000..249654e7e618a4
--- /dev/null
+++ b/lib/routes/cls/templates/description.art
@@ -0,0 +1,21 @@
+{{ if images }}
+ {{ each images image }}
+ {{ if image?.src }}
+
+ {{ /if }}
+ {{ /each }}
+{{ /if }}
+
+{{ if intro }}
+ {{ intro }}
+{{ /if }}
+
+{{ if description }}
+ {{@ description }}
+{{ /if }}
\ No newline at end of file
diff --git a/lib/routes/cma/namespace.ts b/lib/routes/cma/namespace.ts
index e66f2e669d8dfc..876cd450ff2eba 100644
--- a/lib/routes/cma/namespace.ts
+++ b/lib/routes/cma/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '中国气象局',
url: 'weather.cma.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cmde/namespace.ts b/lib/routes/cmde/namespace.ts
index 7e5f807cba8c5e..da9ba07bd72fc7 100644
--- a/lib/routes/cmde/namespace.ts
+++ b/lib/routes/cmde/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '国家药品监督管理局医疗器械技术审评中心',
url: 'www.cmde.org.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cmpxchg8b/namespace.ts b/lib/routes/cmpxchg8b/namespace.ts
index 08d1eb8396822b..0d26f1ad75bc4f 100644
--- a/lib/routes/cmpxchg8b/namespace.ts
+++ b/lib/routes/cmpxchg8b/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'cmpxchg8b',
url: 'lock.cmpxchg8b.com',
+ lang: 'en',
};
diff --git a/lib/routes/cn-healthcare/namespace.ts b/lib/routes/cn-healthcare/namespace.ts
index ca76d162687225..aaaef82c3d5180 100644
--- a/lib/routes/cn-healthcare/namespace.ts
+++ b/lib/routes/cn-healthcare/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '健康界',
url: 'cn-healthcare.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cna/namespace.ts b/lib/routes/cna/namespace.ts
index 73b8155d68d77b..b34e748a72b763 100644
--- a/lib/routes/cna/namespace.ts
+++ b/lib/routes/cna/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '中央通讯社',
url: 'cna.com.tw',
+ lang: 'zh-TW',
};
diff --git a/lib/routes/cnbc/namespace.ts b/lib/routes/cnbc/namespace.ts
index 6243dcb5137059..b259fa6783dc8f 100644
--- a/lib/routes/cnbc/namespace.ts
+++ b/lib/routes/cnbc/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'CNBC',
url: 'search.cnbc.com',
+ lang: 'en',
};
diff --git a/lib/routes/cnbc/rss.ts b/lib/routes/cnbc/rss.ts
index 5e81b99d02aebb..ecc6620f1d59bb 100644
--- a/lib/routes/cnbc/rss.ts
+++ b/lib/routes/cnbc/rss.ts
@@ -65,7 +65,7 @@ async function handler(ctx) {
}
const meta = JSON.parse($('[type=application/ld+json]').last().text());
- item.author = meta.author ? meta.author.name ?? meta.author.map((a) => a.name).join(', ') : null;
+ item.author = meta.author ? (meta.author.name ?? meta.author.map((a) => a.name).join(', ')) : null;
item.category = meta.keywords;
return item;
diff --git a/lib/routes/cnbeta/namespace.ts b/lib/routes/cnbeta/namespace.ts
index 16da5989dec3ef..d703fd32ee9c3a 100644
--- a/lib/routes/cnbeta/namespace.ts
+++ b/lib/routes/cnbeta/namespace.ts
@@ -3,5 +3,6 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'cnBeta.COM',
url: 'cnbeta.com.tw',
- categories: ['new-media'],
+ categories: ['new-media', 'popular'],
+ lang: 'zh-TW',
};
diff --git a/lib/routes/cnblogs/namespace.ts b/lib/routes/cnblogs/namespace.ts
index c7434c16ae9cba..055c2a06967560 100644
--- a/lib/routes/cnblogs/namespace.ts
+++ b/lib/routes/cnblogs/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '博客园',
url: 'www.cnblogs.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cncf/namespace.ts b/lib/routes/cncf/namespace.ts
index 87bbbe17832996..a479718a158368 100644
--- a/lib/routes/cncf/namespace.ts
+++ b/lib/routes/cncf/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'CNCF',
url: 'cncf.io',
+ lang: 'en',
};
diff --git a/lib/routes/cneb/namespace.ts b/lib/routes/cneb/namespace.ts
index 7a03e739e19b99..06ffe10bf6fa37 100644
--- a/lib/routes/cneb/namespace.ts
+++ b/lib/routes/cneb/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '中国国家应急广播',
url: 'cneb.gov.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cngal/namespace.ts b/lib/routes/cngal/namespace.ts
index a562b96ab2dd2e..f5f6dbd51b00e9 100644
--- a/lib/routes/cngal/namespace.ts
+++ b/lib/routes/cngal/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'CnGal',
url: 'www.cngal.org',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cngal/weekly.ts b/lib/routes/cngal/weekly.ts
index 0ee1e8eb317e6a..03f351af45bbba 100644
--- a/lib/routes/cngal/weekly.ts
+++ b/lib/routes/cngal/weekly.ts
@@ -1,4 +1,4 @@
-import { Route } from '@/types';
+import { Route, ViewType } from '@/types';
import { getCurrentPath } from '@/utils/helpers';
const __dirname = getCurrentPath(import.meta.url);
@@ -9,7 +9,8 @@ import { parseDate } from '@/utils/parse-date';
export const route: Route = {
path: '/weekly',
- categories: ['anime'],
+ categories: ['anime', 'popular'],
+ view: ViewType.Articles,
example: '/cngal/weekly',
parameters: {},
features: {
diff --git a/lib/routes/cngold/index.ts b/lib/routes/cngold/index.ts
new file mode 100644
index 00000000000000..3787256522bc11
--- /dev/null
+++ b/lib/routes/cngold/index.ts
@@ -0,0 +1,195 @@
+import { Route } from '@/types';
+
+import cache from '@/utils/cache';
+import got from '@/utils/got';
+import { load } from 'cheerio';
+import { parseDate } from '@/utils/parse-date';
+
+export const handler = async (ctx) => {
+ const { category = 'news-325' } = ctx.req.param();
+ const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 12;
+
+ const rootUrl = 'https://www.cngold.org.cn';
+ const currentUrl = new URL(`${category}.html`, rootUrl).href;
+
+ const { data: response } = await got(currentUrl);
+
+ const $ = load(response);
+
+ const language = $('html').prop('lang');
+
+ let items = $('ul.newsList li')
+ .slice(0, limit)
+ .toArray()
+ .map((item) => {
+ item = $(item);
+
+ return {
+ title: item.find('t1').text(),
+ pubDate: parseDate(item.find('div.min, div.day').text(), ['YYYY-MM-DD', 'MM-DD']),
+ link: new URL(item.find('a').prop('href'), rootUrl).href,
+ language,
+ };
+ });
+
+ items = await Promise.all(
+ items.map((item) =>
+ cache.tryGet(item.link, async () => {
+ const { data: detailResponse } = await got(item.link);
+
+ const $$ = load(detailResponse);
+
+ const title = $$('div.details_top div.t1').text();
+ const description = $$('div.details_con').html();
+
+ item.title = title;
+ item.description = description;
+ item.pubDate = parseDate($$('div.details_top div.min span').first().text());
+ item.author = $$('div.details_top div.min span').last().text().split(/:/).pop();
+ item.content = {
+ html: description,
+ text: $$('div.details_con').text(),
+ };
+ item.language = language;
+
+ return item;
+ })
+ )
+ );
+
+ const image = new URL($('div.logo img').prop('src'), rootUrl).href;
+
+ return {
+ title: `${$('title').text()} - ${$('div.tab a.current').text()}`,
+ description: $('meta[name="description"]').prop('content'),
+ link: currentUrl,
+ item: items,
+ allowEmpty: true,
+ image,
+ author: $('meta[name="keywords"]').prop('content'),
+ language,
+ };
+};
+
+export const route: Route = {
+ path: '/:category?',
+ name: '分类',
+ url: 'www.cngold.org.cn',
+ maintainers: ['nczitzk'],
+ handler,
+ example: '/cngold/news-325',
+ parameters: { category: '分类,默认为 `news-325`,即行业资讯,可在对应分类页 URL 中找到, Category, `news-325`,即行业资讯by default' },
+ description: `:::tip
+ 若订阅 [行业资讯](https://www.cngold.org.cn/news-325.html),网址为 \`https://www.cngold.org.cn/news-325.html\`。截取 \`https://www.cngold.org.cn/\` 到末尾 \`.html\` 的部分 \`news-325\` 作为参数填入,此时路由为 [\`/cngold/news-325\`](https://rsshub.app/cngold/news-325)。
+ :::
+
+ #### 资讯中心
+
+ | [图片新闻](https://www.cngold.org.cn/news-323.html) | [通知公告](https://www.cngold.org.cn/news-324.html) | [党建工作](https://www.cngold.org.cn/news-326.html) | [行业资讯](https://www.cngold.org.cn/news-325.html) | [黄金矿业](https://www.cngold.org.cn/news-327.html) | [黄金消费](https://www.cngold.org.cn/news-328.html) |
+ | --------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------- |
+ | [news-323](https://rsshub.app/cngold/news-323) | [news-324](https://rsshub.app/cngold/news-324) | [news-326](https://rsshub.app/cngold/news-326) | [news-325](https://rsshub.app/cngold/news-325) | [news-327](https://rsshub.app/cngold/news-327) | [news-328](https://rsshub.app/cngold/news-328) |
+
+ | [黄金市场](https://www.cngold.org.cn/news-329.html) | [社会责任](https://www.cngold.org.cn/news-330.html) | [黄金书屋](https://www.cngold.org.cn/news-331.html) | [工作交流](https://www.cngold.org.cn/news-332.html) | [黄金统计](https://www.cngold.org.cn/news-333.html) | [协会动态](https://www.cngold.org.cn/news-334.html) |
+ | --------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------- |
+ | [news-329](https://rsshub.app/cngold/news-329) | [news-330](https://rsshub.app/cngold/news-330) | [news-331](https://rsshub.app/cngold/news-331) | [news-332](https://rsshub.app/cngold/news-332) | [news-333](https://rsshub.app/cngold/news-333) | [news-334](https://rsshub.app/cngold/news-334) |
+
+ 更多分类
+
+ #### [政策法规](https://www.cngold.org.cn/policies.html)
+
+ | [法律法规](https://www.cngold.org.cn/policies-245.html) | [产业政策](https://www.cngold.org.cn/policies-262.html) | [黄金标准](https://www.cngold.org.cn/policies-281.html) |
+ | ------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------- |
+ | [policies-245](https://rsshub.app/cngold/policies-245) | [policies-262](https://rsshub.app/cngold/policies-262) | [policies-281](https://rsshub.app/cngold/policies-281) |
+
+ #### [行业培训](https://www.cngold.org.cn/training.html)
+
+ | [黄金投资分析师](https://www.cngold.org.cn/training-242.html) | [教育部1+X](https://www.cngold.org.cn/training-246.html) | [矿业权评估师](https://www.cngold.org.cn/training-338.html) | [其他培训](https://www.cngold.org.cn/training-247.html) |
+ | ------------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------- |
+ | [training-242](https://rsshub.app/cngold/training-242) | [training-246](https://rsshub.app/cngold/training-246) | [training-338](https://rsshub.app/cngold/training-338) | [training-247](https://rsshub.app/cngold/training-247) |
+
+ #### [黄金科技](https://www.cngold.org.cn/technology.html)
+
+ | [黄金协会科学技术奖](https://www.cngold.org.cn/technology-318.html) | [科学成果评价](https://www.cngold.org.cn/technology-319.html) | [新技术推广](https://www.cngold.org.cn/technology-320.html) | [黄金技术大会](https://www.cngold.org.cn/technology-350.html) |
+ | ------------------------------------------------------------------- | ------------------------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------- |
+ | [technology-318](https://rsshub.app/cngold/technology-318) | [technology-319](https://rsshub.app/cngold/technology-319) | [technology-320](https://rsshub.app/cngold/technology-320) | [technology-350](https://rsshub.app/cngold/technology-350) |
+
+ More languages
+
+| 语言代码 | 语言名称 |
+| ------------------------------------------------- | ---------- |
+| English | english |
+| Español - España (Spanish - Spain) | spanish |
+| Français (French) | french |
+| Italiano (Italian) | italian |
+| Deutsch (German) | german |
+| Ελληνικά (Greek) | greek |
+| 한국어 (Korean) | koreana |
+| 简体中文 (Simplified Chinese) | schinese |
+| 繁體中文 (Traditional Chinese) | tchinese |
+| Русский (Russian) | russian |
+| ไทย (Thai) | thai |
+| 日本語 (Japanese) | japanese |
+| Português (Portuguese) | portuguese |
+| Português - Brasil (Portuguese - Brazil) | brazilian |
+| Polski (Polish) | polish |
+| Dansk (Danish) | danish |
+| Nederlands (Dutch) | dutch |
+| Suomi (Finnish) | finnish |
+| Norsk (Norwegian) | norwegian |
+| Svenska (Swedish) | swedish |
+| Čeština (Czech) | czech |
+| Magyar (Hungarian) | hungarian |
+| Română (Romanian) | romanian |
+| Български (Bulgarian) | bulgarian |
+| Türkçe (Turkish) | turkish |
+| Українська (Ukrainian) | ukrainian |
+| Tiếng Việt (Vietnamese) | vietnamese |
+| Español - Latinoamérica (Spanish - Latin America) | latam |
+
+ 更多栏目
+更多栏目
- #### 要闻
+ #### 要闻
- | 财经要闻 | 观点评论 | 民生消费 |
- | -------- | -------- | --------- |
- | xwzx/hg | xwzx/jr | xwzx/msxf |
+ | 财经要闻 | 观点评论 | 民生消费 |
+ | -------- | -------- | --------- |
+ | xwzx/hg | xwzx/jr | xwzx/msxf |
- #### 公司
+ #### 公司
- | 公司要闻 | 公司深度 | 公司巡礼 |
- | --------- | --------- | --------- |
- | ssgs/gsxw | ssgs/gssd | ssgs/gsxl |
+ | 公司要闻 | 公司深度 | 公司巡礼 |
+ | --------- | --------- | --------- |
+ | ssgs/gsxw | ssgs/gssd | ssgs/gsxl |
- #### 市场
+ #### 市场
- | A 股市场 | 港股资讯 | 债市研究 | 海外报道 | 期货报道 |
- | --------- | --------- | --------- | --------- | --------- |
- | gppd/gsyj | gppd/ggzx | gppd/zqxw | gppd/hwbd | gppd/qhbd |
+ | A 股市场 | 港股资讯 | 债市研究 | 海外报道 | 期货报道 |
+ | --------- | --------- | --------- | --------- | --------- |
+ | gppd/gsyj | gppd/ggzx | gppd/zqxw | gppd/hwbd | gppd/qhbd |
- #### 基金
+ #### 基金
- | 基金动态 | 基金视点 | 基金持仓 | 私募基金 | 基民学苑 |
- | --------- | --------- | --------- | --------- | --------- |
- | tzjj/jjdt | tzjj/jjks | tzjj/jjcs | tzjj/smjj | tzjj/tjdh |
+ | 基金动态 | 基金视点 | 基金持仓 | 私募基金 | 基民学苑 |
+ | --------- | --------- | --------- | --------- | --------- |
+ | tzjj/jjdt | tzjj/jjks | tzjj/jjcs | tzjj/smjj | tzjj/tjdh |
- #### 机构
+ #### 机构
- | 券商 | 银行 | 保险 |
- | ---- | ---- | ---- |
- | qs | yh | bx |
+ | 券商 | 银行 | 保险 |
+ | ---- | ---- | ---- |
+ | qs | yh | bx |
- #### 其他
+ #### 其他
- | 中证快讯 7x24 | IPO 鉴真 | 公司能见度 |
- | ------------- | -------- | ---------- |
- | sylm/jsbd | yc/ipojz | yc/gsnjd |
-
${$.html()}`;
+
+ return {
+ title: item.field_5,
+ description,
+ pubDate: parseDate(item.field_2.iso_timestamp),
+ link,
+ guid: `cybersecurityventures:${item.id}`,
+ } as DataItem;
+ }),
+ };
+}
diff --git a/lib/routes/cybersecurityventures/types.ts b/lib/routes/cybersecurityventures/types.ts
new file mode 100644
index 00000000000000..8440b884946461
--- /dev/null
+++ b/lib/routes/cybersecurityventures/types.ts
@@ -0,0 +1,17 @@
+export interface RawRecord {
+ id: string;
+ field_2: {
+ date: string;
+ date_formatted: string;
+ hours: string;
+ minutes: string;
+ am_pm: string;
+ unix_timestamp: number;
+ iso_timestamp: string;
+ timestamp: string;
+ time: number;
+ };
+ field_3: string;
+ field_4: string;
+ field_5: string;
+}
diff --git a/lib/routes/cyzone/author.ts b/lib/routes/cyzone/author.ts
index 1c75e31bfda411..dfa0254fe1ed35 100644
--- a/lib/routes/cyzone/author.ts
+++ b/lib/routes/cyzone/author.ts
@@ -4,7 +4,7 @@ import { rootUrl, apiRootUrl, processItems, getInfo } from './util';
export const route: Route = {
path: '/author/:id',
- categories: ['new-media'],
+ categories: ['new-media', 'popular'],
example: '/cyzone/author/1225562',
parameters: { id: '作者 id,可在对应作者页 URL 中找到' },
features: {
diff --git a/lib/routes/cyzone/label.ts b/lib/routes/cyzone/label.ts
index 9defe810e88ada..7259e5e83d05e0 100644
--- a/lib/routes/cyzone/label.ts
+++ b/lib/routes/cyzone/label.ts
@@ -4,7 +4,7 @@ import { rootUrl, apiRootUrl, processItems, getInfo } from './util';
export const route: Route = {
path: '/label/:name',
- categories: ['new-media'],
+ categories: ['new-media', 'popular'],
example: '/cyzone/label/创业邦周报',
parameters: { name: '标签名称,可在对应标签页 URL 中找到' },
features: {
diff --git a/lib/routes/cyzone/namespace.ts b/lib/routes/cyzone/namespace.ts
index e9a9f2c62c7657..5a92c11f3f0d05 100644
--- a/lib/routes/cyzone/namespace.ts
+++ b/lib/routes/cyzone/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '创业邦',
url: 'cyzone.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/cztv/namespace.ts b/lib/routes/cztv/namespace.ts
index 2c7a7f915fa825..04f6f4434f1bbc 100644
--- a/lib/routes/cztv/namespace.ts
+++ b/lib/routes/cztv/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '新蓝网(浙江广播电视集团)',
url: 'cztv.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/dahecube/namespace.ts b/lib/routes/dahecube/namespace.ts
index 44cf03e514201c..f99b69a0b7deb9 100644
--- a/lib/routes/dahecube/namespace.ts
+++ b/lib/routes/dahecube/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '大河财立方',
url: 'dahecube.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/daily/discussed.ts b/lib/routes/daily/discussed.ts
index 54a5957276b6d4..23f80e4b57ba9a 100644
--- a/lib/routes/daily/discussed.ts
+++ b/lib/routes/daily/discussed.ts
@@ -1,9 +1,5 @@
import { Route } from '@/types';
-import { getData, getList, getRedirectedLink } from './utils.js';
-
-const variables = {
- first: 15,
-};
+import { baseUrl, getData, getList } from './utils.js';
const query = `
query MostDiscussedFeed(
@@ -19,6 +15,7 @@ const query = `
edges {
node {
...FeedPost
+ contentHtml
}
}
}
@@ -33,6 +30,7 @@ const query = `
image
readTime
permalink
+ commentsPermalink
summary
createdAt
numUpvotes
@@ -52,37 +50,40 @@ const query = `
bio
}
`;
-const graphqlQuery = {
- query,
- variables,
-};
export const route: Route = {
path: '/discussed',
example: '/daily/discussed',
radar: [
{
- source: ['daily.dev/popular'],
+ source: ['app.daily.dev/discussed'],
},
],
name: 'Most Discussed',
maintainers: ['Rjnishant530'],
handler,
- url: 'daily.dev/popular',
+ url: 'app.daily.dev/discussed',
};
-async function handler() {
- const baseUrl = 'https://app.daily.dev/discussed';
- const data = await getData(graphqlQuery);
- const list = getList(data);
- const items = await getRedirectedLink(list);
+async function handler(ctx) {
+ const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20;
+ const link = `${baseUrl}/discussed`;
+
+ const data = await getData({
+ query,
+ variables: {
+ first: limit,
+ },
+ });
+ const items = getList(data);
+
return {
- title: 'Most Discussed',
- link: baseUrl,
+ title: 'Real-time discussions in the developer community | daily.dev',
+ link,
item: items,
- description: 'Most Discussed Posts on Daily.dev',
- logo: 'https://app.daily.dev/favicon-32x32.png',
- icon: 'https://app.daily.dev/favicon-32x32.png',
+ description: 'Stay on top of real-time developer discussions on daily.dev. Join conversations happening now and engage with the most active community members.',
+ logo: `${baseUrl}/favicon-32x32.png`,
+ icon: `${baseUrl}/favicon-32x32.png`,
language: 'en-us',
};
}
diff --git a/lib/routes/daily/index.ts b/lib/routes/daily/index.ts
index 031f47c0a773eb..40f0fdfbeaef7d 100644
--- a/lib/routes/daily/index.ts
+++ b/lib/routes/daily/index.ts
@@ -1,5 +1,5 @@
import { Route } from '@/types';
-import { getData, getList, getRedirectedLink } from './utils.js';
+import { baseUrl, getData, getList } from './utils.js';
const variables = {
version: 11,
@@ -28,6 +28,7 @@ const query = `
edges {
node {
...FeedPost
+ contentHtml
}
}
}
@@ -42,6 +43,7 @@ const query = `
image
readTime
permalink
+ commentsPermalink
summary
createdAt
numUpvotes
@@ -72,27 +74,28 @@ export const route: Route = {
example: '/daily',
radar: [
{
- source: ['daily.dev/popular'],
+ source: ['app.daily.dev/popular'],
},
],
name: 'Popular',
maintainers: ['Rjnishant530'],
handler,
- url: 'daily.dev/popular',
+ url: 'app.daily.dev/popular',
};
async function handler() {
- const baseUrl = 'https://app.daily.dev/popular';
+ const link = `${baseUrl}/popular`;
+
const data = await getData(graphqlQuery);
- const list = getList(data);
- const items = await getRedirectedLink(list);
+ const items = getList(data);
+
return {
- title: 'Popular',
- link: baseUrl,
+ title: 'Popular posts on daily.dev',
+ link,
item: items,
- description: 'Popular Posts on Daily.dev',
- logo: 'https://app.daily.dev/favicon-32x32.png',
- icon: 'https://app.daily.dev/favicon-32x32.png',
+ description: 'daily.dev is the easiest way to stay updated on the latest programming news. Get the best content from the top tech publications on any topic you want.',
+ logo: `${baseUrl}/favicon-32x32.png`,
+ icon: `${baseUrl}/favicon-32x32.png`,
language: 'en-us',
};
}
diff --git a/lib/routes/daily/namespace.ts b/lib/routes/daily/namespace.ts
index 2dbd9bf58eba9c..1b57d03d51c40a 100644
--- a/lib/routes/daily/namespace.ts
+++ b/lib/routes/daily/namespace.ts
@@ -2,6 +2,7 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'Daily.dev',
- url: 'daily.dev',
+ url: 'app.daily.dev',
categories: ['social-media'],
+ lang: 'en',
};
diff --git a/lib/routes/daily/source.ts b/lib/routes/daily/source.ts
new file mode 100644
index 00000000000000..4594daa6d462c1
--- /dev/null
+++ b/lib/routes/daily/source.ts
@@ -0,0 +1,186 @@
+import { Route } from '@/types';
+import { baseUrl, getBuildId, getData, getList } from './utils';
+import ofetch from '@/utils/ofetch';
+import cache from '@/utils/cache';
+import { config } from '@/config';
+
+interface Source {
+ id: string;
+ name: string;
+ handle: string;
+ image: string;
+ permalink: string;
+ description: string;
+ type: string;
+}
+
+const sourceFeedQuery = `
+query SourceFeed($source: ID!, $loggedIn: Boolean! = false, $first: Int, $after: String, $ranking: Ranking, $supportedTypes: [String!]) {
+ page: sourceFeed(
+ source: $source
+ first: $first
+ after: $after
+ ranking: $ranking
+ supportedTypes: $supportedTypes
+ ) {
+ ...FeedPostConnection
+ }
+}
+
+fragment FeedPostConnection on PostConnection {
+ pageInfo {
+ hasNextPage
+ endCursor
+ }
+ edges {
+ node {
+ ...FeedPost
+ pinnedAt
+ contentHtml
+ ...UserPost @include(if: $loggedIn)
+ }
+ }
+}
+
+fragment FeedPost on Post {
+ ...FeedPostInfo
+ sharedPost {
+ id
+ title
+ image
+ readTime
+ permalink
+ commentsPermalink
+ createdAt
+ type
+ tags
+ source {
+ id
+ handle
+ permalink
+ image
+ }
+ slug
+ }
+ trending
+ feedMeta
+ collectionSources {
+ handle
+ image
+ }
+ numCollectionSources
+ updatedAt
+ slug
+}
+
+fragment FeedPostInfo on Post {
+ id
+ title
+ image
+ readTime
+ permalink
+ commentsPermalink
+ createdAt
+ commented
+ bookmarked
+ views
+ numUpvotes
+ numComments
+ summary
+ bookmark {
+ remindAt
+ }
+ author {
+ id
+ name
+ image
+ username
+ permalink
+ }
+ type
+ tags
+ source {
+ id
+ handle
+ name
+ permalink
+ image
+ type
+ }
+ userState {
+ vote
+ flags {
+ feedbackDismiss
+ }
+ }
+ slug
+}
+
+fragment UserPost on Post {
+ read
+ upvoted
+ commented
+ bookmarked
+ downvoted
+}`;
+
+export const route: Route = {
+ path: '/source/:sourceId',
+ example: '/daily/source/hn',
+ parameters: {
+ sourceId: 'The source id',
+ },
+ radar: [
+ {
+ source: ['app.daily.dev/sources/:sourceId'],
+ },
+ ],
+ name: 'Source Posts',
+ maintainers: ['TonyRL'],
+ handler,
+ url: 'app.daily.dev',
+};
+
+async function handler(ctx) {
+ const sourceId = ctx.req.param('sourceId');
+ const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 10;
+ const link = `${baseUrl}/sources/${sourceId}`;
+
+ const buildId = await getBuildId();
+
+ const userData = (await cache.tryGet(`daily:source:${sourceId}`, async () => {
+ const response = await ofetch(`${baseUrl}/_next/data/${buildId}/en/sources/${sourceId}.json`);
+ return response.pageProps.source;
+ })) as Source;
+
+ const items = await cache.tryGet(
+ `daily:source:${sourceId}:posts`,
+ async () => {
+ const edges = await getData({
+ query: sourceFeedQuery,
+ variables: {
+ source: sourceId,
+ supportedTypes: ['article', 'video:youtube', 'collection'],
+ period: 30,
+ first: limit,
+ after: '',
+ loggedIn: false,
+ },
+ });
+ return getList(edges);
+ },
+ config.cache.routeExpire,
+ false
+ );
+
+ return {
+ title: `${userData.name} posts on daily.dev`,
+ description: userData.description,
+ link,
+ item: items,
+ image: userData.image,
+ logo: userData.image,
+ icon: userData.image,
+ language: 'en-us',
+ };
+}
diff --git a/lib/routes/daily/upvoted.ts b/lib/routes/daily/upvoted.ts
index 68a2a19d6cc784..8326d0618ed6d0 100644
--- a/lib/routes/daily/upvoted.ts
+++ b/lib/routes/daily/upvoted.ts
@@ -1,5 +1,5 @@
import { Route } from '@/types';
-import { getData, getList, getRedirectedLink } from './utils.js';
+import { baseUrl, getData, getList } from './utils.js';
const variables = {
period: 7,
@@ -21,6 +21,7 @@ const query = `
edges {
node {
...FeedPost
+ contentHtml
}
}
}
@@ -35,6 +36,7 @@ const query = `
image
readTime
permalink
+ commentsPermalink
summary
createdAt
numUpvotes
@@ -56,37 +58,35 @@ const query = `
`;
-const graphqlQuery = {
- query,
- variables,
-};
-
export const route: Route = {
path: '/upvoted',
example: '/daily/upvoted',
radar: [
{
- source: ['daily.dev/popular'],
+ source: ['app.daily.dev/upvoted'],
},
],
name: 'Most upvoted',
maintainers: ['Rjnishant530'],
handler,
- url: 'daily.dev/popular',
+ url: 'app.daily.dev/upvoted',
};
async function handler() {
- const baseUrl = 'https://app.daily.dev/upvoted';
- const data = await getData(graphqlQuery);
- const list = getList(data);
- const items = await getRedirectedLink(list);
+ const link = `${baseUrl}/upvoted`;
+ const data = await getData({
+ query,
+ variables,
+ });
+ const items = getList(data);
+
return {
- title: 'Most Upvoted',
- link: baseUrl,
+ title: 'Most upvoted posts for developers | daily.dev',
+ link,
item: items,
- description: 'Most Upvoted Posts on Daily.dev',
- logo: 'https://app.daily.dev/favicon-32x32.png',
- icon: 'https://app.daily.dev/favicon-32x32.png',
+ description: 'Find the most upvoted developer posts on daily.dev. Explore top-rated content in coding, tutorials, and tech news from the largest developer network in the world.',
+ logo: `${baseUrl}/favicon-32x32.png`,
+ icon: `${baseUrl}/favicon-32x32.png`,
language: 'en-us',
};
}
diff --git a/lib/routes/daily/user.ts b/lib/routes/daily/user.ts
index 7abaf15a029cb8..cdf7383684c7b7 100644
--- a/lib/routes/daily/user.ts
+++ b/lib/routes/daily/user.ts
@@ -1,13 +1,8 @@
import { Route } from '@/types';
-import { baseUrl, getBuildId, getData } from './utils';
+import { baseUrl, getBuildId, getData, getList } from './utils';
import ofetch from '@/utils/ofetch';
import cache from '@/utils/cache';
import { config } from '@/config';
-import { parseDate } from '@/utils/parse-date';
-import { art } from '@/utils/render';
-import path from 'path';
-import { getCurrentPath } from '@/utils/helpers';
-const __dirname = getCurrentPath(import.meta.url);
const userPostQuery = `
query AuthorFeed(
@@ -155,20 +150,18 @@ const userPostQuery = `
downvoted
}`;
-const render = (data) => art(path.join(__dirname, 'templates/posts.art'), data);
-
export const route: Route = {
path: '/user/:userId',
example: '/daily/user/kramer',
radar: [
{
- source: ['daily.dev/:userId/posts', 'daily.dev/:userId'],
+ source: ['app.daily.dev/:userId/posts', 'app.daily.dev/:userId'],
},
],
name: 'User Posts',
maintainers: ['TonyRL'],
handler,
- url: 'daily.dev',
+ url: 'app.daily.dev',
};
async function handler(ctx) {
@@ -178,12 +171,12 @@ async function handler(ctx) {
const buildId = await getBuildId();
const userData = await cache.tryGet(`daily:user:${userId}`, async () => {
- const resposne = await ofetch(`${baseUrl}/_next/data/${buildId}/en/${userId}.json`, {
+ const response = await ofetch(`${baseUrl}/_next/data/${buildId}/en/${userId}.json`, {
query: {
userId,
},
});
- return resposne.pageProps;
+ return response.pageProps;
});
const user = (userData as any).user;
@@ -198,17 +191,7 @@ async function handler(ctx) {
loggedIn: false,
},
});
- return edges.map(({ node }) => ({
- title: node.title,
- description: render({
- image: node.image,
- content: node.contentHtml?.replaceAll('\n', '
') ?? node.summary,
- }),
- link: node.permalink,
- author: node.author?.name,
- category: node.tags,
- pubDate: parseDate(node.createdAt),
- }));
+ return getList(edges);
},
config.cache.routeExpire,
false
diff --git a/lib/routes/daily/utils.ts b/lib/routes/daily/utils.ts
index 8f203c744d15a3..4576a869502544 100644
--- a/lib/routes/daily/utils.ts
+++ b/lib/routes/daily/utils.ts
@@ -2,10 +2,15 @@ import { parseDate } from '@/utils/parse-date';
import ofetch from '@/utils/ofetch';
import cache from '@/utils/cache';
import { config } from '@/config';
+import { art } from '@/utils/render';
+import path from 'node:path';
+import { getCurrentPath } from '@/utils/helpers';
+const __dirname = getCurrentPath(import.meta.url);
-const baseUrl = 'https://app.daily.dev';
+export const baseUrl = 'https://app.daily.dev';
+const gqlUrl = `https://api.daily.dev/graphql`;
-const getBuildId = () =>
+export const getBuildId = () =>
cache.tryGet(
'daily:buildId',
async () => {
@@ -17,40 +22,30 @@ const getBuildId = () =>
false
);
-const getData = async (graphqlQuery) => {
- const response = await ofetch(`${baseUrl}/api/graphql`, {
+export const getData = async (graphqlQuery) => {
+ const response = await ofetch(gqlUrl, {
method: 'POST',
body: graphqlQuery,
});
return response.data.page.edges;
};
-const getList = (data) =>
- data.map((value) => {
- const { id, title, image, permalink, summary, createdAt, numUpvotes, author, tags, numComments } = value.node;
- const pubDate = parseDate(createdAt);
- return {
- id,
- title,
- link: permalink,
- description: summary,
- author: author?.name,
- itunes_item_image: image,
- pubDate,
- upvotes: numUpvotes,
- comments: numComments,
- category: tags,
- };
- });
-
-const getRedirectedLink = (data) =>
- Promise.all(
- data.map((v) =>
- cache.tryGet(v.link, async () => {
- const resp = await ofetch.raw(v.link);
- return { ...v, link: resp.headers.get('location') };
- })
- )
- );
+const render = (data) => art(path.join(__dirname, 'templates/posts.art'), data);
-export { baseUrl, getBuildId, getData, getList, getRedirectedLink };
+export const getList = (edges) =>
+ edges.map(({ node }) => ({
+ id: node.id,
+ title: node.title,
+ link: node.commentsPermalink ?? node.permalink,
+ guid: node.permalink,
+ description: render({
+ image: node.image,
+ content: node.contentHtml?.replaceAll('\n', '
') ?? node.summary,
+ }),
+ author: node.author?.name,
+ itunes_item_image: node.image,
+ pubDate: parseDate(node.createdAt),
+ upvotes: node.numUpvotes,
+ comments: node.numComments,
+ category: node.tags,
+ }));
diff --git a/lib/routes/damai/activity.ts b/lib/routes/damai/activity.ts
index ff26f7b209c683..bcf0962bcf926f 100644
--- a/lib/routes/damai/activity.ts
+++ b/lib/routes/damai/activity.ts
@@ -15,13 +15,13 @@ export const route: Route = {
features: {
requireConfig: false,
requirePuppeteer: false,
- antiCrawler: false,
+ antiCrawler: true,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
name: '票务更新',
- maintainers: ['hoilc'],
+ maintainers: ['hoilc', 'Konano'],
handler,
description: `城市、分类名、子分类名,请参见[大麦网搜索页面](https://search.damai.cn/search.htm)`,
};
@@ -55,6 +55,7 @@ async function handler(ctx) {
return {
title: `大麦网票务 - ${city || '全国'} - ${category || '全部分类'}${subcategory ? ' - ' + subcategory : ''}${keyword ? ' - ' + keyword : ''}`,
link: 'https://search.damai.cn/search.htm',
+ allowEmpty: true,
item: list.map((item) => ({
title: item.nameNoHtml,
author: item.actors ? load(item.actors, null, false).text() : '大麦网',
diff --git a/lib/routes/damai/namespace.ts b/lib/routes/damai/namespace.ts
index 72327dc8ae10d8..6ef673c9026deb 100644
--- a/lib/routes/damai/namespace.ts
+++ b/lib/routes/damai/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '大麦网',
url: 'search.damai.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/dangdang/namespace.ts b/lib/routes/dangdang/namespace.ts
new file mode 100644
index 00000000000000..13a9b5270676f9
--- /dev/null
+++ b/lib/routes/dangdang/namespace.ts
@@ -0,0 +1,7 @@
+import type { Namespace } from '@/types';
+
+export const namespace: Namespace = {
+ name: '当当开放平台',
+ url: 'open.dangdang.com',
+ lang: 'zh-CN',
+};
diff --git a/lib/routes/dangdang/notice.ts b/lib/routes/dangdang/notice.ts
new file mode 100644
index 00000000000000..f80069b1a589b9
--- /dev/null
+++ b/lib/routes/dangdang/notice.ts
@@ -0,0 +1,69 @@
+import { Route } from '@/types';
+import cache from '@/utils/cache';
+import got from '@/utils/got';
+import { parseDate } from '@/utils/parse-date';
+import timezone from '@/utils/timezone';
+
+const typeMap = {
+ 0: '全部',
+ 1: '其他',
+ 2: '规则变更',
+};
+
+/**
+ *
+ * @param ctx {import('koa').Context}
+ */
+export const route: Route = {
+ path: '/notice/:type?',
+ categories: ['programming'],
+ example: '/dangdang/notice/1',
+ parameters: { type: '公告分类,默认为全部' },
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: false,
+ supportBT: false,
+ supportPodcast: false,
+ supportScihub: false,
+ },
+ name: '公告',
+ maintainers: ['353325487'],
+ handler,
+ description: `| 类型 | type |
+ | -------- | ---- |
+ | 全部 | 0 |
+ | 其他 | 1 |
+ | 规则变更 | 2 |`,
+};
+
+async function handler(ctx) {
+ const type = ctx.req.param('type');
+ const url = `https://open.dangdang.com/op-api/developer-platform/document/menu/list?categoryId=3&type=${type > 0 ? typeMap[type] : ''}`;
+ const response = await got({ method: 'get', url });
+
+ const list = response.data.data.documentMenu.map((item) => ({
+ title: item.title,
+ description: item.type,
+ documentId: item.documentId,
+ source: `https://open.dangdang.com/op-api/developer-platform/document/info/get?document_id=${item.documentId}`,
+ link: `https://open.dangdang.com/home/notice/message/1/${item.documentId}`,
+ pubDate: timezone(parseDate(item.modifyTime), +8),
+ }));
+
+ const result = await Promise.all(
+ list.map((item) =>
+ cache.tryGet(item.source, async () => {
+ const itemResponse = await got(item.source);
+ item.description = itemResponse.data.data.documentContentList[0].content;
+ return item;
+ })
+ )
+ );
+
+ return {
+ title: `当当开放平台 - ${typeMap[type] || typeMap[0]}`,
+ link: `https://open.dangdang.com/home/notice/message/1`,
+ item: result,
+ };
+}
diff --git a/lib/routes/daoxuan/namespace.ts b/lib/routes/daoxuan/namespace.ts
new file mode 100644
index 00000000000000..19ae3d23a55788
--- /dev/null
+++ b/lib/routes/daoxuan/namespace.ts
@@ -0,0 +1,7 @@
+import type { Namespace } from '@/types';
+
+export const namespace: Namespace = {
+ name: '道宣的窝',
+ url: 'daoxuan.cc',
+ lang: 'zh-CN',
+};
diff --git a/lib/routes/daoxuan/rss.ts b/lib/routes/daoxuan/rss.ts
new file mode 100644
index 00000000000000..46b847728d86e2
--- /dev/null
+++ b/lib/routes/daoxuan/rss.ts
@@ -0,0 +1,43 @@
+import { Route } from '@/types';
+import got from '@/utils/got';
+import { load } from 'cheerio';
+import { parseDate } from '@/utils/parse-date';
+
+export const route: Route = {
+ path: '/',
+ categories: ['blog'],
+ example: '/daoxuan',
+ radar: [
+ {
+ source: ['daoxuan.cc/'],
+ },
+ ],
+ name: '推荐阅读文章',
+ maintainers: ['dx2331lxz'],
+ url: 'daoxuan.cc/',
+ handler,
+};
+
+async function handler() {
+ const url = 'https://daoxuan.cc/';
+ const response = await got({ method: 'get', url });
+ const $ = load(response.data);
+ const items = $('div.recent-post-item')
+ .toArray()
+ .map((item) => {
+ item = $(item);
+ const a = item.find('a.article-title').first();
+ const timeElement = item.find('time').first();
+ return {
+ title: a.attr('title'),
+ link: `https://daoxuan.cc${a.attr('href')}`,
+ pubDate: parseDate(timeElement.attr('datetime')),
+ description: a.attr('title'),
+ };
+ });
+ return {
+ title: '道宣的窝',
+ link: url,
+ item: items,
+ };
+}
diff --git a/lib/routes/dapenti/namespace.ts b/lib/routes/dapenti/namespace.ts
index e3fe6f0a783bb2..6c3b0eba14c33c 100644
--- a/lib/routes/dapenti/namespace.ts
+++ b/lib/routes/dapenti/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '喷嚏',
url: 'dapenti.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/darwinawards/namespace.ts b/lib/routes/darwinawards/namespace.ts
index 20c3aa81fb7b16..e1895ace98e040 100644
--- a/lib/routes/darwinawards/namespace.ts
+++ b/lib/routes/darwinawards/namespace.ts
@@ -4,4 +4,5 @@ export const namespace: Namespace = {
name: 'Darwin Awards',
url: 'darwinawards.com',
categories: ['other'],
+ lang: 'en',
};
diff --git a/lib/routes/dataguidance/index.ts b/lib/routes/dataguidance/index.ts
new file mode 100644
index 00000000000000..74a40c31f5d1cd
--- /dev/null
+++ b/lib/routes/dataguidance/index.ts
@@ -0,0 +1,55 @@
+import { Route } from '@/types';
+import cache from '@/utils/cache';
+import ofetch from '@/utils/ofetch';
+
+import { parseDate } from '@/utils/parse-date';
+
+export const route: Route = {
+ name: 'News',
+ example: '/dataguidance/news',
+ path: '/news',
+ radar: [
+ {
+ source: ['www.dataguidance.com/info'],
+ },
+ ],
+ maintainers: ['harveyqiu'],
+ handler,
+ url: 'https://www.dataguidance.com/info?article_type=news_post',
+};
+
+async function handler() {
+ const rootUrl = 'https://www.dataguidance.com';
+ const url = 'https://dgcb20-ca-northeurope-dglive.yellowground-c1f17366.northeurope.azurecontainerapps.io/api/v1/content/articles?order=DESC_publishedOn&limit=25&article_types=news_post';
+
+ const response = await ofetch(url);
+
+ const data = response.data;
+
+ let items = data.map((item) => ({
+ title: item.title.en,
+ link: `${rootUrl}${item.url}`,
+ url: item.url,
+ pubDate: parseDate(item.publishedOn),
+ }));
+ const baseUrl = 'https://dgcb20-ca-northeurope-dglive.yellowground-c1f17366.northeurope.azurecontainerapps.io/api/v1/content/articles/by_path?path=';
+ items = await Promise.all(
+ items.map((item) =>
+ cache.tryGet(item.link, async () => {
+ const detailUrl = `${baseUrl}${item.url}`;
+
+ const detailResponse = await ofetch(detailUrl);
+
+ item.description = detailResponse.contentBody?.html.en.replaceAll('\n', '
');
+ delete item.url;
+ return item;
+ })
+ )
+ );
+
+ return {
+ title: 'Data Guidance News',
+ link: 'https://www.dataguidance.com/info?article_type=news_post',
+ item: items,
+ };
+}
diff --git a/lib/routes/dataguidance/namespace.ts b/lib/routes/dataguidance/namespace.ts
new file mode 100644
index 00000000000000..14494e43fcb171
--- /dev/null
+++ b/lib/routes/dataguidance/namespace.ts
@@ -0,0 +1,8 @@
+import type { Namespace } from '@/types';
+
+export const namespace: Namespace = {
+ name: 'DataGuidance',
+ url: 'dataguidance.com',
+ categories: ['other'],
+ lang: 'en',
+};
diff --git a/lib/routes/dayanzai/namespace.ts b/lib/routes/dayanzai/namespace.ts
index 3a62c54e3b1836..d9c15d2d0da0c6 100644
--- a/lib/routes/dayanzai/namespace.ts
+++ b/lib/routes/dayanzai/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '大眼仔旭',
url: 'dayanzai.me',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/dbaplus/namespace.ts b/lib/routes/dbaplus/namespace.ts
index 5de237b67c7e95..0d48834010b8a0 100644
--- a/lib/routes/dbaplus/namespace.ts
+++ b/lib/routes/dbaplus/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'dbaplus社群',
url: 'dbaplus.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/dblp/namespace.ts b/lib/routes/dblp/namespace.ts
index 9cd978d440fa05..4a07bf01a91494 100644
--- a/lib/routes/dblp/namespace.ts
+++ b/lib/routes/dblp/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'DBLP',
url: 'dblp.org',
+ lang: 'en',
};
diff --git a/lib/routes/dblp/publication.ts b/lib/routes/dblp/publication.ts
index 90b1f99e0a9b57..e430c9e78794e7 100644
--- a/lib/routes/dblp/publication.ts
+++ b/lib/routes/dblp/publication.ts
@@ -1,6 +1,6 @@
import { Route } from '@/types';
// 导入所需模组
-import got from '@/utils/got'; // 自订的 got
+import ofetch from '@/utils/ofetch';
// import { parseDate } from '@/utils/parse-date';
export const route: Route = {
@@ -35,10 +35,8 @@ async function handler(ctx) {
result: {
hits: { hit: data },
},
- } = await got({
- method: 'get',
- url: 'https://dblp.org/search/publ/api',
- searchParams: {
+ } = await ofetch('https://dblp.org/search/publ/api', {
+ query: {
q: field,
format: 'json',
h: 10,
@@ -46,7 +44,7 @@ async function handler(ctx) {
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
},
- }).json();
+ });
// console.log(data);
diff --git a/lib/routes/dcard/namespace.ts b/lib/routes/dcard/namespace.ts
index 8e7fbc8a1eefdd..6cda36822b7d7c 100644
--- a/lib/routes/dcard/namespace.ts
+++ b/lib/routes/dcard/namespace.ts
@@ -6,4 +6,5 @@ export const namespace: Namespace = {
description: `:::warning
僅能透過台灣 IP 抓取。
:::`,
+ lang: 'zh-TW',
};
diff --git a/lib/routes/dcfever/namespace.ts b/lib/routes/dcfever/namespace.ts
index f14a921ffd9c43..b97d35d0b6c290 100644
--- a/lib/routes/dcfever/namespace.ts
+++ b/lib/routes/dcfever/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'DCFever',
url: 'dcfever.com',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/dcfever/news.ts b/lib/routes/dcfever/news.ts
index e9364c9cf40d10..ff2235fb428dfa 100644
--- a/lib/routes/dcfever/news.ts
+++ b/lib/routes/dcfever/news.ts
@@ -5,7 +5,7 @@ import { baseUrl, parseItem } from './utils';
export const route: Route = {
path: '/news/:type?',
- categories: ['new-media'],
+ categories: ['new-media', 'popular'],
example: '/dcfever/news',
parameters: { type: '分類,預設為所有新聞' },
name: '新聞中心',
diff --git a/lib/routes/dcfever/reviews.ts b/lib/routes/dcfever/reviews.ts
index f7ba620d79ab23..61e58354fb43be 100644
--- a/lib/routes/dcfever/reviews.ts
+++ b/lib/routes/dcfever/reviews.ts
@@ -5,7 +5,7 @@ import { baseUrl, parseItem } from './utils';
export const route: Route = {
path: '/reviews/:type?',
- categories: ['new-media'],
+ categories: ['new-media', 'popular'],
example: '/dcfever/reviews/cameras',
parameters: { type: '分類,預設為 `cameras`' },
radar: [
diff --git a/lib/routes/dcfever/trading-search.ts b/lib/routes/dcfever/trading-search.ts
index b5d85a60a4ba6f..f812a301039186 100644
--- a/lib/routes/dcfever/trading-search.ts
+++ b/lib/routes/dcfever/trading-search.ts
@@ -6,7 +6,7 @@ import { baseUrl, parseTradeItem } from './utils';
export const route: Route = {
path: '/trading/search/:keyword/:mainCat?',
- categories: ['new-media'],
+ categories: ['new-media', 'popular'],
example: '/dcfever/trading/search/Sony',
parameters: { keyword: '關鍵字', mainCat: '主要分類 ID,見上表' },
name: '二手市集 - 物品搜尋',
diff --git a/lib/routes/dcfever/trading.ts b/lib/routes/dcfever/trading.ts
index 6641f7f4e8bb80..6c994e910f50e3 100644
--- a/lib/routes/dcfever/trading.ts
+++ b/lib/routes/dcfever/trading.ts
@@ -6,7 +6,7 @@ import { baseUrl, parseTradeItem } from './utils';
export const route: Route = {
path: '/trading/:id',
- categories: ['new-media'],
+ categories: ['new-media', 'popular'],
example: '/dcfever/trading/1',
parameters: { id: '分類 ID,見下表' },
name: '二手市集',
@@ -29,16 +29,14 @@ async function handler(ctx) {
const response = await ofetch(link.href);
const $ = load(response);
- const list = $('.item_list li a')
+ const list = $('.item_grid_wrap div a')
.toArray()
- .filter((item) => $(item).attr('href') !== '/documents/advertising.php')
.map((item) => {
item = $(item);
- item.find('.optional').remove();
return {
- title: item.find('.trade_title').text(),
+ title: item.find('.lazyloadx').attr('alt'),
link: new URL(item.attr('href'), link.href).href,
- author: item.find('.trade_info').text(),
+ author: item.find('.trade_info div span').eq(1).text(),
};
});
diff --git a/lib/routes/dcfever/utils.ts b/lib/routes/dcfever/utils.ts
index 8f8527bbf96883..c2223147716658 100644
--- a/lib/routes/dcfever/utils.ts
+++ b/lib/routes/dcfever/utils.ts
@@ -63,11 +63,20 @@ const parseTradeItem = (item) =>
const response = await ofetch(item.link);
const $ = load(response);
- $('.selector_text').remove();
- $('.selector_image_div').each((_, div) => {
+ const photoSelector = $('#trading_item_section .description')
+ .contents()
+ .filter((_, e) => e.type === 'comment')
+ .toArray()
+ .map((e) => e.data)
+ .join('');
+
+ const $photo = load(photoSelector, null, false);
+
+ $photo('.selector_text').remove();
+ $photo('.selector_image_div').each((_, div) => {
delete div.attribs.onclick;
});
- $('.desktop_photo_selector img').each((_, img) => {
+ $photo('.desktop_photo_selector img').each((_, img) => {
if (img.attribs.src.endsWith('_sqt.jpg')) {
img.attribs.src = img.attribs.src.replace('_sqt.jpg', '.jpg');
}
@@ -76,7 +85,7 @@ const parseTradeItem = (item) =>
item.description = art(path.join(__dirname, 'templates/trading.art'), {
info: $('.info_col'),
description: $('.description_text').html(),
- photo: $('.desktop_photo_selector').html(),
+ photo: $photo('.desktop_photo_selector').html(),
});
return item;
diff --git a/lib/routes/ddosi/namespace.ts b/lib/routes/ddosi/namespace.ts
index b7215ed8e661db..3eb85fb5ddbe74 100644
--- a/lib/routes/ddosi/namespace.ts
+++ b/lib/routes/ddosi/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '雨苁博客',
url: 'ddosi.org',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/deadbydaylight/index.ts b/lib/routes/deadbydaylight/index.ts
new file mode 100644
index 00000000000000..e32a7bc507e5c2
--- /dev/null
+++ b/lib/routes/deadbydaylight/index.ts
@@ -0,0 +1,69 @@
+import { Route } from '@/types';
+import cache from '@/utils/cache';
+import ofetch from '@/utils/ofetch';
+import { parseDate } from '@/utils/parse-date';
+import MarkdownIt from 'markdown-it';
+const md = MarkdownIt({
+ html: true,
+ linkify: true,
+});
+
+const baseUrl = 'https://deadbydaylight.com';
+
+export const route: Route = {
+ path: '/blog',
+ categories: ['game'],
+ example: '/deadbydaylight/blog',
+ parameters: {},
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: false,
+ supportBT: false,
+ supportPodcast: false,
+ supportScihub: false,
+ },
+ radar: [
+ {
+ source: ['deadbydaylight.com/news'],
+ target: '/news',
+ },
+ ],
+ name: 'Latest News',
+ maintainers: ['NeverBehave'],
+ handler,
+};
+
+async function handler() {
+ const data = await ofetch(`${baseUrl}/page-data/news/page-data.json`);
+
+ const articleMeta = data.result.pageContext.postsData.articles.edges;
+ // { 0: node: { id, locale, slug, title, excerpt, image, published_at, article_category}}
+
+ const items = await Promise.all(
+ Object.keys(articleMeta).map((id) => {
+ const content = articleMeta[id].node;
+ const slug = content.slug;
+ const dataUrl = `${baseUrl}/page-data/news/${slug}/page-data.json`;
+
+ return cache.tryGet(dataUrl, async () => {
+ const articleData = await ofetch(dataUrl);
+ const pageData = articleData.result.data.pageData;
+
+ return {
+ title: pageData.title,
+ link: `${baseUrl}${articleData.path}`,
+ description: md.render(pageData.content),
+ pubDate: parseDate(pageData.published_at),
+ category: pageData.article_category.name,
+ };
+ });
+ })
+ );
+
+ return {
+ title: 'Latest News',
+ link: 'https://deadbydaylight.com/news',
+ item: items,
+ };
+}
diff --git a/lib/routes/deadbydaylight/namespace.ts b/lib/routes/deadbydaylight/namespace.ts
new file mode 100644
index 00000000000000..7333371e3183d3
--- /dev/null
+++ b/lib/routes/deadbydaylight/namespace.ts
@@ -0,0 +1,13 @@
+import type { Namespace } from '@/types';
+
+export const namespace: Namespace = {
+ name: 'DeadbyDaylight',
+ url: 'deadbydaylight.com',
+ description: `
+ DeadbyDaylight Official
+ `,
+ zh: {
+ name: '黎明杀机',
+ },
+ lang: 'en',
+};
diff --git a/lib/routes/deadline/namespace.ts b/lib/routes/deadline/namespace.ts
index 59c3c11569ed9b..1162e861451a8a 100644
--- a/lib/routes/deadline/namespace.ts
+++ b/lib/routes/deadline/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'Deadline',
url: 'deadline.com',
+ lang: 'en',
};
diff --git a/lib/routes/dealstreetasia/home.ts b/lib/routes/dealstreetasia/home.ts
new file mode 100644
index 00000000000000..e1e740c9718ab5
--- /dev/null
+++ b/lib/routes/dealstreetasia/home.ts
@@ -0,0 +1,72 @@
+import { Route } from '@/types';
+// import cache from '@/utils/cache';
+import ofetch from '@/utils/ofetch'; // Unified request library used
+import { load } from 'cheerio'; // An HTML parser with an API similar to jQuery
+// import puppeteer from '@/utils/puppeteer';
+// import { parseDate } from '@/utils/parse-date';
+
+export const route: Route = {
+ path: '/home',
+ categories: ['traditional-media'],
+ example: '/dealstreetasia/home',
+ // parameters: { section: 'target section' },
+ radar: [
+ {
+ source: ['dealstreetasia.com/'],
+ },
+ ],
+ name: 'Home',
+ maintainers: ['jack2game'],
+ handler,
+ url: 'dealstreetasia.com/',
+};
+
+async function handler() {
+ // const section = ctx.req.param('section');
+ const items = await fetchPage();
+
+ return items;
+}
+
+async function fetchPage() {
+ const baseUrl = 'https://dealstreetasia.com'; // Define base URL
+
+ const response = await ofetch(`${baseUrl}/`);
+ const $ = load(response);
+
+ const jsonData = JSON.parse($('#__NEXT_DATA__').text());
+ // const headingText = jsonData.props.pageProps.sectionData.name;
+
+ const pageProps = jsonData.props.pageProps;
+ const list = [
+ ...pageProps.topStories,
+ ...pageProps.privateEquity,
+ ...pageProps.ventureCapital,
+ ...pageProps.unicorns,
+ ...pageProps.interviews,
+ ...pageProps.deals,
+ ...pageProps.analysis,
+ ...pageProps.ipos,
+ ...pageProps.opinion,
+ ...pageProps.policyAndRegulations,
+ ...pageProps.people,
+ ...pageProps.earningsAndResults,
+ ...pageProps.theLpView,
+ ...pageProps.dvNewsletters,
+ ...pageProps.reports,
+ ].map((item) => ({
+ title: item.post_title || item.title || 'No Title',
+ link: item.post_url || item.link || '',
+ description: item.post_excerpt || item.excerpt || '',
+ pubDate: item.post_date ? new Date(item.post_date).toUTCString() : item.date ? new Date(item.date).toUTCString() : '',
+ category: item.category_link ? item.category_link.replaceAll(/(<([^>]+)>)/gi, '') : '', // Clean HTML if category_link exists
+ image: item.image_url ? item.image_url.replace(/\?.*$/, '') : '', // Remove query parameters if image_url exists
+ }));
+
+ return {
+ title: 'Deal Street Asia',
+ language: 'en',
+ item: list,
+ link: 'https://dealstreetasia.com/',
+ };
+}
diff --git a/lib/routes/dealstreetasia/namespace.ts b/lib/routes/dealstreetasia/namespace.ts
new file mode 100644
index 00000000000000..8395378e8187c2
--- /dev/null
+++ b/lib/routes/dealstreetasia/namespace.ts
@@ -0,0 +1,7 @@
+import type { Namespace } from '@/types';
+
+export const namespace: Namespace = {
+ name: 'DealStreetAsia',
+ url: 'dealstreetasia.com',
+ lang: 'en',
+};
diff --git a/lib/routes/dealstreetasia/section.ts b/lib/routes/dealstreetasia/section.ts
new file mode 100644
index 00000000000000..d8fc0d7dbca592
--- /dev/null
+++ b/lib/routes/dealstreetasia/section.ts
@@ -0,0 +1,57 @@
+import { Route } from '@/types';
+// import cache from '@/utils/cache';
+import ofetch from '@/utils/ofetch'; // Unified request library used
+import { load } from 'cheerio'; // An HTML parser with an API similar to jQuery
+// import puppeteer from '@/utils/puppeteer';
+// import { parseDate } from '@/utils/parse-date';
+
+export const route: Route = {
+ path: '/section/:section',
+ categories: ['traditional-media'],
+ example: '/dealstreetasia/section/private-equity',
+ parameters: { section: 'target section' },
+ radar: [
+ {
+ source: ['dealstreetasia.com/'],
+ },
+ ],
+ name: 'Section',
+ maintainers: ['jack2game'],
+ handler,
+ url: 'dealstreetasia.com/',
+};
+
+async function handler(ctx) {
+ const section = ctx.req.param('section');
+ const items = await fetchPage(section);
+
+ return items;
+}
+
+async function fetchPage(section: string) {
+ const baseUrl = 'https://dealstreetasia.com'; // Define base URL
+
+ const response = await ofetch(`${baseUrl}/section/${section}/`);
+ const $ = load(response);
+
+ const jsonData = JSON.parse($('#__NEXT_DATA__').text());
+ const headingText = jsonData.props.pageProps.sectionData.name;
+
+ const items = jsonData.props.pageProps.sectionData.stories.nodes;
+
+ const feedItems = items.map((item) => ({
+ title: item.title || 'No Title',
+ link: item.uri ? `https://www.dealstreetasia.com${item.uri}` : '',
+ description: item.excerpt || '', // Default to empty string if undefined
+ pubDate: item.post_date ? new Date(item.post_date).toUTCString() : '',
+ category: item.sections.nodes.map((section) => section.name),
+ image: item.featuredImage?.node?.mediaItemUrl.replace(/\?.*$/, ''), // Use .replace to sanitize the image URL
+ }));
+
+ return {
+ title: 'Deal Street Asia - ' + headingText,
+ language: 'en',
+ item: feedItems,
+ link: 'https://dealstreetasia.com/section/' + section + '/',
+ };
+}
diff --git a/lib/routes/dedao/articles.ts b/lib/routes/dedao/articles.ts
new file mode 100644
index 00000000000000..58e4a9b0f92575
--- /dev/null
+++ b/lib/routes/dedao/articles.ts
@@ -0,0 +1,149 @@
+import { Route } from '@/types';
+import cache from '@/utils/cache';
+import got from '@/utils/got';
+import { load } from 'cheerio';
+
+export const route: Route = {
+ path: '/articles/:id?',
+ categories: ['new-media'],
+ example: '/articles/9', // 示例路径更新
+ parameters: { id: '文章类型 ID,8 为得到头条,9 为得到精选,默认为 8' },
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: false,
+ supportBT: false,
+ supportPodcast: false,
+ supportScihub: false,
+ },
+ radar: [
+ {
+ source: ['igetget.com'],
+ target: '/articles/:id',
+ },
+ ],
+ name: '得到文章',
+ maintainers: ['Jacky-Chen-Pro'],
+ handler,
+ url: 'www.igetget.com',
+};
+
+function handleParagraph(data) {
+ let html = '
';
+}
+
+function extractArticleContent(data) {
+ if (!data || typeof data !== 'object') {
+ return '';
+ }
+
+ switch (data.type) {
+ case 'paragraph':
+ return handleParagraph(data);
+ case 'text':
+ return handleText(data);
+ case 'image':
+ return handleImage(data);
+ case 'hr':
+ return handleHr();
+ default:
+ return '';
+ }
+}
+
+async function handler(ctx) {
+ const { id = '8' } = ctx.req.param();
+ const rootUrl = 'https://www.igetget.com';
+ const headers = {
+ Accept: 'application/json, text/plain, */*',
+ 'Content-Type': 'application/json;charset=UTF-8',
+ Referer: `https://m.igetget.com/share/course/free/detail?id=nb9L2q1e3OxKBPNsdoJrgN8P0Rwo6B`,
+ Origin: 'https://m.igetget.com',
+ };
+ const max_id = 0;
+
+ const response = await got.post('https://m.igetget.com/share/api/course/free/pageTurning', {
+ json: {
+ chapter_id: 0,
+ count: 5,
+ max_id,
+ max_order_num: 0,
+ pid: Number(id),
+ ptype: 24,
+ reverse: true,
+ since_id: 0,
+ since_order_num: 0,
+ },
+ headers,
+ });
+
+ const data = JSON.parse(response.body);
+ if (!data || !data.article_list) {
+ throw new Error('文章列表不存在或为空');
+ }
+
+ const articles = data.article_list;
+
+ const items = await Promise.all(
+ articles.map((article) => {
+ const postUrl = `https://m.igetget.com/share/course/article/article_id/${article.id}`;
+ const postTitle = article.title;
+ const postTime = new Date(article.publish_time * 1000).toUTCString();
+
+ return cache.tryGet(postUrl, async () => {
+ const detailResponse = await got.get(postUrl, { headers });
+ const $ = load(detailResponse.body);
+
+ const scriptTag = $('script')
+ .filter((_, el) => $(el).text()?.includes('window.__INITIAL_STATE__'))
+ .text();
+
+ if (scriptTag) {
+ const jsonStr = scriptTag.match(/window\.__INITIAL_STATE__\s*=\s*(\{.*\});/)?.[1];
+ if (jsonStr) {
+ const articleData = JSON.parse(jsonStr);
+
+ const description = JSON.parse(articleData.articleContent.content)
+ .map((data) => extractArticleContent(data))
+ .join('');
+
+ return {
+ title: postTitle,
+ link: postUrl,
+ description,
+ pubDate: postTime,
+ };
+ }
+ }
+ return null;
+ });
+ })
+ );
+
+ return {
+ title: `得到文章 - ${id === '8' ? '头条' : '精选'}`,
+ link: rootUrl,
+ item: items.filter(Boolean),
+ };
+}
diff --git a/lib/routes/dedao/index.ts b/lib/routes/dedao/index.ts
index d2b6150f6ec593..bd713c36e01870 100644
--- a/lib/routes/dedao/index.ts
+++ b/lib/routes/dedao/index.ts
@@ -6,8 +6,14 @@ import { parseDate } from '@/utils/parse-date';
export const route: Route = {
path: '/:category?',
- name: 'Unknown',
- maintainers: [],
+ name: '文章',
+ maintainers: ['nczitzk', 'pseudoyu'],
+ categories: ['new-media', 'popular'],
+ example: '/dedao',
+ parameters: { category: '分类,见下表,默认为`news`' },
+ description: `| 新闻 | 人物故事 | 视频 |
+ | ---- | ---- | ---- |
+ | news | figure | video |`,
handler,
};
diff --git a/lib/routes/dedao/knowledge.ts b/lib/routes/dedao/knowledge.ts
index 68ad5bae13a1a4..d6c6ca4accc646 100644
--- a/lib/routes/dedao/knowledge.ts
+++ b/lib/routes/dedao/knowledge.ts
@@ -9,7 +9,7 @@ import path from 'node:path';
export const route: Route = {
path: '/knowledge/:topic?/:type?',
- categories: ['new-media'],
+ categories: ['new-media', 'popular'],
example: '/dedao/knowledge',
parameters: { topic: '话题 id,可在对应话题页 URL 中找到', type: '分享类型,`true` 指精选,`false` 指最新,默认为精选' },
features: {
diff --git a/lib/routes/dedao/list.ts b/lib/routes/dedao/list.ts
index 28e11dd69f4fd3..cca1c51fba7c53 100644
--- a/lib/routes/dedao/list.ts
+++ b/lib/routes/dedao/list.ts
@@ -5,7 +5,7 @@ import { load } from 'cheerio';
export const route: Route = {
path: '/list/:category?',
- categories: ['new-media'],
+ categories: ['new-media', 'popular'],
example: '/dedao/list/年度日更',
parameters: { category: '分类名,默认为年度日更' },
features: {
diff --git a/lib/routes/dedao/namespace.ts b/lib/routes/dedao/namespace.ts
index c77830eb8651cc..9a61a637f8007e 100644
--- a/lib/routes/dedao/namespace.ts
+++ b/lib/routes/dedao/namespace.ts
@@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '得到',
url: 'dedao.cn',
+ lang: 'zh-CN',
};
diff --git a/lib/routes/dedao/user.ts b/lib/routes/dedao/user.ts
index 7e8a41bee4ecf2..e679ab52143113 100644
--- a/lib/routes/dedao/user.ts
+++ b/lib/routes/dedao/user.ts
@@ -15,7 +15,7 @@ const types = {
export const route: Route = {
path: '/user/:id/:type?',
- categories: ['new-media'],
+ categories: ['new-media', 'popular'],
example: '/dedao/user/VkA5OqLX4RyGxmZRNBMlwBrDaJQ9og',
parameters: { id: '用户 id,可在对应用户主页 URL 中找到', type: '类型,见下表,默认为`0`,即动态' },
features: {
diff --git a/lib/routes/deepin/namespace.ts b/lib/routes/deepin/namespace.ts
index eae1d434b24f66..9222bac2855b79 100644
--- a/lib/routes/deepin/namespace.ts
+++ b/lib/routes/deepin/namespace.ts
@@ -6,4 +6,5 @@ export const namespace: Namespace = {
zh: {
name: '深度Linux',
},
+ lang: 'zh-CN',
};
diff --git a/lib/routes/deepin/thread.ts b/lib/routes/deepin/thread.ts
new file mode 100644
index 00000000000000..99c30b9ec01f3d
--- /dev/null
+++ b/lib/routes/deepin/thread.ts
@@ -0,0 +1,102 @@
+import { Route, DataItem } from '@/types';
+import ofetch from '@/utils/ofetch';
+import cache from '@/utils/cache';
+import { parseDate } from '@/utils/parse-date';
+
+export const route: Route = {
+ path: '/threads/:type?',
+ categories: ['bbs'],
+ example: '/deepin/threads/latest',
+ parameters: {
+ type: {
+ description: '主题类型',
+ options: [
+ {
+ value: 'hot',
+ label: '最热主题',
+ },
+ {
+ value: 'latest',
+ label: '最新主题',
+ },
+ ],
+ },
+ },
+ name: '首页主题列表',
+ maintainers: ['myml'],
+ radar: [
+ {
+ source: ['bbs.deepin.org'],
+ target: '/threads/latest',
+ },
+ ],
+ handler,
+};
+
+interface ThreadIndexResult {
+ ThreadIndex: {
+ id: number;
+ subject: string;
+ created_at: string;
+ user: { nickname: string };
+ forum: { name: string };
+ }[];
+}
+interface ThreadInfoResult {
+ data: {
+ id: number;
+ subject: string;
+ created_at: string;
+ user: { nickname: string };
+ post: { message: string };
+ };
+}
+
+const TypeMap = {
+ hot: { where: 'hot_value', title: '最热主题' },
+ latest: { where: 'id', title: '最新主题' },
+};
+
+async function handler(ctx) {
+ let type = TypeMap.latest;
+ if (ctx.req.param('type') === 'hot') {
+ type = TypeMap.hot;
+ }
+ const res = await ofetch{{ intro }}
+{{ /if }}
+
+{{ if description }}
+ {{@ description }}
+{{ /if }}
\ No newline at end of file
diff --git a/lib/routes/deeplearning/the-batch.ts b/lib/routes/deeplearning/the-batch.ts
new file mode 100644
index 00000000000000..11d50796b6a26d
--- /dev/null
+++ b/lib/routes/deeplearning/the-batch.ts
@@ -0,0 +1,296 @@
+import { Route } from '@/types';
+import { getCurrentPath } from '@/utils/helpers';
+const __dirname = getCurrentPath(import.meta.url);
+
+import cache from '@/utils/cache';
+import ofetch from '@/utils/ofetch';
+import { load } from 'cheerio';
+import { parseDate } from '@/utils/parse-date';
+import { art } from '@/utils/render';
+import path from 'node:path';
+
+export const handler = async (ctx) => {
+ const { tag } = ctx.req.param();
+ const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 1;
+
+ const rootUrl = 'https://www.deeplearning.ai';
+ const currentUrl = new URL(`the-batch${tag ? `/tag/${tag.replace(/^tag\//, '').replace(/\/$/, '')}` : ''}/`, rootUrl).href;
+
+ const response = await ofetch(currentUrl);
+
+ const $ = load(response);
+
+ const language = $('html').prop('lang');
+
+ const data = JSON.parse($('script#__NEXT_DATA__').text());
+
+ const nextBuildId = data.buildId;
+ const posts = data.props?.pageProps?.posts ?? [];
+
+ let items = posts.slice(0, limit).map((item) => {
+ const title = item.title;
+ const description = art(path.join(__dirname, 'templates/description.art'), {
+ images: item.feature_image
+ ? [
+ {
+ src: item.feature_image,
+ alt: item.feature_image_alt,
+ },
+ ]
+ : undefined,
+ intro: item.excerpt ?? item.custom_excerpt,
+ });
+ const image = item.feature_image;
+ const guid = `the-batch-${item.slug}`;
+
+ return {
+ title,
+ description,
+ pubDate: parseDate(item.published_at),
+ link: new URL(`_next/data/${nextBuildId}/the-batch/${item.slug}.json`, rootUrl).href,
+ category: item.tags.map((t) => t.name),
+ guid,
+ id: guid,
+ content: {
+ html: description,
+ text: item.excerpt ?? item.custom_excerpt,
+ },
+ image,
+ banner: image,
+ language,
+ };
+ });
+
+ items = await Promise.all(
+ items.map((item) =>
+ cache.tryGet(item.link, async () => {
+ const detailResponse = await ofetch(item.link);
+
+ const post = detailResponse.pageProps?.cmsData?.post ?? undefined;
+
+ if (!post) {
+ return item;
+ }
+
+ const $$ = load(post.html);
+
+ $$('a').each((_, ele) => {
+ if (ele.attribs.href?.includes('utm_campaign')) {
+ const url = new URL(ele.attribs.href);
+ url.searchParams.delete('utm_campaign');
+ url.searchParams.delete('utm_source');
+ url.searchParams.delete('utm_medium');
+ url.searchParams.delete('_hsenc');
+ ele.attribs.href = url.href;
+ }
+ });
+
+ const title = post.title;
+ const description = art(path.join(__dirname, 'templates/description.art'), {
+ images: post.feature_image
+ ? [
+ {
+ src: post.feature_image,
+ alt: post.feature_image_alt,
+ },
+ ]
+ : undefined,
+ intro: post.excerpt ?? post.custom_excerpt,
+ description: $$.html(),
+ });
+ const guid = `the-batch-${post.slug}`;
+ const image = post.feature_image;
+
+ item.title = title;
+ item.description = description;
+ item.pubDate = parseDate(post.published_at);
+ item.link = new URL(`the-batch/${post.slug}`, rootUrl).href;
+ item.category = post.tags.map((t) => t.name);
+ item.author = post.authors.map((a) => a.name).join('/');
+ item.guid = guid;
+ item.id = guid;
+ item.content = {
+ html: description,
+ text: post.excerpt ?? post.custom_excerpt,
+ };
+ item.image = image;
+ item.banner = image;
+ item.updated = parseDate(post.updated_at);
+ item.language = language;
+
+ return item;
+ })
+ )
+ );
+
+ const image = new URL($('meta[property="og:image"]').prop('content'), rootUrl).href;
+
+ return {
+ title: $('title').text(),
+ description: $('meta[property="og:description"]').prop('content'),
+ link: currentUrl,
+ item: items,
+ allowEmpty: true,
+ image,
+ author: $('meta[property="og:site_name"]').prop('content'),
+ language,
+ };
+};
+
+export const route: Route = {
+ path: '/the-batch/:tag{.+}?',
+ name: 'The Batch',
+ url: 'www.deeplearning.ai',
+ maintainers: ['nczitzk', 'juvenn', 'TonyRL'],
+ handler,
+ example: '/deeplearning/the-batch',
+ parameters: { tag: 'Tag, Weekly Issues by default' },
+ description: `::: tip
+ If you subscribe to [Data Points](https://www.deeplearning.ai/the-batch/tag/data-points/),where the URL is \`https://www.deeplearning.ai/the-batch/tag/data-points/\`, extract the part \`https://www.deeplearning.ai/the-batch/tag\` to the end, which is \`data-points\`, and use it as the parameter to fill in. Therefore, the route will be [\`/deeplearning/the-batch/data-points\`](https://rsshub.app/deeplearning/the-batch/data-points).
+
+ :::
+
+ | Tag | ID |
+ | ---------------------------------------------------------------------- | -------------------------------------------------------------------- |
+ | [Weekly Issues](https://www.deeplearning.ai/the-batch/) | [*null*](https://rsshub.app/deeplearning/the-batch) |
+ | [Andrew's Letters](https://www.deeplearning.ai/the-batch/tag/letters/) | [letters](https://rsshub.app/deeplearning/the-batch/letters) |
+ | [Data Points](https://www.deeplearning.ai/the-batch/tag/data-points/) | [data-points](https://rsshub.app/deeplearning/the-batch/data-points) |
+ | [ML Research](https://www.deeplearning.ai/the-batch/tag/research/) | [research](https://rsshub.app/deeplearning/the-batch/research) |
+ | [Business](https://www.deeplearning.ai/the-batch/tag/business/) | [business](https://rsshub.app/deeplearning/the-batch/business) |
+ | [Science](https://www.deeplearning.ai/the-batch/tag/science/) | [science](https://rsshub.app/deeplearning/the-batch/science) |
+ | [AI & Society](https://www.deeplearning.ai/the-batch/tag/ai-society/) | [ai-society](https://rsshub.app/deeplearning/the-batch/ai-society) |
+ | [Culture](https://www.deeplearning.ai/the-batch/tag/culture/) | [culture](https://rsshub.app/deeplearning/the-batch/culture) |
+ | [Hardware](https://www.deeplearning.ai/the-batch/tag/hardware/) | [hardware](https://rsshub.app/deeplearning/the-batch/hardware) |
+ | [AI Careers](https://www.deeplearning.ai/the-batch/tag/ai-careers/) | [ai-careers](https://rsshub.app/deeplearning/the-batch/ai-careers) |
+
+ #### [Letters from Andrew Ng](https://www.deeplearning.ai/the-batch/tag/letters/)
+
+ | Tag | ID |
+ | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
+ | [All](https://www.deeplearning.ai/the-batch/tag/letters/) | [letters](https://rsshub.app/deeplearning/the-batch/letters) |
+ | [Personal Insights](https://www.deeplearning.ai/the-batch/tag/personal-insights/) | [personal-insights](https://rsshub.app/deeplearning/the-batch/personal-insights) |
+ | [Technical Insights](https://www.deeplearning.ai/the-batch/tag/technical-insights/) | [technical-insights](https://rsshub.app/deeplearning/the-batch/technical-insights) |
+ | [Business Insights](https://www.deeplearning.ai/the-batch/tag/business-insights/) | [business-insights](https://rsshub.app/deeplearning/the-batch/business-insights) |
+ | [Tech & Society](https://www.deeplearning.ai/the-batch/tag/tech-society/) | [tech-society](https://rsshub.app/deeplearning/the-batch/tech-society) |
+ | [DeepLearning.AI News](https://www.deeplearning.ai/the-batch/tag/deeplearning-ai-news/) | [deeplearning-ai-news](https://rsshub.app/deeplearning/the-batch/deeplearning-ai-news) |
+ | [AI Careers](https://www.deeplearning.ai/the-batch/tag/ai-careers/) | [ai-careers](https://rsshub.app/deeplearning/the-batch/ai-careers) |
+ | [Just For Fun](https://www.deeplearning.ai/the-batch/tag/just-for-fun/) | [just-for-fun](https://rsshub.app/deeplearning/the-batch/just-for-fun) |
+ | [Learning & Education](https://www.deeplearning.ai/the-batch/tag/learning-education/) | [learning-education](https://rsshub.app/deeplearning/the-batch/learning-education) |
+ `,
+ categories: ['programming'],
+
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: false,
+ supportRadar: true,
+ supportBT: false,
+ supportPodcast: false,
+ supportScihub: false,
+ },
+ radar: [
+ {
+ source: ['www.deeplearning.ai/the-batch', 'www.deeplearning.ai/the-batch/tag/:tag/'],
+ target: (params) => {
+ const tag = params.tag;
+
+ return `/the-batch${tag ? `/${tag}` : ''}`;
+ },
+ },
+ {
+ title: 'Weekly Issues',
+ source: ['www.deeplearning.ai/the-batch/'],
+ target: '/the-batch',
+ },
+ {
+ title: "Andrew's Letters",
+ source: ['www.deeplearning.ai/the-batch/tag/letters/'],
+ target: '/the-batch/letters',
+ },
+ {
+ title: 'Data Points',
+ source: ['www.deeplearning.ai/the-batch/tag/data-points/'],
+ target: '/the-batch/data-points',
+ },
+ {
+ title: 'ML Research',
+ source: ['www.deeplearning.ai/the-batch/tag/research/'],
+ target: '/the-batch/research',
+ },
+ {
+ title: 'Business',
+ source: ['www.deeplearning.ai/the-batch/tag/business/'],
+ target: '/the-batch/business',
+ },
+ {
+ title: 'Science',
+ source: ['www.deeplearning.ai/the-batch/tag/science/'],
+ target: '/the-batch/science',
+ },
+ {
+ title: 'AI & Society',
+ source: ['www.deeplearning.ai/the-batch/tag/ai-society/'],
+ target: '/the-batch/ai-society',
+ },
+ {
+ title: 'Culture',
+ source: ['www.deeplearning.ai/the-batch/tag/culture/'],
+ target: '/the-batch/culture',
+ },
+ {
+ title: 'Hardware',
+ source: ['www.deeplearning.ai/the-batch/tag/hardware/'],
+ target: '/the-batch/hardware',
+ },
+ {
+ title: 'AI Careers',
+ source: ['www.deeplearning.ai/the-batch/tag/ai-careers/'],
+ target: '/the-batch/ai-careers',
+ },
+ {
+ title: 'Letters from Andrew Ng - All',
+ source: ['www.deeplearning.ai/the-batch/tag/letters/'],
+ target: '/the-batch/letters',
+ },
+ {
+ title: 'Letters from Andrew Ng - Personal Insights',
+ source: ['www.deeplearning.ai/the-batch/tag/personal-insights/'],
+ target: '/the-batch/personal-insights',
+ },
+ {
+ title: 'Letters from Andrew Ng - Technical Insights',
+ source: ['www.deeplearning.ai/the-batch/tag/technical-insights/'],
+ target: '/the-batch/technical-insights',
+ },
+ {
+ title: 'Letters from Andrew Ng - Business Insights',
+ source: ['www.deeplearning.ai/the-batch/tag/business-insights/'],
+ target: '/the-batch/business-insights',
+ },
+ {
+ title: 'Letters from Andrew Ng - Tech & Society',
+ source: ['www.deeplearning.ai/the-batch/tag/tech-society/'],
+ target: '/the-batch/tech-society',
+ },
+ {
+ title: 'Letters from Andrew Ng - DeepLearning.AI News',
+ source: ['www.deeplearning.ai/the-batch/tag/deeplearning-ai-news/'],
+ target: '/the-batch/deeplearning-ai-news',
+ },
+ {
+ title: 'Letters from Andrew Ng - AI Careers',
+ source: ['www.deeplearning.ai/the-batch/tag/ai-careers/'],
+ target: '/the-batch/ai-careers',
+ },
+ {
+ title: 'Letters from Andrew Ng - Just For Fun',
+ source: ['www.deeplearning.ai/the-batch/tag/just-for-fun/'],
+ target: '/the-batch/just-for-fun',
+ },
+ {
+ title: 'Letters from Andrew Ng - Learning & Education',
+ source: ['www.deeplearning.ai/the-batch/tag/learning-education/'],
+ target: '/the-batch/learning-education',
+ },
+ ],
+};
diff --git a/lib/routes/deeplearning/thebatch.ts b/lib/routes/deeplearning/thebatch.ts
deleted file mode 100644
index 1264a03a051bcb..00000000000000
--- a/lib/routes/deeplearning/thebatch.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import { Route } from '@/types';
-import cache from '@/utils/cache';
-import got from '@/utils/got';
-
-export const route: Route = {
- path: '/thebatch',
- categories: ['programming'],
- example: '/deeplearning/thebatch',
- parameters: {},
- features: {
- requireConfig: false,
- requirePuppeteer: false,
- antiCrawler: false,
- supportBT: false,
- supportPodcast: false,
- supportScihub: false,
- },
- radar: [
- {
- source: ['www.deeplearning.ai/thebatch', 'www.deeplearning.ai/'],
- },
- ],
- name: 'TheBatch 周报',
- maintainers: ['nczitzk', 'juvenn'],
- handler,
- url: 'www.deeplearning.ai/thebatch',
-};
-
-async function handler() {
- const page = await got({
- method: 'get',
- url: `https://www.deeplearning.ai/the-batch/`,
- });
- const nextJs = page.data.match(/