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.
- package/lib/api/index.ts +1 -6
- package/lib/routes/2048/index.ts +24 -23
- package/lib/routes/anthropic/news.ts +27 -13
- package/lib/routes/asianfanfics/namespace.ts +7 -0
- package/lib/routes/asianfanfics/tag.ts +89 -0
- package/lib/routes/asianfanfics/text-search.ts +68 -0
- package/lib/routes/blockworks/index.ts +128 -0
- package/lib/routes/blockworks/namespace.ts +7 -0
- package/lib/routes/cmu/andypavlo/blog.ts +55 -0
- package/lib/routes/cmu/namespace.ts +7 -0
- package/lib/routes/coindesk/{index.ts → consensus-magazine.ts} +17 -21
- package/lib/routes/coindesk/namespace.ts +2 -1
- package/lib/routes/coindesk/news.ts +47 -0
- package/lib/routes/coindesk/utils.ts +26 -0
- package/lib/routes/cointelegraph/index.ts +106 -0
- package/lib/routes/cointelegraph/namespace.ts +7 -0
- package/lib/routes/collabo-cafe/category.ts +37 -0
- package/lib/routes/collabo-cafe/index.ts +35 -0
- package/lib/routes/collabo-cafe/namespace.ts +9 -0
- package/lib/routes/collabo-cafe/parser.ts +29 -0
- package/lib/routes/collabo-cafe/tag.ts +37 -0
- package/lib/routes/cryptoslate/index.ts +98 -0
- package/lib/routes/cryptoslate/namespace.ts +7 -0
- package/lib/routes/decrypt/index.ts +115 -0
- package/lib/routes/decrypt/namespace.ts +7 -0
- package/lib/routes/discuz/discuz.ts +7 -9
- package/lib/routes/fangchan/list.ts +224 -0
- package/lib/routes/fangchan/namespace.ts +9 -0
- package/lib/routes/fangchan/templates/description.art +7 -0
- package/lib/routes/foreignaffairs/namespace.ts +7 -0
- package/lib/routes/foreignaffairs/rss.ts +55 -0
- package/lib/routes/forklog/index.ts +72 -0
- package/lib/routes/forklog/namespace.ts +7 -0
- package/lib/routes/gcores/categories.ts +129 -0
- package/lib/routes/gcores/collections.ts +129 -0
- package/lib/routes/gcores/topics.ts +63 -0
- package/lib/routes/gov/moa/gjs.ts +210 -0
- package/lib/routes/gov/tianjin/tjftz.ts +53 -0
- package/lib/routes/gov/tianjin/tjrcgzw.ts +51 -0
- package/lib/routes/grainoil/category.ts +207 -0
- package/lib/routes/grainoil/namespace.ts +9 -0
- package/lib/routes/huxiu/util.ts +11 -9
- package/lib/routes/ifanr/category.ts +7 -2
- package/lib/routes/ifanr/digest.ts +1 -1
- package/lib/routes/ifanr/index.ts +1 -1
- package/lib/routes/instructables/projects.ts +20 -15
- package/lib/routes/juejin/collections.ts +1 -1
- package/lib/routes/komiic/comic.ts +88 -0
- package/lib/routes/komiic/namespace.ts +7 -0
- package/lib/routes/leagueoflegends/namespace.ts +8 -0
- package/lib/routes/leagueoflegends/patch-notes.ts +76 -0
- package/lib/routes/likeshop/index.ts +43 -0
- package/lib/routes/likeshop/namespace.ts +7 -0
- package/lib/routes/ltaaa/article.ts +180 -0
- package/lib/routes/ltaaa/namespace.ts +9 -0
- package/lib/routes/ltaaa/templates/description.art +7 -0
- package/lib/routes/mashiro/index.ts +1 -0
- package/lib/routes/nhentai/util.ts +4 -1
- package/lib/routes/pinterest/user.ts +9 -0
- package/lib/routes/sohu/mp.ts +3 -2
- package/lib/routes/spotify/show.ts +1 -1
- package/lib/routes/stcn/index.ts +241 -136
- package/lib/routes/stcn/kx.ts +144 -0
- package/lib/routes/swjtu/namespace.ts +1 -1
- package/lib/routes/swjtu/{scai/bks.ts → scai.ts} +34 -20
- package/lib/routes/swjtu/sports.ts +77 -0
- package/lib/routes/theblock/index.ts +142 -0
- package/lib/routes/theblock/namespace.ts +7 -0
- package/lib/routes/theverge/index.ts +73 -62
- package/lib/routes/theverge/templates/header.art +19 -0
- package/lib/routes/threads/index.ts +73 -54
- package/lib/routes/threads/utils.ts +60 -78
- package/lib/routes/tmtpost/column.ts +298 -0
- package/lib/routes/tmtpost/new.ts +4 -199
- package/lib/routes/tmtpost/util.ts +207 -0
- package/lib/routes/toranoana/namespace.ts +7 -0
- package/lib/routes/toranoana/news.ts +110 -0
- package/lib/routes/wainao/templates/description.art +9 -0
- package/lib/routes/wainao/topics.ts +214 -0
- package/lib/routes/xiaoyuzhou/podcast.ts +27 -27
- package/lib/routes/xjtu/yz.ts +74 -0
- package/lib/routes/youmemark/index.ts +6 -6
- package/lib/routes/zaobao/util.ts +11 -3
- package/lib/routes/zhihu/answers.ts +26 -54
- package/package.json +36 -35
- package/lib/routes/gcores/category.ts +0 -171
- package/lib/routes/gcores/collection.ts +0 -161
- package/lib/routes-deprecated/ltaaa/index.js +0 -69
package/lib/routes/stcn/index.ts
CHANGED
|
@@ -1,159 +1,264 @@
|
|
|
1
|
-
import { Route, ViewType } from '@/types';
|
|
1
|
+
import { type Data, type DataItem, type Route, ViewType } from '@/types';
|
|
2
|
+
|
|
2
3
|
import cache from '@/utils/cache';
|
|
3
|
-
import
|
|
4
|
-
import { load } from 'cheerio';
|
|
5
|
-
import timezone from '@/utils/timezone';
|
|
4
|
+
import ofetch from '@/utils/ofetch';
|
|
6
5
|
import { parseDate } from '@/utils/parse-date';
|
|
6
|
+
import timezone from '@/utils/timezone';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
categories: ['finance', 'popular'],
|
|
11
|
-
view: ViewType.Articles,
|
|
12
|
-
example: '/stcn/yw',
|
|
13
|
-
parameters: {
|
|
14
|
-
id: {
|
|
15
|
-
description: '栏目 id',
|
|
16
|
-
options: [
|
|
17
|
-
{ value: 'kx', label: '快讯' },
|
|
18
|
-
{ value: 'yw', label: '要闻' },
|
|
19
|
-
{ value: 'gs', label: '股市' },
|
|
20
|
-
{ value: 'company', label: '公司' },
|
|
21
|
-
{ value: 'data', label: '数据' },
|
|
22
|
-
{ value: 'fund', label: '基金' },
|
|
23
|
-
{ value: 'finance', label: '金融' },
|
|
24
|
-
{ value: 'comment', label: '评论' },
|
|
25
|
-
{ value: 'cj', label: '产经' },
|
|
26
|
-
{ value: 'ct', label: '创投' },
|
|
27
|
-
{ value: 'kcb', label: '科创板' },
|
|
28
|
-
{ value: 'xsb', label: '新三板' },
|
|
29
|
-
{ value: 'tj', label: '投教' },
|
|
30
|
-
{ value: 'zk', label: 'ESG' },
|
|
31
|
-
{ value: 'gd', label: '滚动' },
|
|
32
|
-
{ value: 'gsyl', label: '股市一览' },
|
|
33
|
-
{ value: 'djjd', label: '独家解读' },
|
|
34
|
-
{ value: 'gsxw', label: '公司新闻' },
|
|
35
|
-
{ value: 'gsdt', label: '公司动态' },
|
|
36
|
-
{ value: 'djsj', label: '独家数据' },
|
|
37
|
-
{ value: 'kd', label: '看点数据' },
|
|
38
|
-
{ value: 'zj', label: '资金流向' },
|
|
39
|
-
{ value: 'sj_kcb', label: '科创板' },
|
|
40
|
-
{ value: 'hq', label: '行情总貌' },
|
|
41
|
-
{ value: 'zl', label: '专栏' },
|
|
42
|
-
{ value: 'author', label: '作者' },
|
|
43
|
-
{ value: 'cjhy', label: '行业' },
|
|
44
|
-
{ value: 'cjqc', label: '汽车' },
|
|
45
|
-
{ value: 'tjkt', label: '投教课堂' },
|
|
46
|
-
{ value: 'zczs', label: '政策知识' },
|
|
47
|
-
{ value: 'tjdt', label: '投教动态' },
|
|
48
|
-
{ value: 'zthd', label: '专题活动' },
|
|
49
|
-
],
|
|
50
|
-
default: 'yw',
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
features: {
|
|
54
|
-
requireConfig: false,
|
|
55
|
-
requirePuppeteer: false,
|
|
56
|
-
antiCrawler: false,
|
|
57
|
-
supportBT: false,
|
|
58
|
-
supportPodcast: false,
|
|
59
|
-
supportScihub: false,
|
|
60
|
-
},
|
|
61
|
-
name: '栏目',
|
|
62
|
-
maintainers: ['nczitzk'],
|
|
63
|
-
handler,
|
|
64
|
-
description: `| 快讯 | 要闻 | 股市 | 公司 | 数据 |
|
|
65
|
-
| ---- | ---- | ---- | ------- | ---- |
|
|
66
|
-
| kx | yw | gs | company | data |
|
|
67
|
-
|
|
68
|
-
| 基金 | 金融 | 评论 | 产经 | 创投 |
|
|
69
|
-
| ---- | ------- | ------- | ---- | ---- |
|
|
70
|
-
| fund | finance | comment | cj | ct |
|
|
71
|
-
|
|
72
|
-
| 科创板 | 新三板 | 投教 | ESG | 滚动 |
|
|
73
|
-
| ------ | ------ | ---- | --- | ---- |
|
|
74
|
-
| kcb | xsb | tj | zk | gd |
|
|
75
|
-
|
|
76
|
-
| 股市一览 | 独家解读 |
|
|
77
|
-
| -------- | -------- |
|
|
78
|
-
| gsyl | djjd |
|
|
8
|
+
import { type CheerioAPI, type Cheerio, type Element, load } from 'cheerio';
|
|
9
|
+
import { type Context } from 'hono';
|
|
79
10
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
11
|
+
export const handler = async (ctx: Context): Promise<Data> => {
|
|
12
|
+
const { id = 'yw' } = ctx.req.param();
|
|
13
|
+
const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10);
|
|
83
14
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
| djsj | kd | zj | sj\_kcb | hq |
|
|
15
|
+
const baseUrl: string = 'https://www.stcn.com';
|
|
16
|
+
const targetUrl: string = new URL(`article/list/${id}.html`, baseUrl).href;
|
|
87
17
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
18
|
+
const response = await ofetch(targetUrl);
|
|
19
|
+
const $: CheerioAPI = load(response);
|
|
20
|
+
const language = $('html').attr('lang') ?? 'zh-CN';
|
|
91
21
|
|
|
92
|
-
|
|
93
|
-
| ---- | ---- |
|
|
94
|
-
| cjhy | cjqc |
|
|
22
|
+
let items: DataItem[] = [];
|
|
95
23
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
24
|
+
items = $('ul.infinite-list li')
|
|
25
|
+
.slice(0, limit)
|
|
26
|
+
.toArray()
|
|
27
|
+
.map((el): Element => {
|
|
28
|
+
const $el: Cheerio<Element> = $(el);
|
|
100
29
|
|
|
101
|
-
|
|
102
|
-
const id = ctx.req.param('id') ?? 'yw';
|
|
30
|
+
const $aEl: Cheerio<Element> = $el.find('div.tt a');
|
|
103
31
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
32
|
+
const title: string = $aEl.text();
|
|
33
|
+
const description: string = $el.find('div.text').html();
|
|
34
|
+
const pubDateStr: string | undefined = $el.find('div.info span').last().text().trim();
|
|
35
|
+
const linkUrl: string | undefined = $aEl.attr('href');
|
|
36
|
+
const categoryEls: Element[] = $el.find('div.tags span').toArray();
|
|
37
|
+
const categories: string[] = [...new Set(categoryEls.map((el) => $(el).text()).filter(Boolean))];
|
|
38
|
+
const authors: DataItem['author'] = $el.find('div.info span').first().text();
|
|
39
|
+
const image: string | undefined = $el.find('div.side a img').attr('src');
|
|
40
|
+
const upDatedStr: string | undefined = pubDateStr;
|
|
107
41
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
42
|
+
const processedItem: DataItem = {
|
|
43
|
+
title,
|
|
44
|
+
description,
|
|
45
|
+
pubDate: pubDateStr ? timezone(parseDate(pubDateStr, ['HH:mm', 'MM-DD HH:mm', 'YYYY-MM-DD HH:mm']), +8) : undefined,
|
|
46
|
+
link: linkUrl ? new URL(linkUrl, baseUrl).href : undefined,
|
|
47
|
+
category: categories,
|
|
48
|
+
author: authors,
|
|
49
|
+
content: {
|
|
50
|
+
html: description,
|
|
51
|
+
text: description,
|
|
52
|
+
},
|
|
53
|
+
image,
|
|
54
|
+
banner: image,
|
|
55
|
+
updated: upDatedStr ? timezone(parseDate(upDatedStr, ['HH:mm', 'MM-DD HH:mm', 'YYYY-MM-DD HH:mm']), +8) : undefined,
|
|
56
|
+
language,
|
|
57
|
+
};
|
|
112
58
|
|
|
113
|
-
|
|
59
|
+
return processedItem;
|
|
60
|
+
});
|
|
114
61
|
|
|
115
|
-
|
|
116
|
-
.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
62
|
+
items = (
|
|
63
|
+
await Promise.all(
|
|
64
|
+
items.map((item) => {
|
|
65
|
+
if (!item.link) {
|
|
66
|
+
return item;
|
|
67
|
+
}
|
|
120
68
|
|
|
121
|
-
|
|
69
|
+
return cache.tryGet(item.link, async (): Promise<DataItem> => {
|
|
70
|
+
const detailResponse = await ofetch(item.link);
|
|
71
|
+
const $$: CheerioAPI = load(detailResponse);
|
|
122
72
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
73
|
+
const title: string = $$('div.detail-title').text();
|
|
74
|
+
const description: string = $$('div.detail-content').html() ?? '';
|
|
75
|
+
const pubDateStr: string | undefined = $$('div.detail-info span').last().text().trim();
|
|
76
|
+
const categories: string[] = $$('meta[name="keywords"]').attr('content')?.split(/,/) ?? [];
|
|
77
|
+
const authors: DataItem['author'] = $$('div.detail-info span').first().text().split(/:/).pop();
|
|
78
|
+
const upDatedStr: string | undefined = pubDateStr;
|
|
128
79
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
item.pubDate = timezone(parseDate(content('.detail-info span').last().text()), +8);
|
|
143
|
-
item.category = content('.detail-content-tags div')
|
|
144
|
-
.toArray()
|
|
145
|
-
.map((t) => content(t).text());
|
|
146
|
-
item.description = content('.detail-content').html();
|
|
147
|
-
}
|
|
80
|
+
const processedItem: DataItem = {
|
|
81
|
+
title,
|
|
82
|
+
description,
|
|
83
|
+
pubDate: pubDateStr ? timezone(parseDate(pubDateStr), +8) : item.pubDate,
|
|
84
|
+
category: categories,
|
|
85
|
+
author: authors,
|
|
86
|
+
content: {
|
|
87
|
+
html: description,
|
|
88
|
+
text: description,
|
|
89
|
+
},
|
|
90
|
+
updated: upDatedStr ? timezone(parseDate(upDatedStr), +8) : item.updated,
|
|
91
|
+
language,
|
|
92
|
+
};
|
|
148
93
|
|
|
149
|
-
|
|
94
|
+
return {
|
|
95
|
+
...item,
|
|
96
|
+
...processedItem,
|
|
97
|
+
};
|
|
98
|
+
});
|
|
150
99
|
})
|
|
151
100
|
)
|
|
152
|
-
);
|
|
101
|
+
).filter((_): _ is DataItem => true);
|
|
153
102
|
|
|
154
103
|
return {
|
|
155
|
-
title:
|
|
156
|
-
|
|
104
|
+
title: $('title').text(),
|
|
105
|
+
description: $('meta[name="description"]').attr('content'),
|
|
106
|
+
link: targetUrl,
|
|
157
107
|
item: items,
|
|
108
|
+
allowEmpty: true,
|
|
109
|
+
image: $('img.stcn-logo').attr('src'),
|
|
110
|
+
author: $('meta[name="keywords"]').attr('content')?.split(/,/)[0],
|
|
111
|
+
language,
|
|
112
|
+
id: targetUrl,
|
|
158
113
|
};
|
|
159
|
-
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const route: Route = {
|
|
117
|
+
path: '/article/list/:id?',
|
|
118
|
+
name: '列表',
|
|
119
|
+
url: 'www.stcn.com',
|
|
120
|
+
maintainers: ['nczitzk'],
|
|
121
|
+
handler,
|
|
122
|
+
example: '/stcn/article/list/yw',
|
|
123
|
+
parameters: {
|
|
124
|
+
category: {
|
|
125
|
+
description: '分类,默认为 `yw`,即要闻,可在对应分类页 URL 中找到',
|
|
126
|
+
options: [
|
|
127
|
+
{
|
|
128
|
+
label: '要闻',
|
|
129
|
+
value: 'yw',
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
label: '股市',
|
|
133
|
+
value: 'gs',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
label: '公司',
|
|
137
|
+
value: 'company',
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
label: '基金',
|
|
141
|
+
value: 'fund',
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
label: '金融',
|
|
145
|
+
value: 'finance',
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
label: '评论',
|
|
149
|
+
value: 'comment',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
label: '产经',
|
|
153
|
+
value: 'cj',
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
label: '科创板',
|
|
157
|
+
value: 'kcb',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
label: '新三板',
|
|
161
|
+
value: 'xsb',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
label: 'ESG',
|
|
165
|
+
value: 'zk',
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
label: '滚动',
|
|
169
|
+
value: 'gd',
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
description: `:::tip
|
|
175
|
+
若订阅 [要闻](https://www.stcn.com/article/list/yw.html),网址为 \`https://www.stcn.com/article/list/yw.html\`,请截取 \`https://www.stcn.com/article/list/\` 到末尾 \`.html\` 的部分 \`yw\` 作为 \`id\` 参数填入,此时目标路由为 [\`/stcn/article/list/yw\`](https://rsshub.app/stcn/article/list/yw)。
|
|
176
|
+
|
|
177
|
+
:::
|
|
178
|
+
|
|
179
|
+
| 要闻 | 股市 | 公司 | 基金 | 金融 | 评论 |
|
|
180
|
+
| ---- | ---- | ------- | ---- | ------- | ------- |
|
|
181
|
+
| yw | gs | company | fund | finance | comment |
|
|
182
|
+
|
|
183
|
+
| 产经 | 科创板 | 新三板 | ESG | 滚动 |
|
|
184
|
+
| ---- | ------ | ------ | --- | ---- |
|
|
185
|
+
| cj | kcb | xsb | zk | gd |
|
|
186
|
+
`,
|
|
187
|
+
categories: ['finance'],
|
|
188
|
+
features: {
|
|
189
|
+
requireConfig: false,
|
|
190
|
+
requirePuppeteer: false,
|
|
191
|
+
antiCrawler: false,
|
|
192
|
+
supportRadar: true,
|
|
193
|
+
supportBT: false,
|
|
194
|
+
supportPodcast: false,
|
|
195
|
+
supportScihub: false,
|
|
196
|
+
},
|
|
197
|
+
radar: [
|
|
198
|
+
{
|
|
199
|
+
source: ['www.stcn.com/article/list.html', 'www.stcn.com/article/list/:id'],
|
|
200
|
+
target: (params, url) => {
|
|
201
|
+
const urlObj: URL = new URL(url);
|
|
202
|
+
const id: string | undefined = urlObj.searchParams.get('type') ?? params.id;
|
|
203
|
+
|
|
204
|
+
return `/stcn/article/list${id ? `/${id}` : ''}`;
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
title: '要闻',
|
|
209
|
+
source: ['www.stcn.com/article/list.html', 'www.stcn.com/article/list/yw.html'],
|
|
210
|
+
target: '/article/list/yw',
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
title: '股市',
|
|
214
|
+
source: ['www.stcn.com/article/list.html', 'www.stcn.com/article/list/gs.html'],
|
|
215
|
+
target: '/article/list/gs',
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
title: '公司',
|
|
219
|
+
source: ['www.stcn.com/article/list.html', 'www.stcn.com/article/list/company.html'],
|
|
220
|
+
target: '/article/list/company',
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
title: '基金',
|
|
224
|
+
source: ['www.stcn.com/article/list.html', 'www.stcn.com/article/list/fund.html'],
|
|
225
|
+
target: '/article/list/fund',
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
title: '金融',
|
|
229
|
+
source: ['www.stcn.com/article/list.html', 'www.stcn.com/article/list/finance.html'],
|
|
230
|
+
target: '/article/list/finance',
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
title: '评论',
|
|
234
|
+
source: ['www.stcn.com/article/list.html', 'www.stcn.com/article/list/comment.html'],
|
|
235
|
+
target: '/article/list/comment',
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
title: '产经',
|
|
239
|
+
source: ['www.stcn.com/article/list.html', 'www.stcn.com/article/list/cj.html'],
|
|
240
|
+
target: '/article/list/cj',
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
title: '科创板',
|
|
244
|
+
source: ['www.stcn.com/article/list.html', 'www.stcn.com/article/list/kcb.html'],
|
|
245
|
+
target: '/article/list/kcb',
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
title: '新三板',
|
|
249
|
+
source: ['www.stcn.com/article/list.html', 'www.stcn.com/article/list/xsb.html'],
|
|
250
|
+
target: '/article/list/xsb',
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
title: 'ESG',
|
|
254
|
+
source: ['www.stcn.com/article/list.html', 'www.stcn.com/article/list/zk.html'],
|
|
255
|
+
target: '/article/list/zk',
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
title: '滚动',
|
|
259
|
+
source: ['www.stcn.com/article/list.html', 'www.stcn.com/article/list/gd.html'],
|
|
260
|
+
target: '/article/list/gd',
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
view: ViewType.Articles,
|
|
264
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { type Data, type DataItem, type Route, ViewType } from '@/types';
|
|
2
|
+
|
|
3
|
+
import cache from '@/utils/cache';
|
|
4
|
+
import ofetch from '@/utils/ofetch';
|
|
5
|
+
import { parseDate } from '@/utils/parse-date';
|
|
6
|
+
import timezone from '@/utils/timezone';
|
|
7
|
+
|
|
8
|
+
import { type CheerioAPI, load } from 'cheerio';
|
|
9
|
+
import { type Context } from 'hono';
|
|
10
|
+
|
|
11
|
+
export const handler = async (ctx: Context): Promise<Data> => {
|
|
12
|
+
const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10);
|
|
13
|
+
|
|
14
|
+
const baseUrl: string = 'https://www.stcn.com';
|
|
15
|
+
const targetUrl: string = new URL('article/list/kx.html', baseUrl).href;
|
|
16
|
+
const apiUrl: string = new URL('article/list.html', baseUrl).href;
|
|
17
|
+
|
|
18
|
+
const targetResponse = await ofetch(targetUrl);
|
|
19
|
+
|
|
20
|
+
const response = await ofetch(apiUrl, {
|
|
21
|
+
headers: {
|
|
22
|
+
'x-requested-with': 'XMLHttpRequest',
|
|
23
|
+
},
|
|
24
|
+
query: {
|
|
25
|
+
type: 'kx',
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const $: CheerioAPI = load(targetResponse);
|
|
30
|
+
const language = $('html').attr('lang') ?? 'zh-CN';
|
|
31
|
+
|
|
32
|
+
let items: DataItem[] = [];
|
|
33
|
+
|
|
34
|
+
items = response.data.slice(0, limit).map((item): DataItem => {
|
|
35
|
+
const title: string = item.title;
|
|
36
|
+
const description: string = item.content;
|
|
37
|
+
const pubDate: number | string = item.time;
|
|
38
|
+
const linkUrl: string | undefined = item.url;
|
|
39
|
+
const categories: string[] = item.tags ? item.tags.map((c) => c.name) : [];
|
|
40
|
+
const authors: DataItem['author'] = item.source;
|
|
41
|
+
const image: string | undefined = item.share?.image;
|
|
42
|
+
const updated: number | string = pubDate;
|
|
43
|
+
|
|
44
|
+
const processedItem: DataItem = {
|
|
45
|
+
title,
|
|
46
|
+
description,
|
|
47
|
+
pubDate: pubDate ? parseDate(pubDate, 'X') : undefined,
|
|
48
|
+
link: linkUrl ? new URL(linkUrl, baseUrl).href : undefined,
|
|
49
|
+
category: categories,
|
|
50
|
+
author: authors,
|
|
51
|
+
content: {
|
|
52
|
+
html: description,
|
|
53
|
+
text: item.content ?? description,
|
|
54
|
+
},
|
|
55
|
+
image,
|
|
56
|
+
banner: image,
|
|
57
|
+
updated: updated ? parseDate(updated, 'X') : undefined,
|
|
58
|
+
language,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return processedItem;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
items = (
|
|
65
|
+
await Promise.all(
|
|
66
|
+
items.map((item) => {
|
|
67
|
+
if (!item.link) {
|
|
68
|
+
return item;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return cache.tryGet(item.link, async (): Promise<DataItem> => {
|
|
72
|
+
const detailResponse = await ofetch(item.link);
|
|
73
|
+
const $$: CheerioAPI = load(detailResponse);
|
|
74
|
+
|
|
75
|
+
const title: string = $$('div.detail-title').text();
|
|
76
|
+
const description: string = $$('div.detail-content').html() ?? '';
|
|
77
|
+
const pubDateStr: string | undefined = $$('div.detail-info span').last().text().trim();
|
|
78
|
+
const categories: string[] = [...new Set([...(item.category as string[]), ...($$('meta[name="keywords"]').attr('content')?.split(/,/) ?? [])])];
|
|
79
|
+
const authors: DataItem['author'] = $$('div.detail-info span').first().text().split(/:/).pop();
|
|
80
|
+
const upDatedStr: string | undefined = pubDateStr;
|
|
81
|
+
|
|
82
|
+
const processedItem: DataItem = {
|
|
83
|
+
title,
|
|
84
|
+
description,
|
|
85
|
+
pubDate: pubDateStr ? timezone(parseDate(pubDateStr), +8) : item.pubDate,
|
|
86
|
+
category: categories,
|
|
87
|
+
author: authors,
|
|
88
|
+
content: {
|
|
89
|
+
html: description,
|
|
90
|
+
text: description,
|
|
91
|
+
},
|
|
92
|
+
updated: upDatedStr ? timezone(parseDate(upDatedStr), +8) : item.updated,
|
|
93
|
+
language,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
...item,
|
|
98
|
+
...processedItem,
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
})
|
|
102
|
+
)
|
|
103
|
+
).filter((_): _ is DataItem => true);
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
title: $('title').text(),
|
|
107
|
+
description: $('meta[name="description"]').attr('content'),
|
|
108
|
+
link: targetUrl,
|
|
109
|
+
item: items,
|
|
110
|
+
allowEmpty: true,
|
|
111
|
+
image: $('img.stcn-logo').attr('src'),
|
|
112
|
+
author: $('meta[name="keywords"]').attr('content')?.split(/,/)[0],
|
|
113
|
+
language,
|
|
114
|
+
id: targetUrl,
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export const route: Route = {
|
|
119
|
+
path: '/article/list/kx',
|
|
120
|
+
name: '快讯',
|
|
121
|
+
url: 'www.stcn.com',
|
|
122
|
+
maintainers: ['nczitzk'],
|
|
123
|
+
handler,
|
|
124
|
+
example: '/stcn/article/list/kx',
|
|
125
|
+
parameters: undefined,
|
|
126
|
+
description: undefined,
|
|
127
|
+
categories: ['finance'],
|
|
128
|
+
features: {
|
|
129
|
+
requireConfig: false,
|
|
130
|
+
requirePuppeteer: false,
|
|
131
|
+
antiCrawler: false,
|
|
132
|
+
supportRadar: true,
|
|
133
|
+
supportBT: false,
|
|
134
|
+
supportPodcast: false,
|
|
135
|
+
supportScihub: false,
|
|
136
|
+
},
|
|
137
|
+
radar: [
|
|
138
|
+
{
|
|
139
|
+
source: ['www.stcn.com/article/list/kx.html'],
|
|
140
|
+
target: '/article/list/kx',
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
view: ViewType.Articles,
|
|
144
|
+
};
|
|
@@ -5,13 +5,12 @@ import { parseDate } from '@/utils/parse-date';
|
|
|
5
5
|
import ofetch from '@/utils/ofetch';
|
|
6
6
|
|
|
7
7
|
const rootURL = 'https://scai.swjtu.edu.cn';
|
|
8
|
-
const pageURL = `${rootURL}/web/page-module.html?mid=B730BEB095B31840`;
|
|
9
8
|
|
|
10
9
|
export const route: Route = {
|
|
11
|
-
path: '/scai
|
|
10
|
+
path: '/scai/:type',
|
|
12
11
|
categories: ['university'],
|
|
13
12
|
example: '/swjtu/scai/bks',
|
|
14
|
-
parameters: {},
|
|
13
|
+
parameters: { type: '通知类型' },
|
|
15
14
|
features: {
|
|
16
15
|
requireConfig: false,
|
|
17
16
|
requirePuppeteer: false,
|
|
@@ -26,11 +25,32 @@ export const route: Route = {
|
|
|
26
25
|
},
|
|
27
26
|
],
|
|
28
27
|
name: '计算机与人工智能学院',
|
|
29
|
-
description:
|
|
28
|
+
description: `
|
|
29
|
+
| 分区 | 参数 |
|
|
30
|
+
| ----------------- | ----------- |
|
|
31
|
+
| 本科生教育 | bks |
|
|
32
|
+
| 研究生教育 | yjs |
|
|
33
|
+
| 学生工作 | xsgz |
|
|
34
|
+
`,
|
|
30
35
|
maintainers: ['AzureG03', 'SuperJeason'],
|
|
31
36
|
handler,
|
|
32
37
|
};
|
|
33
38
|
|
|
39
|
+
const partition = {
|
|
40
|
+
bks: {
|
|
41
|
+
title: '本科生教育',
|
|
42
|
+
url: `${rootURL}/web/page-module.html?mid=B730BEB095B31840`,
|
|
43
|
+
},
|
|
44
|
+
yjs: {
|
|
45
|
+
title: '研究生教育',
|
|
46
|
+
url: `${rootURL}/web/page-module.html?mid=6A69B0E32021446B`,
|
|
47
|
+
},
|
|
48
|
+
xsgz: {
|
|
49
|
+
title: '学生工作',
|
|
50
|
+
url: `${rootURL}/web/page-module.html?mid=F3D3909EB1861B5D`,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
34
54
|
const getItem = (item, cache) => {
|
|
35
55
|
const title = item.find('a').text();
|
|
36
56
|
const link = `${rootURL}${item.find('a').attr('href').slice(2)}`;
|
|
@@ -39,22 +59,14 @@ const getItem = (item, cache) => {
|
|
|
39
59
|
const res = await ofetch(link);
|
|
40
60
|
const $ = load(res);
|
|
41
61
|
|
|
42
|
-
// 尝试获取日期
|
|
43
62
|
let dateText = $('div.news-info span:nth-of-type(2)').text();
|
|
63
|
+
// 转教务通知时的时间获取方法
|
|
44
64
|
if (!dateText) {
|
|
45
65
|
dateText = $('div.news-top-bar span:nth-of-type(1)').text();
|
|
46
66
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const dateMatch = dateText.match(/\d{4}(-|\/|.)\d{1,2}\1\d{1,2}/);
|
|
53
|
-
if (!dateMatch) {
|
|
54
|
-
// console.error('Invalid date format for', link);
|
|
55
|
-
return null; // 或者返回一个默认值
|
|
56
|
-
}
|
|
57
|
-
const pubDate = parseDate(dateMatch[0]);
|
|
67
|
+
// 'date' may be undefined. and 'parseDate' will return current time.
|
|
68
|
+
const date = dateText.match(/\d{4}(-|\/|.)\d{1,2}\1\d{1,2}/)?.[0];
|
|
69
|
+
const pubDate = parseDate(date);
|
|
58
70
|
const description = $('div.content-main').html();
|
|
59
71
|
|
|
60
72
|
return {
|
|
@@ -66,8 +78,10 @@ const getItem = (item, cache) => {
|
|
|
66
78
|
});
|
|
67
79
|
};
|
|
68
80
|
|
|
69
|
-
async function handler() {
|
|
70
|
-
const
|
|
81
|
+
async function handler(ctx) {
|
|
82
|
+
const { type } = ctx.req.param();
|
|
83
|
+
const url = partition[type].url;
|
|
84
|
+
const res = await ofetch(url);
|
|
71
85
|
|
|
72
86
|
const $ = load(res);
|
|
73
87
|
const $list = $('div.list-top-item, div.item-wrapper');
|
|
@@ -80,8 +94,8 @@ async function handler() {
|
|
|
80
94
|
);
|
|
81
95
|
|
|
82
96
|
return {
|
|
83
|
-
title:
|
|
84
|
-
link:
|
|
97
|
+
title: `西南交大计院-${partition[type].title}`,
|
|
98
|
+
link: url,
|
|
85
99
|
item: items,
|
|
86
100
|
allowEmpty: true,
|
|
87
101
|
};
|