rsshub 1.0.0-master.ff3a9ce → 1.0.0-master.ff3ea5a

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.

Potentially problematic release.


This version of rsshub might be problematic. Click here for more details.

Files changed (388) hide show
  1. package/lib/config.ts +38 -0
  2. package/lib/errors/index.tsx +3 -2
  3. package/lib/middleware/debug.ts +6 -4
  4. package/lib/middleware/header.ts +4 -2
  5. package/lib/registry.test.ts +24 -1
  6. package/lib/registry.ts +40 -11
  7. package/lib/router.js +1 -1
  8. package/lib/routes/121/namespace.ts +9 -0
  9. package/lib/routes/121/templates/description.art +17 -0
  10. package/lib/routes/121/weather-live.ts +104 -0
  11. package/lib/routes/141jav/index.ts +3 -0
  12. package/lib/routes/141ppv/index.ts +3 -0
  13. package/lib/routes/163/news/rank.ts +1 -1
  14. package/lib/routes/18comic/album.ts +59 -64
  15. package/lib/routes/18comic/index.ts +1 -0
  16. package/lib/routes/18comic/search.ts +53 -4
  17. package/lib/routes/18comic/utils.ts +70 -1
  18. package/lib/routes/2048/index.ts +1 -0
  19. package/lib/routes/30secondsofcode/utils.ts +0 -1
  20. package/lib/routes/6park/index.ts +1 -1
  21. package/lib/routes/7mmtv/index.ts +1 -0
  22. package/lib/routes/91porn/index.ts +1 -0
  23. package/lib/routes/95mm/tab.ts +1 -0
  24. package/lib/routes/agefans/detail.ts +1 -1
  25. package/lib/routes/aliyun/database-month.ts +1 -1
  26. package/lib/routes/anthropic/research.ts +112 -0
  27. package/lib/routes/apnews/topics.ts +2 -1
  28. package/lib/routes/apnews/utils.ts +1 -1
  29. package/lib/routes/apple/design.ts +54 -0
  30. package/lib/routes/apple/namespace.ts +1 -1
  31. package/lib/routes/aqara/post.ts +1 -1
  32. package/lib/routes/augmentcode/blog.ts +161 -0
  33. package/lib/routes/augmentcode/namespace.ts +9 -0
  34. package/lib/routes/augmentcode/templates/description.art +17 -0
  35. package/lib/routes/azul/namespace.ts +9 -0
  36. package/lib/routes/azul/packages.ts +105 -0
  37. package/lib/routes/banshujiang/index.ts +763 -0
  38. package/lib/routes/banshujiang/namespace.ts +9 -0
  39. package/lib/routes/banshujiang/templates/description.art +17 -0
  40. package/lib/routes/bfl/announcements.ts +1 -1
  41. package/lib/routes/bilibili/cache.ts +78 -0
  42. package/lib/routes/bilibili/danmaku.ts +1 -1
  43. package/lib/routes/bilibili/dynamic.ts +8 -2
  44. package/lib/routes/bilibili/video.ts +31 -73
  45. package/lib/routes/biquge/index.ts +1 -1
  46. package/lib/routes/bsky/keyword.ts +15 -10
  47. package/lib/routes/capitalmind/insights.ts +40 -0
  48. package/lib/routes/capitalmind/namespace.ts +8 -0
  49. package/lib/routes/capitalmind/podcasts.ts +41 -0
  50. package/lib/routes/capitalmind/utils.ts +122 -0
  51. package/lib/routes/cartoonmad/comic.ts +1 -1
  52. package/lib/routes/chikubi/index.ts +1 -0
  53. package/lib/routes/chikubi/tag.ts +1 -0
  54. package/lib/routes/chocolatey/namespace.ts +9 -0
  55. package/lib/routes/chocolatey/packages.ts +122 -0
  56. package/lib/routes/cmde/index.ts +1 -1
  57. package/lib/routes/cockroachlabs/blog.ts +113 -0
  58. package/lib/routes/cockroachlabs/namespace.ts +7 -0
  59. package/lib/routes/cool18/index.ts +4 -1
  60. package/lib/routes/coomer/index.ts +12 -6
  61. package/lib/routes/coomer/namespace.ts +1 -1
  62. package/lib/routes/copymanga/comic.ts +7 -9
  63. package/lib/routes/css-tricks/articles.ts +48 -0
  64. package/lib/routes/css-tricks/collections.ts +62 -0
  65. package/lib/routes/css-tricks/namespace.ts +8 -0
  66. package/lib/routes/css-tricks/popular.ts +39 -0
  67. package/lib/routes/css-tricks/utils.ts +103 -0
  68. package/lib/routes/cursor/changelog.ts +26 -17
  69. package/lib/routes/cursor/namespace.ts +1 -1
  70. package/lib/routes/cw/utils.ts +6 -13
  71. package/lib/routes/dealstreetasia/home.ts +1 -1
  72. package/lib/routes/dedao/index.ts +3 -3
  73. package/lib/routes/deepl/blog.ts +422 -0
  74. package/lib/routes/deepl/namespace.ts +9 -0
  75. package/lib/routes/deepl/templates/description.art +21 -0
  76. package/lib/routes/deeplearning/the-batch.ts +1 -1
  77. package/lib/routes/dev.to/guides.ts +100 -0
  78. package/lib/routes/dev.to/namespace.ts +8 -0
  79. package/lib/routes/dev.to/top.ts +105 -0
  80. package/lib/routes/dgut/jwb.ts +73 -0
  81. package/lib/routes/dgut/namespace.ts +9 -0
  82. package/lib/routes/digitalpolicyalert/activity-tracker.ts +135 -0
  83. package/lib/routes/digitalpolicyalert/namespace.ts +9 -0
  84. package/lib/routes/dnaindia/common.ts +26 -8
  85. package/lib/routes/douban/other/list.ts +3 -3
  86. package/lib/routes/e-hentai/index.ts +4 -1
  87. package/lib/routes/ehentai/tag.ts +1 -0
  88. package/lib/routes/englishhome/index.ts +55 -0
  89. package/lib/routes/englishhome/namespace.ts +6 -0
  90. package/lib/routes/epicgames/index.ts +2 -2
  91. package/lib/routes/espn/news.ts +1 -1
  92. package/lib/routes/expats/czech-news.ts +272 -0
  93. package/lib/routes/expats/namespace.ts +9 -0
  94. package/lib/routes/fanbox/index.ts +1 -0
  95. package/lib/routes/fanbox/utils.ts +1 -1
  96. package/lib/routes/fansly/tag.ts +1 -0
  97. package/lib/routes/fantia/search.ts +1 -1
  98. package/lib/routes/fantube/creator.ts +69 -0
  99. package/lib/routes/fantube/namespace.ts +7 -0
  100. package/lib/routes/fantube/templates/post.art +17 -0
  101. package/lib/routes/fantube/types.ts +100 -0
  102. package/lib/routes/fantube/utils.ts +268 -0
  103. package/lib/routes/follow/profile.ts +1 -1
  104. package/lib/routes/followin/news.ts +1 -1
  105. package/lib/routes/foresightnews/util.ts +1 -1
  106. package/lib/routes/freexcomic/book.ts +4 -1
  107. package/lib/routes/gamer/hot.ts +5 -5
  108. package/lib/routes/gaoyu/blog.ts +145 -0
  109. package/lib/routes/gaoyu/namespace.ts +9 -0
  110. package/lib/routes/gaoyu/templates/description.art +7 -0
  111. package/lib/routes/geocaching/blogs.ts +2 -2
  112. package/lib/routes/github/namespace.ts +1 -1
  113. package/lib/routes/github/private-feed.ts +229 -0
  114. package/lib/routes/github/star.ts +1 -1
  115. package/lib/routes/google/extension.ts +9 -10
  116. package/lib/routes/gov/beijing/bphc/index.ts +1 -1
  117. package/lib/routes/gov/ccdi/index.ts +1 -1
  118. package/lib/routes/gov/chongqing/sydwgkzp.ts +22 -6
  119. package/lib/routes/gov/customs/list.ts +1 -1
  120. package/lib/routes/gov/general/general.ts +1 -1
  121. package/lib/routes/gov/hangzhou/zwfw.ts +1 -1
  122. package/lib/routes/gov/pbc/goutongjiaoliu.ts +1 -1
  123. package/lib/routes/gov/shenzhen/szlh/index.ts +77 -0
  124. package/lib/routes/gov/shenzhen/szlh/namespace.ts +8 -0
  125. package/lib/routes/grist/utils.ts +5 -4
  126. package/lib/routes/hanime1/search.ts +4 -2
  127. package/lib/routes/hellogithub/report.ts +1 -1
  128. package/lib/routes/hit/hitgs.ts +240 -54
  129. package/lib/routes/hit/templates/description.art +7 -0
  130. package/lib/routes/hlju/namespace.ts +8 -0
  131. package/lib/routes/hlju/news.ts +141 -0
  132. package/lib/routes/hostmonit/cloudflareyes.ts +1 -1
  133. package/lib/routes/hotukdeals/index.ts +1 -1
  134. package/lib/routes/hyperdash/namespace.ts +7 -0
  135. package/lib/routes/hyperdash/templates/description.art +34 -0
  136. package/lib/routes/hyperdash/top-traders.ts +84 -0
  137. package/lib/routes/hyperdash/utils.ts +49 -0
  138. package/lib/routes/hypergryph/arknights/announce.ts +1 -1
  139. package/lib/routes/ielts/index.ts +1 -1
  140. package/lib/routes/infoq/topic.ts +16 -13
  141. package/lib/routes/instagram/web-api/index.ts +17 -13
  142. package/lib/routes/instagram/web-api/utils.ts +46 -63
  143. package/lib/routes/jamesclear/book-summaries.ts +40 -0
  144. package/lib/routes/jamesclear/great-speeches.ts +40 -0
  145. package/lib/routes/jamesclear/namespace.ts +8 -0
  146. package/lib/routes/jamesclear/quotes.ts +40 -0
  147. package/lib/routes/jamesclear/three-two-one.ts +41 -0
  148. package/lib/routes/jamesclear/utils.ts +22 -0
  149. package/lib/routes/japanpost/utils.ts +2 -2
  150. package/lib/routes/javbus/index.ts +3 -0
  151. package/lib/routes/javdb/tags.ts +1 -0
  152. package/lib/routes/javlibrary/star.ts +1 -0
  153. package/lib/routes/javtiful/actress.ts +3 -0
  154. package/lib/routes/javtiful/channel.ts +3 -0
  155. package/lib/routes/javtrailers/casts.ts +3 -0
  156. package/lib/routes/jetbrains/comments.ts +99 -0
  157. package/lib/routes/jetbrains/namespace.ts +8 -0
  158. package/lib/routes/jimmyspa/books.ts +1 -1
  159. package/lib/routes/jimmyspa/news.ts +1 -1
  160. package/lib/routes/jpxgmn/tab.ts +3 -0
  161. package/lib/routes/kakuyomu/works.ts +3 -0
  162. package/lib/routes/kaopu/news.ts +1 -1
  163. package/lib/routes/kemono/const.ts +1 -1
  164. package/lib/routes/kemono/index.ts +12 -11
  165. package/lib/routes/kemono/namespace.ts +1 -1
  166. package/lib/routes/kiro/blog.ts +131 -0
  167. package/lib/routes/kiro/changelog.ts +128 -0
  168. package/lib/routes/kiro/namespace.ts +9 -0
  169. package/lib/routes/konachan/post.ts +3 -0
  170. package/lib/routes/kovidgoyal/kitty/changelog.ts +83 -0
  171. package/lib/routes/kovidgoyal/namespace.ts +7 -0
  172. package/lib/routes/kunchengblog/essay.ts +1 -1
  173. package/lib/routes/landiannews/category.ts +1 -1
  174. package/lib/routes/landiannews/index.ts +1 -1
  175. package/lib/routes/landiannews/tag.ts +1 -1
  176. package/lib/routes/line/utils.ts +1 -1
  177. package/lib/routes/lineageos/changes.ts +79 -0
  178. package/lib/routes/lineageos/namespace.ts +9 -0
  179. package/lib/routes/linkedin/cn/renderer.ts +1 -1
  180. package/lib/routes/liquipedia/cs-matches.ts +42 -14
  181. package/lib/routes/literotica/new.ts +1 -0
  182. package/lib/routes/lofter/tag.ts +27 -3
  183. package/lib/routes/makerworld/contest.ts +51 -0
  184. package/lib/routes/makerworld/namespace.ts +7 -0
  185. package/lib/routes/makerworld/trending.ts +45 -0
  186. package/lib/routes/makerworld/user-upload.ts +54 -0
  187. package/lib/routes/makerworld/utils.ts +18 -0
  188. package/lib/routes/manhuagui/comic.ts +1 -1
  189. package/lib/routes/manus/blog.ts +77 -0
  190. package/lib/routes/manus/namespace.ts +7 -0
  191. package/lib/routes/mastodon/tag.ts +48 -0
  192. package/lib/routes/mathpix/blog.ts +155 -0
  193. package/lib/routes/mathpix/namespace.ts +9 -0
  194. package/lib/routes/mathpix/templates/description.art +21 -0
  195. package/lib/routes/mckinsey/cn/category-map.ts +9 -15
  196. package/lib/routes/mckinsey/cn/index.ts +77 -40
  197. package/lib/routes/melonbooks/search.ts +1 -0
  198. package/lib/routes/metacritic/index.ts +2 -2
  199. package/lib/routes/meteoblue/namespace.ts +8 -0
  200. package/lib/routes/meteoblue/weathernews.ts +75 -0
  201. package/lib/routes/meteor/index.ts +1 -1
  202. package/lib/routes/microsoft/addon.ts +7 -9
  203. package/lib/routes/mihoyo/zzz/news.ts +106 -0
  204. package/lib/routes/missav/new.ts +2 -1
  205. package/lib/routes/mit/hanlab.ts +61 -0
  206. package/lib/routes/mit/namespace.ts +6 -0
  207. package/lib/routes/mittrchina/index.ts +3 -3
  208. package/lib/routes/mixcloud/config.ts +129 -0
  209. package/lib/routes/mixcloud/index.ts +163 -107
  210. package/lib/routes/mixcloud/user-playlist.ts +31 -0
  211. package/lib/routes/mixi2/community.ts +72 -0
  212. package/lib/routes/mixi2/discovery.ts +52 -0
  213. package/lib/routes/mixi2/home.ts +51 -0
  214. package/lib/routes/mixi2/namespace.ts +8 -0
  215. package/lib/routes/mixi2/user.ts +77 -0
  216. package/lib/routes/mixi2/utils.ts +56 -0
  217. package/lib/routes/modelscope/learn.ts +94 -0
  218. package/lib/routes/moodysmismicrosite/report.ts +1 -1
  219. package/lib/routes/musify/index.ts +130 -0
  220. package/lib/routes/musify/namespace.ts +9 -0
  221. package/lib/routes/musikguru/namespace.ts +9 -0
  222. package/lib/routes/musikguru/news.ts +146 -0
  223. package/lib/routes/musikguru/templates/description.art +21 -0
  224. package/lib/routes/myfans/post.ts +1 -0
  225. package/lib/routes/nankai/cc-notice.ts +114 -0
  226. package/lib/routes/nankai/jwc.ts +131 -0
  227. package/lib/routes/nankai/namespace.ts +7 -0
  228. package/lib/routes/nankai/notice.ts +84 -0
  229. package/lib/routes/nankai/yzb.ts +97 -0
  230. package/lib/routes/neu/yz.ts +172 -0
  231. package/lib/routes/newslaundry/explainer.ts +30 -0
  232. package/lib/routes/newslaundry/namespace.ts +8 -0
  233. package/lib/routes/newslaundry/nl-cheatsheet.ts +30 -0
  234. package/lib/routes/newslaundry/nl-collaborations.ts +30 -0
  235. package/lib/routes/newslaundry/podcast.ts +61 -0
  236. package/lib/routes/newslaundry/reports.ts +30 -0
  237. package/lib/routes/newslaundry/shot.ts +30 -0
  238. package/lib/routes/newslaundry/subscriber-only.ts +30 -0
  239. package/lib/routes/newslaundry/templates/description.art +28 -0
  240. package/lib/routes/newslaundry/utils.ts +108 -0
  241. package/lib/routes/newswav/latest.ts +100 -0
  242. package/lib/routes/newswav/namespace.ts +7 -0
  243. package/lib/routes/nhentai/index.ts +1 -0
  244. package/lib/routes/nifd/research.ts +16 -0
  245. package/lib/routes/nio/namespace.ts +9 -0
  246. package/lib/routes/nio/nioradio.ts +75 -0
  247. package/lib/routes/njust/cs.ts +58 -0
  248. package/lib/routes/njust/utils.ts +1 -1
  249. package/lib/routes/ntdm/video.ts +1 -1
  250. package/lib/routes/nuaa/utils/pypasswaf.ts +1 -1
  251. package/lib/routes/oilchem/index.ts +1 -1
  252. package/lib/routes/oncc/money18.ts +1 -1
  253. package/lib/routes/openai/cookbook.ts +1 -1
  254. package/lib/routes/paulgraham/article.ts +2 -2
  255. package/lib/routes/picuki/profile.ts +4 -0
  256. package/lib/routes/pixiv/bookmarks.ts +1 -1
  257. package/lib/routes/pixiv/novel-api/series/sfw.ts +1 -1
  258. package/lib/routes/pixiv/novels.ts +2 -2
  259. package/lib/routes/pixiv/user.ts +1 -1
  260. package/lib/routes/playno1/av.ts +1 -0
  261. package/lib/routes/pornhub/model.ts +3 -7
  262. package/lib/routes/pornhub/pornstar.ts +2 -7
  263. package/lib/routes/pornhub/users.ts +2 -7
  264. package/lib/routes/pornhub/utils.ts +12 -1
  265. package/lib/routes/qingting/podcast.ts +2 -1
  266. package/lib/routes/qipamaijia/index.ts +1 -0
  267. package/lib/routes/questmobile/report.ts +1 -1
  268. package/lib/routes/railway/index.ts +73 -0
  269. package/lib/routes/railway/namespace.ts +8 -0
  270. package/lib/routes/reuters/common.ts +0 -3
  271. package/lib/routes/rockthejvm/articles.ts +167 -0
  272. package/lib/routes/rockthejvm/namespace.ts +9 -0
  273. package/lib/routes/rockthejvm/templates/description.art +7 -0
  274. package/lib/routes/samrdprc/namespace.ts +7 -0
  275. package/lib/routes/samrdprc/news.ts +79 -0
  276. package/lib/routes/sankei/namespace.ts +7 -0
  277. package/lib/routes/sankei/news.ts +68 -0
  278. package/lib/routes/sankei/topics.ts +71 -0
  279. package/lib/routes/sciencenet/user.ts +1 -1
  280. package/lib/routes/scoop/apps.ts +188 -0
  281. package/lib/routes/scoop/namespace.ts +9 -0
  282. package/lib/routes/scoop/templates/description.art +56 -0
  283. package/lib/routes/scpta/namespace.ts +8 -0
  284. package/lib/routes/scpta/news.ts +101 -0
  285. package/lib/routes/seekingalpha/index.ts +1 -1
  286. package/lib/routes/sehuatang/index.ts +29 -31
  287. package/lib/routes/setn/index.ts +11 -2
  288. package/lib/routes/seu/cyber/index.ts +78 -0
  289. package/lib/routes/sicau/jiaowu.ts +42 -34
  290. package/lib/routes/sis001/forum.ts +1 -0
  291. package/lib/routes/sjtu/seiee/icisee.ts +67 -0
  292. package/lib/routes/sketis/isabelle-dev/blog/index.ts +1 -1
  293. package/lib/routes/smartlink/index.ts +13 -3
  294. package/lib/routes/sohu/mp.ts +17 -6
  295. package/lib/routes/sotwe/namespace.ts +1 -0
  296. package/lib/routes/sotwe/user.ts +98 -0
  297. package/lib/routes/spankbang/new-videos.ts +1 -0
  298. package/lib/routes/spglobal/ratings.ts +1 -1
  299. package/lib/routes/stanford/blog.ts +77 -0
  300. package/lib/routes/stanford/namespace.ts +7 -0
  301. package/lib/routes/syosetu/index.ts +1 -1
  302. package/lib/routes/t66y/index.ts +1 -0
  303. package/lib/routes/taobao/mysql.ts +157 -0
  304. package/lib/routes/taobao/namespace.ts +2 -2
  305. package/lib/routes/techcrunch/category.ts +48 -0
  306. package/lib/routes/telegram/channel.ts +2 -2
  307. package/lib/routes/tesla/cx.ts +1 -1
  308. package/lib/routes/test/index.ts +1 -1
  309. package/lib/routes/themoviedb/episodes.ts +1 -1
  310. package/lib/routes/thewirehindi/category.ts +78 -0
  311. package/lib/routes/thewirehindi/index.ts +43 -0
  312. package/lib/routes/thewirehindi/namespace.ts +7 -0
  313. package/lib/routes/thewirehindi/templates/description.art +9 -0
  314. package/lib/routes/thewirehindi/utils.ts +33 -0
  315. package/lib/routes/threads/utils.ts +2 -2
  316. package/lib/routes/tidb/blog.ts +314 -0
  317. package/lib/routes/tidb/namespace.ts +9 -0
  318. package/lib/routes/tumblr/namespace.ts +17 -0
  319. package/lib/routes/tumblr/posts.ts +74 -0
  320. package/lib/routes/tumblr/utils.ts +110 -0
  321. package/lib/routes/tver/namespace.ts +7 -0
  322. package/lib/routes/tver/series.ts +98 -0
  323. package/lib/routes/twitter/api/web-api/login.ts +1 -3
  324. package/lib/routes/twitter/api/web-api/utils.ts +23 -1
  325. package/lib/routes/twitter/utils.ts +21 -21
  326. package/lib/routes/udn/breaking-news.ts +2 -2
  327. package/lib/routes/uestc/auto.ts +1 -1
  328. package/lib/routes/uestc/cqe.ts +1 -1
  329. package/lib/routes/uestc/scse.ts +1 -1
  330. package/lib/routes/uestc/sice.ts +1 -1
  331. package/lib/routes/uestc/sise.ts +1 -1
  332. package/lib/routes/upc/jwc.ts +32 -46
  333. package/lib/routes/uptimerobot/rss.ts +1 -1
  334. package/lib/routes/visionias/daily-news-summary.ts +65 -0
  335. package/lib/routes/visionias/monthly-magazine.ts +68 -0
  336. package/lib/routes/visionias/news-today.ts +3 -13
  337. package/lib/routes/visionias/utils.ts +5 -5
  338. package/lib/routes/visionias/weekly-focus.ts +2 -2
  339. package/lib/routes/wdfxw/bookfree.ts +528 -0
  340. package/lib/routes/wdfxw/namespace.ts +8 -0
  341. package/lib/routes/wdfxw/templates/description.art +13 -0
  342. package/lib/routes/whu/swrh.ts +1 -1
  343. package/lib/routes/windsurf/blog.ts +118 -0
  344. package/lib/routes/windsurf/changelog.ts +100 -0
  345. package/lib/routes/windsurf/namespace.ts +9 -0
  346. package/lib/routes/windsurf/templates/description.art +21 -0
  347. package/lib/routes/x6d/index.ts +1 -1
  348. package/lib/routes/xbookcn/blog.ts +1 -0
  349. package/lib/routes/xiaohongshu/util.ts +1 -3
  350. package/lib/routes/xiaoyuzhou/pickup.ts +1 -1
  351. package/lib/routes/ximalaya/album.ts +32 -70
  352. package/lib/routes/ximalaya/types.ts +48 -0
  353. package/lib/routes/ximalaya/utils.ts +9 -7
  354. package/lib/routes/xmanhua/index.ts +1 -0
  355. package/lib/routes/xueqiu/cookies.ts +1 -1
  356. package/lib/routes/xueqiu/user.ts +1 -1
  357. package/lib/routes/xwenming/index.ts +184 -0
  358. package/lib/routes/xwenming/namespace.ts +9 -0
  359. package/lib/routes/yahoo/news/utils.ts +1 -1
  360. package/lib/routes/yande/post.ts +3 -0
  361. package/lib/routes/ymgal/game.ts +1 -0
  362. package/lib/routes/youtube/api/google.ts +27 -0
  363. package/lib/routes/youtube/api/youtubei.ts +70 -20
  364. package/lib/routes/youtube/community.ts +3 -1
  365. package/lib/routes/youtube/utils.ts +16 -14
  366. package/lib/routes/zaobao/util.ts +8 -5
  367. package/lib/routes/zhihu/activities.ts +3 -0
  368. package/lib/routes/zhihu/execlib/x-zse-96-v3.ts +5 -5
  369. package/lib/routes/zhizhuan100/namespace.ts +7 -0
  370. package/lib/routes/zhizhuan100/report.ts +79 -0
  371. package/lib/routes/zimuxia/portfolio.ts +1 -1
  372. package/lib/routes/zju/sis/index.ts +161 -0
  373. package/lib/server.ts +5 -0
  374. package/lib/types.ts +58 -54
  375. package/lib/utils/helpers.ts +1 -1
  376. package/lib/utils/logger.ts +1 -1
  377. package/lib/utils/proxy/index.ts +102 -18
  378. package/lib/utils/proxy/multi-proxy.ts +139 -0
  379. package/lib/utils/proxy/unify-proxy.ts +3 -1
  380. package/lib/utils/puppeteer-utils.test.ts +1 -1
  381. package/lib/utils/puppeteer.test.ts +14 -27
  382. package/lib/utils/puppeteer.ts +51 -37
  383. package/lib/utils/readable-social.test.ts +65 -0
  384. package/lib/utils/readable-social.ts +15 -1
  385. package/lib/utils/request-rewriter/fetch.ts +42 -4
  386. package/package.json +54 -56
  387. package/lib/routes/mixcloud/queries.ts +0 -2274
  388. package/lib/routes-deprecated/dev.to/top.js +0 -40
@@ -0,0 +1,314 @@
1
+ import { type Data, type DataItem, type Route, ViewType } from '@/types';
2
+
3
+ import cache from '@/utils/cache';
4
+ import ofetch from '@/utils/ofetch';
5
+ import { parseDate } from '@/utils/parse-date';
6
+
7
+ import { type CheerioAPI, load } from 'cheerio';
8
+ import { type Context } from 'hono';
9
+
10
+ const escapeHtml = (text: string): string => text?.replace(/&/g, '&amp;')?.replace(/</g, '&lt;')?.replace(/>/g, '&gt;')?.replace(/'/g, '&quot;')?.replace(/'/g, '&#039;') ?? text;
11
+
12
+ const parseTextChildren = (children: any[]): string => children.map((child: any) => escapeHtml(child.text)).join('');
13
+
14
+ const parseImageNode = (node: any): string => {
15
+ const titleAttr = node.title ? ` title="${escapeHtml(node.title)}"` : '';
16
+ const altAttr = node.alt ? ` alt="${escapeHtml(node.alt)}"` : '';
17
+ const styleAttr = node.size ? ` style="width:${node.size.width}px;height:${node.size.height}px;"` : '';
18
+ return `<img src="${escapeHtml(node.url)}"${titleAttr}${altAttr}${styleAttr}>`;
19
+ };
20
+
21
+ const parseListItemNode = (listItem: any): string => `<li>${parseContentToHtml(listItem.children)}</li>`;
22
+
23
+ const parseListNode = (node: any): string => {
24
+ const tag = node.ordered ? 'ol' : 'ul';
25
+ const startAttr = node.ordered && node.start !== 1 ? ` start="${node.start}"` : '';
26
+ const listItemsHtml = node.children.map((item: any) => parseListItemNode(item)).join('');
27
+ return `<${tag}${startAttr}>${listItemsHtml}</${tag}>`;
28
+ };
29
+
30
+ const parseParagraphChildren = (children: any[]): string =>
31
+ children
32
+ .map((child: any) => {
33
+ if (child.text !== undefined) {
34
+ return escapeHtml(child.text);
35
+ } else if (child.type === 'image') {
36
+ return parseImageNode(child);
37
+ }
38
+ return '';
39
+ })
40
+ .join('');
41
+
42
+ const parseContentToHtml = (content: any[]): string =>
43
+ content
44
+ ?.map((node: any) => {
45
+ switch (node.type) {
46
+ case 'paragraph':
47
+ return `<p>${parseParagraphChildren(node.children)}</p>`;
48
+ case 'image':
49
+ return parseImageNode(node);
50
+ case 'heading':
51
+ return `<h${node.depth}>${parseTextChildren(node.children)}</h${node.depth ?? ''}>`;
52
+ case 'code':
53
+ return `<pre><code${node.lang ? ` class="language-${node.lang}"` : ''}>${parseTextChildren(node.children)}</code></pre>`;
54
+ case 'list':
55
+ return parseListNode(node);
56
+ case 'blockquote':
57
+ return `<blockquote>${parseContentToHtml(node.children)}</blockquote>`;
58
+ default:
59
+ return '';
60
+ }
61
+ })
62
+ .join('') ?? '';
63
+
64
+ export const handler = async (ctx: Context): Promise<Data> => {
65
+ const { category = 'latest' } = ctx.req.param();
66
+ const limit: number = Number.parseInt(ctx.req.query('limit') ?? '20', 10);
67
+
68
+ const baseUrl: string = 'https://tidb.net';
69
+ const targetUrl: string = new URL(`blog${category === 'latest' ? '' : `/c/${category}`}`, baseUrl).href;
70
+ const targetResponse = await ofetch(targetUrl);
71
+
72
+ const buildId: string | undefined = targetResponse.match(/"buildId":"(.*?)"/)?.[1];
73
+
74
+ if (!buildId) {
75
+ throw new Error('Build ID not found.');
76
+ }
77
+
78
+ const $: CheerioAPI = load(targetResponse);
79
+ const language = $('html').attr('lang') ?? 'zh';
80
+
81
+ const apiUrl: string = new URL(`_next/data/${buildId}/${language}/blog${category === 'latest' ? '' : `/c/${category}`}.json`, baseUrl).href;
82
+
83
+ const response = await ofetch(apiUrl, {
84
+ query: {
85
+ latest: true,
86
+ },
87
+ });
88
+
89
+ let items: DataItem[] = [];
90
+
91
+ items = response.pageProps.blogs.content.slice(0, limit).map((item): DataItem => {
92
+ const title: string = item.title;
93
+ const description: string | undefined = item.summary;
94
+ const pubDate: number | string = item.publishedAt;
95
+ const linkUrl: string | undefined = item.slug ? `blog/${item.slug}` : undefined;
96
+ const categories: string[] = [...new Set([item.category?.name, ...(item.tags ?? []).map((c) => c.name)].filter(Boolean))];
97
+ const authors: DataItem['author'] = item.author?.username
98
+ ? [
99
+ {
100
+ name: item.author.username,
101
+ url: new URL(`u/${item.author.username}`, baseUrl).href,
102
+ avatar: item.author.avatarURL,
103
+ },
104
+ ]
105
+ : undefined;
106
+ const guid: string = item.slug ?? '';
107
+ const updated: number | string = item.lastModifiedAt ?? pubDate;
108
+
109
+ const processedItem: DataItem = {
110
+ title,
111
+ description,
112
+ pubDate: pubDate ? parseDate(pubDate) : undefined,
113
+ link: linkUrl ? new URL(linkUrl, baseUrl).href : undefined,
114
+ category: categories,
115
+ author: authors,
116
+ guid,
117
+ id: guid,
118
+ content: {
119
+ html: description,
120
+ text: description,
121
+ },
122
+ updated: updated ? parseDate(updated) : undefined,
123
+ language,
124
+ };
125
+
126
+ return processedItem;
127
+ });
128
+
129
+ items = await Promise.all(
130
+ items.map((item) => {
131
+ if (!item.link) {
132
+ return item;
133
+ }
134
+
135
+ return cache.tryGet(item.link, async (): Promise<DataItem> => {
136
+ const detailUrl: string = new URL(`blog/api/posts/${item.guid}/detail`, baseUrl).href;
137
+
138
+ const detailResponse = await ofetch(detailUrl, {
139
+ query: {
140
+ visit: true,
141
+ },
142
+ });
143
+
144
+ const title: string = detailResponse.title;
145
+ const description: string | undefined = detailResponse.content ? parseContentToHtml(JSON.parse(detailResponse.content)) : item.description;
146
+ const pubDate: number | string = detailResponse.publishedAt;
147
+ const linkUrl: string | undefined = `blog/${detailResponse.slug}`;
148
+ const categories: string[] = [...new Set([detailResponse.category?.name, ...(detailResponse.tags ?? []).map((c) => c.name)].filter(Boolean))];
149
+ const authors: DataItem['author'] = detailResponse.author?.username
150
+ ? [
151
+ {
152
+ name: detailResponse.author.username,
153
+ url: new URL(`u/${detailResponse.author.username}`, baseUrl).href,
154
+ avatar: detailResponse.author.avatarURL,
155
+ },
156
+ ]
157
+ : undefined;
158
+ const guid: string = `tidb-blog-${detailResponse.slug}`;
159
+ const updated: number | string = detailResponse.lastModifiedAt ?? pubDate;
160
+
161
+ const processedItem: DataItem = {
162
+ title,
163
+ description,
164
+ pubDate: pubDate ? parseDate(pubDate) : undefined,
165
+ link: new URL(linkUrl, baseUrl).href,
166
+ category: categories,
167
+ author: authors,
168
+ guid,
169
+ id: guid,
170
+ content: {
171
+ html: description,
172
+ text: description,
173
+ },
174
+ updated: updated ? parseDate(updated) : undefined,
175
+ language,
176
+ };
177
+
178
+ return {
179
+ ...item,
180
+ ...processedItem,
181
+ };
182
+ });
183
+ })
184
+ );
185
+
186
+ const title: string = $('title').text();
187
+
188
+ return {
189
+ title,
190
+ description: $('meta[property="og:description"]').attr('content'),
191
+ link: targetUrl,
192
+ item: items,
193
+ allowEmpty: true,
194
+ image: $('meta[property="og:image"]').attr('content'),
195
+ author: title.split(/\|/).pop()?.trim(),
196
+ language,
197
+ id: targetUrl,
198
+ };
199
+ };
200
+
201
+ export const route: Route = {
202
+ path: '/blog/c/:category?',
203
+ name: '专栏分类',
204
+ url: 'tidb.net',
205
+ maintainers: ['nczitzk'],
206
+ handler,
207
+ example: '/tidb/blog/c/latest',
208
+ parameters: {
209
+ category: {
210
+ description: '分类,默认为 `latest`,即全部文章,可在对应分类页 URL 中找到',
211
+ options: [
212
+ {
213
+ label: '全部文章',
214
+ value: 'latest',
215
+ },
216
+ {
217
+ label: '管理与运维',
218
+ value: 'management-and-operation',
219
+ },
220
+ {
221
+ label: '实践案例',
222
+ value: 'practical-case',
223
+ },
224
+ {
225
+ label: '架构选型',
226
+ value: 'architecture-selection',
227
+ },
228
+ {
229
+ label: '原理解读',
230
+ value: 'principle-interpretation',
231
+ },
232
+ {
233
+ label: '应用开发',
234
+ value: 'application-development',
235
+ },
236
+ {
237
+ label: '社区动态',
238
+ value: 'community-feeds',
239
+ },
240
+ ],
241
+ },
242
+ },
243
+ description: `:::tip
244
+ 订阅 [管理与运维](https://tidb.net/blog/c/management-and-operation),其源网址为 \`https://tidb.net/blog/c/management-and-operation\`,请参考该 URL 指定部分构成参数,此时路由为 [\`/tidb/blog/c/management-and-operation\`](https://rsshub.app/tidb/blog/c/management-and-operation)。
245
+ :::
246
+
247
+ | 分类 | ID |
248
+ | -------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
249
+ | [全部文章](https://tidb.net/blog) | [latest](https://rsshub.app/tidb/blog) |
250
+ | [管理与运维](https://tidb.net/blog/c/management-and-operation) | [management-and-operation](https://rsshub.app/tidb/blog/c/management-and-operation) |
251
+ | [实践案例](https://tidb.net/blog/c/practical-case) | [practical-case](https://rsshub.app/tidb/blog/c/practical-case) |
252
+ | [架构选型](https://tidb.net/blog/c/architecture-selection) | [architecture-selection](https://rsshub.app/tidb/blog/c/architecture-selection) |
253
+ | [原理解读](https://tidb.net/blog/c/principle-interpretation) | [principle-interpretation](https://rsshub.app/tidb/blog/c/principle-interpretation) |
254
+ | [应用开发](https://tidb.net/blog/c/application-development) | [application-development](https://rsshub.app/tidb/blog/c/application-development) |
255
+ | [社区动态](https://tidb.net/blog/c/community-feeds) | [community-feeds](https://rsshub.app/tidb/blog/c/community-feeds) |
256
+
257
+ `,
258
+ categories: ['programming'],
259
+ features: {
260
+ requireConfig: false,
261
+ requirePuppeteer: false,
262
+ antiCrawler: false,
263
+ supportRadar: true,
264
+ supportBT: false,
265
+ supportPodcast: false,
266
+ supportScihub: false,
267
+ },
268
+ radar: [
269
+ {
270
+ source: ['tidb.net/blog', 'tidb.net/blog/c/:category'],
271
+ target: (params) => {
272
+ const category: string = params.category;
273
+
274
+ return `/tidb/blog/c${category ? `/${category}` : ''}`;
275
+ },
276
+ },
277
+ {
278
+ title: '全部文章',
279
+ source: ['tidb.net/blog'],
280
+ target: '/blog/c/latest',
281
+ },
282
+ {
283
+ title: '管理与运维',
284
+ source: ['tidb.net/blog/c/management-and-operation'],
285
+ target: '/blog/c/management-and-operation',
286
+ },
287
+ {
288
+ title: '实践案例',
289
+ source: ['tidb.net/blog/c/practical-case'],
290
+ target: '/blog/c/practical-case',
291
+ },
292
+ {
293
+ title: '架构选型',
294
+ source: ['tidb.net/blog/c/architecture-selection'],
295
+ target: '/blog/c/architecture-selection',
296
+ },
297
+ {
298
+ title: '原理解读',
299
+ source: ['tidb.net/blog/c/principle-interpretation'],
300
+ target: '/blog/c/principle-interpretation',
301
+ },
302
+ {
303
+ title: '应用开发',
304
+ source: ['tidb.net/blog/c/application-development'],
305
+ target: '/blog/c/application-development',
306
+ },
307
+ {
308
+ title: '社区动态',
309
+ source: ['tidb.net/blog/c/community-feeds'],
310
+ target: '/blog/c/community-feeds',
311
+ },
312
+ ],
313
+ view: ViewType.Articles,
314
+ };
@@ -0,0 +1,9 @@
1
+ import type { Namespace } from '@/types';
2
+
3
+ export const namespace: Namespace = {
4
+ name: 'TiDB 社区',
5
+ url: 'tidb.net',
6
+ categories: ['programming'],
7
+ description: '',
8
+ lang: 'zh-CN',
9
+ };
@@ -0,0 +1,17 @@
1
+ import type { Namespace } from '@/types';
2
+
3
+ export const namespace: Namespace = {
4
+ name: 'Tumblr',
5
+ url: 'tumblr.com',
6
+ lang: 'en',
7
+ description: `Register an application on \`https://www.tumblr.com/oauth/apps\`.
8
+
9
+ - \`TUMBLR_CLIENT_ID\`: The key is labelled as \`OAuth consumer Key\` in the info page of the registered application.
10
+ - \`TUMBLR_CLIENT_SECRET\`: The key is labelled as \`OAuth consumer Secret\` in the info page of the registered application.
11
+ - \`TUMBLR_REFRESH_TOKEN\`: Navigate to \`https://www.tumblr.com/oauth2/authorize?client_id=\${CLIENT_ID}&response_type=code&scope=basic%20offline_access&state=mystate\` in your browser and login. After doing so, you'll be redirected to the URL you defined when registering the application. Look for the \`code\` parameter in the URL. You can then call \`curl -F grant_type=authorization_code -F "code=\${CODE}" -F "client_id=\${CLIENT_ID}" -F "client_secret=\${CLIENT_SECRET}" "https://api.tumblr.com/v2/oauth2/token"\`
12
+
13
+ Two login methods are currently supported:
14
+
15
+ - \`TUMBLR_CLIENT_ID\`: The key never expires, however blogs that are "dashboard only" cannot be accessed.
16
+ - \`TUMBLR_CLIENT_ID\` + \`TUMBLR_CLIENT_SECRET\` + \`TUMBLR_REFRESH_TOKEN\`: The refresh token will expire and will need to be regenerated, "dashboard only" blogs can be accessed.`,
17
+ };
@@ -0,0 +1,74 @@
1
+ import { Data, Route } from '@/types';
2
+ import got from '@/utils/got';
3
+ import utils from './utils';
4
+ import type { Context } from 'hono';
5
+ import { config } from '@/config';
6
+ import ConfigNotFoundError from '@/errors/types/config-not-found';
7
+ import { fallback, queryToInteger } from '@/utils/readable-social';
8
+
9
+ export const route: Route = {
10
+ path: '/posts/:blog',
11
+ categories: ['blog'],
12
+ example: '/tumblr/posts/biketouring-nearby',
13
+ parameters: {
14
+ blog: 'Blog identifier (see `https://www.tumblr.com/docs/en/api/v2#blog-identifiers`)',
15
+ },
16
+ radar: [],
17
+ features: {
18
+ requireConfig: [
19
+ {
20
+ name: 'TUMBLR_CLIENT_ID',
21
+ description: 'Please see above for details.',
22
+ },
23
+ {
24
+ name: 'TUMBLR_CLIENT_SECRET',
25
+ description: 'Please see above for details.',
26
+ },
27
+ {
28
+ name: 'TUMBLR_REFRESH_TOKEN',
29
+ description: 'Please see above for details.',
30
+ },
31
+ ],
32
+ requirePuppeteer: false,
33
+ antiCrawler: false,
34
+ supportBT: false,
35
+ supportPodcast: false,
36
+ supportScihub: false,
37
+ },
38
+ name: 'Posts',
39
+ maintainers: ['Rakambda'],
40
+ description: `::: tip
41
+ Tumblr provides official RSS feeds for non "dashboard only" blogs, for instance [https://biketouring-nearby.tumblr.com](https://biketouring-nearby.tumblr.com/rss).
42
+ :::`,
43
+ handler,
44
+ };
45
+
46
+ async function handler(ctx: Context): Promise<Data> {
47
+ if (!config.tumblr || !config.tumblr.clientId) {
48
+ throw new ConfigNotFoundError('Tumblr RSS is disabled due to the lack of <a href="https://docs.rsshub.app/deploy/config#route-specific-configurations">relevant config</a>');
49
+ }
50
+
51
+ const blogIdentifier = ctx.req.param('blog');
52
+ const limit = fallback(undefined, queryToInteger(ctx.req.query('limit')), 20);
53
+
54
+ const response = await got.get(`https://api.tumblr.com/v2/blog/${blogIdentifier}/posts`, {
55
+ searchParams: {
56
+ ...utils.generateAuthParams(),
57
+ limit,
58
+ },
59
+ headers: await utils.generateAuthHeaders(),
60
+ });
61
+
62
+ const blog = response.data.response.blog;
63
+ const posts = response.data.response.posts.map((post: any) => utils.processPost(post));
64
+
65
+ return {
66
+ title: `Tumblr - ${blogIdentifier} - Posts`,
67
+ author: blog?.name,
68
+ link: blog?.url ?? `https://${blogIdentifier}/`,
69
+ item: posts,
70
+ allowEmpty: true,
71
+ image: blog?.avatar?.slice(-1)?.url,
72
+ description: blog?.description,
73
+ };
74
+ }
@@ -0,0 +1,110 @@
1
+ import { parseDate } from '@/utils/parse-date';
2
+ import { DataItem } from '@/types';
3
+ import { config } from '@/config';
4
+ import cache from '@/utils/cache';
5
+ import logger from '@/utils/logger';
6
+ import got from '@/utils/got';
7
+
8
+ const getAccessToken: () => Promise<string | null> = async () => {
9
+ let accessToken: string | null = await cache.get('tumblr:accessToken', false);
10
+ if (!accessToken) {
11
+ try {
12
+ const newAccessToken = await tokenRefresher();
13
+ if (newAccessToken) {
14
+ accessToken = newAccessToken;
15
+ }
16
+ } catch (error) {
17
+ // Return the `accessToken=null` value to indicate that the token is not available. Calls will only use the `apiKey` as a fallback to maybe hit non "dashborad only" blogs.
18
+ logger.error('Failed to refresh Tumblr token, using only client id as fallback', error);
19
+ }
20
+ }
21
+ return accessToken;
22
+ };
23
+
24
+ const generateAuthHeaders: () => Promise<{ Authorization?: string }> = async () => {
25
+ const accessToken = await getAccessToken();
26
+ if (!accessToken) {
27
+ return {};
28
+ }
29
+ return {
30
+ Authorization: `Bearer ${accessToken}`,
31
+ };
32
+ };
33
+
34
+ const generateAuthParams: () => { apiKey?: string } = () => ({
35
+ apiKey: config.tumblr.clientId,
36
+ });
37
+
38
+ const processPost: (post: any) => DataItem = (post) => {
39
+ let description = '';
40
+
41
+ switch (post.type) {
42
+ case 'text':
43
+ description = post.body;
44
+ break;
45
+ case 'photo':
46
+ for (const photo of post.photos ?? []) {
47
+ description += `<img src="${photo.original_size.url}"/><br/>`;
48
+ }
49
+ break;
50
+ case 'link':
51
+ description = post.url;
52
+ break;
53
+ case 'audio':
54
+ description = post.embed;
55
+ break;
56
+ default:
57
+ break;
58
+ }
59
+
60
+ return {
61
+ id: post.id_string,
62
+ title: post.summary ?? `New post from ${post.blog_name}`,
63
+ link: post.post_url,
64
+ pubDate: parseDate(post.timestamp * 1000),
65
+ category: post.tags,
66
+ description,
67
+ };
68
+ };
69
+
70
+ let tokenRefresher: () => Promise<string | null> = () => Promise.resolve(null);
71
+ if (config.tumblr && config.tumblr.clientId && config.tumblr.clientSecret && config.tumblr.refreshToken) {
72
+ tokenRefresher = async (): Promise<string | null> => {
73
+ let refreshToken = config.tumblr.refreshToken;
74
+
75
+ // Restore already refreshed tokens
76
+ const previousRefreshTokenSerialized = await cache.get('tumblr:refreshToken', false);
77
+ if (previousRefreshTokenSerialized) {
78
+ const previousRefreshToken = JSON.parse(previousRefreshTokenSerialized);
79
+ if (previousRefreshToken.startToken === refreshToken) {
80
+ refreshToken = previousRefreshToken.currentToken;
81
+ }
82
+ }
83
+ const response = await got.post('https://api.tumblr.com/v2/oauth2/token', {
84
+ form: {
85
+ grant_type: 'refresh_token',
86
+ client_id: config.tumblr.clientId,
87
+ client_secret: config.tumblr.clientSecret,
88
+ refresh_token: refreshToken,
89
+ },
90
+ });
91
+ if (!response.data?.access_token || !response.data?.refresh_token) {
92
+ return null;
93
+ }
94
+ const accessToken = response.data.access_token;
95
+ const newRefreshToken = response.data.refresh_token;
96
+ const expiresIn = response.data.expires_in;
97
+
98
+ // Access tokens expire after 42 minutes, remove 30 seconds to renew the token before it expires (to avoid making a request right when it ends).
99
+ await cache.set('tumblr:accessToken', accessToken, (expiresIn ?? 2520) - 30);
100
+ // Store the new refresh token associated with the one that was provided first.
101
+ // We may be able to restore the new token if the app is restarted. This will avoid reusing the old token and have a failing request.
102
+ // Keep it for a year (not clear how long the refresh token lasts).
103
+ const cacheEntry = { startToken: config.tumblr.refreshToken, currentToken: newRefreshToken };
104
+ await cache.set(`tumblr:refreshToken`, JSON.stringify(cacheEntry), 31_536_000);
105
+
106
+ return accessToken;
107
+ };
108
+ }
109
+
110
+ export default { processPost, generateAuthParams, generateAuthHeaders };
@@ -0,0 +1,7 @@
1
+ import type { Namespace } from '@/types';
2
+
3
+ export const namespace: Namespace = {
4
+ name: 'TVer',
5
+ url: 'tver.jp',
6
+ lang: 'ja',
7
+ };
@@ -0,0 +1,98 @@
1
+ import { Route, Data, DataItem } from '@/types';
2
+ import ofetch from '@/utils/ofetch';
3
+ import { parseDate } from '@/utils/parse-date';
4
+ import timezone from '@/utils/timezone';
5
+ import { Context } from 'hono';
6
+
7
+ export const route: Route = {
8
+ path: '/series/:id',
9
+ categories: ['traditional-media'],
10
+ example: '/tver/series/srx2o7o3c8',
11
+ parameters: {
12
+ id: 'Series ID (as it appears in URLs). For example, in https://tver.jp/series/srx2o7o3c8, the ID is "srx2o7o3c8".',
13
+ },
14
+ radar: [
15
+ {
16
+ source: ['tver.jp/series/:id'],
17
+ target: '/series/:id',
18
+ },
19
+ ],
20
+ name: 'Series',
21
+ maintainers: ['yuikisaito'],
22
+ handler,
23
+ };
24
+
25
+ const commonHeaders = {
26
+ Accept: '*/*',
27
+ 'Accept-Language': 'ja,en-US;q=0.7,en;q=0.3',
28
+ 'Cache-Control': 'no-cache',
29
+ Pragma: 'no-cache',
30
+ 'Sec-GPC': '1',
31
+ 'Sec-Fetch-Dest': 'empty',
32
+ 'Sec-Fetch-Mode': 'cors',
33
+ 'Sec-Fetch-Site': 'same-site',
34
+ };
35
+
36
+ async function handler(ctx: Context): Promise<Data> {
37
+ const { id } = ctx.req.param();
38
+
39
+ const { result: browser } = await ofetch('https://platform-api.tver.jp/v2/api/platform_users/browser/create', {
40
+ method: 'POST',
41
+ body: 'device_type=pc',
42
+ headers: {
43
+ ...commonHeaders,
44
+ 'Content-Type': 'application/x-www-form-urlencoded',
45
+ },
46
+ referrer: 'https://s.tver.jp/',
47
+ credentials: 'omit',
48
+ mode: 'cors',
49
+ });
50
+
51
+ const { platform_uid, platform_token } = browser;
52
+
53
+ const { title, description, broadcastProvider } = await ofetch(`https://statics.tver.jp/content/series/${id}.json`, {
54
+ method: 'GET',
55
+ headers: {
56
+ ...commonHeaders,
57
+ },
58
+ referrer: 'https://tver.jp/',
59
+ credentials: 'omit',
60
+ mode: 'cors',
61
+ });
62
+
63
+ const { result } = await ofetch(`https://platform-api.tver.jp/service/api/v1/callSeriesEpisodes/${id}?platform_uid=${platform_uid}&platform_token=${platform_token}`, {
64
+ method: 'GET',
65
+ headers: {
66
+ ...commonHeaders,
67
+ 'x-tver-platform-type': 'web',
68
+ },
69
+ referrer: 'https://tver.jp/',
70
+ credentials: 'omit',
71
+ mode: 'cors',
72
+ });
73
+
74
+ const items: DataItem[] = (result.contents?.[0]?.contents ?? [])
75
+ .filter((i) => i.type === 'episode')
76
+ .map((i) => {
77
+ const rawPubDate = i.content.broadcastDateLabel;
78
+ const cleanedPubDate = rawPubDate.replaceAll(/\(.*?\)|放送分/g, '').trim();
79
+ const parsedPubDate = timezone(parseDate(cleanedPubDate, 'M月D日'), +9).toDateString();
80
+
81
+ return {
82
+ title: i.content.title,
83
+ link: `https://tver.jp/episodes/${i.content.id}`,
84
+ image: `https://statics.tver.jp/images/content/thumbnail/episode/xlarge/${i.content.id}.jpg`,
85
+ pubDate: parsedPubDate,
86
+ };
87
+ });
88
+
89
+ return {
90
+ title: 'TVer - ' + title,
91
+ description,
92
+ author: broadcastProvider.name,
93
+ link: `https://tver.jp/series/${id}`,
94
+ image: `https://statics.tver.jp/images/content/thumbnail/series/xlarge/${id}.jpg`,
95
+ language: 'ja',
96
+ item: items,
97
+ };
98
+ }
@@ -28,9 +28,7 @@ async function login({ username, password, authenticationSecret }) {
28
28
  await loginLimiterQueue.removeTokens(1);
29
29
 
30
30
  const cookieJar = new CookieJar();
31
- const browser = await puppeteer({
32
- stealth: true,
33
- });
31
+ const browser = await puppeteer();
34
32
  const page = await browser.newPage();
35
33
  await page.goto('https://x.com/i/flow/login');
36
34
  await page.waitForSelector('input[autocomplete="username"]');