rsshub 1.0.0-master.f9ec483 → 1.0.0-master.fa1dcc5

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 (334) hide show
  1. package/lib/config.ts +42 -1
  2. package/lib/middleware/access-control.ts +3 -3
  3. package/lib/middleware/cache.ts +5 -3
  4. package/lib/middleware/template.test.ts +6 -0
  5. package/lib/middleware/template.tsx +3 -1
  6. package/lib/router.js +0 -6
  7. package/lib/routes/141jav/index.ts +1 -1
  8. package/lib/routes/141ppv/index.ts +1 -1
  9. package/lib/routes/36kr/hot-list.ts +1 -1
  10. package/lib/routes/36kr/index.ts +1 -1
  11. package/lib/routes/78dm/index.ts +429 -43
  12. package/lib/routes/78dm/namespace.ts +2 -0
  13. package/lib/routes/78dm/templates/description.art +17 -0
  14. package/lib/routes/abc/index.ts +10 -9
  15. package/lib/routes/aeon/category.ts +1 -1
  16. package/lib/routes/aeon/type.ts +1 -1
  17. package/lib/routes/afr/latest.ts +69 -0
  18. package/lib/routes/afr/namespace.ts +7 -0
  19. package/lib/routes/afr/navigation.ts +75 -0
  20. package/lib/routes/afr/query.ts +349 -0
  21. package/lib/routes/afr/utils.ts +80 -0
  22. package/lib/routes/aibase/discover.ts +1 -1
  23. package/lib/routes/aibase/news.ts +1 -1
  24. package/lib/routes/aibase/topic.ts +1 -1
  25. package/lib/routes/air-level/index.ts +49 -0
  26. package/lib/routes/air-level/levelrank.ts +65 -0
  27. package/lib/routes/air-level/namespace.ts +12 -0
  28. package/lib/routes/ajcass/namespace.ts +11 -0
  29. package/lib/routes/ajcass/shxyj.ts +73 -0
  30. package/lib/routes/alpinelinux/pkgs.ts +2 -2
  31. package/lib/routes/anime1/anime.ts +1 -1
  32. package/lib/routes/apnews/utils.ts +6 -2
  33. package/lib/routes/appleinsider/index.ts +1 -1
  34. package/lib/routes/aqara/region.ts +1 -1
  35. package/lib/routes/asus/bios.ts +0 -1
  36. package/lib/routes/baoyu/index.ts +57 -0
  37. package/lib/routes/baoyu/namespace.ts +8 -0
  38. package/lib/routes/bilibili/article.ts +37 -14
  39. package/lib/routes/bilibili/dynamic.ts +15 -7
  40. package/lib/routes/bilibili/followings-dynamic.ts +2 -2
  41. package/lib/routes/bilibili/manga-update.ts +3 -0
  42. package/lib/routes/bilibili/ranking.ts +188 -117
  43. package/lib/routes/bilibili/reply.ts +2 -0
  44. package/lib/routes/bilibili/templates/description.art +2 -0
  45. package/lib/routes/bing/daily-wallpaper.ts +5 -1
  46. package/lib/routes/bsky/posts.ts +0 -1
  47. package/lib/routes/bsky/templates/post.art +8 -1
  48. package/lib/routes/buaa/lib/space/newbook.ts +1 -1
  49. package/lib/routes/c114/namespace.ts +2 -0
  50. package/lib/routes/c114/roll.ts +71 -42
  51. package/lib/routes/cags/edu/index.ts +84 -0
  52. package/lib/routes/cags/namespace.ts +9 -0
  53. package/lib/routes/caixin/article.ts +1 -1
  54. package/lib/routes/caixin/blog.ts +2 -3
  55. package/lib/routes/caixin/category.ts +1 -1
  56. package/lib/routes/caixin/latest.ts +12 -16
  57. package/lib/routes/caixin/namespace.ts +1 -1
  58. package/lib/routes/caixin/utils-fulltext.ts +51 -0
  59. package/lib/routes/caixin/utils.ts +37 -37
  60. package/lib/routes/cara/likes.ts +1 -1
  61. package/lib/routes/cara/portfolio.ts +1 -1
  62. package/lib/routes/cara/timeline.ts +1 -1
  63. package/lib/routes/ccfa/index.ts +11 -11
  64. package/lib/routes/chub/characters.ts +1 -1
  65. package/lib/routes/cnbeta/namespace.ts +1 -1
  66. package/lib/routes/cngold/index.ts +195 -0
  67. package/lib/routes/cngold/namespace.ts +9 -0
  68. package/lib/routes/cnki/author.ts +120 -48
  69. package/lib/routes/cnki/utils.ts +4 -4
  70. package/lib/routes/coomer/artist.ts +1 -1
  71. package/lib/routes/coomer/namespace.ts +1 -1
  72. package/lib/routes/coomer/posts.ts +2 -2
  73. package/lib/routes/coomer/utils.ts +1 -1
  74. package/lib/routes/cpcaauto/index.ts +22 -22
  75. package/lib/routes/cs/zzkx.ts +1 -1
  76. package/lib/routes/csdn/blog.ts +17 -13
  77. package/lib/routes/cybersecurityventures/namespace.ts +7 -0
  78. package/lib/routes/cybersecurityventures/news.ts +121 -0
  79. package/lib/routes/cybersecurityventures/types.ts +17 -0
  80. package/lib/routes/daily/discussed.ts +22 -21
  81. package/lib/routes/daily/index.ts +14 -11
  82. package/lib/routes/daily/namespace.ts +1 -1
  83. package/lib/routes/daily/source.ts +186 -0
  84. package/lib/routes/daily/upvoted.ts +17 -17
  85. package/lib/routes/daily/user.ts +6 -23
  86. package/lib/routes/daily/utils.ts +27 -32
  87. package/lib/routes/dataguidance/index.ts +19 -30
  88. package/lib/routes/dcfever/news.ts +1 -1
  89. package/lib/routes/dcfever/reviews.ts +1 -1
  90. package/lib/routes/dcfever/trading-search.ts +1 -1
  91. package/lib/routes/dcfever/trading.ts +1 -1
  92. package/lib/routes/deeplearning/namespace.ts +3 -1
  93. package/lib/routes/deeplearning/templates/description.art +21 -0
  94. package/lib/routes/deeplearning/the-batch.ts +296 -0
  95. package/lib/routes/deepmind/blog.ts +1 -1
  96. package/lib/routes/digitalcameraworld/news.ts +1 -1
  97. package/lib/routes/discord/discord-api.ts +44 -2
  98. package/lib/routes/discord/search.ts +106 -0
  99. package/lib/routes/dn/news.ts +1 -1
  100. package/lib/routes/dockerhub/repositories.ts +42 -0
  101. package/lib/routes/dongqiudi/daily.ts +1 -1
  102. package/lib/routes/dw/namespace.ts +6 -0
  103. package/lib/routes/dw/news.ts +89 -0
  104. package/lib/routes/dw/rss.ts +62 -0
  105. package/lib/routes/dw/templates/description.art +23 -0
  106. package/lib/routes/dw/templates/liveblog.art +13 -0
  107. package/lib/routes/dw/templates/video.art +14 -0
  108. package/lib/routes/dw/utils.ts +172 -0
  109. package/lib/routes/ecnu/jwc.ts +5 -1
  110. package/lib/routes/espn/news.ts +1 -1
  111. package/lib/routes/fashionnetwork/namespace.ts +1 -1
  112. package/lib/routes/flyert/forum.ts +99 -0
  113. package/lib/routes/flyert/namespace.ts +1 -0
  114. package/lib/routes/flyert/templates/description.art +21 -0
  115. package/lib/routes/flyert/util.ts +188 -0
  116. package/lib/routes/follow/profile.ts +2 -2
  117. package/lib/routes/foresightnews/article.ts +1 -1
  118. package/lib/routes/foresightnews/column.ts +1 -1
  119. package/lib/routes/foresightnews/index.ts +3 -1
  120. package/lib/routes/foresightnews/news.ts +1 -1
  121. package/lib/routes/freecomputerbooks/index.ts +0 -10
  122. package/lib/routes/ftm/index.ts +1 -1
  123. package/lib/routes/furaffinity/art.ts +85 -0
  124. package/lib/routes/furaffinity/browse.ts +58 -0
  125. package/lib/routes/furaffinity/commissions.ts +55 -0
  126. package/lib/routes/furaffinity/home.ts +80 -0
  127. package/lib/routes/furaffinity/journal-comments.ts +64 -0
  128. package/lib/routes/furaffinity/journals.ts +56 -0
  129. package/lib/routes/furaffinity/namespace.ts +7 -0
  130. package/lib/routes/furaffinity/search.ts +72 -0
  131. package/lib/routes/furaffinity/shouts.ts +56 -0
  132. package/lib/routes/furaffinity/status.ts +58 -0
  133. package/lib/routes/furaffinity/submission-comments.ts +64 -0
  134. package/lib/{routes-deprecated/furaffinity/user.js → routes/furaffinity/user.ts} +53 -41
  135. package/lib/routes/furaffinity/watchers.ts +62 -0
  136. package/lib/routes/furaffinity/watching.ts +62 -0
  137. package/lib/routes/google/developers.ts +71 -0
  138. package/lib/routes/google/news.ts +26 -6
  139. package/lib/routes/gov/cbirc/index.ts +35 -35
  140. package/lib/routes/hamel/index.ts +81 -0
  141. package/lib/routes/hamel/namespace.ts +7 -0
  142. package/lib/routes/hellogithub/volume.ts +3 -0
  143. package/lib/routes/hket/index.ts +13 -1
  144. package/lib/routes/hostmonit/cloudflareyesv6.ts +1 -1
  145. package/lib/routes/hrbeu/sec/list.ts +80 -0
  146. package/lib/routes/hrbust/jwzx.ts +6 -0
  147. package/lib/routes/hunau/gfxy/index.ts +1 -1
  148. package/lib/routes/hunau/ied.ts +1 -1
  149. package/lib/routes/hunau/jwc.ts +1 -1
  150. package/lib/routes/hunau/xky/index.ts +1 -1
  151. package/lib/routes/hust/gs.ts +4 -3
  152. package/lib/routes/idolmaster/namespace.ts +7 -0
  153. package/lib/routes/idolmaster/news.ts +112 -0
  154. package/lib/routes/ieee/author.ts +95 -0
  155. package/lib/routes/ikea/cn/low-price.ts +2 -1
  156. package/lib/routes/imdb/chart.ts +1 -1
  157. package/lib/routes/inspirehep/author.ts +9 -1
  158. package/lib/routes/isct/namespace.ts +11 -0
  159. package/lib/routes/isct/news.ts +66 -0
  160. package/lib/routes/javtrailers/casts.ts +41 -0
  161. package/lib/routes/javtrailers/categories.ts +40 -0
  162. package/lib/routes/javtrailers/namespace.ts +7 -0
  163. package/lib/routes/javtrailers/studios.ts +39 -0
  164. package/lib/routes/javtrailers/templates/description.art +31 -0
  165. package/lib/routes/javtrailers/types.ts +59 -0
  166. package/lib/routes/javtrailers/utils.ts +48 -0
  167. package/lib/routes/jiemian/list.ts +1 -1
  168. package/lib/routes/jiuyangongshe/community.ts +1 -1
  169. package/lib/routes/jlu/phy/index.ts +55 -0
  170. package/lib/routes/joins/chinese.ts +13 -13
  171. package/lib/routes/kcna/news.ts +7 -5
  172. package/lib/routes/keep/user.ts +1 -1
  173. package/lib/routes/langchain/index.ts +74 -0
  174. package/lib/routes/langchain/namespace.ts +7 -0
  175. package/lib/routes/last-origin/namespace.ts +7 -0
  176. package/lib/routes/last-origin/news.ts +65 -0
  177. package/lib/routes/linkresearcher/index.ts +108 -46
  178. package/lib/routes/linkresearcher/namespace.ts +8 -2
  179. package/lib/routes/linkresearcher/templates/bilingual.art +7 -0
  180. package/lib/routes/linkresearcher/types.ts +103 -0
  181. package/lib/routes/liulinblog/itnews.ts +1 -1
  182. package/lib/routes/lofter/collection.ts +1 -1
  183. package/lib/routes/lofter/tag.ts +1 -1
  184. package/lib/routes/logrocket/index.ts +71 -0
  185. package/lib/routes/logrocket/namespace.ts +7 -0
  186. package/lib/routes/m-78/namespace.ts +7 -0
  187. package/lib/routes/m-78/news.ts +115 -0
  188. package/lib/routes/m-78/types.ts +13 -0
  189. package/lib/routes/mastodon/account-id.ts +1 -1
  190. package/lib/routes/mastodon/acct.ts +1 -1
  191. package/lib/routes/mastodon/timeline-local.ts +2 -2
  192. package/lib/routes/mastodon/timeline-remote.ts +2 -2
  193. package/lib/routes/mastodon/utils.ts +1 -1
  194. package/lib/routes/metacritic/index.ts +1 -1
  195. package/lib/routes/mi/utils.ts +1 -1
  196. package/lib/routes/mihoyo/bbs/official.ts +2 -1
  197. package/lib/routes/misskey/utils.ts +19 -1
  198. package/lib/routes/mittrchina/index.ts +2 -2
  199. package/lib/routes/natgeo/natgeo.ts +25 -24
  200. package/lib/routes/nbd/daily.ts +1 -1
  201. package/lib/routes/nielsberglund/index.ts +78 -0
  202. package/lib/routes/nielsberglund/namespace.ts +7 -0
  203. package/lib/routes/nytimes/rss.ts +57 -0
  204. package/lib/routes/openrice/chart.ts +68 -0
  205. package/lib/routes/openrice/templates/chart.art +9 -0
  206. package/lib/routes/openrice/voting.ts +84 -0
  207. package/lib/routes/oschina/news.ts +26 -7
  208. package/lib/routes/patreon/feed.ts +126 -0
  209. package/lib/routes/patreon/namespace.ts +7 -0
  210. package/lib/routes/patreon/templates/description.art +39 -0
  211. package/lib/routes/patreon/types.ts +387 -0
  212. package/lib/routes/pixiv/api/get-illust-detail.ts +22 -0
  213. package/lib/routes/pixiv/novel-api/content/nsfw.ts +73 -0
  214. package/lib/routes/pixiv/novel-api/content/sfw.ts +63 -0
  215. package/lib/routes/pixiv/novel-api/content/types.ts +254 -0
  216. package/lib/routes/pixiv/novel-api/content/utils.ts +133 -0
  217. package/lib/routes/pixiv/novel-api/series/nsfw.ts +84 -0
  218. package/lib/routes/pixiv/novel-api/series/sfw.ts +70 -0
  219. package/lib/routes/pixiv/novel-api/series/types.ts +94 -0
  220. package/lib/routes/pixiv/novel-api/user-novels/nsfw.ts +82 -0
  221. package/lib/routes/pixiv/novel-api/user-novels/sfw.ts +74 -0
  222. package/lib/routes/pixiv/novel-api/user-novels/types.ts +133 -0
  223. package/lib/routes/pixiv/novel-series.ts +52 -0
  224. package/lib/routes/pixiv/novels.ts +69 -44
  225. package/lib/routes/pixiv/utils.ts +3 -0
  226. package/lib/routes/plurk/anonymous.ts +1 -1
  227. package/lib/routes/plurk/hotlinks.ts +1 -1
  228. package/lib/routes/plurk/news.ts +1 -1
  229. package/lib/routes/plurk/search.ts +1 -1
  230. package/lib/routes/qstheory/index.ts +121 -0
  231. package/lib/routes/qstheory/magazine.ts +64 -0
  232. package/lib/routes/qstheory/namespace.ts +7 -0
  233. package/lib/routes/qstheory/utils.ts +25 -0
  234. package/lib/routes/ruc/ai.ts +1 -1
  235. package/lib/routes/scmp/coronavirus.ts +1 -1
  236. package/lib/routes/scu/jwc/tzgg.ts +75 -0
  237. package/lib/routes/scu/namespace.ts +7 -0
  238. package/lib/routes/scu/scupi/_utils.ts +62 -0
  239. package/lib/routes/scu/scupi/notice.ts +41 -0
  240. package/lib/routes/solidot/namespace.ts +1 -1
  241. package/lib/routes/stbu/jsjxy.ts +4 -1
  242. package/lib/routes/steam/search.ts +7 -0
  243. package/lib/routes/sycl/feeds.ts +1 -1
  244. package/lib/routes/syosetu/dev.ts +67 -0
  245. package/lib/routes/syosetu/index.ts +85 -0
  246. package/lib/routes/syosetu/namespace.ts +2 -2
  247. package/lib/routes/syosetu/ranking-isekai.ts +93 -0
  248. package/lib/routes/syosetu/ranking-r18.ts +192 -0
  249. package/lib/routes/syosetu/ranking.ts +285 -0
  250. package/lib/routes/syosetu/search.ts +161 -0
  251. package/lib/routes/syosetu/templates/description.art +73 -0
  252. package/lib/routes/syosetu/types/ranking-r18.ts +60 -0
  253. package/lib/routes/syosetu/types/ranking.ts +67 -0
  254. package/lib/routes/syosetu/types/search.ts +138 -0
  255. package/lib/routes/syosetu/utils.ts +50 -0
  256. package/lib/routes/telegram/channel.ts +31 -1
  257. package/lib/routes/telegram/scripts/get-telegram-session.mjs +16 -6
  258. package/lib/routes/telegram/stickerpack.ts +1 -1
  259. package/lib/routes/telegram/tglib/channel.ts +12 -9
  260. package/lib/routes/telegram/tglib/client.ts +13 -9
  261. package/lib/routes/test/index.ts +4 -0
  262. package/lib/routes/theblockbeats/index.ts +1 -1
  263. package/lib/routes/thegradient/index.ts +80 -0
  264. package/lib/routes/thegradient/namespace.ts +7 -0
  265. package/lib/routes/theinitium/app.ts +4 -0
  266. package/lib/routes/thepaper/channel.ts +1 -1
  267. package/lib/routes/thepaper/factpaper.ts +1 -1
  268. package/lib/routes/thepaper/featured.ts +1 -1
  269. package/lib/routes/thepaper/list.ts +1 -1
  270. package/lib/routes/thepaper/user.ts +174 -0
  271. package/lib/routes/tongji/sem/_utils.ts +65 -0
  272. package/lib/routes/tongji/sem/notice.ts +58 -0
  273. package/lib/routes/toutiao/a-bogus.ts +540 -0
  274. package/lib/routes/toutiao/namespace.ts +7 -0
  275. package/lib/routes/toutiao/templates/video.art +7 -0
  276. package/lib/routes/toutiao/types.ts +443 -0
  277. package/lib/routes/toutiao/user.ts +127 -0
  278. package/lib/routes/twitter/api/mobile-api/api.ts +8 -0
  279. package/lib/routes/twitter/api/web-api/utils.ts +21 -9
  280. package/lib/routes/twitter/namespace.ts +1 -0
  281. package/lib/routes/twitter/utils.ts +7 -1
  282. package/lib/routes/vertikal/latest.ts +73 -0
  283. package/lib/routes/vertikal/namespace.ts +7 -0
  284. package/lib/routes/vice/topic.ts +1 -1
  285. package/lib/routes/vocus/publication.ts +1 -1
  286. package/lib/routes/vocus/user.ts +1 -1
  287. package/lib/routes/wallstreetcn/calendar.ts +90 -0
  288. package/lib/routes/wallstreetcn/hot.ts +2 -2
  289. package/lib/routes/wallstreetcn/live.ts +1 -1
  290. package/lib/routes/wallstreetcn/news.ts +5 -3
  291. package/lib/routes/weibo/timeline.ts +3 -2
  292. package/lib/routes/wellcee/rent.ts +1 -1
  293. package/lib/routes/wufazhuce/namespace.ts +7 -0
  294. package/lib/routes/wufazhuce/one.ts +90 -0
  295. package/lib/routes/xiaoheihe/discount.ts +72 -28
  296. package/lib/routes/xiaohongshu/user.ts +40 -3
  297. package/lib/routes/xiaohongshu/util.ts +112 -105
  298. package/lib/routes/xsijishe/rank.ts +33 -7
  299. package/lib/routes/xueqiu/cookies.ts +13 -18
  300. package/lib/routes/yahoo/news/listid.ts +4 -1
  301. package/lib/routes/yahoo/news/provider-helper.ts +4 -1
  302. package/lib/routes/yahoo/news/provider.ts +4 -1
  303. package/lib/routes/yande/post.ts +13 -3
  304. package/lib/routes/youtube/user.ts +37 -25
  305. package/lib/routes/zhonglun/index.ts +5 -6
  306. package/lib/routes/zjut/cs/index.ts +1 -1
  307. package/lib/utils/got.ts +2 -1
  308. package/lib/utils/helpers.test.ts +13 -1
  309. package/lib/utils/helpers.ts +10 -0
  310. package/lib/utils/ofetch.ts +3 -0
  311. package/lib/utils/request-rewriter/fetch.test.ts +94 -0
  312. package/lib/utils/request-rewriter/fetch.ts +14 -1
  313. package/lib/views/error.tsx +1 -1
  314. package/package.json +40 -37
  315. package/lib/routes/78dm/templates/image.art +0 -1
  316. package/lib/routes/deeplearning/thebatch.ts +0 -74
  317. package/lib/routes/syosetu/chapter.ts +0 -87
  318. package/lib/routes/xiaohongshu/notes.ts +0 -39
  319. package/lib/routes-deprecated/furaffinity/browse.js +0 -44
  320. package/lib/routes-deprecated/furaffinity/commissions.js +0 -39
  321. package/lib/routes-deprecated/furaffinity/favorites.js +0 -56
  322. package/lib/routes-deprecated/furaffinity/gallery.js +0 -56
  323. package/lib/routes-deprecated/furaffinity/home.js +0 -72
  324. package/lib/routes-deprecated/furaffinity/journal-comments.js +0 -50
  325. package/lib/routes-deprecated/furaffinity/journals.js +0 -49
  326. package/lib/routes-deprecated/furaffinity/scraps.js +0 -56
  327. package/lib/routes-deprecated/furaffinity/search.js +0 -56
  328. package/lib/routes-deprecated/furaffinity/shouts.js +0 -40
  329. package/lib/routes-deprecated/furaffinity/status.js +0 -38
  330. package/lib/routes-deprecated/furaffinity/submission-comments.js +0 -50
  331. package/lib/routes-deprecated/furaffinity/watchers.js +0 -47
  332. package/lib/routes-deprecated/furaffinity/watching.js +0 -47
  333. package/lib/routes-deprecated/ieee/author.js +0 -30
  334. package/lib/routes-deprecated/qstheory/index.js +0 -122
@@ -2,6 +2,30 @@ import { config } from '@/config';
2
2
  import logger from '@/utils/logger';
3
3
  import { parseDate } from '@/utils/parse-date';
4
4
  import puppeteer from '@/utils/puppeteer';
5
+ import { ofetch } from 'ofetch';
6
+ import { load } from 'cheerio';
7
+ import cache from '@/utils/cache';
8
+
9
+ // Common headers for requests
10
+ const getHeaders = (cookie?: string) => ({
11
+ Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
12
+ 'Accept-Encoding': 'gzip, deflate, br',
13
+ 'Accept-Language': 'en-US,en;q=0.9',
14
+ 'Cache-Control': 'no-cache',
15
+ Connection: 'keep-alive',
16
+ Host: 'www.xiaohongshu.com',
17
+ Pragma: 'no-cache',
18
+ 'Sec-Ch-Ua': '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
19
+ 'Sec-Ch-Ua-Mobile': '?0',
20
+ 'Sec-Ch-Ua-Platform': '"Windows"',
21
+ 'Sec-Fetch-Dest': 'document',
22
+ 'Sec-Fetch-Mode': 'navigate',
23
+ 'Sec-Fetch-Site': 'none',
24
+ 'Sec-Fetch-User': '?1',
25
+ 'Upgrade-Insecure-Requests': '1',
26
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
27
+ ...(cookie ? { Cookie: cookie } : {}),
28
+ });
5
29
 
6
30
  const getUser = (url, cache) =>
7
31
  cache.tryGet(
@@ -23,7 +47,7 @@ const getUser = (url, cache) =>
23
47
  });
24
48
  await page.waitForSelector('div.reds-tab-item:nth-child(2)');
25
49
 
26
- const initialState = await page.evaluate(() => window.__INITIAL_STATE__);
50
+ const initialState = await page.evaluate(() => (window as any).__INITIAL_STATE__);
27
51
 
28
52
  if (!(await page.$('.lock-icon'))) {
29
53
  await page.click('div.reds-tab-item:nth-child(2)');
@@ -68,7 +92,7 @@ const getBoard = (url, cache) =>
68
92
  logger.http(`Requesting ${url}`);
69
93
  await page.goto(url);
70
94
  await page.waitForSelector('.pc-container');
71
- const initialSsrState = await page.evaluate(() => window.__INITIAL_SSR_STATE__);
95
+ const initialSsrState = await page.evaluate(() => (window as any).__INITIAL_SSR_STATE__);
72
96
  return initialSsrState.Main;
73
97
  } finally {
74
98
  browser.close();
@@ -78,108 +102,6 @@ const getBoard = (url, cache) =>
78
102
  false
79
103
  );
80
104
 
81
- const setPageFilter = async (page) => {
82
- await page.setRequestInterception(true);
83
- page.on('request', (req) => {
84
- req.resourceType() === 'document' || req.resourceType() === 'script' || req.resourceType() === 'xhr' || req.resourceType() === 'other' ? req.continue() : req.abort();
85
- });
86
- };
87
-
88
- const getNotes = (url, cache) =>
89
- cache.tryGet(
90
- url + '/notes', // To avoid mixing with the cache for `user.js`
91
- async () => {
92
- let user = '';
93
- let notes = [];
94
-
95
- const browser = await puppeteer({ stealth: true });
96
- try {
97
- const page = await browser.newPage();
98
- await setPageFilter(page);
99
-
100
- logger.http(`Requesting ${url}`);
101
- await page.goto(url);
102
-
103
- let otherInfo = {};
104
- let userPosted = {};
105
- try {
106
- [otherInfo, userPosted] = await Promise.all(
107
- ['/api/sns/web/v1/user/otherinfo', '/api/sns/web/v1/user_posted'].map((p) =>
108
- page
109
- .waitForResponse((res) => {
110
- const req = res.request();
111
- return req.url().includes(p) && req.method() === 'GET';
112
- })
113
- .then((r) => r.json())
114
- )
115
- );
116
- } catch (error) {
117
- throw new Error(`Could not get user information and note list\n${error}`);
118
- }
119
-
120
- await page.close();
121
-
122
- // Get full text for each note
123
- const notesPromise = userPosted.data.notes.map((n) => {
124
- const noteUrl = url + '/' + n.note_id;
125
-
126
- return cache.tryGet(noteUrl, async () => {
127
- const notePage = await browser.newPage();
128
- await setPageFilter(notePage);
129
-
130
- logger.http(`Requesting ${noteUrl}`);
131
- await notePage.goto(noteUrl);
132
-
133
- let feed = {};
134
- try {
135
- feed = await notePage.evaluate(() => window.__INITIAL_STATE__);
136
-
137
- // Sometimes the page is not server-side rendered
138
- if (feed?.note?.note === undefined || JSON.stringify(feed?.note?.note) === '{}') {
139
- const res = await notePage.waitForResponse((res) => {
140
- const req = res.request();
141
- return req.url().includes('/api/sns/web/v1/feed') && req.method() === 'POST';
142
- });
143
-
144
- const json = await res.json();
145
- const note_card = json.data.items[0].note_card;
146
- feed.note.note = {
147
- title: note_card.title,
148
- noteId: note_card.id,
149
- desc: note_card.desc,
150
- tagList: note_card.tag_list,
151
- imageList: note_card.image_list,
152
- user: note_card.user,
153
- time: note_card.time,
154
- lastUpdateTime: note_card.last_update_time,
155
- };
156
- }
157
- } catch (error) {
158
- throw new Error(`Could not get note ${n.note_id}\n${error}`);
159
- }
160
-
161
- await notePage.close();
162
-
163
- if (feed?.note?.note !== undefined && JSON.stringify(feed?.note?.note) !== '{}') {
164
- return feed.note.note;
165
- } else {
166
- throw new Error(`Could not get note ${n.note_id}`);
167
- }
168
- });
169
- });
170
-
171
- user = otherInfo.data.basic_info;
172
- notes = await Promise.all(notesPromise);
173
- } finally {
174
- await browser.close();
175
- }
176
-
177
- return { user, notes };
178
- },
179
- config.cache.routeExpire,
180
- false
181
- );
182
-
183
105
  const formatText = (text) => text.replaceAll(/(\r\n|\r|\n)/g, '<br>').replaceAll('\t', '&emsp;');
184
106
 
185
107
  // tag_list.id has nothing to do with its url
@@ -196,4 +118,89 @@ const formatNote = (url, note) => ({
196
118
  updated: parseDate(note.lastUpdateTime, 'x'),
197
119
  });
198
120
 
199
- export { getUser, getBoard, getNotes, formatText, formatNote };
121
+ async function renderNotesFulltext(notes, urlPrex) {
122
+ const data: Array<{
123
+ title: string;
124
+ link: string;
125
+ description: string;
126
+ author: string;
127
+ guid: string;
128
+ pubDate: Date;
129
+ }> = [];
130
+ const promises = notes.flatMap((note) =>
131
+ note.map(async ({ noteCard, id }) => {
132
+ const link = `${urlPrex}/${id}`;
133
+ const { title, description, pubDate } = await getFullNote(link);
134
+ return {
135
+ title,
136
+ link,
137
+ description,
138
+ author: noteCard.user.nickName,
139
+ guid: noteCard.noteId,
140
+ pubDate,
141
+ };
142
+ })
143
+ );
144
+ data.push(...(await Promise.all(promises)));
145
+ return data;
146
+ }
147
+
148
+ async function getFullNote(link) {
149
+ const data = (await cache.tryGet(link, async () => {
150
+ const res = await ofetch(link, {
151
+ headers: getHeaders(config.xiaohongshu.cookie),
152
+ });
153
+ const $ = load(res);
154
+ const script = extractInitialState($);
155
+ const state = JSON.parse(script);
156
+ const note = state.note.noteDetailMap[state.note.firstNoteId].note;
157
+ const images = note.imageList.map((image) => image.urlDefault);
158
+ const title = note.title;
159
+ let desc = note.desc;
160
+ desc = desc.replaceAll(/\[.*?\]/g, '');
161
+ desc = desc.replaceAll(/#(.*?)#/g, '#$1');
162
+ desc = desc.replaceAll('\n', '<br>');
163
+ const pubDate = new Date(note.time);
164
+ const description = `${images.map((image) => `<img src="${image}">`).join('')}<br>${title}<br>${desc}`;
165
+ return {
166
+ title,
167
+ description,
168
+ pubDate,
169
+ };
170
+ })) as Promise<{ title: string; description: string; pubDate: Date }>;
171
+ return data;
172
+ }
173
+
174
+ async function getUserWithCookie(url: string, cookie: string) {
175
+ const res = await ofetch(url, {
176
+ headers: getHeaders(cookie),
177
+ });
178
+ const $ = load(res);
179
+ const paths = $('#userPostedFeeds > section > div > a.cover.ld.mask').map((i, item) => item.attributes[3].value);
180
+ const script = extractInitialState($);
181
+ const state = JSON.parse(script);
182
+ let index = 0;
183
+ for (const item of state.user.notes.flat()) {
184
+ const path = paths[index];
185
+ if (path && path.includes('?')) {
186
+ item.id = item.id + path?.substring(path.indexOf('?'));
187
+ }
188
+ index = index + 1;
189
+ }
190
+ return state.user;
191
+ }
192
+
193
+ // Add helper function to extract initial state
194
+ function extractInitialState($) {
195
+ let script = $('script')
196
+ .filter((i, script) => {
197
+ const text = script.children[0]?.data;
198
+ return text?.startsWith('window.__INITIAL_STATE__=');
199
+ })
200
+ .text();
201
+ script = script.slice('window.__INITIAL_STATE__='.length);
202
+ script = script.replaceAll('undefined', 'null');
203
+ return script;
204
+ }
205
+
206
+ export { getUser, getBoard, formatText, formatNote, renderNotesFulltext, getFullNote, getUserWithCookie };
@@ -8,9 +8,17 @@ const baseUrl = 'https://xsijishe.com';
8
8
 
9
9
  export const route: Route = {
10
10
  path: '/rank/:type',
11
- categories: ['bbs'],
11
+ categories: ['bbs', 'popular'],
12
12
  example: '/xsijishe/rank/weekly',
13
- parameters: { type: '排行榜类型: weekly | monthly' },
13
+ parameters: {
14
+ type: {
15
+ description: '排行榜类型',
16
+ options: [
17
+ { value: 'weekly', label: '周榜' },
18
+ { value: 'monthly', label: '月榜' },
19
+ ],
20
+ },
21
+ },
14
22
  features: {
15
23
  requireConfig: [
16
24
  {
@@ -29,23 +37,25 @@ export const route: Route = {
29
37
  supportScihub: false,
30
38
  },
31
39
  name: '排行榜',
32
- maintainers: ['akynazh'],
40
+ maintainers: ['akynazh', 'AiraNadih'],
33
41
  handler,
34
42
  };
35
43
 
36
44
  async function handler(ctx) {
37
45
  const rankType = ctx.req.param('type');
38
46
  let title;
39
- let rankId;
47
+ let index; // 用于选择第几个 li
48
+
40
49
  if (rankType === 'weekly') {
41
50
  title = '司机社综合周排行榜';
42
- rankId = 'nex_recons_demens';
51
+ index = 0; // 第一个 li 是周榜
43
52
  } else if (rankType === 'monthly') {
44
53
  title = '司机社综合月排行榜';
45
- rankId = 'nex_recons_demens1';
54
+ index = 1; // 第二个 li 是月榜
46
55
  } else {
47
56
  throw new InvalidParameterError('Invalid rank type');
48
57
  }
58
+
49
59
  const url = `${baseUrl}/portal.php`;
50
60
  const headers = {
51
61
  'Accept-Encoding': 'gzip, deflate, br',
@@ -53,11 +63,26 @@ async function handler(ctx) {
53
63
  Cookie: config.xsijishe.cookie,
54
64
  'User-Agent': config.xsijishe.user_agent,
55
65
  };
66
+
56
67
  const resp = await got(url, {
57
68
  headers,
58
69
  });
70
+
71
+ const redirectMatch = resp.data.match(/window\.location\.href\s*=\s*"([^"]+)"/);
72
+ if (redirectMatch) {
73
+ const redirectUrl = `${baseUrl}${redirectMatch[1]}`;
74
+ // 使用提取到的地址重新请求
75
+ const realResp = await got(redirectUrl, {
76
+ headers,
77
+ });
78
+ resp.data = realResp.data;
79
+ }
80
+
59
81
  const $ = load(resp.data);
60
- let items = $(`#${rankId} dd`)
82
+ // 根据 index 选择对应的 li,然后获取其中的 dd 元素
83
+ let items = $('.nex_recon_lists ul li')
84
+ .eq(index)
85
+ .find('.nex_recons_demens dl dd')
61
86
  .toArray()
62
87
  .map((item) => {
63
88
  item = $(item);
@@ -68,6 +93,7 @@ async function handler(ctx) {
68
93
  link: `${baseUrl}/${link}`,
69
94
  };
70
95
  });
96
+
71
97
  items = await Promise.all(
72
98
  items.map((item) =>
73
99
  cache.tryGet(item.link, async () => {
@@ -1,29 +1,24 @@
1
- import ofetch from '@/utils/ofetch';
2
1
  import cache from '@/utils/cache';
3
2
  import { config } from '@/config';
4
- import { getAcwScV2ByArg1 } from '@/routes/5eplay/utils';
3
+ import puppeteer from '@/utils/puppeteer';
4
+ import { getCookies } from '@/utils/puppeteer-utils';
5
5
 
6
6
  export const parseToken = (link: string) =>
7
7
  cache.tryGet(
8
8
  'xueqiu:token',
9
9
  async () => {
10
- const r = await ofetch(link);
11
-
12
- let acw_sc__v2 = '';
13
- const matches = r.match(/var arg1='(.*?)';/);
14
- if (matches) {
15
- acw_sc__v2 = getAcwScV2ByArg1(matches[1]);
16
- }
17
- const acw_sc__v2_cookie = `acw_sc__v2=${acw_sc__v2}`;
18
- const res = await ofetch.raw(link, {
19
- headers: {
20
- Cookie: acw_sc__v2_cookie,
21
- },
10
+ const browser = await puppeteer({ stealth: true });
11
+ const page = await browser.newPage();
12
+ await page.setRequestInterception(true);
13
+ page.on('request', (request) => {
14
+ request.resourceType() === 'document' ? request.continue() : request.abort();
22
15
  });
23
- const cookieArray = res.headers.getSetCookie();
24
- const xq_a_token_cookie = cookieArray.find((c) => c.startsWith('xq_a_token='));
25
-
26
- return `${acw_sc__v2_cookie}; ${xq_a_token_cookie}`;
16
+ await page.goto(link, {
17
+ waitUntil: 'domcontentloaded',
18
+ });
19
+ await page.evaluate(() => document.documentElement.innerHTML);
20
+ const cookies = await getCookies(page);
21
+ return cookies;
27
22
  },
28
23
  config.cache.routeExpire,
29
24
  false
@@ -18,7 +18,10 @@ export const route: Route = {
18
18
  },
19
19
  radar: [
20
20
  {
21
- source: ['hk.news.yahoo.com/', 'tw.news.yahoo.com/'],
21
+ source: ['hk.news.yahoo.com/'],
22
+ },
23
+ {
24
+ source: ['tw.news.yahoo.com/'],
22
25
  },
23
26
  ],
24
27
  name: '合作媒體',
@@ -18,7 +18,10 @@ export const route: Route = {
18
18
  },
19
19
  radar: [
20
20
  {
21
- source: ['hk.news.yahoo.com/', 'tw.news.yahoo.com/'],
21
+ source: ['hk.news.yahoo.com/'],
22
+ },
23
+ {
24
+ source: ['tw.news.yahoo.com/'],
22
25
  },
23
26
  ],
24
27
  name: '新聞來源列表',
@@ -18,7 +18,10 @@ export const route: Route = {
18
18
  },
19
19
  radar: [
20
20
  {
21
- source: ['hk.news.yahoo.com/', 'tw.news.yahoo.com/'],
21
+ source: ['hk.news.yahoo.com/'],
22
+ },
23
+ {
24
+ source: ['tw.news.yahoo.com/'],
22
25
  },
23
26
  ],
24
27
  name: '新聞來源',
@@ -1,13 +1,23 @@
1
- import { Route } from '@/types';
1
+ import { Route, ViewType } from '@/types';
2
2
  import got from '@/utils/got';
3
3
  import queryString from 'query-string';
4
4
 
5
5
  export const route: Route = {
6
6
  path: '/post/popular_recent/:period?',
7
- categories: ['picture'],
7
+ categories: ['picture', 'popular'],
8
+ view: ViewType.Pictures,
8
9
  example: '/yande/post/popular_recent/1d',
9
10
  parameters: {
10
- period: '展示时间',
11
+ period: {
12
+ description: '展示时间',
13
+ options: [
14
+ { value: '1d', label: '最近 24 小时' },
15
+ { value: '1w', label: '最近一周' },
16
+ { value: '1m', label: '最近一月' },
17
+ { value: '1y', label: '最近一年' },
18
+ ],
19
+ default: '1d',
20
+ },
11
21
  },
12
22
  radar: [
13
23
  {
@@ -3,9 +3,10 @@ import cache from '@/utils/cache';
3
3
  import utils from './utils';
4
4
  import { config } from '@/config';
5
5
  import { parseDate } from '@/utils/parse-date';
6
- import got from '@/utils/got';
7
- import { load } from 'cheerio';
6
+ import ofetch from '@/utils/ofetch';
7
+ import * as cheerio from 'cheerio';
8
8
  import ConfigNotFoundError from '@/errors/types/config-not-found';
9
+ import NotFoundError from '@/errors/types/not-found';
9
10
 
10
11
  export const route: Route = {
11
12
  path: '/user/:username/:embed?',
@@ -44,35 +45,46 @@ async function handler(ctx) {
44
45
  const username = ctx.req.param('username');
45
46
  const embed = !ctx.req.param('embed');
46
47
 
47
- let playlistId;
48
- let channelName;
49
- let image;
50
- let description;
48
+ let userHandleData;
51
49
  if (username.startsWith('@')) {
52
- const link = `https://www.youtube.com/${username}`;
53
- const response = await got(link);
54
- const $ = load(response.data);
55
- const ytInitialData = JSON.parse(
56
- $('script')
57
- .text()
58
- .match(/ytInitialData = ({.*?});/)?.[1] || '{}'
59
- );
60
- const channelId = ytInitialData.metadata.channelMetadataRenderer.externalId;
61
- channelName = ytInitialData.metadata.channelMetadataRenderer.title;
62
- image = ytInitialData.metadata.channelMetadataRenderer.avatar?.thumbnails?.[0]?.url;
63
- description = ytInitialData.metadata.channelMetadataRenderer.description;
64
- playlistId = (await utils.getChannelWithId(channelId, 'contentDetails', cache)).data.items[0].contentDetails.relatedPlaylists.uploads;
50
+ userHandleData = await cache.tryGet(`youtube:handle:${username}`, async () => {
51
+ const link = `https://www.youtube.com/${username}`;
52
+ const response = await ofetch(link);
53
+ const $ = cheerio.load(response);
54
+ const ytInitialData = JSON.parse(
55
+ $('script')
56
+ .text()
57
+ .match(/ytInitialData = ({.*?});/)?.[1] || '{}'
58
+ );
59
+ const metadataRenderer = ytInitialData.metadata.channelMetadataRenderer;
60
+
61
+ const channelId = metadataRenderer.externalId;
62
+ const channelName = metadataRenderer.title;
63
+ const image = metadataRenderer.avatar?.thumbnails?.[0]?.url;
64
+ const description = metadataRenderer.description;
65
+ const playlistId = (await utils.getChannelWithId(channelId, 'contentDetails', cache)).data.items[0].contentDetails.relatedPlaylists.uploads;
66
+
67
+ return {
68
+ channelName,
69
+ image,
70
+ description,
71
+ playlistId,
72
+ };
73
+ });
65
74
  }
66
- playlistId = playlistId || (await utils.getChannelWithUsername(username, 'contentDetails', cache)).data.items[0].contentDetails.relatedPlaylists.uploads;
75
+ const playlistId = userHandleData?.playlistId || (await utils.getChannelWithUsername(username, 'contentDetails', cache)).data.items[0].contentDetails.relatedPlaylists.uploads;
67
76
 
68
- const data = (await utils.getPlaylistItems(playlistId, 'snippet', cache)).data.items;
77
+ const playlistItems = await utils.getPlaylistItems(playlistId, 'snippet', cache);
78
+ if (!playlistItems) {
79
+ throw new NotFoundError("This channel doesn't have any content.");
80
+ }
69
81
 
70
82
  return {
71
- title: `${channelName || username} - YouTube`,
83
+ title: `${userHandleData?.channelName || username} - YouTube`,
72
84
  link: username.startsWith('@') ? `https://www.youtube.com/${username}` : `https://www.youtube.com/user/${username}`,
73
- description: description || `YouTube user ${username}`,
74
- image,
75
- item: data
85
+ description: userHandleData?.description || `YouTube user ${username}`,
86
+ image: userHandleData?.image,
87
+ item: playlistItems.data.items
76
88
  .filter((d) => d.snippet.title !== 'Private video' && d.snippet.title !== 'Deleted video')
77
89
  .map((item) => {
78
90
  const snippet = item.snippet;
@@ -20,22 +20,21 @@ export const handler = async (ctx) => {
20
20
 
21
21
  const $ = load(response);
22
22
 
23
- let items = $('div#dataList h3')
23
+ let items = $('div#dataList > dl > dd, div#dataList > ul > li')
24
24
  .slice(0, limit)
25
25
  .toArray()
26
26
  .map((item) => {
27
27
  item = $(item);
28
28
 
29
- const title = item.text();
30
29
  const description = art(path.join(__dirname, 'templates/description.art'), {
31
- intro: item.next().text(),
30
+ intro: item.find('p').text(),
32
31
  });
33
32
 
34
33
  return {
35
- title,
34
+ title: item.find('h3 > a').text(),
36
35
  description,
37
- pubDate: parseDate(item.find('span').first().text()),
38
- link: item.find('a').prop('href'),
36
+ pubDate: parseDate(item.find('span').text()),
37
+ link: item.find('h3 > a').prop('href'),
39
38
  language,
40
39
  };
41
40
  });
@@ -27,7 +27,7 @@ export const route: Route = {
27
27
  handler,
28
28
  radar: [
29
29
  {
30
- source: ['cs.zjut.edu.cn/jsp/newsclass.jsp?wcId=:type'],
30
+ source: ['cs.zjut.edu.cn/jsp/newsclass.jsp'],
31
31
  target: '/cs/:type',
32
32
  },
33
33
  ],
package/lib/utils/got.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { destr } from 'destr';
2
2
  import ofetch from '@/utils/ofetch';
3
+ import { getSearchParamsString } from './helpers';
3
4
 
4
5
  const getFakeGot = (defaultOptions?: any) => {
5
6
  const fakeGot = (request, options?: any) => {
@@ -35,7 +36,7 @@ const getFakeGot = (defaultOptions?: any) => {
35
36
  delete options.form;
36
37
  }
37
38
  if (options?.searchParams) {
38
- request += '?' + new URLSearchParams(options.searchParams).toString();
39
+ request += '?' + getSearchParamsString(options.searchParams);
39
40
  delete options.searchParams;
40
41
  }
41
42
 
@@ -1,8 +1,20 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { getRouteNameFromPath } from '@/utils/helpers';
2
+ import { getRouteNameFromPath, getSearchParamsString } from '@/utils/helpers';
3
3
 
4
4
  describe('helpers', () => {
5
5
  it('getRouteNameFromPath', () => {
6
6
  expect(getRouteNameFromPath('/test/1')).toBe('test');
7
7
  });
8
+
9
+ it('getSearchParamsString', () => {
10
+ expect(getSearchParamsString({ a: 1, b: 2 })).toBe('a=1&b=2');
11
+ expect(getSearchParamsString({ a: 1, b: undefined })).toBe('a=1');
12
+ expect(getSearchParamsString({ a: undefined })).toBe('');
13
+ expect(getSearchParamsString({})).toBe('');
14
+
15
+ const searchParams = new URLSearchParams();
16
+ searchParams.append('ids[]', '1');
17
+ searchParams.append('ids[]', '2');
18
+ expect(getSearchParamsString(searchParams)).toBe('ids%5B%5D=1&ids%5B%5D=2');
19
+ });
8
20
  });
@@ -1,5 +1,6 @@
1
1
  import { fileURLToPath } from 'url';
2
2
  import path from 'node:path';
3
+ import { stringifyQuery } from 'ufo';
3
4
 
4
5
  export const getRouteNameFromPath = (path: string) => {
5
6
  const p = path.split('/').filter(Boolean);
@@ -30,3 +31,12 @@ export const getCurrentPath = (metaUrl: string) => {
30
31
  const __filename = path.join(fileURLToPath(metaUrl));
31
32
  return path.dirname(__filename);
32
33
  };
34
+
35
+ function isPureObject(o: any) {
36
+ return Object.prototype.toString.call(o) === '[object Object]';
37
+ }
38
+
39
+ export function getSearchParamsString(searchParams: any) {
40
+ const searchParamsString = isPureObject(searchParams) ? stringifyQuery(searchParams) : null;
41
+ return searchParamsString ?? new URLSearchParams(searchParams).toString();
42
+ }
@@ -1,6 +1,9 @@
1
1
  import { createFetch } from 'ofetch';
2
2
  import { config } from '@/config';
3
3
  import logger from '@/utils/logger';
4
+ import { register } from 'node-network-devtools';
5
+
6
+ config.enableRemoteDebugging && process.env.NODE_ENV === 'dev' && register();
4
7
 
5
8
  const rofetch = createFetch().create({
6
9
  retryStatusCodes: [400, 408, 409, 425, 429, 500, 502, 503, 504],