rsshub 1.0.0-master.f72af1b → 1.0.0-master.f7347d9
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 +18 -0
- package/lib/middleware/template.tsx +10 -2
- package/lib/routes/163/music/playlist.ts +2 -1
- package/lib/routes/5eplay/index.ts +1 -51
- package/lib/routes/a9vg/index.ts +214 -0
- package/lib/routes/a9vg/namespace.ts +1 -0
- package/lib/routes/a9vg/templates/description.art +17 -0
- package/lib/routes/aibase/discover.ts +388 -0
- package/lib/routes/aibase/namespace.ts +8 -0
- package/lib/routes/aibase/news.ts +118 -0
- package/lib/routes/aibase/templates/description.art +100 -0
- package/lib/routes/aibase/topic.ts +614 -0
- package/lib/routes/aibase/util.ts +114 -0
- package/lib/routes/apnews/api.ts +18 -3
- package/lib/routes/apnews/rss.ts +1 -1
- package/lib/routes/apnews/topics.ts +4 -3
- package/lib/routes/apple/podcast.ts +19 -15
- package/lib/routes/bangumi/templates/tv/subject.art +6 -0
- package/lib/routes/bangumi/tv/other/followrank.ts +19 -22
- package/lib/routes/bangumi/tv/user/collections.ts +172 -0
- package/lib/routes/bilibili/cache.ts +16 -8
- package/lib/routes/bilibili/utils.ts +85 -3
- package/lib/routes/bilibili/video.ts +7 -9
- package/lib/routes/bilibili/vsearch.ts +1 -1
- package/lib/routes/bjnews/cat.ts +9 -1
- package/lib/routes/cbpanet/index.ts +380 -0
- package/lib/routes/cbpanet/namespace.ts +8 -0
- package/lib/routes/ceph/blog.ts +72 -0
- package/lib/routes/ceph/namespace.ts +7 -0
- package/lib/routes/chinanews/index.ts +1 -1
- package/lib/routes/chinaventure/index.ts +1 -1
- package/lib/routes/cisia/index.ts +264 -0
- package/lib/routes/cisia/namespace.ts +8 -0
- package/lib/routes/cjlu/namespace.ts +10 -0
- package/lib/routes/cjlu/yjsy/index.ts +107 -0
- package/lib/routes/cnki/journals.ts +31 -2
- package/lib/routes/cohere/index.ts +54 -0
- package/lib/routes/cohere/namespace.ts +6 -0
- package/lib/routes/coolapk/dyh.ts +7 -1
- package/lib/routes/coolapk/hot.ts +7 -1
- package/lib/routes/coolapk/huati.ts +7 -1
- package/lib/routes/coolapk/namespace.ts +6 -0
- package/lib/routes/coolapk/toutiao.ts +7 -1
- package/lib/routes/coolapk/tuwen.ts +10 -5
- package/lib/routes/coolapk/user-dynamic.ts +7 -1
- package/lib/routes/damai/activity.ts +3 -2
- package/lib/routes/dcfever/trading.ts +3 -5
- package/lib/routes/dcfever/utils.ts +13 -4
- package/lib/routes/dealstreetasia/home.ts +72 -0
- package/lib/routes/dealstreetasia/namespace.ts +6 -0
- package/lib/routes/dealstreetasia/section.ts +57 -0
- package/lib/routes/dlsite/campaign.ts +1 -2
- package/lib/routes/dlsite/new.ts +1 -2
- package/lib/routes/dlsite/{index.ts → z-index/index.ts} +1 -1
- package/lib/routes/douban/other/topic.ts +10 -4
- package/lib/routes/douyin/types.ts +795 -0
- package/lib/routes/douyin/user.ts +51 -23
- package/lib/routes/douyin/utils.ts +1 -87
- package/lib/routes/dribbble/keyword.ts +1 -1
- package/lib/routes/dribbble/popular.ts +1 -1
- package/lib/routes/dribbble/user.ts +1 -1
- package/lib/routes/dribbble/utils.ts +16 -18
- package/lib/routes/famitsu/category.ts +1 -1
- package/lib/routes/fanbox/index.ts +1 -1
- package/lib/routes/fanbox/types.ts +1 -5
- package/lib/routes/fediverse/timeline.ts +21 -3
- package/lib/routes/follow/profile.ts +4 -2
- package/lib/routes/follow/types.ts +11 -1
- package/lib/routes/github/advisor.ts +97 -0
- package/lib/routes/github/discussions.ts +194 -0
- package/lib/routes/github/issue.ts +1 -1
- package/lib/routes/github/pulls.ts +1 -1
- package/lib/routes/gmcmonline/chinacustoms.ts +130 -0
- package/lib/routes/gmcmonline/namespace.ts +8 -0
- package/lib/routes/gov/csrc/csrc.ts +541 -0
- package/lib/routes/gov/customs/list.ts +5 -2
- package/lib/routes/gov/customs/namespace.ts +7 -0
- package/lib/routes/gov/jgjcndrc/index.ts +95 -66
- package/lib/routes/gov/moa/moa.ts +3 -3
- package/lib/routes/gov/moa/szcpxx.ts +115 -0
- package/lib/routes/gov/moa/zdscxx.ts +71 -21
- package/lib/routes/gov/ndrc/zfxxgk.ts +106 -0
- package/lib/routes/gov/pudong/zwgk.ts +65 -0
- package/lib/routes/gov/zj/search.ts +17 -12
- package/lib/routes/guancha/member.ts +1 -1
- package/lib/routes/hellogithub/index.ts +17 -67
- package/lib/routes/hellogithub/report.ts +1 -1
- package/lib/routes/hex-rays/index.ts +23 -29
- package/lib/routes/hfut/hf/notice.ts +43 -0
- package/lib/routes/hfut/hf/utils.ts +80 -0
- package/lib/routes/hfut/namespace.ts +6 -0
- package/lib/routes/hfut/xc/notice.ts +43 -0
- package/lib/routes/hfut/xc/utils.ts +73 -0
- package/lib/routes/hko/earthquake.ts +56 -0
- package/lib/routes/hko/namespace.ts +8 -0
- package/lib/routes/hko/weather.ts +56 -0
- package/lib/routes/huggingface/blog-zh.ts +1 -1
- package/lib/routes/hust/mse.ts +575 -0
- package/lib/routes/i-cable/namespace.ts +6 -0
- package/lib/routes/i-cable/news.ts +77 -0
- package/lib/routes/i-cable/templates/description.art +8 -0
- package/lib/routes/infoq/presentations.ts +203 -0
- package/lib/routes/infoq/templates/description.art +41 -0
- package/lib/routes/ipsw.dev/index.ts +64 -0
- package/lib/routes/ipsw.dev/namespace.ts +7 -0
- package/lib/routes/ipsw.dev/templates/description.art +20 -0
- package/lib/routes/ixigua/user-video.ts +18 -7
- package/lib/routes/javtiful/actress.ts +37 -0
- package/lib/routes/javtiful/channel.ts +37 -0
- package/lib/routes/javtiful/namespace.ts +6 -0
- package/lib/routes/javtiful/templates/description.art +7 -0
- package/lib/routes/javtiful/utils.ts +18 -0
- package/lib/routes/kisskiss/blog.ts +74 -0
- package/lib/routes/kisskiss/namespace.ts +6 -0
- package/lib/routes/ktown4u/artist-brandlist.ts +61 -0
- package/lib/routes/ktown4u/namespace.ts +6 -0
- package/lib/routes/lorientlejour/index.ts +186 -0
- package/lib/routes/lorientlejour/namespace.ts +7 -0
- package/lib/routes/lorientlejour/templates/description.art +18 -0
- package/lib/routes/lovelive-anime/namespace.ts +1 -1
- package/lib/routes/lovelive-anime/news.ts +56 -26
- package/lib/routes/lovelive-anime/schedules.ts +18 -6
- package/lib/routes/lovelive-anime/templates/description.art +1 -1
- package/lib/routes/lovelive-anime/templates/scheduleDesc.art +0 -1
- package/lib/routes/lovelive-anime/topics.ts +1 -1
- package/lib/routes/manhuagui/comic.ts +1 -1
- package/lib/routes/mi/crowdfunding.ts +43 -22
- package/lib/routes/mi/templates/crowdfunding.art +28 -0
- package/lib/routes/mi/types.ts +40 -0
- package/lib/routes/mi/utils.ts +80 -0
- package/lib/routes/misskey/utils.ts +1 -0
- package/lib/routes/misskon/namespace.ts +6 -0
- package/lib/routes/misskon/posts.ts +33 -0
- package/lib/routes/misskon/tag.ts +38 -0
- package/lib/routes/misskon/top.ts +67 -0
- package/lib/routes/misskon/utils.ts +41 -0
- package/lib/routes/natgeo/dailyphoto.ts +1 -1
- package/lib/routes/natgeo/dailyselection.ts +1 -1
- package/lib/routes/ncku/namespace.ts +1 -1
- package/lib/routes/ncku/phys.ts +95 -0
- package/lib/routes/news/namespace.ts +1 -1
- package/lib/routes/nikkei/cn/index.ts +12 -1
- package/lib/routes/nudt/yjszs.ts +33 -13
- package/lib/routes/nytimes/book.ts +11 -11
- package/lib/routes/nytimes/daily-briefing-chinese.ts +4 -4
- package/lib/routes/nytimes/namespace.ts +1 -1
- package/lib/routes/oncc/templates/article.art +1 -1
- package/lib/routes/papers/index.ts +7 -1
- package/lib/routes/parliament.uk/commonslibrary.ts +55 -0
- package/lib/routes/parliament.uk/lordslibrary.ts +55 -0
- package/lib/routes/parliament.uk/namespace.ts +6 -0
- package/lib/routes/picuki/profile.ts +50 -39
- package/lib/routes/pornhub/model.ts +3 -5
- package/lib/routes/pornhub/pornstar.ts +3 -5
- package/lib/routes/pornhub/users.ts +3 -5
- package/lib/routes/resonac/namespace.ts +6 -0
- package/lib/routes/resonac/products.ts +85 -0
- package/lib/routes/rsshub/transform/html.ts +114 -75
- package/lib/routes/rsshub/transform/json.ts +14 -8
- package/lib/routes/sciencenet/user.ts +3 -1
- package/lib/routes/scmp/utils.ts +2 -2
- package/lib/routes/skeb/following-creators.ts +52 -0
- package/lib/routes/skeb/following-works.ts +52 -0
- package/lib/routes/skeb/friend-works.ts +52 -0
- package/lib/routes/skeb/index.ts +131 -0
- package/lib/routes/skeb/namespace.ts +6 -0
- package/lib/routes/skeb/search.ts +77 -0
- package/lib/routes/skeb/templates/creator.art +8 -0
- package/lib/routes/skeb/templates/work.art +10 -0
- package/lib/routes/skeb/utils.ts +155 -0
- package/lib/routes/skeb/works.ts +88 -0
- package/lib/routes/skebetter/illust.ts +83 -0
- package/lib/routes/skebetter/index.ts +83 -0
- package/lib/routes/skebetter/manga.ts +65 -0
- package/lib/routes/skebetter/namespace.ts +6 -0
- package/lib/routes/skebetter/utils.ts +72 -0
- package/lib/routes/spankbang/namespace.ts +6 -0
- package/lib/routes/spankbang/new-videos.ts +90 -0
- package/lib/routes/spankbang/templates/video.art +7 -0
- package/lib/routes/straitstimes/index.ts +135 -0
- package/lib/routes/straitstimes/namespace.ts +7 -0
- package/lib/routes/straitstimes/templates/description.art +27 -0
- package/lib/routes/szftedu/dongtai.ts +62 -0
- package/lib/routes/szftedu/gonggao.ts +62 -0
- package/lib/routes/szftedu/namespace.ts +6 -0
- package/lib/routes/the/index.ts +1 -1
- package/lib/routes/tkww/index.ts +83 -0
- package/lib/routes/tkww/namespace.ts +6 -0
- package/lib/routes/ttv/index.ts +1 -1
- package/lib/routes/tvb/news.ts +2 -1
- package/lib/routes/twitter/api/mobile-api/login.ts +5 -0
- package/lib/routes/twitter/api/web-api/api.ts +16 -10
- package/lib/routes/twitter/api/web-api/constants.ts +1 -1
- package/lib/routes/twitter/api/web-api/utils.ts +94 -56
- package/lib/routes/twitter/list.ts +4 -12
- package/lib/routes/twitter/media.ts +13 -4
- package/lib/routes/twitter/user.ts +15 -6
- package/lib/routes/udn/breaking-news.ts +2 -2
- package/lib/routes/uestc/gr.ts +65 -37
- package/lib/routes/uestc/jwc.ts +49 -28
- package/lib/routes/wechat/ershcimi.ts +4 -2
- package/lib/routes/weibo/user.ts +10 -2
- package/lib/routes/xaut/index.ts +13 -20
- package/lib/routes/xaut/namespace.ts +1 -1
- package/lib/routes/xbookcn/blog.ts +66 -0
- package/lib/routes/xbookcn/namespace.ts +6 -0
- package/lib/routes/xiaoyuzhou/podcast.ts +35 -7
- package/lib/routes/xueqiu/cookies.ts +5 -3
- package/lib/routes/xueqiu/stock-info.ts +6 -12
- package/lib/routes/yande/namespace.ts +1 -1
- package/lib/routes/yande/post.ts +6 -6
- package/lib/routes/zaobao/util.ts +41 -43
- package/lib/routes/zhihu/activities.ts +2 -1
- package/lib/routes/zhihu/timeline.ts +6 -1
- package/lib/routes/zhihu/utils.ts +1 -1
- package/lib/routes/zjut/cs/index.ts +105 -0
- package/lib/routes/zjut/jwc/index.ts +117 -0
- package/lib/utils/cache/redis.ts +1 -1
- package/package.json +35 -36
- package/lib/routes/a9vg/a9vg.ts +0 -45
- package/lib/routes/gov/ndrc/zfxxgk/articles.ts +0 -73
- package/lib/routes-deprecated/hko/weather.js +0 -44
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Route } from '@/types';
|
|
2
|
+
import cache from '@/utils/cache';
|
|
3
|
+
import got from '@/utils/got';
|
|
4
|
+
import { load } from 'cheerio';
|
|
5
|
+
import { parseDate } from '@/utils/parse-date';
|
|
6
|
+
import timezone from '@/utils/timezone';
|
|
7
|
+
|
|
8
|
+
const host = 'https://ylxx.szftedu.cn/xx_5828/xygg_5832/';
|
|
9
|
+
|
|
10
|
+
export const route: Route = {
|
|
11
|
+
path: '/gonggao',
|
|
12
|
+
categories: ['university'],
|
|
13
|
+
example: '/szftedu/gonggao',
|
|
14
|
+
parameters: {},
|
|
15
|
+
features: {
|
|
16
|
+
requireConfig: false,
|
|
17
|
+
requirePuppeteer: false,
|
|
18
|
+
antiCrawler: false,
|
|
19
|
+
supportBT: false,
|
|
20
|
+
supportPodcast: false,
|
|
21
|
+
supportScihub: false,
|
|
22
|
+
},
|
|
23
|
+
name: '公告',
|
|
24
|
+
maintainers: ['valuex'],
|
|
25
|
+
handler,
|
|
26
|
+
description: '',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
async function handler() {
|
|
30
|
+
const link = host;
|
|
31
|
+
const response = await got(link);
|
|
32
|
+
const $ = load(response.data);
|
|
33
|
+
|
|
34
|
+
const lists = $('div.pagenews04 div ul li')
|
|
35
|
+
.toArray()
|
|
36
|
+
.map((el) => ({
|
|
37
|
+
title: $('a', el).text().trim(),
|
|
38
|
+
link: $('a', el).attr('href'),
|
|
39
|
+
pubDate: timezone(parseDate($('span[class=canedit]', el).text()), 8),
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
const items = await Promise.all(
|
|
43
|
+
lists.map((item) =>
|
|
44
|
+
cache.tryGet(item.link, async () => {
|
|
45
|
+
const thisUrl = item.link;
|
|
46
|
+
const trueLink = thisUrl.includes('http') ? thisUrl : host + thisUrl.substring(1);
|
|
47
|
+
const response = await got(trueLink);
|
|
48
|
+
const $ = load(response.data);
|
|
49
|
+
item.description = thisUrl.includes('http') ? $('#page-content').html() : $('div.TRS_Editor').html();
|
|
50
|
+
item.pubDate = timezone(parseDate($('.item').first().text().replace('发布时间:', '')), 8);
|
|
51
|
+
return item;
|
|
52
|
+
})
|
|
53
|
+
)
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
title: '园岭小学公告',
|
|
58
|
+
link: host,
|
|
59
|
+
description: '园岭小学公告',
|
|
60
|
+
item: items,
|
|
61
|
+
};
|
|
62
|
+
}
|
package/lib/routes/the/index.ts
CHANGED
|
@@ -13,7 +13,7 @@ import timezone from '@/utils/timezone';
|
|
|
13
13
|
|
|
14
14
|
export const handler = async (ctx) => {
|
|
15
15
|
const { filter } = ctx.req.param();
|
|
16
|
-
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) :
|
|
16
|
+
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 40;
|
|
17
17
|
|
|
18
18
|
const rootUrl = 'https://the.bi/s';
|
|
19
19
|
const filters = parseFilterStr(filter);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Route } from '@/types';
|
|
2
|
+
import cache from '@/utils/cache';
|
|
3
|
+
import got from '@/utils/got';
|
|
4
|
+
import { config } from '@/config';
|
|
5
|
+
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
|
6
|
+
|
|
7
|
+
export const route: Route = {
|
|
8
|
+
path: '/:column{.+}?',
|
|
9
|
+
categories: ['traditional-media'],
|
|
10
|
+
example: '/tkww/hong_kong',
|
|
11
|
+
parameters: {
|
|
12
|
+
column: '欄目,默認為 home (首頁)',
|
|
13
|
+
},
|
|
14
|
+
features: {
|
|
15
|
+
requirePuppeteer: false,
|
|
16
|
+
antiCrawler: false,
|
|
17
|
+
supportBT: false,
|
|
18
|
+
supportPodcast: false,
|
|
19
|
+
supportScihub: false,
|
|
20
|
+
},
|
|
21
|
+
name: '新聞',
|
|
22
|
+
maintainers: ['quiniapiezoelectricity'],
|
|
23
|
+
radar: [
|
|
24
|
+
{
|
|
25
|
+
source: ['www.tkww.hk/:column'],
|
|
26
|
+
target: '/:column',
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
handler,
|
|
30
|
+
description: `
|
|
31
|
+
:::tip
|
|
32
|
+
欄目可用\`名稱\`或對應網頁的\`path\`,
|
|
33
|
+
如 \`https://www.tkww.hk/hong_kong\` 的欄目可以填\`香港\`或是\`hong_kong\`
|
|
34
|
+
而 \`https://www.tkww.hk/china/shanghai\` 的欄目則需填\`china/shanghai\`
|
|
35
|
+
:::`,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
async function handler(ctx) {
|
|
39
|
+
const column = ctx.req.param('column') ?? 'home';
|
|
40
|
+
|
|
41
|
+
const columns = await cache.tryGet('https://www.tkww.hk/columns.json', async () => await got('https://www.tkww.hk/columns.json'), config.cache.routeExpire, false);
|
|
42
|
+
|
|
43
|
+
let metadata;
|
|
44
|
+
let scope = columns.data.data;
|
|
45
|
+
for (const segment of column.split('/').filter((item) => typeof item === 'string')) {
|
|
46
|
+
metadata = scope.find((item) => item.name === segment || item.dirname === segment);
|
|
47
|
+
scope = metadata?.children ?? [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (metadata === undefined) {
|
|
51
|
+
throw new InvalidParameterError(`Invalid Column: ${column}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const stories = await got(`https://www.tkww.hk/columns/${metadata.uuid}/tkww/app/stories.json`);
|
|
55
|
+
|
|
56
|
+
const items = await Promise.all(
|
|
57
|
+
stories.data.data.stories.map((item) =>
|
|
58
|
+
cache.tryGet(item.url, async () => {
|
|
59
|
+
item.link = item.url;
|
|
60
|
+
item.description = item.summary;
|
|
61
|
+
item.pubDate = item.publishTime;
|
|
62
|
+
item.category = [];
|
|
63
|
+
if (item.keywords) {
|
|
64
|
+
item.category = [...item.category, ...item.keywords];
|
|
65
|
+
}
|
|
66
|
+
if (item.tags) {
|
|
67
|
+
item.category = [...item.category, ...item.tags];
|
|
68
|
+
}
|
|
69
|
+
item.category = [...new Set(item.category)];
|
|
70
|
+
const response = await got(item.jsonUrl);
|
|
71
|
+
item.description = response.data.data.content;
|
|
72
|
+
return item;
|
|
73
|
+
})
|
|
74
|
+
)
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
title: metadata.seoTitle,
|
|
79
|
+
description: metadata.seoDescription,
|
|
80
|
+
link: metadata.url,
|
|
81
|
+
item: items,
|
|
82
|
+
};
|
|
83
|
+
}
|
package/lib/routes/ttv/index.ts
CHANGED
|
@@ -34,7 +34,7 @@ async function handler(ctx) {
|
|
|
34
34
|
const $ = load(response.data);
|
|
35
35
|
|
|
36
36
|
let items = $('div.news-list li')
|
|
37
|
-
.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.query
|
|
37
|
+
.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 30)
|
|
38
38
|
.toArray()
|
|
39
39
|
.map((item) => {
|
|
40
40
|
item = $(item);
|
package/lib/routes/tvb/news.ts
CHANGED
|
@@ -85,6 +85,7 @@ async function handler(ctx) {
|
|
|
85
85
|
const language = ctx.req.param('language') ?? 'tc';
|
|
86
86
|
|
|
87
87
|
const rootUrl = 'https://inews-api.tvb.com';
|
|
88
|
+
const linkRootUrl = 'https://news.tvb.com';
|
|
88
89
|
const apiUrl = `${rootUrl}/news/entry/category`;
|
|
89
90
|
const currentUrl = `${rootUrl}/${language}/${category}`;
|
|
90
91
|
|
|
@@ -102,7 +103,7 @@ async function handler(ctx) {
|
|
|
102
103
|
|
|
103
104
|
const items = response.data.content.map((item) => ({
|
|
104
105
|
title: item.title,
|
|
105
|
-
link: `${
|
|
106
|
+
link: `${linkRootUrl}/${language}/${category}/${item.id}`,
|
|
106
107
|
pubDate: parseDate(item.publish_datetime),
|
|
107
108
|
category: [...item.category.map((c) => c.title), ...item.tags],
|
|
108
109
|
description: art(path.join(__dirname, 'templates/description.art'), {
|
|
@@ -133,6 +133,11 @@ async function login({ username, password, authenticationSecret }) {
|
|
|
133
133
|
},
|
|
134
134
|
});
|
|
135
135
|
logger.debug('Twitter login 4 finished: LoginEnterPassword.');
|
|
136
|
+
for (const subtask of task3.data?.subtasks || []) {
|
|
137
|
+
if (subtask.open_account) {
|
|
138
|
+
return subtask.open_account;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
136
141
|
|
|
137
142
|
const task4 = await got.post('https://api.x.com/1.1/onboarding/task.json', {
|
|
138
143
|
headers,
|
|
@@ -18,16 +18,22 @@ const getUserData = (id) =>
|
|
|
18
18
|
}),
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
|
-
return twitterGot(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
return twitterGot(
|
|
22
|
+
`${baseUrl}${gqlMap.UserByScreenName}`,
|
|
23
|
+
{
|
|
24
|
+
variables: JSON.stringify({
|
|
25
|
+
screen_name: id,
|
|
26
|
+
withSafetyModeUserFields: true,
|
|
27
|
+
}),
|
|
28
|
+
features: JSON.stringify(gqlFeatures.UserByScreenName),
|
|
29
|
+
fieldToggles: JSON.stringify({
|
|
30
|
+
withAuxiliaryUserLabels: false,
|
|
31
|
+
}),
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
allowNoAuth: true,
|
|
35
|
+
}
|
|
36
|
+
);
|
|
31
37
|
});
|
|
32
38
|
|
|
33
39
|
const cacheTryGet = async (_id, params, func) => {
|
|
@@ -7,7 +7,7 @@ const graphQLEndpointsPlain = [
|
|
|
7
7
|
'/graphql/DiTkXJgLqBBxCs7zaYsbtA/HomeLatestTimeline',
|
|
8
8
|
'/graphql/bt4TKuFz4T7Ckk-VvQVSow/UserTweetsAndReplies',
|
|
9
9
|
'/graphql/dexO_2tohK86JDudXXG3Yw/UserMedia',
|
|
10
|
-
'/graphql/
|
|
10
|
+
'/graphql/Qw77dDjp9xCpUY-AXwt-yQ/UserByRestId',
|
|
11
11
|
'/graphql/UN1i3zUiCWa-6r-Uaho4fw/SearchTimeline',
|
|
12
12
|
'/graphql/Pa45JvqZuKcW1plybfgBlQ/ListLatestTweetsTimeline',
|
|
13
13
|
'/graphql/QuBlQ6SxNAQCt6-kBiCXCQ/TweetDetail',
|
|
@@ -24,9 +24,19 @@ const token2Cookie = (token) =>
|
|
|
24
24
|
uri: proxy.proxyUri,
|
|
25
25
|
})
|
|
26
26
|
: new CookieAgent({ cookies: { jar } });
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
if (token) {
|
|
28
|
+
await ofetch('https://x.com', {
|
|
29
|
+
dispatcher: agent,
|
|
30
|
+
});
|
|
31
|
+
} else {
|
|
32
|
+
const data = await ofetch('https://x.com/narendramodi?mx=2', {
|
|
33
|
+
dispatcher: agent,
|
|
34
|
+
});
|
|
35
|
+
const gt = data.match(/document\.cookie="gt=(\d+)/)?.[1];
|
|
36
|
+
if (gt) {
|
|
37
|
+
jar.setCookieSync(`gt=${gt}`, 'https://x.com');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
30
40
|
return JSON.stringify(jar.serializeSync());
|
|
31
41
|
} catch {
|
|
32
42
|
// ignore
|
|
@@ -34,17 +44,19 @@ const token2Cookie = (token) =>
|
|
|
34
44
|
}
|
|
35
45
|
});
|
|
36
46
|
|
|
47
|
+
const lockPrefix = 'twitter:lock-token1:';
|
|
48
|
+
|
|
37
49
|
const getAuth = async (retry: number) => {
|
|
38
50
|
if (config.twitter.authToken && retry > 0) {
|
|
39
51
|
const index = authTokenIndex++ % config.twitter.authToken.length;
|
|
40
52
|
const token = config.twitter.authToken[index];
|
|
41
|
-
const lock = await cache.get(
|
|
53
|
+
const lock = await cache.get(`${lockPrefix}${token}`, false);
|
|
42
54
|
if (lock) {
|
|
43
55
|
await new Promise((resolve) => setTimeout(resolve, Math.random() * 500 + 500));
|
|
44
56
|
return await getAuth(retry - 1);
|
|
45
57
|
} else {
|
|
46
58
|
logger.debug(`twitter debug: lock twitter cookie for token ${token}`);
|
|
47
|
-
await cache.set(
|
|
59
|
+
await cache.set(`${lockPrefix}${token}`, '1', 20);
|
|
48
60
|
return {
|
|
49
61
|
token,
|
|
50
62
|
username: config.twitter.username?.[index],
|
|
@@ -55,17 +67,23 @@ const getAuth = async (retry: number) => {
|
|
|
55
67
|
}
|
|
56
68
|
};
|
|
57
69
|
|
|
58
|
-
export const twitterGot = async (
|
|
70
|
+
export const twitterGot = async (
|
|
71
|
+
url,
|
|
72
|
+
params,
|
|
73
|
+
options?: {
|
|
74
|
+
allowNoAuth?: boolean;
|
|
75
|
+
}
|
|
76
|
+
) => {
|
|
59
77
|
const auth = await getAuth(30);
|
|
60
78
|
|
|
61
|
-
if (!auth) {
|
|
79
|
+
if (!auth && !options?.allowNoAuth) {
|
|
62
80
|
throw new ConfigNotFoundError('No valid Twitter token found');
|
|
63
81
|
}
|
|
64
82
|
|
|
65
83
|
const requestUrl = `${url}?${queryString.stringify(params)}`;
|
|
66
84
|
|
|
67
|
-
let cookie: string | Record<string, any> | null | undefined = await token2Cookie(auth
|
|
68
|
-
if (!cookie) {
|
|
85
|
+
let cookie: string | Record<string, any> | null | undefined = await token2Cookie(auth?.token);
|
|
86
|
+
if (!cookie && auth) {
|
|
69
87
|
cookie = await login({
|
|
70
88
|
username: auth.username,
|
|
71
89
|
password: auth.password,
|
|
@@ -79,7 +97,7 @@ export const twitterGot = async (url, params) => {
|
|
|
79
97
|
}
|
|
80
98
|
| undefined;
|
|
81
99
|
if (cookie) {
|
|
82
|
-
logger.debug(`twitter debug: got twitter cookie for token ${auth
|
|
100
|
+
logger.debug(`twitter debug: got twitter cookie for token ${auth?.token}`);
|
|
83
101
|
if (typeof cookie === 'string') {
|
|
84
102
|
cookie = JSON.parse(cookie);
|
|
85
103
|
}
|
|
@@ -97,16 +115,18 @@ export const twitterGot = async (url, params) => {
|
|
|
97
115
|
jar,
|
|
98
116
|
agent,
|
|
99
117
|
};
|
|
100
|
-
} else {
|
|
101
|
-
throw new ConfigNotFoundError(`Twitter cookie for token ${auth
|
|
118
|
+
} else if (auth) {
|
|
119
|
+
throw new ConfigNotFoundError(`Twitter cookie for token ${auth?.token?.replace(/(\w{8})(\w+)/, (_, v1, v2) => v1 + '*'.repeat(v2.length))} is not valid`);
|
|
102
120
|
}
|
|
103
|
-
const jsonCookie =
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
121
|
+
const jsonCookie = dispatchers
|
|
122
|
+
? Object.fromEntries(
|
|
123
|
+
dispatchers.jar
|
|
124
|
+
.getCookieStringSync(url)
|
|
125
|
+
.split(';')
|
|
126
|
+
.map((c) => Cookie.parse(c)?.toJSON())
|
|
127
|
+
.map((c) => [c?.key, c?.value])
|
|
128
|
+
)
|
|
129
|
+
: {};
|
|
110
130
|
|
|
111
131
|
const response = await ofetch.raw(requestUrl, {
|
|
112
132
|
retry: 0,
|
|
@@ -119,57 +139,75 @@ export const twitterGot = async (url, params) => {
|
|
|
119
139
|
'content-type': 'application/json',
|
|
120
140
|
dnt: '1',
|
|
121
141
|
pragma: 'no-cache',
|
|
122
|
-
referer: 'https://x.com/
|
|
142
|
+
referer: 'https://x.com/',
|
|
123
143
|
'x-twitter-active-user': 'yes',
|
|
124
|
-
'x-twitter-auth-type': 'OAuth2Session',
|
|
125
144
|
'x-twitter-client-language': 'en',
|
|
126
145
|
'x-csrf-token': jsonCookie.ct0,
|
|
146
|
+
...(auth?.token
|
|
147
|
+
? {
|
|
148
|
+
'x-twitter-auth-type': 'OAuth2Session',
|
|
149
|
+
}
|
|
150
|
+
: {
|
|
151
|
+
'x-guest-token': jsonCookie.gt,
|
|
152
|
+
}),
|
|
127
153
|
},
|
|
128
|
-
dispatcher: dispatchers
|
|
154
|
+
dispatcher: dispatchers?.agent,
|
|
129
155
|
onResponse: async ({ response }) => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
156
|
+
const remaining = response.headers.get('x-rate-limit-remaining');
|
|
157
|
+
const remainingInt = Number.parseInt(remaining || '0');
|
|
158
|
+
const reset = response.headers.get('x-rate-limit-reset');
|
|
159
|
+
logger.debug(`twitter debug: twitter rate limit remaining for token ${auth?.token} is ${remaining} and reset at ${reset}`);
|
|
160
|
+
if (auth) {
|
|
161
|
+
if (remaining && remainingInt < 2 && reset) {
|
|
162
|
+
const resetTime = new Date(Number.parseInt(reset) * 1000);
|
|
163
|
+
const delay = (resetTime.getTime() - Date.now()) / 1000;
|
|
164
|
+
logger.debug(`twitter debug: twitter rate limit exceeded for token ${auth.token} with status ${response.status}, will unlock after ${delay}s`);
|
|
165
|
+
await cache.set(`${lockPrefix}${auth.token}`, '1', Math.ceil(delay));
|
|
166
|
+
} else if (response.status === 429 || JSON.stringify(response._data?.data) === '{"user":{}}') {
|
|
167
|
+
logger.debug(`twitter debug: twitter rate limit exceeded for token ${auth.token} with status ${response.status}`);
|
|
168
|
+
await cache.set(`${lockPrefix}${auth.token}`, '1', 2000);
|
|
169
|
+
} else if (response.status === 403 || response.status === 401) {
|
|
170
|
+
const newCookie = await login({
|
|
171
|
+
username: auth.username,
|
|
172
|
+
password: auth.password,
|
|
173
|
+
authenticationSecret: auth.authenticationSecret,
|
|
174
|
+
});
|
|
175
|
+
if (newCookie) {
|
|
176
|
+
logger.debug(`twitter debug: reset twitter cookie for token ${auth.token}, ${newCookie}`);
|
|
177
|
+
await cache.set(`twitter:cookie:${auth.token}`, newCookie, config.cache.contentExpire);
|
|
178
|
+
logger.debug(`twitter debug: unlock twitter cookie for token ${auth.token} with error1`);
|
|
179
|
+
await cache.set(`${lockPrefix}${auth.token}`, '', 1);
|
|
180
|
+
} else {
|
|
181
|
+
const tokenIndex = config.twitter.authToken?.indexOf(auth.token);
|
|
182
|
+
if (tokenIndex !== undefined && tokenIndex !== -1) {
|
|
183
|
+
config.twitter.authToken?.splice(tokenIndex, 1);
|
|
153
184
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
185
|
+
if (auth.username) {
|
|
186
|
+
const usernameIndex = config.twitter.username?.indexOf(auth.username);
|
|
187
|
+
if (usernameIndex !== undefined && usernameIndex !== -1) {
|
|
188
|
+
config.twitter.username?.splice(usernameIndex, 1);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (auth.password) {
|
|
192
|
+
const passwordIndex = config.twitter.password?.indexOf(auth.password);
|
|
193
|
+
if (passwordIndex !== undefined && passwordIndex !== -1) {
|
|
194
|
+
config.twitter.password?.splice(passwordIndex, 1);
|
|
195
|
+
}
|
|
159
196
|
}
|
|
197
|
+
logger.debug(`twitter debug: delete twitter cookie for token ${auth.token} with status ${response.status}, remaining tokens: ${config.twitter.authToken?.length}`);
|
|
198
|
+
await cache.set(`${lockPrefix}${auth.token}`, '1', 86400);
|
|
160
199
|
}
|
|
161
|
-
|
|
162
|
-
|
|
200
|
+
} else {
|
|
201
|
+
logger.debug(`twitter debug: unlock twitter cookie with success for token ${auth.token}`);
|
|
202
|
+
await cache.set(`${lockPrefix}${auth.token}`, '', 1);
|
|
163
203
|
}
|
|
164
204
|
}
|
|
165
205
|
},
|
|
166
206
|
});
|
|
167
207
|
|
|
168
|
-
if (auth
|
|
208
|
+
if (auth?.token) {
|
|
169
209
|
logger.debug(`twitter debug: update twitter cookie for token ${auth.token}`);
|
|
170
|
-
await cache.set(`twitter:cookie:${auth.token}`, JSON.stringify(dispatchers
|
|
171
|
-
logger.debug(`twitter debug: unlock twitter cookie with success for token ${auth.token}`);
|
|
172
|
-
await cache.set(`twitter:lock-token:${auth.token}`, '', 1);
|
|
210
|
+
await cache.set(`twitter:cookie:${auth.token}`, JSON.stringify(dispatchers?.jar.serializeSync()), config.cache.contentExpire);
|
|
173
211
|
}
|
|
174
212
|
|
|
175
213
|
return response._data;
|
|
@@ -4,19 +4,11 @@ import utils from './utils';
|
|
|
4
4
|
|
|
5
5
|
export const route: Route = {
|
|
6
6
|
path: '/list/:id/:routeParams?',
|
|
7
|
-
categories: ['social-media'],
|
|
8
|
-
example: '/twitter/list/
|
|
9
|
-
parameters: { id: '
|
|
7
|
+
categories: ['social-media', 'popular'],
|
|
8
|
+
example: '/twitter/list/1502570462752219136',
|
|
9
|
+
parameters: { id: 'list id, get from url', routeParams: 'extra parameters, see the table above' },
|
|
10
10
|
features: {
|
|
11
11
|
requireConfig: [
|
|
12
|
-
{
|
|
13
|
-
name: 'TWITTER_USERNAME',
|
|
14
|
-
description: 'Please see above for details.',
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
name: 'TWITTER_PASSWORD',
|
|
18
|
-
description: 'Please see above for details.',
|
|
19
|
-
},
|
|
20
12
|
{
|
|
21
13
|
name: 'TWITTER_AUTH_TOKEN',
|
|
22
14
|
description: 'Please see above for details.',
|
|
@@ -29,7 +21,7 @@ export const route: Route = {
|
|
|
29
21
|
supportScihub: false,
|
|
30
22
|
},
|
|
31
23
|
name: 'List timeline',
|
|
32
|
-
maintainers: ['DIYgod', 'xyqfer'],
|
|
24
|
+
maintainers: ['DIYgod', 'xyqfer', 'pseudoyu'],
|
|
33
25
|
handler,
|
|
34
26
|
radar: [
|
|
35
27
|
{
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Route, ViewType } from '@/types';
|
|
2
2
|
import api from './api';
|
|
3
3
|
import utils from './utils';
|
|
4
|
+
import logger from '@/utils/logger';
|
|
4
5
|
|
|
5
6
|
export const route: Route = {
|
|
6
7
|
path: '/media/:id/:routeParams?',
|
|
@@ -47,7 +48,12 @@ async function handler(ctx) {
|
|
|
47
48
|
|
|
48
49
|
await api.init();
|
|
49
50
|
const userInfo = await api.getUser(id);
|
|
50
|
-
|
|
51
|
+
let data;
|
|
52
|
+
try {
|
|
53
|
+
data = await api.getUserMedia(id, params);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
logger.error(error);
|
|
56
|
+
}
|
|
51
57
|
const profileImageUrl = userInfo?.profile_image_url || userInfo?.profile_image_url_https;
|
|
52
58
|
|
|
53
59
|
return {
|
|
@@ -55,8 +61,11 @@ async function handler(ctx) {
|
|
|
55
61
|
link: `https://x.com/${userInfo?.screen_name}/media`,
|
|
56
62
|
image: profileImageUrl.replace(/_normal.jpg$/, '.jpg'),
|
|
57
63
|
description: userInfo?.description,
|
|
58
|
-
item:
|
|
59
|
-
data
|
|
60
|
-
|
|
64
|
+
item:
|
|
65
|
+
data &&
|
|
66
|
+
utils.ProcessFeed(ctx, {
|
|
67
|
+
data,
|
|
68
|
+
}),
|
|
69
|
+
allowEmpty: true,
|
|
61
70
|
};
|
|
62
71
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Route, ViewType } from '@/types';
|
|
2
2
|
import utils from './utils';
|
|
3
3
|
import api from './api';
|
|
4
|
+
import logger from '@/utils/logger';
|
|
4
5
|
|
|
5
6
|
export const route: Route = {
|
|
6
7
|
path: '/user/:id/:routeParams?',
|
|
@@ -58,9 +59,14 @@ async function handler(ctx) {
|
|
|
58
59
|
|
|
59
60
|
await api.init();
|
|
60
61
|
const userInfo = await api.getUser(id);
|
|
61
|
-
let data
|
|
62
|
-
|
|
63
|
-
data =
|
|
62
|
+
let data;
|
|
63
|
+
try {
|
|
64
|
+
data = await (exclude_replies ? api.getUserTweets(id, params) : api.getUserTweetsAndReplies(id, params));
|
|
65
|
+
if (!include_rts) {
|
|
66
|
+
data = utils.excludeRetweet(data);
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
logger.error(error);
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
const profileImageUrl = userInfo?.profile_image_url || userInfo?.profile_image_url_https;
|
|
@@ -70,8 +76,11 @@ async function handler(ctx) {
|
|
|
70
76
|
link: `https://x.com/${userInfo?.screen_name}`,
|
|
71
77
|
image: profileImageUrl.replace(/_normal.jpg$/, '.jpg'),
|
|
72
78
|
description: userInfo?.description,
|
|
73
|
-
item:
|
|
74
|
-
data
|
|
75
|
-
|
|
79
|
+
item:
|
|
80
|
+
data &&
|
|
81
|
+
utils.ProcessFeed(ctx, {
|
|
82
|
+
data,
|
|
83
|
+
}),
|
|
84
|
+
allowEmpty: true,
|
|
76
85
|
};
|
|
77
86
|
}
|
|
@@ -87,11 +87,11 @@ async function handler(ctx) {
|
|
|
87
87
|
|
|
88
88
|
if (data.publisher.name === '轉角國際 udn Global') {
|
|
89
89
|
// 轉角24小時
|
|
90
|
-
description
|
|
90
|
+
description = $('.story_body_content')
|
|
91
91
|
.html()
|
|
92
92
|
.split(/<!--\d+?-->/g)
|
|
93
93
|
.slice(1, -1)
|
|
94
|
-
.join('
|
|
94
|
+
.join('');
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
return {
|