rsshub 1.0.0-master.f71451d → 1.0.0-master.f75997f
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/config.ts +14 -0
- package/lib/errors/index.test.ts +2 -2
- package/lib/middleware/template.tsx +12 -3
- package/lib/routes/0x80/index.ts +87 -0
- package/lib/routes/0x80/namespace.ts +7 -0
- package/lib/routes/aljazeera/index.ts +17 -14
- package/lib/routes/apple/podcast.ts +64 -0
- package/lib/routes/bilibili/cache.ts +1 -1
- package/lib/routes/bing/daily-wallpaper.ts +9 -8
- package/lib/routes/byau/namespace.ts +6 -0
- package/lib/routes/byau/xinwen/index.ts +72 -0
- package/lib/routes/cpcaauto/index.ts +255 -0
- package/lib/routes/cpcaauto/namespace.ts +8 -0
- package/lib/routes/dehenglaw/index.ts +128 -0
- package/lib/routes/dehenglaw/namespace.ts +8 -0
- package/lib/routes/dehenglaw/templates/description.art +7 -0
- package/lib/routes/gov/stats/index.ts +25 -22
- package/lib/routes/gxmzu/ai.ts +1 -1
- package/lib/routes/gxmzu/lib.ts +9 -26
- package/lib/routes/gxmzu/utils/index.ts +31 -13
- package/lib/routes/gxmzu/yjs.ts +1 -1
- package/lib/routes/jou/utils/index.ts +35 -25
- package/lib/routes/lofter/tag.ts +3 -3
- package/lib/routes/lofter/user.ts +3 -3
- package/lib/routes/njxzc/utils/index.ts +31 -13
- package/lib/routes/qingting/podcast.ts +61 -39
- package/lib/routes/reuters/common.ts +2 -2
- package/lib/routes/sara/index.ts +66 -0
- package/lib/routes/sara/namespace.ts +6 -0
- package/lib/routes/tencent/news/author.ts +13 -11
- package/lib/routes/test/index.ts +11 -1
- package/lib/routes/twitter/api/mobile-api/login.ts +29 -28
- package/lib/routes/twitter/namespace.ts +2 -2
- package/lib/routes/twitter/user.ts +5 -0
- package/lib/routes/u3c3/index.ts +1 -1
- package/lib/routes/u3c3/namespace.ts +1 -1
- package/lib/routes/u9a9/index.ts +2 -2
- package/lib/routes/u9a9/namespace.ts +1 -1
- package/lib/routes/zsxq/group.ts +63 -0
- package/lib/routes/zsxq/namespace.ts +6 -0
- package/lib/routes/zsxq/types.ts +149 -0
- package/lib/routes/zsxq/user.ts +58 -0
- package/lib/routes/zsxq/utils.ts +70 -0
- package/lib/setup.test.ts +183 -12
- package/lib/utils/render.ts +1 -1
- package/lib/utils/request-rewriter/get.ts +8 -1
- package/lib/utils/wechat-mp.test.ts +411 -32
- package/lib/utils/wechat-mp.ts +447 -76
- package/lib/views/{rss3-ums.ts → rss3.ts} +2 -2
- package/package.json +14 -14
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
import cache from '@/utils/cache';
|
|
2
|
-
//
|
|
3
|
-
import got from '@/utils/got';
|
|
4
|
-
// 导入cheerio库,该库用来解析网页数据
|
|
2
|
+
import ofetch from '@/utils/ofetch'; // 使用ofetch库
|
|
5
3
|
import { load } from 'cheerio';
|
|
6
|
-
// 导入parseDate函数,该函数用于日期处理
|
|
7
4
|
import { parseDate } from '@/utils/parse-date';
|
|
8
|
-
// 导入timezone库,该库用于时区处理
|
|
9
5
|
import timezone from '@/utils/timezone';
|
|
10
6
|
|
|
11
7
|
async function getItems(ctx, url, host, tableClass, timeStyleClass1, titleStyleClass, timeStyleClass2) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
const response = await ofetch(url).catch(() => null);
|
|
9
|
+
if (!response) {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
const $ = load(response);
|
|
16
13
|
|
|
17
|
-
// 通知公告的items的标题、url链接、发布日期
|
|
18
14
|
const list = $(`table.${tableClass} > tbody > tr[height=20]`)
|
|
19
15
|
.toArray()
|
|
20
16
|
.map((item) => {
|
|
21
|
-
const currentItem = $(item);
|
|
22
|
-
const item1 = currentItem.find('td:eq(1)');
|
|
23
|
-
const item2 = currentItem.find('td:eq(2)');
|
|
17
|
+
const currentItem = $(item);
|
|
18
|
+
const item1 = currentItem.find('td:eq(1)');
|
|
19
|
+
const item2 = currentItem.find('td:eq(2)');
|
|
24
20
|
const link = new URL(item1.find('a').attr('href'), host).href;
|
|
25
21
|
|
|
26
22
|
return {
|
|
@@ -32,22 +28,36 @@ async function getItems(ctx, url, host, tableClass, timeStyleClass1, titleStyleC
|
|
|
32
28
|
|
|
33
29
|
const out = await Promise.all(
|
|
34
30
|
list.map((item) =>
|
|
35
|
-
// 使用缓存
|
|
36
31
|
cache.tryGet(item.link, async () => {
|
|
37
|
-
const response = await
|
|
38
|
-
if (response.
|
|
39
|
-
|
|
32
|
+
const response = await ofetch(item.link).catch(() => null);
|
|
33
|
+
if (!response || (response.status >= 300 && response.status < 400)) {
|
|
34
|
+
// 响应为空或状态码表明发生了重定向
|
|
35
|
+
return {
|
|
36
|
+
...item,
|
|
37
|
+
description: '该通知无法直接预览,请点击原文链接↑查看',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const $ = load(response);
|
|
41
|
+
|
|
42
|
+
item.title = $(`.${titleStyleClass}`).text();
|
|
43
|
+
const hasEmbeddedPDFScript = $('script:contains("showVsbpdfIframe")').length > 0;
|
|
44
|
+
|
|
45
|
+
if (hasEmbeddedPDFScript) {
|
|
40
46
|
item.description = '该通知无法直接预览,请点击原文链接↑查看';
|
|
41
47
|
} else {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
const contentHtml = $('.v_news_content').html();
|
|
49
|
+
const $content = load(contentHtml);
|
|
50
|
+
$content('a').each(function () {
|
|
51
|
+
const a = $(this);
|
|
52
|
+
const href = a.attr('href');
|
|
53
|
+
if (href && !href.startsWith('http')) {
|
|
54
|
+
a.attr('href', new URL(href, host).href);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
item.description = $content.html();
|
|
50
58
|
}
|
|
59
|
+
item.pubDate = timezone(parseDate($(`.${timeStyleClass2}`).text().replace('发布时间:', '')), +8);
|
|
60
|
+
|
|
51
61
|
return item;
|
|
52
62
|
})
|
|
53
63
|
)
|
package/lib/routes/lofter/tag.ts
CHANGED
|
@@ -18,7 +18,7 @@ export const route: Route = {
|
|
|
18
18
|
supportScihub: false,
|
|
19
19
|
},
|
|
20
20
|
name: 'Tag',
|
|
21
|
-
maintainers: ['hoilc', 'nczitzk'],
|
|
21
|
+
maintainers: ['hoilc', 'nczitzk', 'LucunJi'],
|
|
22
22
|
handler,
|
|
23
23
|
description: `| new | date | week | month | total |
|
|
24
24
|
| ---- | ---- | ---- | ----- | ----- |
|
|
@@ -38,7 +38,7 @@ async function handler(ctx) {
|
|
|
38
38
|
const response = await got({
|
|
39
39
|
method: 'post',
|
|
40
40
|
url: apiUrl,
|
|
41
|
-
|
|
41
|
+
body: new URLSearchParams({
|
|
42
42
|
callCount: 1,
|
|
43
43
|
scriptSessionId: '${scriptSessionId}187',
|
|
44
44
|
httpSessionId: '',
|
|
@@ -55,7 +55,7 @@ async function handler(ctx) {
|
|
|
55
55
|
'c0-param7': `number:${startingIndex}`,
|
|
56
56
|
'c0-param8': 'number:0',
|
|
57
57
|
batchId: 493053,
|
|
58
|
-
},
|
|
58
|
+
}),
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
const dom = new JSDOM(
|
|
@@ -18,7 +18,7 @@ export const route: Route = {
|
|
|
18
18
|
supportScihub: false,
|
|
19
19
|
},
|
|
20
20
|
name: 'User',
|
|
21
|
-
maintainers: ['hondajojo', 'nczitzk'],
|
|
21
|
+
maintainers: ['hondajojo', 'nczitzk', 'LucunJi'],
|
|
22
22
|
handler,
|
|
23
23
|
};
|
|
24
24
|
|
|
@@ -34,7 +34,7 @@ async function handler(ctx) {
|
|
|
34
34
|
const response = await got({
|
|
35
35
|
method: 'post',
|
|
36
36
|
url: `http://api.lofter.com/v2.0/blogHomePage.api?product=lofter-iphone-10.0.0`,
|
|
37
|
-
|
|
37
|
+
body: new URLSearchParams({
|
|
38
38
|
blogdomain: rootUrl,
|
|
39
39
|
checkpwd: '1',
|
|
40
40
|
following: '0',
|
|
@@ -44,7 +44,7 @@ async function handler(ctx) {
|
|
|
44
44
|
offset: '0',
|
|
45
45
|
postdigestnew: '1',
|
|
46
46
|
supportposttypes: '1,2,3,4,5,6',
|
|
47
|
-
},
|
|
47
|
+
}),
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
if (!response.data.response || response.data.response.posts.length === 0) {
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import cache from '@/utils/cache';
|
|
2
|
-
import got from '@/utils/got';
|
|
3
2
|
import { load } from 'cheerio';
|
|
4
3
|
import { parseDate } from '@/utils/parse-date';
|
|
5
4
|
import timezone from '@/utils/timezone';
|
|
5
|
+
import ofetch from '@/utils/ofetch'; // 使用默认导出的方式导入ofetch
|
|
6
6
|
|
|
7
7
|
async function getNoticeList(ctx, url, host, titleSelector, dateSelector, contentSelector, listSelector) {
|
|
8
|
-
const response = await
|
|
9
|
-
|
|
8
|
+
const response = await ofetch(url).catch(() => null);
|
|
9
|
+
if (!response) {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
const $ = load(response);
|
|
10
13
|
|
|
11
14
|
const list = $(listSelector)
|
|
12
15
|
.toArray()
|
|
@@ -22,20 +25,35 @@ async function getNoticeList(ctx, url, host, titleSelector, dateSelector, conten
|
|
|
22
25
|
const out = await Promise.all(
|
|
23
26
|
list.map((item) =>
|
|
24
27
|
cache.tryGet(item.link, async () => {
|
|
25
|
-
const response = await
|
|
26
|
-
if (response.
|
|
27
|
-
|
|
28
|
+
const response = await ofetch(item.link).catch(() => null);
|
|
29
|
+
if (!response || (response.status >= 300 && response.status < 400)) {
|
|
30
|
+
return {
|
|
31
|
+
...item,
|
|
32
|
+
description: '该通知无法直接预览,请点击原文链接↑查看',
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const $ = load(response);
|
|
36
|
+
|
|
37
|
+
if ($('.wp_error_msg').length > 0) {
|
|
38
|
+
item.description = '您当前ip并非校内地址,该信息仅允许校内地址访问';
|
|
39
|
+
} else if ($('.wp_pdf_player').length > 0) {
|
|
28
40
|
item.description = '该通知无法直接预览,请点击原文链接↑查看';
|
|
29
41
|
} else {
|
|
30
|
-
const
|
|
42
|
+
const contentHtml = $(contentSelector.content).html();
|
|
43
|
+
const $content = load(contentHtml);
|
|
44
|
+
$content('a').each(function () {
|
|
45
|
+
const a = $(this);
|
|
46
|
+
const href = a.attr('href');
|
|
47
|
+
if (href && !href.startsWith('http')) {
|
|
48
|
+
a.attr('href', new URL(href, host).href);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
item.description = $content.html();
|
|
31
52
|
item.title = $(contentSelector.title).text();
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
.replaceAll('src="/', `src="${new URL('.', host).href}`)
|
|
35
|
-
.replaceAll('href="/', `href="${new URL('.', host).href}`)
|
|
36
|
-
.trim();
|
|
37
|
-
item.pubDate = timezone(parseDate($(contentSelector.date).text().replace('编辑:', '').replace('发布日期:', '').replace('发布时间:', '')), +8);
|
|
53
|
+
const dateText = $(contentSelector.date).text().replace('编辑:', '').replace('发布日期:', '').replace('发布时间:', '');
|
|
54
|
+
item.pubDate = timezone(parseDate(dateText, 'YYYY-MM-DD'), +8);
|
|
38
55
|
}
|
|
56
|
+
|
|
39
57
|
return item;
|
|
40
58
|
})
|
|
41
59
|
)
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { Route } from '@/types';
|
|
1
|
+
import type { DataItem, Route } from '@/types';
|
|
2
2
|
import cache from '@/utils/cache';
|
|
3
3
|
import crypto from 'crypto';
|
|
4
4
|
import got from '@/utils/got';
|
|
5
5
|
import timezone from '@/utils/timezone';
|
|
6
6
|
import { parseDate } from '@/utils/parse-date';
|
|
7
|
+
import { config } from '@/config';
|
|
8
|
+
|
|
9
|
+
const qingtingId = config.qingting.id ?? '';
|
|
7
10
|
|
|
8
11
|
export const route: Route = {
|
|
9
12
|
path: '/podcast/:id',
|
|
@@ -11,12 +14,14 @@ export const route: Route = {
|
|
|
11
14
|
example: '/qingting/podcast/293411',
|
|
12
15
|
parameters: { id: '专辑id, 可在专辑页 URL 中找到' },
|
|
13
16
|
features: {
|
|
14
|
-
requireConfig: false,
|
|
15
|
-
requirePuppeteer: false,
|
|
16
|
-
antiCrawler: false,
|
|
17
|
-
supportBT: false,
|
|
18
17
|
supportPodcast: true,
|
|
19
|
-
|
|
18
|
+
requireConfig: [
|
|
19
|
+
{
|
|
20
|
+
name: 'QINGTING_ID',
|
|
21
|
+
optional: true,
|
|
22
|
+
description: '用户id, 部分专辑需要会员身份,用户id可以通过从网页端登录蜻蜓fm后使用开发者工具,在控制台中运行JSON.parse(localStorage.getItem("user")).qingting_id获取',
|
|
23
|
+
},
|
|
24
|
+
],
|
|
20
25
|
},
|
|
21
26
|
radar: [
|
|
22
27
|
{
|
|
@@ -29,21 +34,35 @@ export const route: Route = {
|
|
|
29
34
|
description: `获取的播放 URL 有效期只有 1 天,需要开启播客 APP 的自动下载功能。`,
|
|
30
35
|
};
|
|
31
36
|
|
|
37
|
+
function getMediaUrl(channelId: string, mediaId: string) {
|
|
38
|
+
const path = `/audiostream/redirect/${channelId}/${mediaId}?access_token=&device_id=MOBILESITE&qingting_id=${qingtingId}&t=${Date.now()}`;
|
|
39
|
+
const sign = crypto.createHmac('md5', 'fpMn12&38f_2e').update(path).digest('hex').toString();
|
|
40
|
+
return `https://audio.qingting.fm${path}&sign=${sign}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
32
43
|
async function handler(ctx) {
|
|
33
|
-
const
|
|
34
|
-
|
|
44
|
+
const channelId = ctx.req.param('id');
|
|
45
|
+
|
|
46
|
+
const channelUrl = `https://i.qingting.fm/capi/v3/channel/${channelId}`;
|
|
47
|
+
const response = await got({
|
|
35
48
|
method: 'get',
|
|
36
49
|
url: channelUrl,
|
|
37
50
|
headers: {
|
|
38
51
|
Referer: 'https://www.qingting.fm/',
|
|
39
52
|
},
|
|
40
53
|
});
|
|
54
|
+
|
|
41
55
|
const title = response.data.data.title;
|
|
42
56
|
const channel_img = response.data.data.thumbs['400_thumb'];
|
|
43
57
|
const authors = response.data.data.podcasters.map((author) => author.nick_name).join(',');
|
|
44
58
|
const desc = response.data.data.description;
|
|
45
|
-
const programUrl = `https://i.qingting.fm/capi/channel/${
|
|
46
|
-
|
|
59
|
+
const programUrl = `https://i.qingting.fm/capi/channel/${channelId}/programs/${response.data.data.v}?curpage=1&pagesize=10&order=asc`;
|
|
60
|
+
|
|
61
|
+
const {
|
|
62
|
+
data: {
|
|
63
|
+
data: { programs },
|
|
64
|
+
},
|
|
65
|
+
} = await got({
|
|
47
66
|
method: 'get',
|
|
48
67
|
url: programUrl,
|
|
49
68
|
headers: {
|
|
@@ -51,45 +70,48 @@ async function handler(ctx) {
|
|
|
51
70
|
},
|
|
52
71
|
});
|
|
53
72
|
|
|
73
|
+
const {
|
|
74
|
+
data: { data: channelInfo },
|
|
75
|
+
} = await got(`https://i.qingting.fm/capi/v3/channel/${channelId}?user_id=${qingtingId}`);
|
|
76
|
+
|
|
77
|
+
const isCharged = channelInfo.purchase?.item_type !== 0;
|
|
78
|
+
|
|
79
|
+
const isPaid = channelInfo.user_relevance?.sale_status === 'paid';
|
|
80
|
+
|
|
54
81
|
const resultItems = await Promise.all(
|
|
55
|
-
|
|
56
|
-
cache.tryGet(`qingting:podcast:${
|
|
57
|
-
const link = `https://www.qingting.fm/channels/${
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
headers: {
|
|
67
|
-
Referer: 'https://www.qingting.fm/',
|
|
68
|
-
},
|
|
69
|
-
}),
|
|
70
|
-
got({
|
|
71
|
-
method: 'get',
|
|
72
|
-
url: `https://audio.qingting.fm${path}&sign=${sign}`,
|
|
73
|
-
headers: {
|
|
74
|
-
Referer: 'https://www.qingting.fm/',
|
|
75
|
-
},
|
|
76
|
-
}),
|
|
77
|
-
]);
|
|
82
|
+
programs.map(async (item) => {
|
|
83
|
+
const data = (await cache.tryGet(`qingting:podcast:${channelId}:${item.id}`, async () => {
|
|
84
|
+
const link = `https://www.qingting.fm/channels/${channelId}/programs/${item.id}/`;
|
|
85
|
+
|
|
86
|
+
const detailRes = await got({
|
|
87
|
+
method: 'get',
|
|
88
|
+
url: link,
|
|
89
|
+
headers: {
|
|
90
|
+
Referer: 'https://www.qingting.fm/',
|
|
91
|
+
},
|
|
92
|
+
});
|
|
78
93
|
|
|
79
94
|
const detail = JSON.parse(detailRes.data.match(/},"program":(.*?),"plist":/)[1]);
|
|
80
95
|
|
|
81
|
-
|
|
96
|
+
const rssItem = {
|
|
82
97
|
title: item.title,
|
|
83
98
|
link,
|
|
84
99
|
itunes_item_image: item.cover,
|
|
85
100
|
itunes_duration: item.duration,
|
|
86
101
|
pubDate: timezone(parseDate(item.update_time), +8),
|
|
87
102
|
description: detail.richtext,
|
|
88
|
-
enclosure_url: mediaRes.url,
|
|
89
|
-
enclosure_type: 'audio/x-m4a',
|
|
90
103
|
};
|
|
91
|
-
|
|
92
|
-
|
|
104
|
+
|
|
105
|
+
return rssItem;
|
|
106
|
+
})) as DataItem;
|
|
107
|
+
|
|
108
|
+
if (!isCharged || isPaid || item.isfree) {
|
|
109
|
+
data.enclosure_url = getMediaUrl(channelId, item.id);
|
|
110
|
+
data.enclosure_type = 'audio/x-m4a';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return data;
|
|
114
|
+
})
|
|
93
115
|
);
|
|
94
116
|
|
|
95
117
|
return {
|
|
@@ -97,7 +119,7 @@ async function handler(ctx) {
|
|
|
97
119
|
description: desc,
|
|
98
120
|
itunes_author: authors,
|
|
99
121
|
image: channel_img,
|
|
100
|
-
link: `https://www.qingting.fm/channels/${
|
|
122
|
+
link: `https://www.qingting.fm/channels/${channelId}`,
|
|
101
123
|
item: resultItems,
|
|
102
124
|
};
|
|
103
125
|
}
|
|
@@ -64,13 +64,13 @@ export const route: Route = {
|
|
|
64
64
|
};
|
|
65
65
|
|
|
66
66
|
async function handler(ctx) {
|
|
67
|
-
const MUST_FETCH_BY_TOPICS = new Set(['authors']);
|
|
67
|
+
const MUST_FETCH_BY_TOPICS = new Set(['authors', 'tags']);
|
|
68
68
|
const CAN_USE_SOPHI = ['world'];
|
|
69
69
|
|
|
70
70
|
const category = ctx.req.param('category');
|
|
71
71
|
const topic = ctx.req.param('topic') ?? (category === 'authors' ? 'reuters' : '');
|
|
72
72
|
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 20;
|
|
73
|
-
const useSophi = ctx.req.query('sophi') === 'true' &&
|
|
73
|
+
const useSophi = ctx.req.query('sophi') === 'true' && topic !== '' && CAN_USE_SOPHI.includes(category);
|
|
74
74
|
|
|
75
75
|
const section_id = `/${category}/${topic ? `${topic}/` : ''}`;
|
|
76
76
|
const { title, description, rootUrl, response } = await (async () => {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Route, DataItem } from '@/types';
|
|
2
|
+
import cache from '@/utils/cache';
|
|
3
|
+
import { load } from 'cheerio';
|
|
4
|
+
import { ofetch } from 'ofetch';
|
|
5
|
+
|
|
6
|
+
const typeMap = {
|
|
7
|
+
dynamic: '协会动态',
|
|
8
|
+
announcement: '通知公告',
|
|
9
|
+
industry: '行业动态',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const route: Route = {
|
|
13
|
+
path: '/:type',
|
|
14
|
+
categories: ['government'],
|
|
15
|
+
example: '/sara/announcement',
|
|
16
|
+
parameters: { type: 'dynamic | announcement | industry' },
|
|
17
|
+
features: {
|
|
18
|
+
requireConfig: false,
|
|
19
|
+
requirePuppeteer: false,
|
|
20
|
+
antiCrawler: false,
|
|
21
|
+
supportBT: false,
|
|
22
|
+
supportPodcast: false,
|
|
23
|
+
supportScihub: false,
|
|
24
|
+
},
|
|
25
|
+
description: `| 协会动态 | 通知公告 |行业动态 |
|
|
26
|
+
| -------- | ------------ | -------- |
|
|
27
|
+
| dynamic | announcement | industry |`,
|
|
28
|
+
|
|
29
|
+
name: '新闻资讯',
|
|
30
|
+
maintainers: ['HChenZi'],
|
|
31
|
+
handler: async (ctx) => {
|
|
32
|
+
const baseUrl = 'http://www.sara.org.cn';
|
|
33
|
+
const type = ctx.req.param('type');
|
|
34
|
+
|
|
35
|
+
const url = `${baseUrl}/news/${type}.htm`;
|
|
36
|
+
const response = await ofetch(url);
|
|
37
|
+
const $ = load(response);
|
|
38
|
+
const list = $('.newsItem_total > dd')
|
|
39
|
+
.toArray()
|
|
40
|
+
.map((item) => {
|
|
41
|
+
const a = $(item).find('a').first();
|
|
42
|
+
return {
|
|
43
|
+
link: `${baseUrl}${a.attr('href')}`,
|
|
44
|
+
title: a.attr('title'),
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
const items = (await Promise.all(list.map(getFeedItem))) as DataItem[];
|
|
48
|
+
return {
|
|
49
|
+
title: typeMap[type],
|
|
50
|
+
link: url,
|
|
51
|
+
item: items,
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
async function getFeedItem(item) {
|
|
57
|
+
return await cache.tryGet(item.link, async () => {
|
|
58
|
+
const response = await ofetch(item.link);
|
|
59
|
+
const $ = load(response);
|
|
60
|
+
return {
|
|
61
|
+
description: $('.text').html(),
|
|
62
|
+
language: 'zh-cn',
|
|
63
|
+
...item,
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
}
|
|
@@ -68,17 +68,19 @@ async function handler(ctx) {
|
|
|
68
68
|
.text()
|
|
69
69
|
.match(/window\.DATA = ({.+});/)[1]
|
|
70
70
|
);
|
|
71
|
-
const $data = load(data.originContent
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
71
|
+
const $data = load(data.originContent?.text || '', null, false);
|
|
72
|
+
if ($data) {
|
|
73
|
+
// Not video page
|
|
74
|
+
$data('*')
|
|
75
|
+
.contents()
|
|
76
|
+
.filter((_, elem) => elem.type === 'comment')
|
|
77
|
+
.replaceWith((_, elem) =>
|
|
78
|
+
art(path.join(__dirname, '../templates/news/image.art'), {
|
|
79
|
+
attribute: elem.data.trim(),
|
|
80
|
+
originAttribute: data.originAttribute,
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
}
|
|
82
84
|
|
|
83
85
|
return {
|
|
84
86
|
title,
|
package/lib/routes/test/index.ts
CHANGED
|
@@ -3,13 +3,14 @@ import { config } from '@/config';
|
|
|
3
3
|
import got from '@/utils/got';
|
|
4
4
|
import wait from '@/utils/wait';
|
|
5
5
|
import cache from '@/utils/cache';
|
|
6
|
+
import { fetchArticle } from '@/utils/wechat-mp';
|
|
6
7
|
import ConfigNotFoundError from '@/errors/types/config-not-found';
|
|
7
8
|
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
|
8
9
|
|
|
9
10
|
let cacheIndex = 0;
|
|
10
11
|
|
|
11
12
|
export const route: Route = {
|
|
12
|
-
path: '/:id',
|
|
13
|
+
path: '/:id/:params?',
|
|
13
14
|
name: 'Unknown',
|
|
14
15
|
maintainers: ['DIYgod', 'NeverBehave'],
|
|
15
16
|
handler,
|
|
@@ -384,6 +385,15 @@ async function handler(ctx) {
|
|
|
384
385
|
];
|
|
385
386
|
}
|
|
386
387
|
|
|
388
|
+
if (ctx.req.param('id') === 'wechat-mp') {
|
|
389
|
+
const params = ctx.req.param('params');
|
|
390
|
+
if (!params) {
|
|
391
|
+
throw new InvalidParameterError('Invalid parameter');
|
|
392
|
+
}
|
|
393
|
+
const mpUrl = 'https:/mp.weixin.qq.com/s' + (params.includes('&') ? '?' : '/') + params;
|
|
394
|
+
item = [await fetchArticle(mpUrl)];
|
|
395
|
+
}
|
|
396
|
+
|
|
387
397
|
return {
|
|
388
398
|
title: `Test ${ctx.req.param('id')}`,
|
|
389
399
|
itunes_author: ctx.req.param('id') === 'enclosure' ? 'DIYgod' : null,
|
|
@@ -6,12 +6,14 @@ import ofetch from '@/utils/ofetch';
|
|
|
6
6
|
import crypto from 'crypto';
|
|
7
7
|
import { config } from '@/config';
|
|
8
8
|
import { v5 as uuidv5 } from 'uuid';
|
|
9
|
+
import { authenticator } from 'otplib';
|
|
9
10
|
import logger from '@/utils/logger';
|
|
10
11
|
import cache from '@/utils/cache';
|
|
11
12
|
|
|
12
13
|
const NAMESPACE = 'd41d092b-b007-48f7-9129-e9538d2d8fe9';
|
|
13
14
|
const username = config.twitter.username;
|
|
14
15
|
const password = config.twitter.password;
|
|
16
|
+
const authenticationSecret = config.twitter.authenticationSecret;
|
|
15
17
|
|
|
16
18
|
let authentication = null;
|
|
17
19
|
|
|
@@ -133,38 +135,37 @@ async function login() {
|
|
|
133
135
|
});
|
|
134
136
|
logger.debug('Twitter login 5 finished: AccountDuplicationCheck.');
|
|
135
137
|
|
|
136
|
-
for (const subtask of task4.data?.subtasks || []) {
|
|
138
|
+
for await (const subtask of task4.data?.subtasks || []) {
|
|
137
139
|
if (subtask.open_account) {
|
|
138
140
|
authentication = subtask.open_account;
|
|
139
141
|
break;
|
|
140
142
|
} else if (subtask.subtask_id === 'LoginTwoFactorAuthChallenge') {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
// }
|
|
143
|
+
const token = authenticator.generate(authenticationSecret);
|
|
144
|
+
|
|
145
|
+
const task5 = await got.post('https://api.twitter.com/1.1/onboarding/task.json', {
|
|
146
|
+
headers,
|
|
147
|
+
json: {
|
|
148
|
+
flow_token: task4.data.flow_token,
|
|
149
|
+
subtask_inputs: [
|
|
150
|
+
{
|
|
151
|
+
enter_text: {
|
|
152
|
+
suggestion_id: null,
|
|
153
|
+
text: token,
|
|
154
|
+
link: 'next_link',
|
|
155
|
+
},
|
|
156
|
+
subtask_id: 'LoginTwoFactorAuthChallenge',
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
logger.debug('Twitter login 6 finished: LoginTwoFactorAuthChallenge.');
|
|
162
|
+
|
|
163
|
+
for (const subtask of task5.data?.subtasks || []) {
|
|
164
|
+
if (subtask.open_account) {
|
|
165
|
+
authentication = subtask.open_account;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
168
169
|
break;
|
|
169
170
|
}
|
|
170
171
|
}
|
|
@@ -40,8 +40,8 @@ generates
|
|
|
40
40
|
|
|
41
41
|
Currently supports two authentication methods:
|
|
42
42
|
|
|
43
|
-
- Using TWITTER_COOKIE (recommended): Configure the cookies of logged-in Twitter Web, at least including the fields auth_token and ct0. RSSHub will use this information to directly access Twitter's web API to obtain data.
|
|
43
|
+
- Using \`TWITTER_COOKIE\` (recommended): Configure the cookies of logged-in Twitter Web, at least including the fields auth_token and ct0. RSSHub will use this information to directly access Twitter's web API to obtain data.
|
|
44
44
|
|
|
45
|
-
- Using TWITTER_USERNAME TWITTER_PASSWORD
|
|
45
|
+
- Using \`TWITTER_USERNAME\` \`TWITTER_PASSWORD\` and \`TWITTER_AUTHENTICATION_SECRET\`: Configure the Twitter username and password. RSSHub will use this information to log in to Twitter and obtain data using the mobile API. Please note that if you have not logged in with the current IP address before, it is easy to trigger Twitter's risk control mechanism.
|
|
46
46
|
`,
|
|
47
47
|
};
|
|
@@ -21,6 +21,11 @@ export const route: Route = {
|
|
|
21
21
|
name: 'TWITTER_PASSWORD',
|
|
22
22
|
description: 'Please see above for details.',
|
|
23
23
|
},
|
|
24
|
+
{
|
|
25
|
+
name: 'TWITTER_AUTHENTICATION_SECRET',
|
|
26
|
+
description: 'TOTP 2FA secret, please see above for details.',
|
|
27
|
+
optional: true,
|
|
28
|
+
},
|
|
24
29
|
{
|
|
25
30
|
name: 'TWITTER_COOKIE',
|
|
26
31
|
description: 'Please see above for details.',
|
package/lib/routes/u3c3/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { load } from 'cheerio';
|
|
|
6
6
|
export const route: Route = {
|
|
7
7
|
path: ['/search/:keyword/:preview?', '/:type?/:preview?'],
|
|
8
8
|
categories: ['multimedia'],
|
|
9
|
-
example: '/
|
|
9
|
+
example: '/u3c3/search/新片速递',
|
|
10
10
|
parameters: { keyword: 'Search keyword', preview: 'Show image preview, off by default, non empty value means on' },
|
|
11
11
|
features: {
|
|
12
12
|
requireConfig: false,
|