diff --git a/lib/routes/gcores/categories.ts b/lib/routes/gcores/categories.ts new file mode 100644 index 00000000000000..a095c01ea0737d --- /dev/null +++ b/lib/routes/gcores/categories.ts @@ -0,0 +1,129 @@ +import { type Data, type Route, ViewType } from '@/types'; + +import { getCurrentPath } from '@/utils/helpers'; +import { type Context } from 'hono'; + +import { baseUrl, processItems } from './util'; + +export const __dirname = getCurrentPath(import.meta.url); + +let viewType: ViewType = ViewType.Articles; + +export const handler = async (ctx: Context): Promise => { + const { id, tab } = ctx.req.param(); + const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10); + + const targetUrl: string = new URL(`categories/${id}${tab ? `?tab=${tab}` : ''}`, baseUrl).href; + const apiUrl: string = new URL(`gapi/v1/categories/${id}/${tab ?? 'originals'}`, baseUrl).href; + + const query = { + 'page[limit]': limit, + sort: '-published-at', + include: 'category,user,media', + 'filter[list-all]': 1, + 'filter[is-news]': tab === 'news' ? 1 : 0, + }; + + if (tab === 'radios') { + viewType = ViewType.Audios; + } else if (tab === 'videos') { + viewType = ViewType.Videos; + } + + return await processItems(limit, query, apiUrl, targetUrl); +}; + +export const route: Route = { + path: '/categories/:id/:tab?', + name: '分类', + url: 'www.gcores.com', + maintainers: ['MoguCloud', 'StevenRCE0', 'nczitzk'], + handler, + example: '/gcores/categories/1/articles', + parameters: { + id: { + description: '分类 ID,可在对应分类页 URL 中找到', + }, + tab: { + description: '类型,默认为空,即全部,可在对应分类页 URL 中找到', + options: [ + { + label: '全部', + value: '', + }, + { + label: '播客', + value: 'radios', + }, + { + label: '文章', + value: 'articles', + }, + { + label: '资讯', + value: 'news', + }, + { + label: '视频', + value: 'videos', + }, + ], + }, + }, + description: `:::tip +若订阅 [文章 - 文章](https://www.gcores.com/categories/1?tab=articles),网址为 \`https://www.gcores.com/categories/1?tab=articles\`,请截取 \`https://www.gcores.com/categories/\` 到末尾的部分 \`1\` 作为 \`id\` 参数填入,截取 \`articles\` 作为 \`tab\` 参数填入,此时目标路由为 [\`/gcores/categories/1/articles\`](https://rsshub.app/gcores/categories/1/articles)。 +::: + +| 全部 | 播客 | 文章 | 资讯 | 视频 | +| ---- | ------ | -------- | ---- | ------ | +| | radios | articles | news | videos | +`, + categories: ['game'], + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportRadar: true, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['www.gcores.com/categories/:id'], + target: (params, url) => { + const urlObj: URL = new URL(url); + const id: string = params.id; + const tab: string | undefined = urlObj.searchParams.get('tab') ?? undefined; + + return `/gcores/categories/${id}/${tab ? `/${tab}` : ''}`; + }, + }, + { + title: '全部', + source: ['www.gcores.com/categories/:id'], + target: '/gcores/categories/:id', + }, + { + title: '播客', + source: ['www.gcores.com/categories/:id'], + target: '/categories/:id/radios', + }, + { + title: '文章', + source: ['www.gcores.com/categories/:id'], + target: '/categories/:id/articles', + }, + { + title: '资讯', + source: ['www.gcores.com/categories/:id'], + target: '/categories/:id/news', + }, + { + title: '视频', + source: ['www.gcores.com/categories/:id'], + target: '/categories/:id/videos', + }, + ], + view: viewType, +}; diff --git a/lib/routes/gcores/category.ts b/lib/routes/gcores/category.ts deleted file mode 100644 index 1cdd9eec53870c..00000000000000 --- a/lib/routes/gcores/category.ts +++ /dev/null @@ -1,171 +0,0 @@ -import InvalidParameterError from '@/errors/types/invalid-parameter'; -import { Route } from '@/types'; -import cache from '@/utils/cache'; -import got from '@/utils/got'; -import { load } from 'cheerio'; - -export const route: Route = { - path: '/category/:category', - categories: ['new-media', 'popular'], - example: '/gcores/category/news', - parameters: { category: '分类名' }, - features: { - requireConfig: false, - requirePuppeteer: false, - antiCrawler: false, - supportBT: false, - supportPodcast: false, - supportScihub: false, - }, - radar: [ - { - source: ['gcores.com/:category'], - }, - ], - name: '分类', - maintainers: ['MoguCloud', 'StevenRCE0'], - handler, - description: `| 资讯 | 视频 | 电台 | 文章 | -| ---- | ------ | ------ | -------- | -| news | videos | radios | articles |`, -}; - -async function handler(ctx) { - const category = ctx.req.param('category'); - const url = `https://www.gcores.com/${category}`; - const res = await got({ - method: 'get', - url, - }); - const data = res.data; - const $ = load(data); - const feedTitle = $('title').text(); - - let list = - category === 'news' - ? $('a.news').map(function () { - const item = { - url: $(this).attr('href'), - title: $(this).find('.news_content>h3').text(), - }; - return item; - }) - : $('.original.am_card.original-normal').map(function () { - const item = { - url: $(this).find('.am_card_inner>a').attr('href'), - title: $(this).find('h3.am_card_title').text(), - category: $(this).find('span.original_category>a').text(), - }; - return item; - }); - list = list.get(); - - if (list.length > 0 && list.every((item) => item.url === undefined)) { - throw new InvalidParameterError('Article URL not found! Please submit an issue on GitHub.'); - } - - const out = await Promise.all( - list.map((item) => { - const articleUrl = `https://www.gcores.com${item.url}`; - - return cache.tryGet(articleUrl, async () => { - const itemRes = await got({ - method: 'get', - url: articleUrl, - }); - - const itemPage = itemRes.data; - const $ = load(itemPage); - - const articleRaw = await got(`https://www.gcores.com/gapi/v1${item.url}?include=media,category,user`); - const articleData = articleRaw.data.data; - const articleMeta = articleRaw.data.included.find((i) => i.type === 'users' && i.id === articleData.relationships.user.data.id); - const author = articleMeta.attributes.nickname; - - let cover; - if (articleData.attributes.cover) { - cover = ``; - } else if (articleData.attributes.thumb) { - cover = ``; - } else { - cover = ''; - } - - // replace figure with img - const articleContent = JSON.parse(articleData.attributes.content); - const entityRangeMap = {}; - for (const block of articleContent.blocks || []) { - if (block.entityRanges.length) { - entityRangeMap[block.key] = block.entityRanges; - } - } - - $('figure').each((i, elem) => { - const keyAttr = elem.attribs['data-offset-key']; - const keyMatch = /^(\w+)-(\d+)-(\d)$/.exec(keyAttr); - let actualContent = ''; - if (keyMatch) { - const [, key, index] = keyMatch; - if (entityRangeMap[key] && entityRangeMap[key][index]) { - const entityKey = entityRangeMap[key] && entityRangeMap[key][index].key; - const entity = articleContent.entityMap[entityKey]; - actualContent = convertEntityToContent(entity); - } - } - - if (actualContent) { - $(elem).replaceWith(actualContent); - } - }); - - // remove editor toolbar img - $('.md-editor-toolbar').replaceWith(''); - // remove hidden tip block - $('.story_hidden').replaceWith(''); - - const content = $('.story.story-show').html(); - const basicItem = { - title: item.title, - description: cover + content, - link: articleUrl, - guid: articleUrl, - author, - pubDate: new Date(articleData.attributes['published-at']), - }; - return category === 'news' ? basicItem : { ...basicItem, category: item.category }; - }); - }) - ); - return { - title: feedTitle, - link: url, - item: out, - }; -} - -function convertEntityToContent(entity) { - const { type, data } = entity; - switch (type) { - case 'IMAGE': - return ` -
-${data.caption || ''} -${data.caption ? `
${data.caption}
` : ''} -
`; - - case 'GALLERY': - return data.images - .map( - (image, i, arr) => ` -
-${image.caption || ''} -
${data.caption || ''} (${i + 1}/${arr.length}) ${image.caption || ''}
-
- ` - ) - .join(''); - - default: - return ''; - } -}