rsshub 1.0.0-master.f6cb490 → 1.0.0-master.f6f0273

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/lib/api/index.ts +1 -6
  2. package/lib/routes/2048/index.ts +24 -23
  3. package/lib/routes/anthropic/news.ts +27 -13
  4. package/lib/routes/asianfanfics/namespace.ts +7 -0
  5. package/lib/routes/asianfanfics/tag.ts +89 -0
  6. package/lib/routes/asianfanfics/text-search.ts +68 -0
  7. package/lib/routes/blockworks/index.ts +128 -0
  8. package/lib/routes/blockworks/namespace.ts +7 -0
  9. package/lib/routes/cmu/andypavlo/blog.ts +55 -0
  10. package/lib/routes/cmu/namespace.ts +7 -0
  11. package/lib/routes/coindesk/{index.ts → consensus-magazine.ts} +17 -21
  12. package/lib/routes/coindesk/namespace.ts +2 -1
  13. package/lib/routes/coindesk/news.ts +47 -0
  14. package/lib/routes/coindesk/utils.ts +26 -0
  15. package/lib/routes/cointelegraph/index.ts +106 -0
  16. package/lib/routes/cointelegraph/namespace.ts +7 -0
  17. package/lib/routes/collabo-cafe/category.ts +37 -0
  18. package/lib/routes/collabo-cafe/index.ts +35 -0
  19. package/lib/routes/collabo-cafe/namespace.ts +9 -0
  20. package/lib/routes/collabo-cafe/parser.ts +29 -0
  21. package/lib/routes/collabo-cafe/tag.ts +37 -0
  22. package/lib/routes/cryptoslate/index.ts +98 -0
  23. package/lib/routes/cryptoslate/namespace.ts +7 -0
  24. package/lib/routes/decrypt/index.ts +115 -0
  25. package/lib/routes/decrypt/namespace.ts +7 -0
  26. package/lib/routes/discuz/discuz.ts +7 -9
  27. package/lib/routes/fangchan/list.ts +224 -0
  28. package/lib/routes/fangchan/namespace.ts +9 -0
  29. package/lib/routes/fangchan/templates/description.art +7 -0
  30. package/lib/routes/foreignaffairs/namespace.ts +7 -0
  31. package/lib/routes/foreignaffairs/rss.ts +55 -0
  32. package/lib/routes/forklog/index.ts +72 -0
  33. package/lib/routes/forklog/namespace.ts +7 -0
  34. package/lib/routes/gcores/categories.ts +129 -0
  35. package/lib/routes/gcores/collections.ts +129 -0
  36. package/lib/routes/gcores/topics.ts +63 -0
  37. package/lib/routes/gov/moa/gjs.ts +210 -0
  38. package/lib/routes/gov/tianjin/tjftz.ts +53 -0
  39. package/lib/routes/gov/tianjin/tjrcgzw.ts +51 -0
  40. package/lib/routes/grainoil/category.ts +207 -0
  41. package/lib/routes/grainoil/namespace.ts +9 -0
  42. package/lib/routes/huxiu/util.ts +11 -9
  43. package/lib/routes/ifanr/category.ts +7 -2
  44. package/lib/routes/ifanr/digest.ts +1 -1
  45. package/lib/routes/ifanr/index.ts +1 -1
  46. package/lib/routes/instructables/projects.ts +20 -15
  47. package/lib/routes/juejin/collections.ts +1 -1
  48. package/lib/routes/komiic/comic.ts +88 -0
  49. package/lib/routes/komiic/namespace.ts +7 -0
  50. package/lib/routes/leagueoflegends/namespace.ts +8 -0
  51. package/lib/routes/leagueoflegends/patch-notes.ts +76 -0
  52. package/lib/routes/likeshop/index.ts +43 -0
  53. package/lib/routes/likeshop/namespace.ts +7 -0
  54. package/lib/routes/ltaaa/article.ts +180 -0
  55. package/lib/routes/ltaaa/namespace.ts +9 -0
  56. package/lib/routes/ltaaa/templates/description.art +7 -0
  57. package/lib/routes/mashiro/index.ts +1 -0
  58. package/lib/routes/nhentai/util.ts +4 -1
  59. package/lib/routes/pinterest/user.ts +9 -0
  60. package/lib/routes/sohu/mp.ts +3 -2
  61. package/lib/routes/spotify/show.ts +1 -1
  62. package/lib/routes/stcn/index.ts +241 -136
  63. package/lib/routes/stcn/kx.ts +144 -0
  64. package/lib/routes/swjtu/namespace.ts +1 -1
  65. package/lib/routes/swjtu/{scai/bks.ts → scai.ts} +34 -20
  66. package/lib/routes/swjtu/sports.ts +77 -0
  67. package/lib/routes/theblock/index.ts +142 -0
  68. package/lib/routes/theblock/namespace.ts +7 -0
  69. package/lib/routes/theverge/index.ts +73 -62
  70. package/lib/routes/theverge/templates/header.art +19 -0
  71. package/lib/routes/threads/index.ts +73 -54
  72. package/lib/routes/threads/utils.ts +60 -78
  73. package/lib/routes/tmtpost/column.ts +298 -0
  74. package/lib/routes/tmtpost/new.ts +4 -199
  75. package/lib/routes/tmtpost/util.ts +207 -0
  76. package/lib/routes/toranoana/namespace.ts +7 -0
  77. package/lib/routes/toranoana/news.ts +110 -0
  78. package/lib/routes/wainao/templates/description.art +9 -0
  79. package/lib/routes/wainao/topics.ts +214 -0
  80. package/lib/routes/xiaoyuzhou/podcast.ts +27 -27
  81. package/lib/routes/xjtu/yz.ts +74 -0
  82. package/lib/routes/youmemark/index.ts +6 -6
  83. package/lib/routes/zaobao/util.ts +11 -3
  84. package/lib/routes/zhihu/answers.ts +26 -54
  85. package/package.json +36 -35
  86. package/lib/routes/gcores/category.ts +0 -171
  87. package/lib/routes/gcores/collection.ts +0 -161
  88. package/lib/routes-deprecated/ltaaa/index.js +0 -69
@@ -0,0 +1,224 @@
1
+ import { type Data, type DataItem, type Route, ViewType } from '@/types';
2
+
3
+ import { art } from '@/utils/render';
4
+ import cache from '@/utils/cache';
5
+ import { getCurrentPath } from '@/utils/helpers';
6
+ import ofetch from '@/utils/ofetch';
7
+ import { parseDate } from '@/utils/parse-date';
8
+ import timezone from '@/utils/timezone';
9
+
10
+ import { type CheerioAPI, type Cheerio, type Element, load } from 'cheerio';
11
+ import { type Context } from 'hono';
12
+ import path from 'node:path';
13
+
14
+ const __dirname = getCurrentPath(import.meta.url);
15
+
16
+ export const handler = async (ctx: Context): Promise<Data> => {
17
+ const { id = 'datalist' } = ctx.req.param();
18
+ const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10);
19
+
20
+ const baseUrl: string = 'http://www.fangchan.com';
21
+ const apiBaseUrl: string = 'http://news.fangchan.com';
22
+ const targetUrl: string = new URL(id.endsWith('/') ? id : `${id}/`, baseUrl).href;
23
+ const apiUrl: string = new URL(`api/${id.endsWith('/') ? id.replace(/\/$/, '') : id}.json`, apiBaseUrl).href;
24
+
25
+ const targetResponse = await ofetch(targetUrl);
26
+ const $: CheerioAPI = load(targetResponse);
27
+ const language = $('html').attr('lang') ?? 'zh-CN';
28
+
29
+ const response = await ofetch(apiUrl, {
30
+ query: {
31
+ pagesize: limit,
32
+ page: 1,
33
+ },
34
+ });
35
+
36
+ let items: DataItem[] = [];
37
+
38
+ items = response.data.slice(0, limit).map((item): DataItem => {
39
+ const title: string = item.title;
40
+ const description: string = art(path.join(__dirname, 'templates/description.art'), {
41
+ intro: item.zhaiyao,
42
+ });
43
+ const pubDate: number | string = item.createtime;
44
+ const linkUrl: string | undefined = item.url;
45
+ const categories: string[] = [...new Set([item.topcolumn, item.subcolumn, ...(item.keyword?.split(/,/) ?? [])].filter(Boolean))];
46
+ const image: string | undefined = item.pic;
47
+ const updated: number | string = item.createtime;
48
+
49
+ const processedItem: DataItem = {
50
+ title,
51
+ description,
52
+ pubDate: pubDate ? parseDate(pubDate, 'X') : undefined,
53
+ link: linkUrl,
54
+ id: categories,
55
+ content: {
56
+ html: description,
57
+ text: item.zhaiyao ?? description,
58
+ },
59
+ image,
60
+ banner: image,
61
+ updated: updated ? parseDate(updated, 'X') : undefined,
62
+ language,
63
+ };
64
+
65
+ return processedItem;
66
+ });
67
+
68
+ items = (
69
+ await Promise.all(
70
+ items.map((item) => {
71
+ if (!item.link) {
72
+ return item;
73
+ }
74
+
75
+ return cache.tryGet(item.link, async (): Promise<DataItem> => {
76
+ const detailResponse = await ofetch(item.link);
77
+ const $$: CheerioAPI = load(detailResponse);
78
+
79
+ const title: string = $$('div.summary-text h').text();
80
+ const description: string = (item.description ?? '') + ($$('div.top-info').html() ?? '') + ($$('div.summary-text-p').html() ?? '');
81
+ const pubDateStr: string | undefined = $$('span.news-date')
82
+ .text()
83
+ .match(/\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}/)?.[1];
84
+ const idEls: Element[] = $$('a.news-column, div.label span').toArray();
85
+ const categories: string[] = [...new Set([...(item.id as string[]), ...idEls.map((el) => $$(el).text()).filter(Boolean)].filter(Boolean))];
86
+ const authors: DataItem['author'] = $$('span.news-date')
87
+ .text()
88
+ ?.split(/\d{4}-\d{2}-\d{2}/)?.[0]
89
+ ?.trim()
90
+ ?.split(/\s/)
91
+ ?.map((author) => ({
92
+ name: author,
93
+ }));
94
+ const upDatedStr: string | undefined = pubDateStr;
95
+
96
+ let processedItem: DataItem = {
97
+ title,
98
+ description,
99
+ pubDate: pubDateStr ? timezone(parseDate(pubDateStr), +8) : item.pubDate,
100
+ id: categories,
101
+ author: authors,
102
+ content: {
103
+ html: description,
104
+ text: description,
105
+ },
106
+ updated: upDatedStr ? timezone(parseDate(upDatedStr), +8) : item.updated,
107
+ language,
108
+ };
109
+
110
+ const extraLinkEls: Element[] = $$('ul.xgxw-ul li a').toArray();
111
+ const extraLinks = extraLinkEls
112
+ .map((extraLinkEl) => {
113
+ const $$extraLinkEl: Cheerio<Element> = $$(extraLinkEl);
114
+
115
+ return {
116
+ url: $$extraLinkEl.attr('href'),
117
+ type: 'related',
118
+ content_html: $$extraLinkEl.text(),
119
+ };
120
+ })
121
+ .filter((_): _ is { url: string; type: string; content_html: string } => true);
122
+
123
+ if (extraLinks) {
124
+ processedItem = {
125
+ ...processedItem,
126
+ _extra: {
127
+ links: extraLinks,
128
+ },
129
+ };
130
+ }
131
+
132
+ return {
133
+ ...item,
134
+ ...processedItem,
135
+ };
136
+ });
137
+ })
138
+ )
139
+ ).filter((_): _ is DataItem => true);
140
+
141
+ const author: string = '中房网';
142
+
143
+ return {
144
+ title: `${author} - ${$('div.curmbs a').text()}`,
145
+ description: $('meta[name="description"]').attr('content'),
146
+ link: targetUrl,
147
+ item: items,
148
+ allowEmpty: true,
149
+ author,
150
+ language,
151
+ };
152
+ };
153
+
154
+ export const route: Route = {
155
+ path: '/list/:id?',
156
+ name: '列表',
157
+ url: 'www.fangchan.com',
158
+ maintainers: ['nczitzk'],
159
+ handler,
160
+ example: '/fangchan/list/datalist',
161
+ parameters: {
162
+ id: {
163
+ description: '分类,默认为 `datalist`,即数据研究,可在对应分类页 URL 中找到',
164
+ options: [
165
+ {
166
+ label: '数据研究',
167
+ value: 'datalist',
168
+ },
169
+ {
170
+ label: '行业测评',
171
+ value: 'industrylist',
172
+ },
173
+ {
174
+ label: '政策法规',
175
+ value: 'policylist',
176
+ },
177
+ ],
178
+ },
179
+ },
180
+ description: `:::tip
181
+ 若订阅 [列表](https://www.fangchan.com/),网址为 \`https://www.fangchan.com/\`,请截取 \`https://www.fangchan.com/\` 到末尾 \`.html\` 的部分 \`datalist\` 作为 \`id\` 参数填入,此时目标路由为 [\`/fangchan/datalist\`](https://rsshub.app/fangchan/datalist)。
182
+ :::
183
+
184
+ | [数据研究](https://www.fangchan.com/datalist) | [行业测评](https://www.fangchan.com/industrylist) | [政策法规](https://www.fangchan.com/policylist) |
185
+ | ----------------------------------------------------- | ------------------------------------------------------------- | --------------------------------------------------------- |
186
+ | [datalist](https://rsshub.app/fangchan/list/datalist) | [industrylist](https://rsshub.app/fangchan/list/industrylist) | [policylist](https://rsshub.app/fangchan/list/policylist) |
187
+ `,
188
+ categories: ['new-media'],
189
+ features: {
190
+ requireConfig: false,
191
+ requirePuppeteer: false,
192
+ antiCrawler: false,
193
+ supportRadar: true,
194
+ supportBT: false,
195
+ supportPodcast: false,
196
+ supportScihub: false,
197
+ },
198
+ radar: [
199
+ {
200
+ source: ['www.fangchan.com/:id'],
201
+ target: (params) => {
202
+ const id: string = params.id;
203
+
204
+ return `/fangchan/list/${id ? `/${id}` : ''}`;
205
+ },
206
+ },
207
+ {
208
+ title: '数据研究',
209
+ source: ['www.fangchan.com/datalist'],
210
+ target: '/list/datalist',
211
+ },
212
+ {
213
+ title: '行业测评',
214
+ source: ['www.fangchan.com/industrylist'],
215
+ target: '/list/industrylist',
216
+ },
217
+ {
218
+ title: '政策法规',
219
+ source: ['www.fangchan.com/policylist'],
220
+ target: '/list/policylist',
221
+ },
222
+ ],
223
+ view: ViewType.Articles,
224
+ };
@@ -0,0 +1,9 @@
1
+ import type { Namespace } from '@/types';
2
+
3
+ export const namespace: Namespace = {
4
+ name: '中房网',
5
+ url: 'fangchan.com',
6
+ categories: ['new-media'],
7
+ description: '',
8
+ lang: 'zh-CN',
9
+ };
@@ -0,0 +1,7 @@
1
+ {{ if intro }}
2
+ <blockquote>{{ intro }}</blockquote>
3
+ {{ /if }}
4
+
5
+ {{ if description }}
6
+ {{@ description }}
7
+ {{ /if }}
@@ -0,0 +1,7 @@
1
+ import type { Namespace } from '@/types';
2
+
3
+ export const namespace: Namespace = {
4
+ name: 'Foreign Affairs',
5
+ url: 'www.foreignaffairs.com',
6
+ lang: 'en',
7
+ };
@@ -0,0 +1,55 @@
1
+ import { Route } from '@/types';
2
+ import cache from '@/utils/cache';
3
+ import got from '@/utils/got';
4
+ import parser from '@/utils/rss-parser';
5
+ import { load } from 'cheerio';
6
+
7
+ export const route: Route = {
8
+ path: '/rss',
9
+ categories: ['traditional-media'],
10
+ example: '/foreignaffairs/rss',
11
+ parameters: {},
12
+ features: {
13
+ requireConfig: false,
14
+ requirePuppeteer: false,
15
+ antiCrawler: false,
16
+ supportBT: false,
17
+ supportPodcast: false,
18
+ supportScihub: false,
19
+ },
20
+ name: 'RSS',
21
+ maintainers: ['dzx-dzx'],
22
+ handler,
23
+ };
24
+
25
+ async function handler() {
26
+ const link = 'https://www.foreignaffairs.com/rss.xml';
27
+
28
+ const feed = await parser.parseURL(link);
29
+
30
+ const items = await Promise.all(
31
+ feed.items.map((item) =>
32
+ cache.tryGet(item.link, async () => {
33
+ const response = await got({
34
+ method: 'get',
35
+ url: item.link,
36
+ });
37
+
38
+ const $ = load(response.data);
39
+
40
+ $('.paywall').remove();
41
+ $('.loading-indicator').remove();
42
+ item.description = $('.article-dropcap').html();
43
+ item.author = item.creator;
44
+
45
+ return item;
46
+ })
47
+ )
48
+ );
49
+
50
+ return {
51
+ title: 'Foreign Affairs - RSS',
52
+ link,
53
+ item: items,
54
+ };
55
+ }
@@ -0,0 +1,72 @@
1
+ import { Route } from '@/types';
2
+ import { parseDate } from '@/utils/parse-date';
3
+ import got from '@/utils/got';
4
+ import timezone from '@/utils/timezone';
5
+
6
+ export const route: Route = {
7
+ path: '/news',
8
+ categories: ['finance'],
9
+ example: '/forklog/news',
10
+ radar: [
11
+ {
12
+ source: ['forklog.com/news'],
13
+ target: '/news',
14
+ },
15
+ ],
16
+ name: 'Новости',
17
+ maintainers: ['raven428'],
18
+ handler,
19
+ url: 'forklog.com/news',
20
+ };
21
+
22
+ async function handler() {
23
+ const response = await got('https://forklog.com/wp-content/themes/forklogv2/ajax/getPosts.php', {
24
+ method: 'POST',
25
+ headers: { 'x-requested-with': 'XMLHttpRequest' },
26
+ form: { action: 'getPostsByCategory', postperpage: '333' },
27
+ });
28
+ const items = JSON.parse(response.body).map((post) => {
29
+ const link = post.link;
30
+ const title = (post.title || post.text?.post_title)?.trim();
31
+ const description = post.text?.post_content.trim();
32
+ const author = post.author_name.trim();
33
+ let pubDate;
34
+ if (post.text?.post_date_gmt) {
35
+ pubDate = timezone(parseDate(post.text.post_date_gmt), +1);
36
+ } else if (post.text?.post_date) {
37
+ pubDate = timezone(parseDate(post.text.post_date), +4);
38
+ } else if (post.date) {
39
+ pubDate = timezone(parseDate(post.date, 'DD.MM.YYYY HH:mm'), +4);
40
+ }
41
+ const imageSrc = post.image || post.image_mobile;
42
+ const views = post.views;
43
+ return {
44
+ link,
45
+ title,
46
+ author,
47
+ pubDate,
48
+ description,
49
+ category: ['news', 'crypto', 'finance'],
50
+ ...(imageSrc
51
+ ? {
52
+ media: {
53
+ thumbnail: {
54
+ url: imageSrc,
55
+ width: 250,
56
+ height: 250,
57
+ },
58
+ },
59
+ }
60
+ : {}),
61
+ extra: {
62
+ views,
63
+ },
64
+ };
65
+ });
66
+ return {
67
+ title: 'Forklog – Новости',
68
+ link: 'https://forklog.com/news',
69
+ description: 'Последние новости из мира блокчейна и криптовалют',
70
+ item: items,
71
+ };
72
+ }
@@ -0,0 +1,7 @@
1
+ import type { Namespace } from '@/types';
2
+
3
+ export const namespace: Namespace = {
4
+ name: 'Forklog',
5
+ url: 'forklog.com',
6
+ lang: 'ru',
7
+ };
@@ -0,0 +1,129 @@
1
+ import { type Data, type Route, ViewType } from '@/types';
2
+
3
+ import { getCurrentPath } from '@/utils/helpers';
4
+ import { type Context } from 'hono';
5
+
6
+ import { baseUrl, processItems } from './util';
7
+
8
+ export const __dirname = getCurrentPath(import.meta.url);
9
+
10
+ let viewType: ViewType = ViewType.Articles;
11
+
12
+ export const handler = async (ctx: Context): Promise<Data> => {
13
+ const { id, tab } = ctx.req.param();
14
+ const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10);
15
+
16
+ const targetUrl: string = new URL(`categories/${id}${tab ? `?tab=${tab}` : ''}`, baseUrl).href;
17
+ const apiUrl: string = new URL(`gapi/v1/categories/${id}/${tab ?? 'originals'}`, baseUrl).href;
18
+
19
+ const query = {
20
+ 'page[limit]': limit,
21
+ sort: '-published-at',
22
+ include: 'category,user,media',
23
+ 'filter[list-all]': 1,
24
+ 'filter[is-news]': tab === 'news' ? 1 : 0,
25
+ };
26
+
27
+ if (tab === 'radios') {
28
+ viewType = ViewType.Audios;
29
+ } else if (tab === 'videos') {
30
+ viewType = ViewType.Videos;
31
+ }
32
+
33
+ return await processItems(limit, query, apiUrl, targetUrl);
34
+ };
35
+
36
+ export const route: Route = {
37
+ path: '/categories/:id/:tab?',
38
+ name: '分类',
39
+ url: 'www.gcores.com',
40
+ maintainers: ['MoguCloud', 'StevenRCE0', 'nczitzk'],
41
+ handler,
42
+ example: '/gcores/categories/1/articles',
43
+ parameters: {
44
+ id: {
45
+ description: '分类 ID,可在对应分类页 URL 中找到',
46
+ },
47
+ tab: {
48
+ description: '类型,默认为空,即全部,可在对应分类页 URL 中找到',
49
+ options: [
50
+ {
51
+ label: '全部',
52
+ value: '',
53
+ },
54
+ {
55
+ label: '播客',
56
+ value: 'radios',
57
+ },
58
+ {
59
+ label: '文章',
60
+ value: 'articles',
61
+ },
62
+ {
63
+ label: '资讯',
64
+ value: 'news',
65
+ },
66
+ {
67
+ label: '视频',
68
+ value: 'videos',
69
+ },
70
+ ],
71
+ },
72
+ },
73
+ description: `:::tip
74
+ 若订阅 [文章 - 文章](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)。
75
+ :::
76
+
77
+ | 全部 | 播客 | 文章 | 资讯 | 视频 |
78
+ | ---- | ------ | -------- | ---- | ------ |
79
+ | | radios | articles | news | videos |
80
+ `,
81
+ categories: ['game'],
82
+ features: {
83
+ requireConfig: false,
84
+ requirePuppeteer: false,
85
+ antiCrawler: false,
86
+ supportRadar: true,
87
+ supportBT: false,
88
+ supportPodcast: false,
89
+ supportScihub: false,
90
+ },
91
+ radar: [
92
+ {
93
+ source: ['www.gcores.com/categories/:id'],
94
+ target: (params, url) => {
95
+ const urlObj: URL = new URL(url);
96
+ const id: string = params.id;
97
+ const tab: string | undefined = urlObj.searchParams.get('tab') ?? undefined;
98
+
99
+ return `/gcores/categories/${id}/${tab ? `/${tab}` : ''}`;
100
+ },
101
+ },
102
+ {
103
+ title: '全部',
104
+ source: ['www.gcores.com/categories/:id'],
105
+ target: '/gcores/categories/:id',
106
+ },
107
+ {
108
+ title: '播客',
109
+ source: ['www.gcores.com/categories/:id'],
110
+ target: '/categories/:id/radios',
111
+ },
112
+ {
113
+ title: '文章',
114
+ source: ['www.gcores.com/categories/:id'],
115
+ target: '/categories/:id/articles',
116
+ },
117
+ {
118
+ title: '资讯',
119
+ source: ['www.gcores.com/categories/:id'],
120
+ target: '/categories/:id/news',
121
+ },
122
+ {
123
+ title: '视频',
124
+ source: ['www.gcores.com/categories/:id'],
125
+ target: '/categories/:id/videos',
126
+ },
127
+ ],
128
+ view: viewType,
129
+ };
@@ -0,0 +1,129 @@
1
+ import { type Data, type Route, ViewType } from '@/types';
2
+
3
+ import { getCurrentPath } from '@/utils/helpers';
4
+ import { type Context } from 'hono';
5
+
6
+ import { baseUrl, processItems } from './util';
7
+
8
+ export const __dirname = getCurrentPath(import.meta.url);
9
+
10
+ let viewType: ViewType = ViewType.Articles;
11
+
12
+ export const handler = async (ctx: Context): Promise<Data> => {
13
+ const { id, tab } = ctx.req.param();
14
+ const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10);
15
+
16
+ const targetUrl: string = new URL(`collections/${id}${tab ? `?tab=${tab}` : ''}`, baseUrl).href;
17
+ const apiUrl: string = new URL(`gapi/v1/collections/${id}/${tab ?? 'originals'}`, baseUrl).href;
18
+
19
+ const query = {
20
+ 'page[limit]': limit,
21
+ sort: '-published-at',
22
+ include: 'category,user,media',
23
+ 'filter[list-all]': 1,
24
+ 'filter[is-news]': tab === 'news' ? 1 : 0,
25
+ };
26
+
27
+ if (tab === 'radios') {
28
+ viewType = ViewType.Audios;
29
+ } else if (tab === 'videos') {
30
+ viewType = ViewType.Videos;
31
+ }
32
+
33
+ return await processItems(limit, query, apiUrl, targetUrl);
34
+ };
35
+
36
+ export const route: Route = {
37
+ path: '/collections/:id/:tab?',
38
+ name: '专题',
39
+ url: 'www.gcores.com',
40
+ maintainers: ['kudryavka1013', 'nczitzk'],
41
+ handler,
42
+ example: '/gcores/collections/64/articles',
43
+ parameters: {
44
+ id: {
45
+ description: '专题 ID,可在对应专题页 URL 中找到',
46
+ },
47
+ tab: {
48
+ description: '类型,默认为空,即全部,可在对应专题页 URL 中找到',
49
+ options: [
50
+ {
51
+ label: '全部',
52
+ value: '',
53
+ },
54
+ {
55
+ label: '播客',
56
+ value: 'radios',
57
+ },
58
+ {
59
+ label: '文章',
60
+ value: 'articles',
61
+ },
62
+ {
63
+ label: '资讯',
64
+ value: 'news',
65
+ },
66
+ {
67
+ label: '视频',
68
+ value: 'videos',
69
+ },
70
+ ],
71
+ },
72
+ },
73
+ description: `:::tip
74
+ 若订阅 [文章 - 文章](https://www.gcores.com/collections/64?tab=articles),网址为 \`https://www.gcores.com/collections/64?tab=articles\`,请截取 \`https://www.gcores.com/collections/\` 到末尾的部分 \`64\` 作为 \`id\` 参数填入,截取 \`articles\` 作为 \`tab\` 参数填入,此时目标路由为 [\`/gcores/collections/64/articles\`](https://rsshub.app/gcores/collections/64/articles)。
75
+ :::
76
+
77
+ | 全部 | 播客 | 文章 | 资讯 | 视频 |
78
+ | ---- | ------ | -------- | ---- | ------ |
79
+ | | radios | articles | news | videos |
80
+ `,
81
+ categories: ['game'],
82
+ features: {
83
+ requireConfig: false,
84
+ requirePuppeteer: false,
85
+ antiCrawler: false,
86
+ supportRadar: true,
87
+ supportBT: false,
88
+ supportPodcast: false,
89
+ supportScihub: false,
90
+ },
91
+ radar: [
92
+ {
93
+ source: ['www.gcores.com/collections/:id'],
94
+ target: (params, url) => {
95
+ const urlObj: URL = new URL(url);
96
+ const id: string = params.id;
97
+ const tab: string | undefined = urlObj.searchParams.get('tab') ?? undefined;
98
+
99
+ return `/gcores/collections/${id}/${tab ? `/${tab}` : ''}`;
100
+ },
101
+ },
102
+ {
103
+ title: '全部',
104
+ source: ['www.gcores.com/collections/:id'],
105
+ target: '/collections/:id',
106
+ },
107
+ {
108
+ title: '播客',
109
+ source: ['www.gcores.com/collections/:id'],
110
+ target: '/collections/:id/radios',
111
+ },
112
+ {
113
+ title: '文章',
114
+ source: ['www.gcores.com/collections/:id'],
115
+ target: '/collections/:id/articles',
116
+ },
117
+ {
118
+ title: '资讯',
119
+ source: ['www.gcores.com/collections/:id'],
120
+ target: '/collections/:id/news',
121
+ },
122
+ {
123
+ title: '视频',
124
+ source: ['www.gcores.com/collections/:id'],
125
+ target: '/collections/:id/videos',
126
+ },
127
+ ],
128
+ view: viewType,
129
+ };