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.
Files changed (222) hide show
  1. package/lib/config.ts +18 -0
  2. package/lib/middleware/template.tsx +10 -2
  3. package/lib/routes/163/music/playlist.ts +2 -1
  4. package/lib/routes/5eplay/index.ts +1 -51
  5. package/lib/routes/a9vg/index.ts +214 -0
  6. package/lib/routes/a9vg/namespace.ts +1 -0
  7. package/lib/routes/a9vg/templates/description.art +17 -0
  8. package/lib/routes/aibase/discover.ts +388 -0
  9. package/lib/routes/aibase/namespace.ts +8 -0
  10. package/lib/routes/aibase/news.ts +118 -0
  11. package/lib/routes/aibase/templates/description.art +100 -0
  12. package/lib/routes/aibase/topic.ts +614 -0
  13. package/lib/routes/aibase/util.ts +114 -0
  14. package/lib/routes/apnews/api.ts +18 -3
  15. package/lib/routes/apnews/rss.ts +1 -1
  16. package/lib/routes/apnews/topics.ts +4 -3
  17. package/lib/routes/apple/podcast.ts +19 -15
  18. package/lib/routes/bangumi/templates/tv/subject.art +6 -0
  19. package/lib/routes/bangumi/tv/other/followrank.ts +19 -22
  20. package/lib/routes/bangumi/tv/user/collections.ts +172 -0
  21. package/lib/routes/bilibili/cache.ts +16 -8
  22. package/lib/routes/bilibili/utils.ts +85 -3
  23. package/lib/routes/bilibili/video.ts +7 -9
  24. package/lib/routes/bilibili/vsearch.ts +1 -1
  25. package/lib/routes/bjnews/cat.ts +9 -1
  26. package/lib/routes/cbpanet/index.ts +380 -0
  27. package/lib/routes/cbpanet/namespace.ts +8 -0
  28. package/lib/routes/ceph/blog.ts +72 -0
  29. package/lib/routes/ceph/namespace.ts +7 -0
  30. package/lib/routes/chinanews/index.ts +1 -1
  31. package/lib/routes/chinaventure/index.ts +1 -1
  32. package/lib/routes/cisia/index.ts +264 -0
  33. package/lib/routes/cisia/namespace.ts +8 -0
  34. package/lib/routes/cjlu/namespace.ts +10 -0
  35. package/lib/routes/cjlu/yjsy/index.ts +107 -0
  36. package/lib/routes/cnki/journals.ts +31 -2
  37. package/lib/routes/cohere/index.ts +54 -0
  38. package/lib/routes/cohere/namespace.ts +6 -0
  39. package/lib/routes/coolapk/dyh.ts +7 -1
  40. package/lib/routes/coolapk/hot.ts +7 -1
  41. package/lib/routes/coolapk/huati.ts +7 -1
  42. package/lib/routes/coolapk/namespace.ts +6 -0
  43. package/lib/routes/coolapk/toutiao.ts +7 -1
  44. package/lib/routes/coolapk/tuwen.ts +10 -5
  45. package/lib/routes/coolapk/user-dynamic.ts +7 -1
  46. package/lib/routes/damai/activity.ts +3 -2
  47. package/lib/routes/dcfever/trading.ts +3 -5
  48. package/lib/routes/dcfever/utils.ts +13 -4
  49. package/lib/routes/dealstreetasia/home.ts +72 -0
  50. package/lib/routes/dealstreetasia/namespace.ts +6 -0
  51. package/lib/routes/dealstreetasia/section.ts +57 -0
  52. package/lib/routes/dlsite/campaign.ts +1 -2
  53. package/lib/routes/dlsite/new.ts +1 -2
  54. package/lib/routes/dlsite/{index.ts → z-index/index.ts} +1 -1
  55. package/lib/routes/douban/other/topic.ts +10 -4
  56. package/lib/routes/douyin/types.ts +795 -0
  57. package/lib/routes/douyin/user.ts +51 -23
  58. package/lib/routes/douyin/utils.ts +1 -87
  59. package/lib/routes/dribbble/keyword.ts +1 -1
  60. package/lib/routes/dribbble/popular.ts +1 -1
  61. package/lib/routes/dribbble/user.ts +1 -1
  62. package/lib/routes/dribbble/utils.ts +16 -18
  63. package/lib/routes/famitsu/category.ts +1 -1
  64. package/lib/routes/fanbox/index.ts +1 -1
  65. package/lib/routes/fanbox/types.ts +1 -5
  66. package/lib/routes/fediverse/timeline.ts +21 -3
  67. package/lib/routes/follow/profile.ts +4 -2
  68. package/lib/routes/follow/types.ts +11 -1
  69. package/lib/routes/github/advisor.ts +97 -0
  70. package/lib/routes/github/discussions.ts +194 -0
  71. package/lib/routes/github/issue.ts +1 -1
  72. package/lib/routes/github/pulls.ts +1 -1
  73. package/lib/routes/gmcmonline/chinacustoms.ts +130 -0
  74. package/lib/routes/gmcmonline/namespace.ts +8 -0
  75. package/lib/routes/gov/csrc/csrc.ts +541 -0
  76. package/lib/routes/gov/customs/list.ts +5 -2
  77. package/lib/routes/gov/customs/namespace.ts +7 -0
  78. package/lib/routes/gov/jgjcndrc/index.ts +95 -66
  79. package/lib/routes/gov/moa/moa.ts +3 -3
  80. package/lib/routes/gov/moa/szcpxx.ts +115 -0
  81. package/lib/routes/gov/moa/zdscxx.ts +71 -21
  82. package/lib/routes/gov/ndrc/zfxxgk.ts +106 -0
  83. package/lib/routes/gov/pudong/zwgk.ts +65 -0
  84. package/lib/routes/gov/zj/search.ts +17 -12
  85. package/lib/routes/guancha/member.ts +1 -1
  86. package/lib/routes/hellogithub/index.ts +17 -67
  87. package/lib/routes/hellogithub/report.ts +1 -1
  88. package/lib/routes/hex-rays/index.ts +23 -29
  89. package/lib/routes/hfut/hf/notice.ts +43 -0
  90. package/lib/routes/hfut/hf/utils.ts +80 -0
  91. package/lib/routes/hfut/namespace.ts +6 -0
  92. package/lib/routes/hfut/xc/notice.ts +43 -0
  93. package/lib/routes/hfut/xc/utils.ts +73 -0
  94. package/lib/routes/hko/earthquake.ts +56 -0
  95. package/lib/routes/hko/namespace.ts +8 -0
  96. package/lib/routes/hko/weather.ts +56 -0
  97. package/lib/routes/huggingface/blog-zh.ts +1 -1
  98. package/lib/routes/hust/mse.ts +575 -0
  99. package/lib/routes/i-cable/namespace.ts +6 -0
  100. package/lib/routes/i-cable/news.ts +77 -0
  101. package/lib/routes/i-cable/templates/description.art +8 -0
  102. package/lib/routes/infoq/presentations.ts +203 -0
  103. package/lib/routes/infoq/templates/description.art +41 -0
  104. package/lib/routes/ipsw.dev/index.ts +64 -0
  105. package/lib/routes/ipsw.dev/namespace.ts +7 -0
  106. package/lib/routes/ipsw.dev/templates/description.art +20 -0
  107. package/lib/routes/ixigua/user-video.ts +18 -7
  108. package/lib/routes/javtiful/actress.ts +37 -0
  109. package/lib/routes/javtiful/channel.ts +37 -0
  110. package/lib/routes/javtiful/namespace.ts +6 -0
  111. package/lib/routes/javtiful/templates/description.art +7 -0
  112. package/lib/routes/javtiful/utils.ts +18 -0
  113. package/lib/routes/kisskiss/blog.ts +74 -0
  114. package/lib/routes/kisskiss/namespace.ts +6 -0
  115. package/lib/routes/ktown4u/artist-brandlist.ts +61 -0
  116. package/lib/routes/ktown4u/namespace.ts +6 -0
  117. package/lib/routes/lorientlejour/index.ts +186 -0
  118. package/lib/routes/lorientlejour/namespace.ts +7 -0
  119. package/lib/routes/lorientlejour/templates/description.art +18 -0
  120. package/lib/routes/lovelive-anime/namespace.ts +1 -1
  121. package/lib/routes/lovelive-anime/news.ts +56 -26
  122. package/lib/routes/lovelive-anime/schedules.ts +18 -6
  123. package/lib/routes/lovelive-anime/templates/description.art +1 -1
  124. package/lib/routes/lovelive-anime/templates/scheduleDesc.art +0 -1
  125. package/lib/routes/lovelive-anime/topics.ts +1 -1
  126. package/lib/routes/manhuagui/comic.ts +1 -1
  127. package/lib/routes/mi/crowdfunding.ts +43 -22
  128. package/lib/routes/mi/templates/crowdfunding.art +28 -0
  129. package/lib/routes/mi/types.ts +40 -0
  130. package/lib/routes/mi/utils.ts +80 -0
  131. package/lib/routes/misskey/utils.ts +1 -0
  132. package/lib/routes/misskon/namespace.ts +6 -0
  133. package/lib/routes/misskon/posts.ts +33 -0
  134. package/lib/routes/misskon/tag.ts +38 -0
  135. package/lib/routes/misskon/top.ts +67 -0
  136. package/lib/routes/misskon/utils.ts +41 -0
  137. package/lib/routes/natgeo/dailyphoto.ts +1 -1
  138. package/lib/routes/natgeo/dailyselection.ts +1 -1
  139. package/lib/routes/ncku/namespace.ts +1 -1
  140. package/lib/routes/ncku/phys.ts +95 -0
  141. package/lib/routes/news/namespace.ts +1 -1
  142. package/lib/routes/nikkei/cn/index.ts +12 -1
  143. package/lib/routes/nudt/yjszs.ts +33 -13
  144. package/lib/routes/nytimes/book.ts +11 -11
  145. package/lib/routes/nytimes/daily-briefing-chinese.ts +4 -4
  146. package/lib/routes/nytimes/namespace.ts +1 -1
  147. package/lib/routes/oncc/templates/article.art +1 -1
  148. package/lib/routes/papers/index.ts +7 -1
  149. package/lib/routes/parliament.uk/commonslibrary.ts +55 -0
  150. package/lib/routes/parliament.uk/lordslibrary.ts +55 -0
  151. package/lib/routes/parliament.uk/namespace.ts +6 -0
  152. package/lib/routes/picuki/profile.ts +50 -39
  153. package/lib/routes/pornhub/model.ts +3 -5
  154. package/lib/routes/pornhub/pornstar.ts +3 -5
  155. package/lib/routes/pornhub/users.ts +3 -5
  156. package/lib/routes/resonac/namespace.ts +6 -0
  157. package/lib/routes/resonac/products.ts +85 -0
  158. package/lib/routes/rsshub/transform/html.ts +114 -75
  159. package/lib/routes/rsshub/transform/json.ts +14 -8
  160. package/lib/routes/sciencenet/user.ts +3 -1
  161. package/lib/routes/scmp/utils.ts +2 -2
  162. package/lib/routes/skeb/following-creators.ts +52 -0
  163. package/lib/routes/skeb/following-works.ts +52 -0
  164. package/lib/routes/skeb/friend-works.ts +52 -0
  165. package/lib/routes/skeb/index.ts +131 -0
  166. package/lib/routes/skeb/namespace.ts +6 -0
  167. package/lib/routes/skeb/search.ts +77 -0
  168. package/lib/routes/skeb/templates/creator.art +8 -0
  169. package/lib/routes/skeb/templates/work.art +10 -0
  170. package/lib/routes/skeb/utils.ts +155 -0
  171. package/lib/routes/skeb/works.ts +88 -0
  172. package/lib/routes/skebetter/illust.ts +83 -0
  173. package/lib/routes/skebetter/index.ts +83 -0
  174. package/lib/routes/skebetter/manga.ts +65 -0
  175. package/lib/routes/skebetter/namespace.ts +6 -0
  176. package/lib/routes/skebetter/utils.ts +72 -0
  177. package/lib/routes/spankbang/namespace.ts +6 -0
  178. package/lib/routes/spankbang/new-videos.ts +90 -0
  179. package/lib/routes/spankbang/templates/video.art +7 -0
  180. package/lib/routes/straitstimes/index.ts +135 -0
  181. package/lib/routes/straitstimes/namespace.ts +7 -0
  182. package/lib/routes/straitstimes/templates/description.art +27 -0
  183. package/lib/routes/szftedu/dongtai.ts +62 -0
  184. package/lib/routes/szftedu/gonggao.ts +62 -0
  185. package/lib/routes/szftedu/namespace.ts +6 -0
  186. package/lib/routes/the/index.ts +1 -1
  187. package/lib/routes/tkww/index.ts +83 -0
  188. package/lib/routes/tkww/namespace.ts +6 -0
  189. package/lib/routes/ttv/index.ts +1 -1
  190. package/lib/routes/tvb/news.ts +2 -1
  191. package/lib/routes/twitter/api/mobile-api/login.ts +5 -0
  192. package/lib/routes/twitter/api/web-api/api.ts +16 -10
  193. package/lib/routes/twitter/api/web-api/constants.ts +1 -1
  194. package/lib/routes/twitter/api/web-api/utils.ts +94 -56
  195. package/lib/routes/twitter/list.ts +4 -12
  196. package/lib/routes/twitter/media.ts +13 -4
  197. package/lib/routes/twitter/user.ts +15 -6
  198. package/lib/routes/udn/breaking-news.ts +2 -2
  199. package/lib/routes/uestc/gr.ts +65 -37
  200. package/lib/routes/uestc/jwc.ts +49 -28
  201. package/lib/routes/wechat/ershcimi.ts +4 -2
  202. package/lib/routes/weibo/user.ts +10 -2
  203. package/lib/routes/xaut/index.ts +13 -20
  204. package/lib/routes/xaut/namespace.ts +1 -1
  205. package/lib/routes/xbookcn/blog.ts +66 -0
  206. package/lib/routes/xbookcn/namespace.ts +6 -0
  207. package/lib/routes/xiaoyuzhou/podcast.ts +35 -7
  208. package/lib/routes/xueqiu/cookies.ts +5 -3
  209. package/lib/routes/xueqiu/stock-info.ts +6 -12
  210. package/lib/routes/yande/namespace.ts +1 -1
  211. package/lib/routes/yande/post.ts +6 -6
  212. package/lib/routes/zaobao/util.ts +41 -43
  213. package/lib/routes/zhihu/activities.ts +2 -1
  214. package/lib/routes/zhihu/timeline.ts +6 -1
  215. package/lib/routes/zhihu/utils.ts +1 -1
  216. package/lib/routes/zjut/cs/index.ts +105 -0
  217. package/lib/routes/zjut/jwc/index.ts +117 -0
  218. package/lib/utils/cache/redis.ts +1 -1
  219. package/package.json +35 -36
  220. package/lib/routes/a9vg/a9vg.ts +0 -45
  221. package/lib/routes/gov/ndrc/zfxxgk/articles.ts +0 -73
  222. 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
+ }
@@ -0,0 +1,6 @@
1
+ import type { Namespace } from '@/types';
2
+
3
+ export const namespace: Namespace = {
4
+ name: '园岭小学',
5
+ url: 'ylxx.szftedu.cn',
6
+ };
@@ -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) : 50;
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
+ }
@@ -0,0 +1,6 @@
1
+ import type { Namespace } from '@/types';
2
+
3
+ export const namespace: Namespace = {
4
+ name: '大公文匯網',
5
+ url: 'www.tkww.hk',
6
+ };
@@ -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.limit) : 30)
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);
@@ -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: `${rootUrl}/${language}/${category}/${item.id}`,
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(`${baseUrl}${gqlMap.UserByScreenName}`, {
22
- variables: JSON.stringify({
23
- screen_name: id,
24
- withSafetyModeUserFields: true,
25
- }),
26
- features: JSON.stringify(gqlFeatures.UserByScreenName),
27
- fieldToggles: JSON.stringify({
28
- withAuxiliaryUserLabels: false,
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/tD8zKvQzwY3kdx5yz6YmOw/UserByRestId',
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
- await ofetch('https://x.com', {
28
- dispatcher: agent,
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(`twitter:lock-token:${token}`);
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(`twitter:lock-token:${token}`, '1', 20);
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 (url, params) => {
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.token);
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.token}`);
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.token} is not valid`);
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 = Object.fromEntries(
104
- dispatchers.jar
105
- .getCookieStringSync(url)
106
- .split(';')
107
- .map((c) => Cookie.parse(c)?.toJSON())
108
- .map((c) => [c?.key, c?.value])
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/narendramodi',
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.agent,
154
+ dispatcher: dispatchers?.agent,
129
155
  onResponse: async ({ response }) => {
130
- if (response.status === 429) {
131
- logger.debug(`twitter debug: twitter rate limit exceeded for token ${auth.token}`);
132
- await cache.set(`twitter:lock-token:${auth.token}`, '1', 1000);
133
- } else if (response.status === 403 || response.status === 401 || JSON.stringify(response._data?.data) === '{"user":{}}') {
134
- const newCookie = await login({
135
- username: auth.username,
136
- password: auth.password,
137
- authenticationSecret: auth.authenticationSecret,
138
- });
139
- if (newCookie) {
140
- logger.debug(`twitter debug: reset twitter cookie for token ${auth.token}, ${newCookie}`);
141
- await cache.set(`twitter:cookie:${auth.token}`, newCookie, config.cache.contentExpire);
142
- logger.debug(`twitter debug: unlock twitter cookie with error1 for token ${auth.token}`);
143
- await cache.set(`twitter:lock-token:${auth.token}`, '', 1);
144
- } else {
145
- const tokenIndex = config.twitter.authToken?.indexOf(auth.token);
146
- if (tokenIndex !== undefined && tokenIndex !== -1) {
147
- config.twitter.authToken?.splice(tokenIndex, 1);
148
- }
149
- if (auth.username) {
150
- const usernameIndex = config.twitter.username?.indexOf(auth.username);
151
- if (usernameIndex !== undefined && usernameIndex !== -1) {
152
- config.twitter.username?.splice(usernameIndex, 1);
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
- if (auth.password) {
156
- const passwordIndex = config.twitter.password?.indexOf(auth.password);
157
- if (passwordIndex !== undefined && passwordIndex !== -1) {
158
- config.twitter.password?.splice(passwordIndex, 1);
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
- logger.debug(`twitter debug: delete twitter cookie for token ${auth.token}, remaining tokens: ${config.twitter.authToken?.length}`);
162
- await cache.set(`twitter:lock-token:${auth.token}`, '1', 86400);
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.token) {
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.jar.serializeSync()), config.cache.contentExpire);
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/ladyleet/javascript',
9
- parameters: { id: 'username', name: 'list name', routeParams: 'extra parameters, see the table above' },
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
- const data = await api.getUserMedia(id, params);
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: utils.ProcessFeed(ctx, {
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 = await (exclude_replies ? api.getUserTweets(id, params) : api.getUserTweetsAndReplies(id, params));
62
- if (!include_rts) {
63
- data = utils.excludeRetweet(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: utils.ProcessFeed(ctx, {
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 += $('.story_body_content')
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 {