rsshub 1.0.0-master.f7cdf8b → 1.0.0-master.f7f8b7a

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 (273) hide show
  1. package/README.md +3 -3
  2. package/lib/config.js +21 -8
  3. package/lib/middleware/cache/index.js +4 -3
  4. package/lib/middleware/cache/redis.js +3 -3
  5. package/lib/middleware/onerror.js +8 -0
  6. package/lib/middleware/template.js +79 -85
  7. package/lib/router.js +7 -7
  8. package/lib/routes/index.js +6 -13
  9. package/lib/utils/common-utils.js +2 -6
  10. package/lib/utils/render.js +2 -0
  11. package/lib/v2/1point3acres/maintainer.js +2 -2
  12. package/lib/v2/95mm/utils.js +3 -2
  13. package/lib/v2/aliyun/notice.js +1 -1
  14. package/lib/v2/amazon/maintainer.js +1 -1
  15. package/lib/v2/apnews/topics.js +1 -1
  16. package/lib/v2/arcteryx/maintainer.js +3 -3
  17. package/lib/v2/bangumi/maintainer.js +1 -0
  18. package/lib/v2/bangumi/radar.js +6 -0
  19. package/lib/v2/bangumi/router.js +1 -0
  20. package/lib/v2/bangumi/tv/user/wish.js +38 -0
  21. package/lib/v2/bellroy/maintainer.js +1 -1
  22. package/lib/v2/bilibili/cache.js +10 -7
  23. package/lib/v2/bilibili/maintainer.js +1 -0
  24. package/lib/v2/bilibili/platform.js +43 -0
  25. package/lib/v2/bilibili/radar.js +8 -0
  26. package/lib/v2/bilibili/router.js +1 -0
  27. package/lib/v2/bilibili/video.js +8 -1
  28. package/lib/v2/bossdesign/index.js +41 -0
  29. package/lib/v2/bossdesign/maintainer.js +3 -0
  30. package/lib/v2/bossdesign/radar.js +13 -0
  31. package/lib/v2/bossdesign/router.js +3 -0
  32. package/lib/v2/brave/latest.js +4 -2
  33. package/lib/v2/buaa/maintainer.js +4 -0
  34. package/lib/v2/buaa/news/index.js +46 -0
  35. package/lib/v2/buaa/radar.js +21 -0
  36. package/lib/v2/buaa/router.js +4 -0
  37. package/lib/v2/buaa/sme.js +59 -0
  38. package/lib/v2/cfachina/analygarden.js +60 -0
  39. package/lib/v2/cfachina/maintainer.js +3 -0
  40. package/lib/v2/cfachina/radar.js +13 -0
  41. package/lib/v2/cfachina/router.js +3 -0
  42. package/lib/v2/cna/maintainer.js +1 -0
  43. package/lib/v2/cna/radar.js +6 -0
  44. package/lib/v2/cna/router.js +1 -0
  45. package/lib/v2/cna/web/index.js +62 -0
  46. package/lib/v2/cste/radar.js +1 -1
  47. package/lib/v2/dahecube/radar.js +40 -4
  48. package/lib/v2/dongqiudi/utils.js +1 -1
  49. package/lib/v2/douban/maintainer.js +1 -0
  50. package/lib/v2/douban/other/discussion.js +58 -0
  51. package/lib/v2/douban/radar.js +8 -0
  52. package/lib/v2/douban/router.js +1 -0
  53. package/lib/v2/dut/defaults.js +4 -0
  54. package/lib/v2/dut/index.js +21 -9
  55. package/lib/v2/dut/maintainer.js +8 -7
  56. package/lib/v2/dut/radar.js +37 -29
  57. package/lib/v2/dut/shortcuts.js +5 -0
  58. package/lib/v2/egsea/flash.js +30 -0
  59. package/lib/v2/egsea/maintainer.js +3 -0
  60. package/lib/v2/egsea/radar.js +13 -0
  61. package/lib/v2/egsea/router.js +3 -0
  62. package/lib/v2/fisher-spb/news.js +1 -2
  63. package/lib/v2/fosshub/radar.js +1 -1
  64. package/lib/v2/fxiaoke/crm.js +63 -0
  65. package/lib/v2/fxiaoke/maintainer.js +3 -0
  66. package/lib/v2/fxiaoke/radar.js +37 -0
  67. package/lib/v2/fxiaoke/router.js +3 -0
  68. package/lib/v2/gdsrx/index.js +72 -0
  69. package/lib/v2/gdsrx/maintainer.js +3 -0
  70. package/lib/v2/gdsrx/radar.js +61 -0
  71. package/lib/v2/gdsrx/router.js +3 -0
  72. package/lib/v2/gogoanimehd/radar.js +1 -1
  73. package/lib/v2/gogoanimehd/recent-releases.js +1 -1
  74. package/lib/v2/google/alerts.js +33 -0
  75. package/lib/v2/google/maintainer.js +1 -0
  76. package/lib/v2/google/router.js +1 -0
  77. package/lib/v2/gov/maintainer.js +9 -2
  78. package/lib/v2/gov/moa/zdscxx.js +104 -0
  79. package/lib/v2/gov/mot/index.js +66 -0
  80. package/lib/v2/gov/nea/ghs.js +58 -0
  81. package/lib/v2/gov/nifdc/index.js +40 -33
  82. package/lib/v2/gov/radar.js +232 -4
  83. package/lib/v2/gov/router.js +10 -2
  84. package/lib/v2/gov/safe/business.js +8 -0
  85. package/lib/v2/gov/safe/complaint.js +8 -0
  86. package/lib/v2/gov/safe/templates/message.art +25 -0
  87. package/lib/v2/gov/safe/util.js +87 -0
  88. package/lib/v2/gov/zhengce/index.js +99 -0
  89. package/lib/v2/greasyfork/maintainer.js +1 -0
  90. package/lib/v2/greasyfork/router.js +1 -0
  91. package/lib/v2/greasyfork/scripts.js +25 -20
  92. package/lib/{routes/universities → v2}/harvard/health/blog.js +8 -7
  93. package/lib/v2/harvard/maintainer.js +3 -0
  94. package/lib/v2/harvard/radar.js +13 -0
  95. package/lib/v2/harvard/router.js +3 -0
  96. package/lib/v2/huxiu/briefColumn.js +13 -25
  97. package/lib/v2/huxiu/channel.js +28 -0
  98. package/lib/v2/huxiu/club.js +30 -0
  99. package/lib/v2/huxiu/collection.js +14 -26
  100. package/lib/v2/huxiu/maintainer.js +8 -5
  101. package/lib/v2/huxiu/member.js +27 -0
  102. package/lib/v2/huxiu/moment.js +11 -25
  103. package/lib/v2/huxiu/radar.js +24 -16
  104. package/lib/v2/huxiu/router.js +7 -4
  105. package/lib/v2/huxiu/search.js +16 -21
  106. package/lib/v2/huxiu/tag.js +13 -25
  107. package/lib/v2/huxiu/templates/description.art +46 -0
  108. package/lib/v2/huxiu/util.js +460 -0
  109. package/lib/v2/ifi-audio/maintainer.js +1 -1
  110. package/lib/v2/imiker/jinghua.js +87 -0
  111. package/lib/v2/imiker/maintainer.js +3 -0
  112. package/lib/v2/imiker/radar.js +13 -0
  113. package/lib/v2/imiker/router.js +3 -0
  114. package/lib/v2/imiker/templates/description.art +32 -0
  115. package/lib/v2/inoreader/maintainer.js +1 -1
  116. package/lib/v2/instagram/common-utils.js +7 -4
  117. package/lib/v2/instagram/templates/images.art +9 -2
  118. package/lib/v2/instagram/templates/video.art +4 -1
  119. package/lib/v2/instagram/web-api/index.js +30 -9
  120. package/lib/v2/instagram/web-api/utils.js +149 -25
  121. package/lib/v2/kemono/index.js +26 -2
  122. package/lib/v2/kemono/radar.js +1 -1
  123. package/lib/v2/kepu/live.js +89 -0
  124. package/lib/v2/kepu/maintainer.js +3 -0
  125. package/lib/v2/kepu/radar.js +13 -0
  126. package/lib/v2/kepu/router.js +3 -0
  127. package/lib/v2/kepu/templates/description.art +33 -0
  128. package/lib/v2/leetcode/maintainer.js +1 -1
  129. package/lib/v2/lightnovel/lightNovel.js +62 -0
  130. package/lib/v2/lightnovel/maintainer.js +3 -0
  131. package/lib/v2/lightnovel/radar.js +13 -0
  132. package/lib/v2/lightnovel/router.js +3 -0
  133. package/lib/v2/line/radar.js +1 -1
  134. package/lib/v2/liquipedia/cs_matches.js +52 -0
  135. package/lib/v2/liquipedia/dota2_matches.js +47 -0
  136. package/lib/v2/liquipedia/maintainer.js +4 -0
  137. package/lib/v2/liquipedia/radar.js +19 -0
  138. package/lib/v2/liquipedia/router.js +4 -0
  139. package/lib/v2/magazinelib/maintainer.js +1 -1
  140. package/lib/v2/mihoyo/radar.js +1 -1
  141. package/lib/v2/missav/maintainer.js +3 -0
  142. package/lib/v2/missav/new.js +36 -0
  143. package/lib/v2/missav/radar.js +13 -0
  144. package/lib/v2/missav/router.js +3 -0
  145. package/lib/v2/missav/templates/preview.art +3 -0
  146. package/lib/v2/modb/maintainer.js +3 -0
  147. package/lib/v2/modb/radar.js +13 -0
  148. package/lib/v2/modb/router.js +3 -0
  149. package/lib/v2/modb/topic.js +59 -0
  150. package/lib/v2/nature/cover.js +32 -42
  151. package/lib/v2/nature/highlight.js +2 -2
  152. package/lib/v2/nature/news-and-comment.js +2 -2
  153. package/lib/v2/nature/news.js +2 -2
  154. package/lib/v2/nature/research.js +2 -2
  155. package/lib/v2/nature/siteindex.js +5 -5
  156. package/lib/v2/nature/utils.js +124 -5
  157. package/lib/v2/ncu/jwc.js +33 -0
  158. package/lib/v2/ncu/maintainer.js +3 -0
  159. package/lib/v2/ncu/radar.js +13 -0
  160. package/lib/v2/ncu/router.js +3 -0
  161. package/lib/v2/nmtv/radar.js +1 -1
  162. package/lib/v2/oo-software/radar.js +1 -1
  163. package/lib/v2/patagonia/maintainer.js +1 -1
  164. package/lib/v2/phoronix/index.js +177 -48
  165. package/lib/v2/phoronix/maintainer.js +1 -1
  166. package/lib/v2/phoronix/radar.js +3 -3
  167. package/lib/v2/phoronix/router.js +1 -1
  168. package/lib/v2/picnob/maintainer.js +1 -1
  169. package/lib/v2/picnob/templates/desc.art +3 -3
  170. package/lib/v2/picnob/user.js +74 -37
  171. package/lib/v2/picnob/utils.js +24 -0
  172. package/lib/v2/qbitai/category.js +27 -0
  173. package/lib/v2/qbitai/maintainer.js +4 -0
  174. package/lib/v2/qbitai/radar.js +19 -0
  175. package/lib/v2/qbitai/router.js +4 -0
  176. package/lib/v2/qbitai/tag.js +27 -0
  177. package/lib/v2/questmobile/maintainer.js +3 -0
  178. package/lib/v2/questmobile/radar.js +18 -0
  179. package/lib/v2/questmobile/report.js +127 -0
  180. package/lib/v2/questmobile/router.js +3 -0
  181. package/lib/v2/questmobile/templates/description.art +17 -0
  182. package/lib/v2/readhub/daily.js +43 -0
  183. package/lib/v2/readhub/index.js +19 -72
  184. package/lib/v2/readhub/maintainer.js +1 -0
  185. package/lib/v2/readhub/radar.js +6 -0
  186. package/lib/v2/readhub/router.js +1 -0
  187. package/lib/v2/readhub/util.js +69 -0
  188. package/lib/v2/routledge/book-series.js +78 -0
  189. package/lib/v2/routledge/maintainer.js +3 -0
  190. package/lib/v2/routledge/radar.js +13 -0
  191. package/lib/v2/routledge/router.js +3 -0
  192. package/lib/v2/routledge/templates/description.art +7 -0
  193. package/lib/v2/showstart/artist.js +15 -0
  194. package/lib/v2/showstart/brand.js +15 -0
  195. package/lib/v2/showstart/const.js +4 -0
  196. package/lib/v2/showstart/event.js +18 -0
  197. package/lib/v2/showstart/maintainer.js +6 -0
  198. package/lib/v2/showstart/radar.js +40 -0
  199. package/lib/v2/showstart/router.js +6 -0
  200. package/lib/v2/showstart/search.js +51 -0
  201. package/lib/v2/showstart/service.js +166 -0
  202. package/lib/v2/showstart/utils.js +92 -0
  203. package/lib/v2/snowpeak/maintainer.js +1 -1
  204. package/lib/v2/sony/maintainer.js +1 -1
  205. package/lib/v2/sse/disclosure.js +4 -3
  206. package/lib/v2/sspu/jwc.js +44 -0
  207. package/lib/v2/sspu/maintainer.js +3 -0
  208. package/lib/v2/sspu/radar.js +13 -0
  209. package/lib/v2/sspu/router.js +3 -0
  210. package/lib/v2/techcrunch/maintainer.js +1 -1
  211. package/lib/v2/theatlantic/maintainer.js +1 -1
  212. package/lib/v2/thepaper/radar.js +2 -2
  213. package/lib/v2/threads/index.js +20 -150
  214. package/lib/v2/threads/utils.js +147 -0
  215. package/lib/v2/twitter/keyword.js +1 -3
  216. package/lib/v2/twitter/media.js +1 -5
  217. package/lib/v2/twitter/user.js +1 -3
  218. package/lib/v2/twitter/utils.js +5 -3
  219. package/lib/v2/twitter/web-api/constants.js +80 -68
  220. package/lib/v2/twitter/web-api/twitter-api.js +96 -103
  221. package/lib/v2/uniqlo/maintainer.js +3 -0
  222. package/lib/v2/uniqlo/new.js +62 -0
  223. package/lib/v2/uniqlo/radar.js +12 -0
  224. package/lib/v2/uniqlo/router.js +3 -0
  225. package/lib/v2/wechat/maintainer.js +1 -1
  226. package/lib/v2/weibo/maintainer.js +1 -1
  227. package/lib/v2/weibo/radar.js +1 -1
  228. package/lib/v2/weibo/router.js +1 -1
  229. package/lib/v2/weibo/search/hot.js +118 -7
  230. package/lib/v2/weibo/search/template/digest.art +17 -0
  231. package/lib/v2/xiaohongshu/util.js +17 -7
  232. package/lib/v2/xmnn/docs.js +0 -0
  233. package/lib/v2/xmnn/maintainer.js +1 -0
  234. package/lib/v2/xmnn/news.js +65 -0
  235. package/lib/v2/xmnn/radar.js +67 -1
  236. package/lib/v2/xmnn/router.js +1 -0
  237. package/lib/v2/xsijishe/maintainer.js +1 -0
  238. package/lib/v2/xsijishe/radar.js +12 -0
  239. package/lib/v2/xsijishe/rank.js +60 -0
  240. package/lib/v2/xsijishe/router.js +1 -0
  241. package/lib/v2/yahoo/maintainer.js +2 -0
  242. package/lib/v2/yahoo/news/tw/index.js +24 -0
  243. package/lib/v2/yahoo/news/tw/provider-helper.js +22 -0
  244. package/lib/v2/yahoo/news/tw/provider.js +24 -0
  245. package/lib/v2/yahoo/news/tw/utils.js +129 -0
  246. package/lib/v2/yahoo/radar.js +29 -1
  247. package/lib/v2/yahoo/router.js +4 -1
  248. package/lib/v2/yahoo/templates/youtube.art +1 -0
  249. package/lib/v2/yuque/book.js +6 -1
  250. package/lib/v2/yuque/utils.js +16 -0
  251. package/lib/v2/zagg/maintainer.js +1 -1
  252. package/lib/views/error.art +1 -1
  253. package/lib/views/rss3-ums.js +62 -0
  254. package/lib/views/welcome.art +1 -1
  255. package/package.json +45 -39
  256. package/lib/routes/egsea/flash.js +0 -40
  257. package/lib/routes/gov/moa/sjzxfb.js +0 -20
  258. package/lib/routes/liquipedia/dota2_matches.js +0 -50
  259. package/lib/routes/questmobile/report.js +0 -121
  260. package/lib/routes/uniqlo/stylingbook.js +0 -24
  261. package/lib/routes/universities/buaa/news/index.js +0 -66
  262. package/lib/routes/universities/buaa/utils.js +0 -57
  263. package/lib/v2/gov/zhengce/zuixin.js +0 -39
  264. package/lib/v2/huxiu/article.js +0 -33
  265. package/lib/v2/huxiu/author.js +0 -36
  266. package/lib/v2/huxiu/templates/brief.art +0 -22
  267. package/lib/v2/huxiu/templates/img.art +0 -3
  268. package/lib/v2/huxiu/templates/moment.art +0 -16
  269. package/lib/v2/huxiu/templates/video.art +0 -7
  270. package/lib/v2/huxiu/utils.js +0 -154
  271. package/lib/v2/twitter/web-api/twitter-got.js +0 -113
  272. /package/lib/{routes → v2}/gov/moa/moa.js +0 -0
  273. /package/lib/v2/yahoo/news/{index.js → us/index.js} +0 -0
@@ -0,0 +1,44 @@
1
+ const got = require('@/utils/got');
2
+ const cheerio = require('cheerio');
3
+ const { parseDate } = require('@/utils/parse-date');
4
+ const timezone = require('@/utils/timezone');
5
+
6
+ module.exports = async (ctx) => {
7
+ const { listId } = ctx.params;
8
+ const baseUrl = 'https://jwc.sspu.edu.cn';
9
+
10
+ const { data: response, url: link } = await got(`${baseUrl}/${listId}/list.htm`);
11
+ const $ = cheerio.load(response);
12
+
13
+ const list = $('.news_list .news')
14
+ .slice(0, ctx.query.limit ? parseInt(ctx.query.limit) : 15)
15
+ .toArray()
16
+ .map((item) => {
17
+ item = $(item);
18
+ const title = item.find('.news_title a');
19
+ return {
20
+ title: title.attr('title'),
21
+ link: `${baseUrl}${title.attr('href')}`,
22
+ };
23
+ });
24
+
25
+ const items = await Promise.all(
26
+ list.map((item) =>
27
+ ctx.cache.tryGet(item.link, async () => {
28
+ const { data: response } = await got(item.link);
29
+ const $ = cheerio.load(response);
30
+
31
+ item.description = $('.wp_articlecontent').html();
32
+ item.pubDate = timezone(parseDate($('.arti_update').text(), 'YYYY-MM-DD HH:mm:ss'), +8);
33
+
34
+ return item;
35
+ })
36
+ )
37
+ );
38
+
39
+ ctx.state.data = {
40
+ title: $('head title').text(),
41
+ link,
42
+ item: items,
43
+ };
44
+ };
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ '/jwc/:listId': ['TonyRL'],
3
+ };
@@ -0,0 +1,13 @@
1
+ module.exports = {
2
+ 'sspu.edu.cn': {
3
+ _name: '上海第二工业大学',
4
+ jwc: [
5
+ {
6
+ title: '教务处',
7
+ docs: 'https://docs.rsshub.app/university#shang-hai-di-er-gong-ye-da-xue',
8
+ source: ['/jwc/:listId/list.htm'],
9
+ target: '/sspu/jwc/:listId',
10
+ },
11
+ ],
12
+ },
13
+ };
@@ -0,0 +1,3 @@
1
+ module.exports = (router) => {
2
+ router.get('/jwc/:listId', require('./jwc'));
3
+ };
@@ -1,3 +1,3 @@
1
1
  module.exports = {
2
- '/news': ['NavePnow'],
2
+ '/news': ['EthanWng97'],
3
3
  };
@@ -1,3 +1,3 @@
1
1
  module.exports = {
2
- '/:category': ['NavePnow'],
2
+ '/:category': ['EthanWng97'],
3
3
  };
@@ -18,13 +18,13 @@ module.exports = {
18
18
  title: '频道',
19
19
  docs: 'https://docs.rsshub.app/routes/traditional-media#peng-pai-xin-wen-pin-dao',
20
20
  source: ['/'],
21
- target: (params, url) => `/thepaper/channel/${new URL(url).search(/channel_(\d+)/)}`,
21
+ target: (params, url) => `/thepaper/channel/${url.match(/channel_(\d+)/)?.[1]}`,
22
22
  },
23
23
  {
24
24
  title: '栏目',
25
25
  docs: 'https://docs.rsshub.app/routes/traditional-media#peng-pai-xin-wen-lie-biao',
26
26
  source: ['/'],
27
- target: (params, url) => `/thepaper/list/${new URL(url).search(/list_(\d+)/)}`,
27
+ target: (params, url) => `/thepaper/list/${url.search(/list_(\d+)/)?.[1]}`,
28
28
  },
29
29
  {
30
30
  title: '澎湃美数组作品集',
@@ -1,146 +1,6 @@
1
1
  const got = require('@/utils/got');
2
- const cheerio = require('cheerio');
3
2
  const { parseDate } = require('@/utils/parse-date');
4
- const dayjs = require('dayjs');
5
-
6
- const profileUrl = (user) => `https://www.threads.net/@${user}`;
7
- const threadUrl = (code) => `https://www.threads.net/t/${code}`;
8
- const apiUrl = 'https://www.threads.net/api/graphql';
9
- const THREADS_QUERY = 6232751443445612;
10
- const REPLIES_QUERY = 6307072669391286;
11
- const USER_AGENT = 'Barcelona 289.0.0.77.109 Android';
12
-
13
- const load = async (url) => {
14
- // user id fetching needs puppeteer
15
- const browser = await require('@/utils/puppeteer')();
16
- const page = await browser.newPage();
17
- await page.setRequestInterception(true);
18
- page.on('request', (request) => {
19
- request.resourceType() === 'document' ? request.continue() : request.abort();
20
- });
21
- await page.goto(url, {
22
- waitUntil: 'domcontentloaded',
23
- });
24
- const response = await page.content();
25
- browser.close();
26
-
27
- return cheerio.load(response);
28
- };
29
-
30
- const extractTokens = async (user, ctx) => {
31
- const $ = await load(profileUrl(user));
32
- const lsd = $('script:contains("LSD"):first')
33
- .html()
34
- .match(/"LSD",\[],\{"token":"([a-zA-Z0-9@_-]+)"},/)?.[1];
35
-
36
- // This needs puppeteer
37
- const userId = $('script:contains("user_id"):first')
38
- .html()
39
- .match(/"user_id":"(\d+)"/)?.[1];
40
-
41
- ctx.state.json = { lsd, userId };
42
- return { lsd, userId };
43
- };
44
-
45
- const makeHeader = (user, lsd) => ({
46
- Accept: 'application/json',
47
- Host: 'www.threads.net',
48
- Origin: 'https://www.threads.net',
49
- Referer: `https://www.threads.net/@${user}`,
50
- 'User-Agent': USER_AGENT,
51
- 'X-FB-LSD': lsd,
52
- 'X-IG-App-ID': '238260118697367',
53
- });
54
-
55
- const hasMedia = (post) => post.image_versions2 || post.carousel_media || post.video_versions;
56
- const buildMedia = (post) => {
57
- let html = '';
58
-
59
- if (!post.carousel_media) {
60
- const mainImage = post.image_versions2?.candidates?.[0];
61
- const mainVideo = post.video_versions?.[0];
62
- if (mainImage) {
63
- if (!mainVideo) {
64
- html += `<img src="${mainImage.url}"/>`;
65
- } else {
66
- html += `<video controls autoplay loop poster="${mainImage.url}">`;
67
- html += `<source src="${mainVideo.url}"/>`;
68
- html += '</video>';
69
- }
70
- }
71
- } else {
72
- post.carousel_media.forEach((media) => {
73
- const firstImage = media.image_versions2?.candidates[0];
74
- const firstVideo = media.video_versions?.[0];
75
- if (!firstVideo) {
76
- html += `<img src="${firstImage.url}"/>`;
77
- } else {
78
- html += `<video controls autoplay loop poster="${firstImage.url}">`;
79
- html += `<source src="${firstVideo.url}"/>`;
80
- html += '</video>';
81
- }
82
- });
83
- }
84
-
85
- return html;
86
- };
87
-
88
- const buildContent = (item, options) => {
89
- let title = '';
90
- let description = '';
91
- const quotedPost = item.post.text_post_app_info?.share_info?.quoted_post;
92
- const repostedPost = item.post.text_post_app_info?.share_info?.reposted_post;
93
- const isReply = item.post.text_post_app_info?.reply_to_author;
94
- const embededPost = quotedPost ?? repostedPost;
95
-
96
- if (options.showAuthorInTitle) {
97
- title += `@${item.post.user?.username}: `;
98
- }
99
-
100
- if (options.showAuthorInDesc) {
101
- description += '<p>';
102
- if (options.showAuthorAvatarInDesc) {
103
- description += `<img src="${item.post.user?.profile_pic_url}" width="48px" height="48px"> `;
104
- }
105
- description += `<strong>@${item.post.user?.username}</strong>`;
106
- if (embededPost) {
107
- description += options.showEmojiForQuotesAndReply ? ' 🔁' : ' quoted';
108
- } else if (isReply) {
109
- description += options.showEmojiForQuotesAndReply ? ' ↩️' : ' replied';
110
- }
111
- description += ':</p>';
112
- }
113
-
114
- if (item.post.caption?.text) {
115
- title += item.post.caption?.text;
116
- description += `<p>${item.post.caption?.text}</p>`;
117
- }
118
-
119
- if (hasMedia(item.post)) {
120
- description += `<p>${buildMedia(item.post)}</p>`;
121
- }
122
-
123
- if (embededPost) {
124
- if (options.showQuotedInTitle) {
125
- title += options.showEmojiForQuotesAndReply ? ' 🔁 ' : ' QT: ';
126
- title += `@${embededPost.user?.username}: `;
127
- title += `"${embededPost.caption?.text}"`;
128
- }
129
- description += '<blockquote>';
130
- description += `<p>${embededPost.caption?.text}</p>`;
131
- if (hasMedia(embededPost)) {
132
- description += `<p>${buildMedia(embededPost)}</p>`;
133
- }
134
- description += '— ';
135
- if (options.showQuotedAuthorAvatarInDesc) {
136
- description += `<img src="${embededPost.user?.profile_pic_url}" width="24px" height="24px"> `;
137
- }
138
- description += `@${embededPost.user?.username} — `;
139
- description += `<a href="${threadUrl(embededPost.code)}">${dayjs(embededPost.taken_at, 'X').toString()}</a>`;
140
- description += '</blockquote>';
141
- }
142
- return { title, description };
143
- };
3
+ const { PROFILE_QUERY, REPLIES_QUERY, THREADS_QUERY, apiUrl, threadUrl, profileUrl, extractTokens, makeHeader, buildContent } = require('./utils');
144
4
 
145
5
  module.exports = async (ctx) => {
146
6
  const { user, routeParams } = ctx.params;
@@ -159,9 +19,17 @@ module.exports = async (ctx) => {
159
19
  replies: params.get('replies') ?? false,
160
20
  };
161
21
 
162
- const headers = makeHeader(user, lsd);
163
- const resp = await got.post(apiUrl, {
164
- headers,
22
+ const { data: profileResponse } = await got.post(apiUrl, {
23
+ headers: makeHeader(user, lsd),
24
+ form: {
25
+ lsd,
26
+ variables: JSON.stringify({ userID: userId }),
27
+ doc_id: PROFILE_QUERY,
28
+ },
29
+ });
30
+
31
+ const { data: threadsResponse, request: threadsRequest } = await got.post(apiUrl, {
32
+ headers: makeHeader(user, lsd),
165
33
  form: {
166
34
  lsd,
167
35
  variables: JSON.stringify({ userID: userId }),
@@ -169,13 +37,14 @@ module.exports = async (ctx) => {
169
37
  },
170
38
  });
171
39
 
40
+ ctx.state.json.profileData = profileResponse;
172
41
  ctx.state.json.request = {
173
- headers: resp.request.options.headers,
174
- body: resp.request.options.body,
42
+ headers: threadsRequest.options.headers,
43
+ body: threadsRequest.options.body,
175
44
  };
176
45
 
177
- const responseBody = resp.data;
178
- const threads = responseBody?.data?.mediaData?.threads || [];
46
+ const userData = profileResponse?.data?.userData?.user || {};
47
+ const threads = threadsResponse?.data?.mediaData?.threads || [];
179
48
 
180
49
  const items = threads.flatMap((thread) =>
181
50
  thread.thread_items
@@ -195,9 +64,10 @@ module.exports = async (ctx) => {
195
64
  ctx.state.json.items = items;
196
65
 
197
66
  ctx.state.data = {
198
- title: user,
67
+ title: `${userData.full_name} (@${userData.username}) on Threads`,
199
68
  link: profileUrl(user),
200
- description: user,
69
+ image: userData.hd_profile_pic_versions.sort((a, b) => b.width - a.width)[0].url,
70
+ description: userData.biography,
201
71
  item: items,
202
72
  };
203
73
  };
@@ -0,0 +1,147 @@
1
+ const got = require('@/utils/got');
2
+ const cheerio = require('cheerio');
3
+ const dayjs = require('dayjs');
4
+
5
+ const profileUrl = (user) => `https://www.threads.net/@${user}`;
6
+ const threadUrl = (code) => `https://www.threads.net/t/${code}`;
7
+
8
+ const apiUrl = 'https://www.threads.net/api/graphql';
9
+ const PROFILE_QUERY = 23996318473300828;
10
+ const THREADS_QUERY = 6232751443445612;
11
+ const REPLIES_QUERY = 6307072669391286;
12
+ const USER_AGENT = 'Barcelona 289.0.0.77.109 Android';
13
+ const appId = '238260118697367';
14
+
15
+ const extractTokens = async (user, ctx) => {
16
+ const { data: response } = await got(profileUrl(user), {
17
+ headers: {
18
+ 'User-Agent': USER_AGENT,
19
+ 'X-IG-App-ID': appId,
20
+ },
21
+ });
22
+ const $ = cheerio.load(response);
23
+
24
+ const data = $('script:contains("LSD"):first').text();
25
+
26
+ const lsd = data.match(/"LSD",\[],\{"token":"([a-zA-Z0-9@_-]+)"\},/)?.[1];
27
+
28
+ const userId = data.match(/\{"user_id":"(\d+)"\},/)?.[1];
29
+
30
+ ctx.state.json = { lsd, userId };
31
+ return { lsd, userId };
32
+ };
33
+
34
+ const makeHeader = (user, lsd) => ({
35
+ Accept: 'application/json',
36
+ Host: 'www.threads.net',
37
+ Origin: 'https://www.threads.net',
38
+ Referer: profileUrl(user),
39
+ 'User-Agent': USER_AGENT,
40
+ 'X-FB-LSD': lsd,
41
+ 'X-IG-App-ID': appId,
42
+ });
43
+
44
+ const hasMedia = (post) => post.image_versions2 || post.carousel_media || post.video_versions;
45
+ const buildMedia = (post) => {
46
+ let html = '';
47
+
48
+ if (!post.carousel_media) {
49
+ const mainImage = post.image_versions2?.candidates?.[0];
50
+ const mainVideo = post.video_versions?.[0];
51
+ if (mainImage) {
52
+ if (!mainVideo) {
53
+ html += `<img src="${mainImage.url}"/>`;
54
+ } else {
55
+ html += `<video controls autoplay loop poster="${mainImage.url}">`;
56
+ html += `<source src="${mainVideo.url}"/>`;
57
+ html += '</video>';
58
+ }
59
+ }
60
+ } else {
61
+ post.carousel_media.forEach((media) => {
62
+ const firstImage = media.image_versions2?.candidates[0];
63
+ const firstVideo = media.video_versions?.[0];
64
+ if (!firstVideo) {
65
+ html += `<img src="${firstImage.url}"/>`;
66
+ } else {
67
+ html += `<video controls autoplay loop poster="${firstImage.url}">`;
68
+ html += `<source src="${firstVideo.url}"/>`;
69
+ html += '</video>';
70
+ }
71
+ });
72
+ }
73
+
74
+ return html;
75
+ };
76
+
77
+ const buildContent = (item, options) => {
78
+ let title = '';
79
+ let description = '';
80
+ const quotedPost = item.post.text_post_app_info?.share_info?.quoted_post;
81
+ const repostedPost = item.post.text_post_app_info?.share_info?.reposted_post;
82
+ const isReply = item.post.text_post_app_info?.reply_to_author;
83
+ const embededPost = quotedPost ?? repostedPost;
84
+
85
+ if (options.showAuthorInTitle) {
86
+ title += `@${item.post.user?.username}: `;
87
+ }
88
+
89
+ if (options.showAuthorInDesc) {
90
+ description += '<p>';
91
+ if (options.showAuthorAvatarInDesc) {
92
+ description += `<img src="${item.post.user?.profile_pic_url}" width="48px" height="48px"> `;
93
+ }
94
+ description += `<strong>@${item.post.user?.username}</strong>`;
95
+ if (embededPost) {
96
+ description += options.showEmojiForQuotesAndReply ? ' 🔁' : ' quoted';
97
+ } else if (isReply) {
98
+ description += options.showEmojiForQuotesAndReply ? ' ↩️' : ' replied';
99
+ }
100
+ description += ':</p>';
101
+ }
102
+
103
+ if (item.post.caption?.text) {
104
+ title += item.post.caption?.text;
105
+ description += `<p>${item.post.caption?.text}</p>`;
106
+ }
107
+
108
+ if (hasMedia(item.post)) {
109
+ description += `<p>${buildMedia(item.post)}</p>`;
110
+ }
111
+
112
+ if (embededPost) {
113
+ if (options.showQuotedInTitle) {
114
+ title += options.showEmojiForQuotesAndReply ? ' 🔁 ' : ' QT: ';
115
+ title += `@${embededPost.user?.username}: `;
116
+ title += `"${embededPost.caption?.text}"`;
117
+ }
118
+ description += '<blockquote>';
119
+ description += `<p>${embededPost.caption?.text}</p>`;
120
+ if (hasMedia(embededPost)) {
121
+ description += `<p>${buildMedia(embededPost)}</p>`;
122
+ }
123
+ description += '— ';
124
+ if (options.showQuotedAuthorAvatarInDesc) {
125
+ description += `<img src="${embededPost.user?.profile_pic_url}" width="24px" height="24px"> `;
126
+ }
127
+ description += `@${embededPost.user?.username} — `;
128
+ description += `<a href="${threadUrl(embededPost.code)}">${dayjs(embededPost.taken_at, 'X').toString()}</a>`;
129
+ description += '</blockquote>';
130
+ }
131
+ return { title, description };
132
+ };
133
+
134
+ module.exports = {
135
+ apiUrl,
136
+ profileUrl,
137
+ threadUrl,
138
+ PROFILE_QUERY,
139
+ THREADS_QUERY,
140
+ REPLIES_QUERY,
141
+ USER_AGENT,
142
+ extractTokens,
143
+ makeHeader,
144
+ hasMedia,
145
+ buildMedia,
146
+ buildContent,
147
+ };
@@ -1,5 +1,3 @@
1
- const devApiImpl = require('./developer-api/search');
2
1
  const webApiImpl = require('./web-api/search');
3
- const apiFallback = require('./api_fallback_common');
4
2
 
5
- module.exports = (ctx) => apiFallback(ctx, devApiImpl, webApiImpl);
3
+ module.exports = async (ctx) => await webApiImpl(ctx);
@@ -1,7 +1,3 @@
1
- // const config = require('@/config').value;
2
- // const devApiImpl = require('./developer-api/user');
3
1
  const webApiImpl = require('./web-api/media');
4
2
 
5
- module.exports = async (ctx) => {
6
- await webApiImpl(ctx);
7
- };
3
+ module.exports = async (ctx) => await webApiImpl(ctx);
@@ -1,5 +1,3 @@
1
- const devApiImpl = require('./developer-api/user');
2
1
  const webApiImpl = require('./web-api/user');
3
- const apiFallback = require('./api_fallback_common');
4
2
 
5
- module.exports = (ctx) => apiFallback(ctx, devApiImpl, webApiImpl);
3
+ module.exports = (ctx) => webApiImpl(ctx);
@@ -240,7 +240,9 @@ const ProcessFeed = (ctx, { data = [] }, params = {}) => {
240
240
  quoteInTitle += `${author.name}: ${formatText(quoteData)}`;
241
241
 
242
242
  if (readable) {
243
- quote += `<br><small>Link: <a href='https://twitter.com/${author.screen_name}/status/${quoteData.id_str}' target='_blank' rel='noopener noreferrer'>https://twitter.com/${author.screen_name}/status/${quoteData.id_str}</a></small>`;
243
+ quote += `<br><small>Link: <a href='https://twitter.com/${author.screen_name}/status/${quoteData.id_str || quoteData.conversation_id_str}' target='_blank' rel='noopener noreferrer'>https://twitter.com/${
244
+ author.screen_name
245
+ }/status/${quoteData.id_str || quoteData.conversation_id_str}</a></small>`;
244
246
  }
245
247
  if (showTimestampInDescription) {
246
248
  quote += '<br><small>' + parseDate(quoteData.created_at);
@@ -370,7 +372,7 @@ const ProcessFeed = (ctx, { data = [] }, params = {}) => {
370
372
  author: authorName,
371
373
  description,
372
374
  pubDate: parseDate(item.created_at),
373
- link: `https://twitter.com/${item.user.screen_name}/status/${item.id_str}`,
375
+ link: `https://twitter.com/${item.user.screen_name}/status/${item.id_str || item.conversation_id_str}`,
374
376
 
375
377
  _extra:
376
378
  (isRetweet && {
@@ -383,7 +385,7 @@ const ProcessFeed = (ctx, { data = [] }, params = {}) => {
383
385
  (item.is_quote_status && {
384
386
  links: [
385
387
  {
386
- url: `https://twitter.com/${item.quoted_status?.user?.screen_name}/status/${item.quoted_status?.id_str}`,
388
+ url: `https://twitter.com/${item.quoted_status?.user?.screen_name}/status/${item.quoted_status?.id_str || item.quoted_status?.conversation_id_str}`,
387
389
  type: 'quote',
388
390
  },
389
391
  ],
@@ -1,77 +1,89 @@
1
- // https://github.com/zedeus/nitter/issues/919#issuecomment-1619067142
2
- // https://git.sr.ht/~cloutier/bird.makeup/tree/4b2495bf2908c648ae9250f353bfdacf2464b98d/item/src/BirdsiteLive.Twitter/Tools/TwitterAuthenticationInitializer.cs#L36
3
- const auth = 'Bearer AAAAAAAAAAAAAAAAAAAAAFQODgEAAAAAVHTp76lzh3rFzcHbmHVvQxYYpTw%3DckAlMINMjmCwxUcaXbAN4XqJVdgMJaHqNOFgPMK0zN1qLqLQCF';
4
- const tokens = [
5
- auth, // tweetdeck new
6
- // 'CjulERsDeqhhjSme66ECg:IQWdVyqFxghAtURHGeGiWAsmCAGmdW3WmbEx6Hck', // iPad, no mixed videos and photos support
7
- // valid, but endpoints differ:
8
- // 'IQKbtAYlXLripLGPWd0HUA:GgDYlkSvaPxGxC4X8liwpUoqKwwr3lCADbz8A7ADU', // iPhone
9
- // '3nVuSoBZnx6U4vzUxf5w:Bcs59EFbbsdF6Sl9Ng71smgStWEGwXXKSjYvPVt7qys', // Android
10
- // '3rJOl1ODzm9yZy63FACdg:5jPoQ5kQvMJFDYRNE8bQ4rHuds4xJqhvgNJM4awaE8', // Mac
11
- ];
1
+ const baseUrl = 'https://api.twitter.com';
2
+
3
+ const consumerKey = '3nVuSoBZnx6U4vzUxf5w';
4
+ const consumerSecret = 'Bcs59EFbbsdF6Sl9Ng71smgStWEGwXXKSjYvPVt7qys';
12
5
 
13
6
  const graphQLEndpointsPlain = [
14
- '/graphql/oUZZZ8Oddwxs8Cd3iW3UEA/UserByScreenName',
15
- '/graphql/Lxg1V9AiIzzXEiP2c8dRnw/UserByRestId',
16
- '/graphql/3XDB26fBve-MmjHaWTUZxA/TweetDetail',
17
- '/graphql/QqZBEqganhHwmU9QscmIug/UserTweets',
18
- '/graphql/wxoVeDnl0mP7VLhe6mTOdg/UserTweetsAndReplies',
19
- '/graphql/Az0-KW6F-FyYTc2OJmvUhg/UserMedia',
20
- '/graphql/kgZtsNyE46T3JaEf2nF9vw/Likes',
21
- // these endpoints are not available if authenticated as other clients
22
- // FYI, endpoints for Android: https://gist.github.com/ScamCast/2e40befbd1b61c4a80cda2745d4df1f4
7
+ '/graphql/u7wQyGi6oExe8_TRWGMq4Q/UserResultByScreenNameQuery',
8
+ '/graphql/oPppcargziU1uDQHAUmH-A/UserResultByIdQuery',
9
+ '/graphql/3JNH4e9dq1BifLxAa3UMWg/UserWithProfileTweetsQueryV2',
10
+ '/graphql/8IS8MaO-2EN6GZZZb8jF0g/UserWithProfileTweetsAndRepliesQueryV2',
11
+ '/graphql/PDfFf8hGeJvUCiTyWtw4wQ/MediaTimelineV2',
12
+ '/graphql/q94uRCEn65LZThakYcPT6g/TweetDetail',
13
+ '/graphql/sITyJdhRPpvpEjg4waUmTA/TweetResultByIdQuery',
14
+ '/graphql/gkjsKepM6gl_HmFWoWKfgg/SearchTimeline',
15
+ '/graphql/iTpgCtbdxrsJfyx0cFjHqg/ListByRestId',
16
+ '/graphql/-kmqNvm5Y-cVrfvBy6docg/ListBySlug',
17
+ '/graphql/P4NpVZDqUD_7MEM84L-8nw/ListMembers',
18
+ '/graphql/BbGLL1ZfMibdFNWlk7a0Pw/ListTimeline',
23
19
  ];
24
20
 
25
- const graphQLMap = Object.fromEntries(graphQLEndpointsPlain.map((endpoint) => [endpoint.split('/')[3], endpoint]));
21
+ const gqlMap = Object.fromEntries(graphQLEndpointsPlain.map((endpoint) => [endpoint.split('/')[3].replace(/V2$|Query$|QueryV2$/, ''), endpoint]));
22
+
23
+ const gqlFeatures = JSON.stringify({
24
+ android_graphql_skip_api_media_color_palette: false,
25
+ blue_business_profile_image_shape_enabled: false,
26
+ creator_subscriptions_subscription_count_enabled: false,
27
+ creator_subscriptions_tweet_preview_api_enabled: true,
28
+ freedom_of_speech_not_reach_fetch_enabled: false,
29
+ graphql_is_translatable_rweb_tweet_is_translatable_enabled: false,
30
+ hidden_profile_likes_enabled: false,
31
+ highlights_tweets_tab_ui_enabled: false,
32
+ interactive_text_enabled: false,
33
+ longform_notetweets_consumption_enabled: true,
34
+ longform_notetweets_inline_media_enabled: false,
35
+ longform_notetweets_richtext_consumption_enabled: true,
36
+ longform_notetweets_rich_text_read_enabled: false,
37
+ responsive_web_edit_tweet_api_enabled: false,
38
+ responsive_web_enhance_cards_enabled: false,
39
+ responsive_web_graphql_exclude_directive_enabled: true,
40
+ responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
41
+ responsive_web_graphql_timeline_navigation_enabled: false,
42
+ responsive_web_media_download_video_enabled: false,
43
+ responsive_web_text_conversations_enabled: false,
44
+ responsive_web_twitter_article_tweet_consumption_enabled: false,
45
+ responsive_web_twitter_blue_verified_badge_is_enabled: true,
46
+ rweb_lists_timeline_redesign_enabled: true,
47
+ spaces_2022_h2_clipping: true,
48
+ spaces_2022_h2_spaces_communities: true,
49
+ standardized_nudges_misinfo: false,
50
+ subscriptions_verification_info_enabled: true,
51
+ subscriptions_verification_info_reason_enabled: true,
52
+ subscriptions_verification_info_verified_since_enabled: true,
53
+ super_follow_badge_privacy_enabled: false,
54
+ super_follow_exclusive_tweet_notifications_enabled: false,
55
+ super_follow_tweet_api_enabled: false,
56
+ super_follow_user_api_enabled: false,
57
+ tweet_awards_web_tipping_enabled: false,
58
+ tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: false,
59
+ tweetypie_unmention_optimization_enabled: false,
60
+ unified_cards_ad_metadata_container_dynamic_card_content_query_enabled: false,
61
+ verified_phone_label_enabled: false,
62
+ vibe_api_enabled: false,
63
+ view_counts_everywhere_api_enabled: false,
64
+ });
26
65
 
27
- // captured from Twitter web
28
- const featuresMap = {
29
- UserByScreenName: JSON.stringify({
30
- hidden_profile_likes_enabled: false,
31
- responsive_web_graphql_exclude_directive_enabled: true,
32
- verified_phone_label_enabled: false,
33
- subscriptions_verification_info_verified_since_enabled: true,
34
- highlights_tweets_tab_ui_enabled: true,
35
- creator_subscriptions_tweet_preview_api_enabled: true,
36
- responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
37
- responsive_web_graphql_timeline_navigation_enabled: true,
38
- }),
39
- UserByRestId: JSON.stringify({
40
- hidden_profile_likes_enabled: false,
41
- responsive_web_graphql_exclude_directive_enabled: true,
42
- verified_phone_label_enabled: false,
43
- highlights_tweets_tab_ui_enabled: true,
44
- creator_subscriptions_tweet_preview_api_enabled: true,
45
- responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
46
- responsive_web_graphql_timeline_navigation_enabled: true,
47
- }),
48
- UserTweets: JSON.stringify({
49
- rweb_lists_timeline_redesign_enabled: true,
50
- responsive_web_graphql_exclude_directive_enabled: true,
51
- verified_phone_label_enabled: false,
52
- creator_subscriptions_tweet_preview_api_enabled: true,
53
- responsive_web_graphql_timeline_navigation_enabled: true,
54
- responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
55
- tweetypie_unmention_optimization_enabled: true,
56
- responsive_web_edit_tweet_api_enabled: true,
57
- graphql_is_translatable_rweb_tweet_is_translatable_enabled: true,
58
- view_counts_everywhere_api_enabled: true,
59
- longform_notetweets_consumption_enabled: true,
60
- responsive_web_twitter_article_tweet_consumption_enabled: false,
61
- tweet_awards_web_tipping_enabled: false,
62
- freedom_of_speech_not_reach_fetch_enabled: true,
63
- standardized_nudges_misinfo: true,
64
- tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: true,
65
- longform_notetweets_rich_text_read_enabled: true,
66
- longform_notetweets_inline_media_enabled: true,
67
- responsive_web_media_download_video_enabled: false,
68
- responsive_web_enhance_cards_enabled: false,
69
- }),
66
+ const timelineParams = {
67
+ include_can_media_tag: 1,
68
+ include_cards: 1,
69
+ include_entities: 1,
70
+ include_profile_interstitial_type: 0,
71
+ include_quote_count: 0,
72
+ include_reply_count: 0,
73
+ include_user_entities: 0,
74
+ include_ext_reply_count: 0,
75
+ include_ext_media_color: 0,
76
+ cards_platform: 'Web-13',
77
+ tweet_mode: 'extended',
78
+ send_error_codes: 1,
79
+ simple_quoted_tweet: 1,
70
80
  };
71
81
 
72
82
  module.exports = {
73
- auth,
74
- tokens,
75
- graphQLMap,
76
- featuresMap,
83
+ baseUrl,
84
+ consumerKey,
85
+ consumerSecret,
86
+ gqlMap,
87
+ gqlFeatures,
88
+ timelineParams,
77
89
  };