rsshub 1.0.0-master.fb6d4f3 → 1.0.0-master.fb7e42c

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 (460) hide show
  1. package/lib/assets/logo.svg +1 -0
  2. package/lib/config.ts +10 -0
  3. package/lib/errors/index.test.ts +15 -5
  4. package/lib/errors/types/captcha.ts +5 -0
  5. package/lib/middleware/cache.ts +4 -0
  6. package/lib/middleware/parameter.ts +1 -1
  7. package/lib/registry.ts +6 -2
  8. package/lib/routes/10000link/info.ts +1 -1
  9. package/lib/routes/18comic/album.ts +1 -0
  10. package/lib/routes/18comic/blogs.ts +1 -0
  11. package/lib/routes/18comic/search.ts +1 -0
  12. package/lib/routes/199it/index.ts +1 -1
  13. package/lib/routes/21caijing/channel.ts +1 -1
  14. package/lib/routes/51cto/utils.ts +1 -1
  15. package/lib/routes/6v123/index.ts +487 -0
  16. package/lib/routes/6v123/namespace.ts +1 -0
  17. package/lib/routes/8kcos/cat.ts +3 -0
  18. package/lib/routes/8kcos/latest.ts +1 -0
  19. package/lib/routes/8kcos/tag.ts +1 -0
  20. package/lib/routes/91porn/author.ts +1 -0
  21. package/lib/routes/95mm/category.ts +1 -0
  22. package/lib/routes/95mm/tag.ts +1 -0
  23. package/lib/routes/aa1/60s.ts +189 -0
  24. package/lib/routes/{xinhuanet → aa1}/namespace.ts +3 -3
  25. package/lib/routes/abc/index.ts +1 -1
  26. package/lib/routes/abskoop/index.ts +3 -0
  27. package/lib/routes/abskoop/nsfw.ts +3 -0
  28. package/lib/routes/acgvinyl/namespace.ts +6 -0
  29. package/lib/routes/acgvinyl/news.ts +86 -0
  30. package/lib/routes/ally/rail.ts +1 -1
  31. package/lib/routes/amazfitwatchfaces/index.ts +1 -2
  32. package/lib/routes/anthropic/news.ts +13 -11
  33. package/lib/routes/apnews/mobile-api.ts +1 -1
  34. package/lib/routes/apnews/sitemap.ts +1 -1
  35. package/lib/routes/app-sales/index.ts +1 -1
  36. package/lib/routes/apple/apps.ts +2 -2
  37. package/lib/routes/apple/podcast.ts +58 -25
  38. package/lib/routes/apple/security-releases.ts +2 -2
  39. package/lib/routes/asiafruitchina/categories.ts +1 -1
  40. package/lib/routes/asiantolick/index.ts +3 -0
  41. package/lib/routes/asmr-200/index.ts +1 -0
  42. package/lib/routes/bandisoft/history.ts +2 -2
  43. package/lib/routes/bangumi.tv/group/reply.ts +1 -1
  44. package/lib/routes/banshujiang/index.ts +1 -1
  45. package/lib/routes/banyuetan/index.ts +1 -1
  46. package/lib/routes/baozimh/index.ts +21 -3
  47. package/lib/routes/baselang/index.ts +117 -0
  48. package/lib/routes/baselang/namespace.ts +6 -0
  49. package/lib/routes/bbc/learningenglish.ts +35 -20
  50. package/lib/routes/bestofjs/monthly.ts +137 -0
  51. package/lib/routes/bestofjs/namespace.ts +7 -0
  52. package/lib/routes/bestofjs/templates/description.art +36 -0
  53. package/lib/routes/bilibili/cache.ts +17 -1
  54. package/lib/routes/bilibili/dynamic.ts +34 -8
  55. package/lib/routes/bilibili/page.ts +1 -1
  56. package/lib/routes/bilibili/ranking.ts +25 -17
  57. package/lib/routes/bilibili/wasm-exec.ts +1 -1
  58. package/lib/routes/bilibili/weekly-recommend.ts +22 -6
  59. package/lib/routes/bjp/apod.ts +1 -1
  60. package/lib/routes/bnext/index.ts +58 -0
  61. package/lib/routes/bnext/namespace.ts +7 -0
  62. package/lib/routes/bookwalker/namespace.ts +9 -0
  63. package/lib/routes/bookwalker/search.ts +114 -0
  64. package/lib/routes/bookwalker/templates/description.art +13 -0
  65. package/lib/routes/booru/mmda.ts +1 -0
  66. package/lib/routes/buaa/jiaowu.ts +1 -1
  67. package/lib/routes/bullionvault/gold-news.ts +4 -4
  68. package/lib/routes/cast/index.ts +1 -1
  69. package/lib/routes/cbndata/information.ts +250 -0
  70. package/lib/routes/cbndata/namespace.ts +9 -0
  71. package/lib/routes/cbndata/templates/description.art +17 -0
  72. package/lib/routes/cccmc/index.ts +1 -1
  73. package/lib/routes/ccg/index.ts +181 -0
  74. package/lib/routes/ccg/namespace.ts +9 -0
  75. package/lib/routes/ccg/templates/description.art +21 -0
  76. package/lib/routes/chikubi/category.ts +1 -0
  77. package/lib/routes/chikubi/navigation.ts +1 -0
  78. package/lib/routes/chikubi/nipple-video-category.ts +1 -0
  79. package/lib/routes/chikubi/nipple-video-maker.ts +1 -0
  80. package/lib/routes/chikubi/search.ts +1 -0
  81. package/lib/routes/chinadaily/language.ts +1 -1
  82. package/lib/routes/chinaratings/credit-research.ts +1 -1
  83. package/lib/routes/chub/characters.ts +3 -0
  84. package/lib/routes/civitai/discussions.ts +1 -0
  85. package/lib/routes/civitai/models.ts +1 -0
  86. package/lib/routes/civitai/user.ts +3 -0
  87. package/lib/routes/cline/blog.ts +34 -23
  88. package/lib/routes/coolapk/utils.ts +5 -4
  89. package/lib/routes/coolbuy/index.ts +106 -0
  90. package/lib/routes/coolbuy/namespace.ts +9 -0
  91. package/lib/routes/coolbuy/templates/description.art +48 -0
  92. package/lib/routes/coolidge/film-guide.ts +60 -0
  93. package/lib/routes/coolidge/namespace.ts +7 -0
  94. package/lib/routes/coolidge/news.ts +65 -0
  95. package/lib/routes/coolidge/templates/description.art +4 -0
  96. package/lib/routes/coomer/index.ts +4 -6
  97. package/lib/routes/copymanga/comic.ts +2 -1
  98. package/lib/routes/cosplaytele/category.ts +1 -0
  99. package/lib/routes/cosplaytele/latest.ts +1 -0
  100. package/lib/routes/cosplaytele/popular.ts +1 -0
  101. package/lib/routes/cosplaytele/tag.ts +1 -0
  102. package/lib/routes/cpta/handler.ts +1 -1
  103. package/lib/routes/creative-comic/book.ts +1 -1
  104. package/lib/routes/crush/index.ts +77 -0
  105. package/lib/routes/crush/namespace.ts +7 -0
  106. package/lib/routes/cursor/blog.ts +84 -0
  107. package/lib/routes/daum/potplayer.ts +3 -3
  108. package/lib/routes/dbaplus/new.ts +392 -0
  109. package/lib/routes/dbaplus/templates/description.art +21 -0
  110. package/lib/routes/deepl/blog.ts +2 -3
  111. package/lib/routes/diariofruticola/filtro.ts +1 -1
  112. package/lib/routes/digitalpolicyalert/activity-tracker.ts +1 -1
  113. package/lib/routes/dlsite/campaign.ts +1 -0
  114. package/lib/routes/dlsite/ci-en/article.ts +1 -0
  115. package/lib/routes/dlsite/new.ts +1 -0
  116. package/lib/routes/dlsite/z-index/index.ts +3 -0
  117. package/lib/routes/dockerhub/utils.ts +1 -1
  118. package/lib/routes/dora-world/article.ts +2 -2
  119. package/lib/routes/dytt/index.ts +1 -2
  120. package/lib/routes/eeo/kuaixun.ts +154 -0
  121. package/lib/routes/eeo/namespace.ts +9 -0
  122. package/lib/routes/eeo/templates/description.art +7 -0
  123. package/lib/routes/ehentai/ehapi.ts +3 -3
  124. package/lib/routes/ehentai/favorites.ts +1 -0
  125. package/lib/routes/ehentai/search.ts +1 -0
  126. package/lib/routes/elecfans/article.ts +64 -0
  127. package/lib/routes/elecfans/namespace.ts +7 -0
  128. package/lib/routes/elecfans/soft.ts +69 -0
  129. package/lib/routes/engineering/namespace.ts +8 -0
  130. package/lib/routes/engineering/tag.ts +55 -0
  131. package/lib/routes/eventbrite/events.ts +152 -0
  132. package/lib/routes/eventbrite/namespace.ts +7 -0
  133. package/lib/routes/eventernote/actors.ts +61 -64
  134. package/lib/routes/everia/category.ts +1 -0
  135. package/lib/routes/everia/latest.ts +1 -0
  136. package/lib/routes/everia/search.ts +1 -0
  137. package/lib/routes/everia/tag.ts +1 -0
  138. package/lib/routes/expats/czech-news.ts +1 -1
  139. package/lib/routes/fangchan/list.ts +1 -1
  140. package/lib/routes/fansly/post.ts +1 -0
  141. package/lib/routes/fantia/search.ts +1 -0
  142. package/lib/routes/fantia/user.ts +1 -0
  143. package/lib/routes/finology/tag.ts +1 -1
  144. package/lib/routes/furaffinity/art.ts +1 -0
  145. package/lib/routes/furaffinity/browse.ts +1 -0
  146. package/lib/routes/furaffinity/commissions.ts +1 -0
  147. package/lib/routes/furaffinity/home.ts +1 -0
  148. package/lib/routes/furaffinity/journal-comments.ts +1 -0
  149. package/lib/routes/furaffinity/journals.ts +1 -0
  150. package/lib/routes/furaffinity/search.ts +1 -0
  151. package/lib/routes/furaffinity/shouts.ts +1 -0
  152. package/lib/routes/furaffinity/status.ts +1 -0
  153. package/lib/routes/furaffinity/submission-comments.ts +1 -0
  154. package/lib/routes/furaffinity/user.ts +1 -0
  155. package/lib/routes/furaffinity/watchers.ts +1 -0
  156. package/lib/routes/furaffinity/watching.ts +1 -0
  157. package/lib/routes/gamebase/news.ts +2 -2
  158. package/lib/routes/gamer/gnn-index.ts +8 -3
  159. package/lib/routes/gcores/categories.ts +1 -1
  160. package/lib/routes/gcores/collections.ts +1 -1
  161. package/lib/routes/gcores/tags.ts +1 -1
  162. package/lib/routes/gcores/topics.ts +1 -1
  163. package/lib/routes/gdufs/xwxy/index.ts +196 -0
  164. package/lib/routes/gelbooru/post.ts +1 -0
  165. package/lib/routes/genossenschaften/index.ts +2 -3
  166. package/lib/routes/github/activity.ts +1 -1
  167. package/lib/routes/gitstar-ranking/index.ts +1 -1
  168. package/lib/routes/go/jihs/idwr.ts +3 -4
  169. package/lib/routes/google/jules.ts +63 -0
  170. package/lib/routes/gov/mem/namespace.ts +7 -0
  171. package/lib/routes/gov/mem/zfxxgkpt.ts +96 -0
  172. package/lib/routes/gov/moa/gjs.ts +1 -1
  173. package/lib/routes/gov/mot/index.ts +157 -53
  174. package/lib/routes/grainoil/category.ts +1 -2
  175. package/lib/routes/gxmzu/lib.ts +2 -2
  176. package/lib/routes/gxmzu/utils/index.ts +2 -2
  177. package/lib/routes/hameln/chapter.ts +1 -1
  178. package/lib/routes/hanime1/previews.ts +1 -0
  179. package/lib/routes/hanime1/search.ts +1 -0
  180. package/lib/routes/hex-rays/index.ts +1 -1
  181. package/lib/routes/hit/hitgs.ts +1 -1
  182. package/lib/routes/hit/namespace.ts +1 -1
  183. package/lib/routes/hpoi/banner-item.ts +28 -11
  184. package/lib/routes/hpoi/info.ts +1 -1
  185. package/lib/routes/hudsonrivertrading/index.ts +122 -0
  186. package/lib/routes/hudsonrivertrading/namespace.ts +7 -0
  187. package/lib/routes/huggingface/daily-papers.ts +45 -9
  188. package/lib/routes/huijin-inv/namespace.ts +7 -0
  189. package/lib/routes/huijin-inv/news.ts +83 -0
  190. package/lib/routes/hupu/index.ts +170 -74
  191. package/lib/routes/hupu/types.ts +163 -0
  192. package/lib/routes/hust/gs.ts +1 -1
  193. package/lib/routes/hust/mse.ts +1 -1
  194. package/lib/routes/huxiu/util.ts +2 -2
  195. package/lib/routes/immich/cursed-knowledge.ts +49 -0
  196. package/lib/routes/immich/namespace.ts +7 -0
  197. package/lib/routes/infoq/presentations.ts +1 -1
  198. package/lib/routes/instagram/common-utils.ts +3 -3
  199. package/lib/routes/investor/index.ts +362 -148
  200. package/lib/routes/itch/devlog.ts +7 -3
  201. package/lib/routes/iwara/index.ts +3 -0
  202. package/lib/routes/iwara/subscriptions.ts +1 -0
  203. package/lib/routes/javbus/index.ts +1 -1
  204. package/lib/routes/javdb/actors.ts +1 -0
  205. package/lib/routes/javdb/index.ts +3 -0
  206. package/lib/routes/javdb/lists.ts +3 -0
  207. package/lib/routes/javdb/makers.ts +1 -0
  208. package/lib/routes/javdb/rankings.ts +1 -0
  209. package/lib/routes/javdb/search.ts +1 -0
  210. package/lib/routes/javdb/series.ts +1 -0
  211. package/lib/routes/javdb/videocodes.ts +1 -0
  212. package/lib/routes/javlibrary/bestrated.ts +3 -0
  213. package/lib/routes/javlibrary/bestreviews.ts +1 -0
  214. package/lib/routes/javlibrary/genre.ts +3 -0
  215. package/lib/routes/javlibrary/maker.ts +1 -0
  216. package/lib/routes/javlibrary/mostwanted.ts +3 -0
  217. package/lib/routes/javlibrary/newentries.ts +3 -0
  218. package/lib/routes/javlibrary/newrelease.ts +3 -0
  219. package/lib/routes/javlibrary/update.ts +3 -0
  220. package/lib/routes/javlibrary/user.ts +3 -0
  221. package/lib/routes/javlibrary/utils.ts +1 -1
  222. package/lib/routes/javtrailers/categories.ts +3 -0
  223. package/lib/routes/javtrailers/studios.ts +3 -0
  224. package/lib/routes/jbma/namespace.ts +17 -0
  225. package/lib/routes/jbma/report.ts +471 -0
  226. package/lib/routes/jetbrains/comments.ts +1 -1
  227. package/lib/routes/jingzhengu/utils.ts +1 -1
  228. package/lib/routes/jou/utils/index.ts +2 -2
  229. package/lib/routes/jpxgmn/search.ts +3 -0
  230. package/lib/routes/jpxgmn/weekly.ts +3 -0
  231. package/lib/routes/juejin/aicoding.ts +102 -0
  232. package/lib/routes/juejin/trending.ts +70 -20
  233. package/lib/routes/juejin/utils.ts +36 -51
  234. package/lib/routes/jumeili/home.ts +1 -1
  235. package/lib/routes/kakuyomu/works.ts +1 -1
  236. package/lib/routes/kemono/index.ts +11 -3
  237. package/lib/routes/kemono/templates/discord.art +1 -1
  238. package/lib/routes/kisskiss/blog.ts +1 -0
  239. package/lib/routes/komiic/comic.ts +2 -1
  240. package/lib/routes/koyso/index.ts +339 -0
  241. package/lib/routes/koyso/namespace.ts +9 -0
  242. package/lib/routes/koyso/templates/description.art +13 -0
  243. package/lib/routes/kpopping/kpics.ts +3 -4
  244. package/lib/routes/kpopping/news.ts +2 -2
  245. package/lib/routes/laimanhua/index.ts +1 -0
  246. package/lib/routes/letterboxd/index.ts +65 -0
  247. package/lib/routes/letterboxd/namespace.ts +8 -0
  248. package/lib/routes/lhratings/research.ts +1 -1
  249. package/lib/routes/literotica/category.ts +3 -0
  250. package/lib/routes/lovelive-anime/topics.ts +5 -6
  251. package/lib/routes/maccms/index.ts +1 -1
  252. package/lib/routes/mangadex/index.ts +3 -0
  253. package/lib/routes/mangadex/mdlist/feed.ts +1 -0
  254. package/lib/routes/mangadex/user/feed.ts +1 -0
  255. package/lib/routes/mangadex/user/follows.ts +1 -0
  256. package/lib/routes/manhuagui/comic.ts +1 -0
  257. package/lib/routes/manhuagui/subscribe.ts +1 -0
  258. package/lib/routes/manyvids/video.ts +3 -0
  259. package/lib/routes/mercari/util.ts +1 -1
  260. package/lib/routes/mihoyo/sr/news.ts +2 -1
  261. package/lib/routes/mihoyo/zzz/news.ts +2 -1
  262. package/lib/routes/mingpao/index.ts +1 -1
  263. package/lib/routes/musify/index.ts +2 -3
  264. package/lib/routes/mycard520/news.ts +2 -2
  265. package/lib/routes/nankai/ai-notice.ts +142 -0
  266. package/lib/routes/nankai/graduate-notice.ts +162 -0
  267. package/lib/routes/natgeo/natgeo.ts +3 -2
  268. package/lib/routes/naturalism/namespace.ts +7 -0
  269. package/lib/routes/naturalism/new.ts +59 -0
  270. package/lib/routes/netflav/index.ts +3 -0
  271. package/lib/routes/nhentai/search.ts +1 -0
  272. package/lib/routes/nhk/news-web-easy.ts +1 -1
  273. package/lib/routes/nicovideo/mylist.ts +39 -0
  274. package/lib/routes/nicovideo/types.ts +27 -0
  275. package/lib/routes/nicovideo/utils.ts +27 -1
  276. package/lib/routes/nikkei/cn/index.ts +1 -4
  277. package/lib/routes/nio/nioradio.ts +1 -1
  278. package/lib/routes/njxzc/utils/index.ts +2 -2
  279. package/lib/routes/notion/namespace.ts +2 -2
  280. package/lib/routes/now/news.ts +1 -1
  281. package/lib/routes/nytimes/index.ts +1 -1
  282. package/lib/routes/olevod/vod.ts +3 -0
  283. package/lib/routes/olevod/vodlist.ts +3 -0
  284. package/lib/routes/oreno3d/main.ts +1 -0
  285. package/lib/routes/oschina/column.ts +1 -2
  286. package/lib/routes/oschina/event.ts +1 -1
  287. package/lib/routes/osu/beatmaps/latest-ranked.ts +2 -3
  288. package/lib/routes/papers/category.ts +3 -4
  289. package/lib/routes/patreon/feed.ts +1 -0
  290. package/lib/routes/picuki/profile.ts +13 -5
  291. package/lib/routes/pixiv/bookmarks.ts +1 -0
  292. package/lib/routes/pixiv/illustfollow.ts +1 -0
  293. package/lib/routes/pixiv/novel-api/user-novels/sfw.ts +1 -1
  294. package/lib/routes/pixiv/novel-series.ts +17 -3
  295. package/lib/routes/pixiv/novels.ts +1 -0
  296. package/lib/routes/pixiv/ranking.ts +1 -0
  297. package/lib/routes/pixiv/search.ts +1 -0
  298. package/lib/routes/pixiv/user.ts +1 -0
  299. package/lib/routes/pixivision/utils.ts +1 -1
  300. package/lib/routes/pku/hr.ts +1 -1
  301. package/lib/routes/pku/scc/recruit.ts +1 -1
  302. package/lib/routes/pornhub/category-url.ts +1 -0
  303. package/lib/routes/pornhub/category.ts +1 -0
  304. package/lib/routes/pornhub/pornstar.ts +1 -0
  305. package/lib/routes/pornhub/search.ts +1 -0
  306. package/lib/routes/pornhub/users.ts +1 -0
  307. package/lib/routes/producthunt/templates/description.art +2 -2
  308. package/lib/routes/producthunt/today.ts +17 -8
  309. package/lib/routes/ps/trophy.ts +1 -1
  310. package/lib/routes/pubscholar/utils.ts +14 -1
  311. package/lib/routes/qq/lol/news.ts +1 -1
  312. package/lib/routes/qweather/3days.ts +14 -14
  313. package/lib/routes/qweather/now.ts +12 -10
  314. package/lib/routes/qweather/util.tsx +89 -0
  315. package/lib/routes/qwenlm/blog.ts +75 -0
  316. package/lib/routes/qwenlm/namespace.ts +6 -0
  317. package/lib/routes/radio-canada/latest.ts +30 -17
  318. package/lib/routes/rawkuma/manga.ts +1 -0
  319. package/lib/routes/ruc/ai.ts +1 -1
  320. package/lib/routes/ruc/hr.ts +1 -1
  321. package/lib/routes/samrdprc/index.ts +241 -0
  322. package/lib/routes/samrdprc/namespace.ts +1 -1
  323. package/lib/routes/scientificamerican/podcast.ts +2 -2
  324. package/lib/routes/scitechvista/index.ts +99 -0
  325. package/lib/routes/scitechvista/namespace.ts +7 -0
  326. package/lib/routes/scitechvista/templates/description.art +6 -0
  327. package/lib/routes/scoop/apps.ts +2 -4
  328. package/lib/routes/sdo/ff14risingstones/api.ts +78 -0
  329. package/lib/routes/sdo/ff14risingstones/constant.ts +338 -0
  330. package/lib/routes/sdo/ff14risingstones/posts.ts +80 -0
  331. package/lib/routes/sdo/ff14risingstones/strats.ts +75 -0
  332. package/lib/routes/sdo/ff14risingstones/templates/duties-party.art +41 -0
  333. package/lib/routes/sdo/ff14risingstones/templates/fc-party.art +26 -0
  334. package/lib/routes/sdo/ff14risingstones/templates/novice-network-party.art +9 -0
  335. package/lib/routes/sdo/ff14risingstones/templates/rp-party.art +15 -0
  336. package/lib/routes/sdo/ff14risingstones/timeline.ts +31 -0
  337. package/lib/routes/sdo/ff14risingstones/types/dynamic.ts +50 -0
  338. package/lib/routes/sdo/ff14risingstones/types/index.ts +3 -0
  339. package/lib/routes/sdo/ff14risingstones/types/other.ts +57 -0
  340. package/lib/routes/sdo/ff14risingstones/types/party.ts +111 -0
  341. package/lib/routes/sdo/ff14risingstones/user-dynamics.ts +32 -0
  342. package/lib/routes/sdo/ff14risingstones/user-posts.ts +32 -0
  343. package/lib/routes/sdo/ff14risingstones/user-resently.ts +38 -0
  344. package/lib/routes/sdo/ff14risingstones/user-strats.ts +32 -0
  345. package/lib/routes/sdo/ff14risingstones/utils.ts +215 -0
  346. package/lib/routes/sdo/namespace.ts +7 -0
  347. package/lib/routes/secretsanfrancisco/namespace.ts +7 -0
  348. package/lib/routes/secretsanfrancisco/rss.ts +101 -0
  349. package/lib/routes/secretsanfrancisco/templates/description.art +9 -0
  350. package/lib/routes/sehuatang/user.ts +1 -0
  351. package/lib/routes/showstart/utils.ts +1 -1
  352. package/lib/routes/shu/global.ts +6 -5
  353. package/lib/routes/shu/xxgk.ts +107 -0
  354. package/lib/routes/shuiguopai/index.ts +3 -0
  355. package/lib/routes/sis001/author.ts +1 -0
  356. package/lib/routes/sjtu/jwc.ts +1 -1
  357. package/lib/routes/skeb/following-creators.ts +1 -0
  358. package/lib/routes/skeb/following-works.ts +1 -0
  359. package/lib/routes/skeb/friend-works.ts +1 -0
  360. package/lib/routes/skeb/index.ts +1 -0
  361. package/lib/routes/skeb/search.ts +1 -0
  362. package/lib/routes/skeb/works.ts +44 -25
  363. package/lib/routes/skebetter/illust.ts +1 -0
  364. package/lib/routes/skebetter/index.ts +1 -0
  365. package/lib/routes/skebetter/manga.ts +1 -0
  366. package/lib/routes/sohu/mp.ts +1 -1
  367. package/lib/routes/sotwe/user.ts +1 -1
  368. package/lib/routes/sspai/index.ts +5 -1
  369. package/lib/routes/stcn/index.ts +1 -2
  370. package/lib/routes/stcn/rank.ts +1 -2
  371. package/lib/routes/surfshark/blog.ts +273 -77
  372. package/lib/routes/surfshark/templates/description.art +16 -4
  373. package/lib/routes/sustainabilitymag/articles.ts +1 -1
  374. package/lib/routes/syosetu/dev.ts +1 -1
  375. package/lib/routes/syosetu/ranking-isekai.ts +1 -1
  376. package/lib/routes/szse/disclosure/listed-notice.ts +44 -6
  377. package/lib/routes/t66y/post.ts +1 -0
  378. package/lib/routes/techcrunch/news.ts +1 -0
  379. package/lib/routes/techpowerup/index.ts +11 -11
  380. package/lib/routes/techpowerup/namespace.ts +1 -1
  381. package/lib/routes/techpowerup/review.ts +12 -12
  382. package/lib/routes/techpowerup/utils.ts +2 -2
  383. package/lib/routes/telegram/channel-media.ts +249 -0
  384. package/lib/routes/telegram/channel.ts +5 -4
  385. package/lib/routes/telegram/stories.ts +130 -0
  386. package/lib/routes/telegram/tglib/channel.ts +146 -117
  387. package/lib/routes/telegram/tglib/client.ts +37 -139
  388. package/lib/routes/tesla/cx.ts +1 -1
  389. package/lib/routes/test/index.ts +4 -0
  390. package/lib/routes/thegadgetflow/namespace.ts +7 -0
  391. package/lib/routes/thegadgetflow/rss.ts +99 -0
  392. package/lib/routes/thegadgetflow/templates/description.art +9 -0
  393. package/lib/routes/theverge/index.ts +20 -6
  394. package/lib/routes/threads/utils.ts +7 -3
  395. package/lib/routes/tidb/blog.ts +2 -2
  396. package/lib/routes/tmtpost/column.ts +1 -1
  397. package/lib/routes/toutiao/user.ts +2 -2
  398. package/lib/routes/trendforce/namespace.ts +9 -0
  399. package/lib/routes/trendforce/new.ts +113 -0
  400. package/lib/routes/tsinghua/news.ts +87 -0
  401. package/lib/routes/twitter/api/mobile-api/api.ts +1 -1
  402. package/lib/routes/twitter/api/web-api/api.ts +5 -1
  403. package/lib/routes/txrjy/fornumtopic.ts +2 -2
  404. package/lib/routes/typst/universe.ts +1 -1
  405. package/lib/routes/uber/blog.ts +87 -46
  406. package/lib/routes/uraaka-joshi/uraaka-joshi-user.ts +1 -0
  407. package/lib/routes/uraaka-joshi/uraaka-joshi.ts +3 -0
  408. package/lib/routes/wainao/topics.ts +1 -1
  409. package/lib/routes/wdfxw/bookfree.ts +1 -1
  410. package/lib/routes/weibo/utils.ts +17 -9
  411. package/lib/routes/wikipedia/current-events.ts +411 -0
  412. package/lib/routes/wikipedia/namespace.ts +7 -0
  413. package/lib/routes/wnacg/category.ts +3 -0
  414. package/lib/routes/wnacg/index.ts +3 -0
  415. package/lib/routes/wnacg/tag.ts +3 -0
  416. package/lib/routes/wohnnet/index.ts +2 -2
  417. package/lib/routes/xiaohongshu/user.ts +2 -2
  418. package/lib/routes/xiaohongshu/util.ts +16 -9
  419. package/lib/routes/xjtu/ee-jzxx.ts +1 -1
  420. package/lib/routes/xjtu/zs.ts +1 -1
  421. package/lib/routes/xsijishe/forum.ts +1 -0
  422. package/lib/routes/xsijishe/rank.ts +1 -0
  423. package/lib/routes/xwenming/index.ts +1 -1
  424. package/lib/routes/yahoo/news/utils.ts +7 -3
  425. package/lib/routes/ymgal/article.ts +1 -1
  426. package/lib/routes/yoasobi-music/media.ts +1 -1
  427. package/lib/routes/youtube/api/google.ts +20 -6
  428. package/lib/routes/youtube/api/subtitles.ts +86 -0
  429. package/lib/routes/youtube/api/youtubei.ts +7 -51
  430. package/lib/routes/youtube/channel.ts +1 -1
  431. package/lib/routes/youtube/community.ts +4 -4
  432. package/lib/routes/youtube/user.ts +1 -1
  433. package/lib/routes/yuanliao/index.ts +1 -1
  434. package/lib/routes/zaimanhua/comic.ts +1 -0
  435. package/lib/routes/zaimanhua/update.ts +1 -0
  436. package/lib/routes/zaker/utils.ts +1 -1
  437. package/lib/routes/zaobao/interactive.ts +18 -7
  438. package/lib/routes/zaobao/other.ts +3 -2
  439. package/lib/routes/zaobao/realtime.ts +3 -2
  440. package/lib/routes/zaobao/util.tsx +185 -0
  441. package/lib/routes/zaobao/zaobao.tsx +23 -0
  442. package/lib/routes/zaobao/znews.ts +3 -2
  443. package/lib/routes/zhihu/daily-section.ts +19 -24
  444. package/lib/routes/zhihu/daily.ts +8 -11
  445. package/lib/routes/zju/cse/index.ts +102 -0
  446. package/lib/routes/zodgame/forum.ts +1 -0
  447. package/lib/server.ts +1 -1
  448. package/lib/types.ts +1 -1
  449. package/lib/utils/puppeteer-utils.test.ts +5 -5
  450. package/lib/utils/puppeteer.test.ts +1 -1
  451. package/lib/views/index.tsx +4 -4
  452. package/package.json +55 -57
  453. package/lib/routes/gdufs/xwxy/xwxy-news.ts +0 -90
  454. package/lib/routes/hit/jwc.ts +0 -86
  455. package/lib/routes/picuki/utils.ts +0 -19
  456. package/lib/routes/qweather/templates/3days.art +0 -22
  457. package/lib/routes/qweather/templates/now.art +0 -16
  458. package/lib/routes/xinhuanet/app.ts +0 -109
  459. package/lib/routes/zaobao/templates/zaobao.art +0 -17
  460. package/lib/routes/zaobao/util.ts +0 -198
@@ -10,6 +10,8 @@ import NotFoundError from '@/errors/types/not-found';
10
10
  import { Data } from '@/types';
11
11
  import dayjs from 'dayjs';
12
12
  import duration from 'dayjs/plugin/duration.js';
13
+ import { getSrtAttachmentBatch } from './subtitles';
14
+
13
15
  dayjs.extend(duration);
14
16
 
15
17
  let count = 0;
@@ -105,8 +107,9 @@ export const getDataByUsername = async ({ username, embed, filterShorts }: { use
105
107
  if (!playlistItems) {
106
108
  throw new NotFoundError("This channel doesn't have any content.");
107
109
  }
108
- const videoIds = playlistItems.data.items.map((item) => item.snippet.resourceId.videoId).join(',');
109
- const videoDetails = await utils.getVideos(videoIds, 'contentDetails', cache);
110
+ const videoIds = playlistItems.data.items.map((item) => item.snippet.resourceId.videoId);
111
+ const videoDetails = await utils.getVideos(videoIds.join(','), 'contentDetails', cache);
112
+ const subtitlesMap = await getSrtAttachmentBatch(videoIds);
110
113
 
111
114
  return {
112
115
  title: `${userHandleData?.channelName || username} - YouTube`,
@@ -120,6 +123,8 @@ export const getDataByUsername = async ({ username, embed, filterShorts }: { use
120
123
  const videoId = snippet.resourceId.videoId;
121
124
  const img = utils.getThumbnail(snippet.thumbnails);
122
125
  const detail = videoDetails?.data.items.find((d) => d.id === videoId);
126
+ const srtAttachments = subtitlesMap[videoId] || [];
127
+
123
128
  return {
124
129
  title: snippet.title,
125
130
  description: utils.renderDescription(embed, videoId, img, utils.formatDescription(snippet.description)),
@@ -133,6 +138,7 @@ export const getDataByUsername = async ({ username, embed, filterShorts }: { use
133
138
  mime_type: 'text/html',
134
139
  duration_in_seconds: detail?.contentDetails.duration ? dayjs.duration(detail.contentDetails.duration).asSeconds() : undefined,
135
140
  },
141
+ ...srtAttachments,
136
142
  ],
137
143
  };
138
144
  }),
@@ -147,8 +153,9 @@ export const getDataByChannelId = async ({ channelId, embed, filterShorts }: { c
147
153
  const playlistId = filterShorts ? utils.getPlaylistWithShortsFilter(channelId) : originalPlaylistId;
148
154
 
149
155
  const data = (await utils.getPlaylistItems(playlistId, 'snippet', cache)).data.items;
150
- const videoIds = data.map((item) => item.snippet.resourceId.videoId).join(',');
151
- const videoDetails = await utils.getVideos(videoIds, 'contentDetails', cache);
156
+ const videoIds = data.map((item) => item.snippet.resourceId.videoId);
157
+ const videoDetails = await utils.getVideos(videoIds.join(','), 'contentDetails', cache);
158
+ const subtitlesMap = await getSrtAttachmentBatch(videoIds);
152
159
 
153
160
  return {
154
161
  title: `${data[0].snippet.channelTitle} - YouTube`,
@@ -161,6 +168,8 @@ export const getDataByChannelId = async ({ channelId, embed, filterShorts }: { c
161
168
  const videoId = snippet.resourceId.videoId;
162
169
  const img = utils.getThumbnail(snippet.thumbnails);
163
170
  const detail = videoDetails?.data.items.find((d) => d.id === videoId);
171
+ const srtAttachments = subtitlesMap[videoId] || [];
172
+
164
173
  return {
165
174
  title: snippet.title,
166
175
  description: utils.renderDescription(embed, videoId, img, utils.formatDescription(snippet.description)),
@@ -174,6 +183,7 @@ export const getDataByChannelId = async ({ channelId, embed, filterShorts }: { c
174
183
  mime_type: 'text/html',
175
184
  duration_in_seconds: detail?.contentDetails.duration ? dayjs.duration(detail.contentDetails.duration).asSeconds() : undefined,
176
185
  },
186
+ ...srtAttachments,
177
187
  ],
178
188
  };
179
189
  }),
@@ -184,8 +194,9 @@ export const getDataByPlaylistId = async ({ playlistId, embed }: { playlistId: s
184
194
  const playlistTitle = (await utils.getPlaylist(playlistId, 'snippet', cache)).data.items[0].snippet.title;
185
195
 
186
196
  const data = (await utils.getPlaylistItems(playlistId, 'snippet', cache)).data.items.filter((d) => d.snippet.title !== 'Private video' && d.snippet.title !== 'Deleted video');
187
- const videoIds = data.map((item) => item.snippet.resourceId.videoId).join(',');
188
- const videoDetails = await utils.getVideos(videoIds, 'contentDetails', cache);
197
+ const videoIds = data.map((item) => item.snippet.resourceId.videoId);
198
+ const videoDetails = await utils.getVideos(videoIds.join(','), 'contentDetails', cache);
199
+ const subtitlesMap = await getSrtAttachmentBatch(videoIds);
189
200
 
190
201
  return {
191
202
  title: `${playlistTitle} by ${data[0].snippet.channelTitle} - YouTube`,
@@ -196,6 +207,8 @@ export const getDataByPlaylistId = async ({ playlistId, embed }: { playlistId: s
196
207
  const videoId = snippet.resourceId.videoId;
197
208
  const img = utils.getThumbnail(snippet.thumbnails);
198
209
  const detail = videoDetails?.data.items.find((d) => d.id === videoId);
210
+ const srtAttachments = subtitlesMap[videoId] || [];
211
+
199
212
  return {
200
213
  title: snippet.title,
201
214
  description: utils.renderDescription(embed, videoId, img, utils.formatDescription(snippet.description)),
@@ -209,6 +222,7 @@ export const getDataByPlaylistId = async ({ playlistId, embed }: { playlistId: s
209
222
  mime_type: 'text/html',
210
223
  duration_in_seconds: detail?.contentDetails.duration ? dayjs.duration(detail.contentDetails.duration).asSeconds() : undefined,
211
224
  },
225
+ ...srtAttachments,
212
226
  ],
213
227
  };
214
228
  }),
@@ -0,0 +1,86 @@
1
+ import { getSubtitles } from 'youtube-caption-extractor';
2
+ import pMap from 'p-map';
3
+ import cache from '@/utils/cache';
4
+
5
+ function pad(n: number, width: number = 2) {
6
+ return String(n).padStart(width, '0');
7
+ }
8
+
9
+ function toSrtTime(seconds: number): string {
10
+ const totalMs = Math.floor(seconds * 1000);
11
+ const hours = Math.floor(totalMs / 3_600_000);
12
+ const minutes = Math.floor((totalMs % 3_600_000) / 60000);
13
+ const secs = Math.floor((totalMs % 60000) / 1000);
14
+ const millis = totalMs % 1000;
15
+ return `${pad(hours)}:${pad(minutes)}:${pad(secs)},${pad(millis, 3)}`;
16
+ }
17
+
18
+ export type Subtitle = {
19
+ start: string;
20
+ dur: string;
21
+ text: string;
22
+ };
23
+
24
+ function convertToSrt(segments: Subtitle[]): string {
25
+ return segments
26
+ .map((seg, index) => {
27
+ const start = Number.parseFloat(seg.start);
28
+ const end = start + Number.parseFloat(seg.dur);
29
+ return `${index + 1}
30
+ ${toSrtTime(start)} --> ${toSrtTime(end)}
31
+ ${seg.text}
32
+ `;
33
+ })
34
+ .join('\n');
35
+ }
36
+
37
+ export const getSubtitlesByVideoId = (videoId: string) =>
38
+ cache.tryGet(`youtube:getSubtitlesByVideoId:${videoId}`, async () => {
39
+ try {
40
+ const subtitles = await getSubtitles({ videoID: videoId });
41
+ const srt = convertToSrt(subtitles);
42
+ return srt;
43
+ } catch {
44
+ // Return empty string if subtitles are not available
45
+ return '';
46
+ }
47
+ });
48
+
49
+ const createSubtitleDataUrl = (srt: string): string => `data:text/plain;charset=utf-8,${encodeURIComponent(srt)}`;
50
+
51
+ function createSrtAttachmentFromSrt(srt: string): Array<{ url: string; mime_type: string; title: string }> {
52
+ if (!srt || srt.trim() === '') {
53
+ return [];
54
+ }
55
+
56
+ const dataUrl = createSubtitleDataUrl(srt);
57
+ return [
58
+ {
59
+ url: dataUrl,
60
+ mime_type: 'text/srt',
61
+ title: 'Subtitles',
62
+ },
63
+ ];
64
+ }
65
+
66
+ export const getSrtAttachment = async (videoId: string): Promise<Array<{ url: string; mime_type: string; title: string }>> => {
67
+ try {
68
+ const srt = await getSubtitlesByVideoId(videoId);
69
+ return createSrtAttachmentFromSrt(srt);
70
+ } catch {
71
+ return [];
72
+ }
73
+ };
74
+
75
+ export const getSrtAttachmentBatch = async (videoIds: string[]) => {
76
+ const results = await pMap(
77
+ videoIds,
78
+ async (videoId) => {
79
+ const srt = await getSubtitlesByVideoId(videoId);
80
+ return { videoId, srt: createSrtAttachmentFromSrt(srt) };
81
+ },
82
+ { concurrency: 5 }
83
+ );
84
+
85
+ return Object.fromEntries(results.map(({ videoId, srt }) => [videoId, srt]));
86
+ };
@@ -1,44 +1,12 @@
1
- import { getSubtitles } from 'youtube-caption-extractor';
1
+ import { Data } from '@/types';
2
2
  import cache from '@/utils/cache';
3
+ import { parseRelativeDate } from '@/utils/parse-date';
3
4
  import { Innertube } from 'youtubei.js';
4
5
  import utils, { getVideoUrl } from '../utils';
5
- import { Data } from '@/types';
6
- import { parseRelativeDate } from '@/utils/parse-date';
6
+ import { getSrtAttachmentBatch } from './subtitles';
7
7
 
8
8
  const innertubePromise = Innertube.create();
9
9
 
10
- function pad(n: number, width: number = 2) {
11
- return String(n).padStart(width, '0');
12
- }
13
-
14
- function toSrtTime(seconds: number): string {
15
- const totalMs = Math.floor(seconds * 1000);
16
- const hours = Math.floor(totalMs / 3_600_000);
17
- const minutes = Math.floor((totalMs % 3_600_000) / 60000);
18
- const secs = Math.floor((totalMs % 60000) / 1000);
19
- const millis = totalMs % 1000;
20
- return `${pad(hours)}:${pad(minutes)}:${pad(secs)},${pad(millis, 3)}`;
21
- }
22
-
23
- type Subtitle = {
24
- start: string;
25
- dur: string;
26
- text: string;
27
- };
28
-
29
- function convertToSrt(segments: Subtitle[]): string {
30
- return segments
31
- .map((seg, index) => {
32
- const start = Number.parseFloat(seg.start);
33
- const end = start + Number.parseFloat(seg.dur);
34
- return `${index + 1}
35
- ${toSrtTime(start)} --> ${toSrtTime(end)}
36
- ${seg.text}
37
- `;
38
- })
39
- .join('\n');
40
- }
41
-
42
10
  export const getChannelIdByUsername = (username: string) =>
43
11
  cache.tryGet(`youtube:getChannelIdByUsername:${username}`, async () => {
44
12
  const innertube = await innertubePromise;
@@ -51,17 +19,11 @@ export const getDataByUsername = async ({ username, embed, filterShorts }: { use
51
19
  return getDataByChannelId({ channelId, embed, filterShorts });
52
20
  };
53
21
 
54
- const getSubtitlesByVideoId = (videoId: string) =>
55
- cache.tryGet(`youtube:getSubtitlesByVideoId:${videoId}`, async () => {
56
- const subtitles = await getSubtitles({ videoID: videoId });
57
- const srt = convertToSrt(subtitles);
58
- return srt;
59
- });
60
-
61
22
  export const getDataByChannelId = async ({ channelId, embed }: { channelId: string; embed: boolean; filterShorts: boolean }): Promise<Data> => {
62
23
  const innertube = await innertubePromise;
63
24
  const channel = await innertube.getChannel(channelId);
64
25
  const videos = await channel.getVideos();
26
+ const videoSubtitles = await getSrtAttachmentBatch(videos.videos.filter((video) => 'video_id' in video).map((video) => video.video_id));
65
27
 
66
28
  return {
67
29
  title: `${channel.metadata.title || channelId} - YouTube`,
@@ -72,10 +34,8 @@ export const getDataByChannelId = async ({ channelId, embed }: { channelId: stri
72
34
  item: await Promise.all(
73
35
  videos.videos
74
36
  .filter((video) => 'video_id' in video)
75
- .map(async (video) => {
76
- const srt = await getSubtitlesByVideoId(video.video_id);
77
- const dataUrl = `data:text/plain;charset=utf-8,${encodeURIComponent(srt)}`;
78
-
37
+ .map((video) => {
38
+ const srtAttachments = videoSubtitles[video.video_id] || [];
79
39
  const img = 'best_thumbnail' in video ? video.best_thumbnail?.url : 'thumbnails' in video ? video.thumbnails?.[0]?.url : undefined;
80
40
 
81
41
  return {
@@ -91,11 +51,7 @@ export const getDataByChannelId = async ({ channelId, embed }: { channelId: stri
91
51
  mime_type: 'text/html',
92
52
  duration_in_seconds: video.duration && 'seconds' in video.duration ? video.duration.seconds : undefined,
93
53
  },
94
- {
95
- url: dataUrl,
96
- mime_type: 'text/srt',
97
- title: 'Subtitles',
98
- },
54
+ ...srtAttachments,
99
55
  ],
100
56
  };
101
57
  })
@@ -21,7 +21,7 @@ export const route: Route = {
21
21
  name: 'Channel with id',
22
22
  maintainers: ['DIYgod', 'pseudoyu'],
23
23
  handler,
24
- description: `:::tip Parameter
24
+ description: `::: tip Parameter
25
25
  | Name | Description | Default |
26
26
  | ---------- | ----------------------------------------------------------------------------------- | ------- |
27
27
  | embed | Whether to embed the video, fill in any value to disable embedding | embed |
@@ -1,6 +1,6 @@
1
1
  import { Route } from '@/types';
2
2
 
3
- import got from '@/utils/got';
3
+ import ofetch from '@/utils/ofetch';
4
4
  import { load } from 'cheerio';
5
5
  import { parseRelativeDate } from '@/utils/parse-date';
6
6
  import { art } from '@/utils/render';
@@ -12,7 +12,7 @@ export const route: Route = {
12
12
  categories: ['social-media'],
13
13
  example: '/youtube/community/@JFlaMusic',
14
14
  parameters: { handle: 'YouTube handles or channel id' },
15
- name: 'Community',
15
+ name: 'Community Posts',
16
16
  maintainers: ['TonyRL'],
17
17
  handler,
18
18
  };
@@ -25,7 +25,7 @@ async function handler(ctx) {
25
25
  urlPath = `channel/${handle}`;
26
26
  }
27
27
 
28
- const { data: response } = await got(`https://www.youtube.com/${urlPath}/community`);
28
+ const response = await ofetch(`https://www.youtube.com/${urlPath}/posts`);
29
29
  const $ = load(response);
30
30
  const ytInitialData = JSON.parse(
31
31
  $('script')
@@ -62,7 +62,7 @@ async function handler(ctx) {
62
62
  });
63
63
 
64
64
  return {
65
- title: `${username} - Community - YouTube`,
65
+ title: `${username} - Community Posts- YouTube`,
66
66
  link: channelMetadata.channelUrl,
67
67
  description: channelMetadata.description,
68
68
  item: items,
@@ -12,7 +12,7 @@ export const route: Route = {
12
12
  username: 'YouTuber handle with @',
13
13
  routeParams: 'Extra parameters, see the table below',
14
14
  },
15
- description: `:::tip Parameter
15
+ description: `::: tip Parameter
16
16
  | Name | Description | Default |
17
17
  | ---------- | ----------------------------------------------------------------------------------- | ------- |
18
18
  | embed | Whether to embed the video, fill in any value to disable embedding | embed |
@@ -139,7 +139,7 @@ export const route: Route = {
139
139
  ],
140
140
  },
141
141
  },
142
- description: `:::tip
142
+ description: `::: tip
143
143
  订阅 [问题反馈](https://yuanliao.info/t/bug-report),其源网址为 \`https://yuanliao.info/t/bug-report\`,请参考该 URL 指定部分构成参数,此时路由为 [\`/yuanliao/bug-report\`](https://rsshub.app/yuanliao/bug-report)。
144
144
  :::
145
145
 
@@ -19,6 +19,7 @@ export const route: Route = {
19
19
  supportBT: false,
20
20
  supportPodcast: false,
21
21
  supportScihub: false,
22
+ nsfw: true,
22
23
  },
23
24
  radar: [
24
25
  {
@@ -18,6 +18,7 @@ export const route: Route = {
18
18
  supportBT: false,
19
19
  supportPodcast: false,
20
20
  supportScihub: false,
21
+ nsfw: true,
21
22
  },
22
23
  radar: [
23
24
  {
@@ -84,7 +84,7 @@ export const getSafeLineCookieWithData = async (link): Promise<{ cookie: string;
84
84
  query: {
85
85
  once_id: onceId,
86
86
  v: '1.0.0',
87
- hints: hints.sort(() => Math.random() - 0.5).join(','),
87
+ hints: hints.toSorted(() => Math.random() - 0.5).join(','),
88
88
  },
89
89
  });
90
90
 
@@ -1,6 +1,7 @@
1
1
  import { Route } from '@/types';
2
- import { parseList } from './util';
3
- const baseUrl = 'https://www.zaobao.com';
2
+ import { logo } from './util';
3
+ import ofetch from '@/utils/ofetch';
4
+ import { parseDate } from '@/utils/parse-date';
4
5
 
5
6
  export const route: Route = {
6
7
  path: '/interactive-graphics',
@@ -11,15 +12,25 @@ export const route: Route = {
11
12
  handler,
12
13
  };
13
14
 
14
- async function handler(ctx) {
15
+ async function handler() {
16
+ const baseUrl = 'https://www.zaobao.com.sg'; // SG Only
15
17
  const sectionLink = '/interactive-graphics';
16
18
 
17
- const { resultList } = await parseList(ctx, sectionLink);
19
+ const response = await ofetch(`${baseUrl}/_plat/api/v2/page-content/interactive-graphics`);
20
+
21
+ const items = response.response.articles.map((i) => ({
22
+ title: i.title,
23
+ description: i.summary,
24
+ link: new URL(i.href, baseUrl).href,
25
+ pubDate: parseDate(i.timestamp, 'X'),
26
+ image: i.thumbnail,
27
+ }));
18
28
 
19
29
  return {
20
- title: '《联合早报》互动新闻',
30
+ title: response.seoMetaInfo.seoTitle,
21
31
  link: baseUrl + sectionLink,
22
- description: '新加坡、中国、亚洲和国际的即时、评论、商业、体育、生活、科技与多媒体新闻,尽在联合早报。',
23
- item: resultList,
32
+ description: response.seoMetaInfo.seoDescription,
33
+ image: logo,
34
+ item: items,
24
35
  };
25
36
  }
@@ -1,5 +1,5 @@
1
1
  import { Route } from '@/types';
2
- import { parseList } from './util';
2
+ import { logo, parseList } from './util';
3
3
  const baseUrl = 'https://www.zaobao.com';
4
4
 
5
5
  export const route: Route = {
@@ -18,12 +18,13 @@ async function handler(ctx) {
18
18
  const section = ctx.req.param('section') ?? 'china';
19
19
  const sectionLink = `/${type}/${section}`;
20
20
 
21
- const { title, resultList } = await parseList(ctx, sectionLink);
21
+ const { title, resultList } = await parseList(sectionLink);
22
22
 
23
23
  return {
24
24
  title: `《联合早报》${title}`,
25
25
  link: baseUrl + sectionLink,
26
26
  description: '新加坡、中国、亚洲和国际的即时、评论、商业、体育、生活、科技与多媒体新闻,尽在联合早报。',
27
+ image: logo,
27
28
  item: resultList,
28
29
  };
29
30
  }
@@ -1,5 +1,5 @@
1
1
  import { Route } from '@/types';
2
- import { parseList } from './util';
2
+ import { logo, parseList } from './util';
3
3
  const baseUrl = 'https://www.zaobao.com';
4
4
 
5
5
  export const route: Route = {
@@ -48,12 +48,13 @@ async function handler(ctx) {
48
48
  break;
49
49
  }
50
50
 
51
- const { resultList } = await parseList(ctx, sectionLink);
51
+ const { resultList } = await parseList(sectionLink);
52
52
 
53
53
  return {
54
54
  title: `《联合早报》-${name}-即时`,
55
55
  link: baseUrl + sectionLink,
56
56
  description: '新加坡、中国、亚洲和国际的即时、评论、商业、体育、生活、科技与多媒体新闻,尽在联合早报。',
57
+ image: logo,
57
58
  item: resultList,
58
59
  };
59
60
  }
@@ -0,0 +1,185 @@
1
+ import cache from '@/utils/cache';
2
+ import ofetch from '@/utils/ofetch';
3
+ import { load } from 'cheerio';
4
+ import { parseDate } from '@/utils/parse-date';
5
+ import { renderToString } from 'hono/jsx/dom/server';
6
+ import { base32 } from 'rfc4648';
7
+ import Zaobao from './zaobao';
8
+
9
+ const baseUrl = 'https://www.zaobao.com';
10
+ export const logo = 'https://www.zaobao.com.sg/favicon.ico';
11
+
12
+ /**
13
+ * 通用解析页面类似 https://www.zaobao.com/realtime/china 的网站
14
+ *
15
+ * @param {string} sectionUrl 形如 /realtime/china 的字符串
16
+ * @returns 新闻标题以及新闻列表
17
+ */
18
+ export const parseList = async (
19
+ sectionUrl: string
20
+ ): Promise<{
21
+ title: string;
22
+ resultList: {
23
+ title: string;
24
+ description: string;
25
+ pubDate: Date;
26
+ link: string;
27
+ category: string[];
28
+ }[];
29
+ }> => {
30
+ const pageResponse = await ofetch.raw(baseUrl + sectionUrl);
31
+ const $ = load(pageResponse._data);
32
+ let data = $('.card-listing .card .content-header a');
33
+ if (data.length === 0) {
34
+ // for HK version
35
+ data = $('[data-testid="article-list"] article div div a.article-link');
36
+ }
37
+ const origin = new URL(pageResponse.url).origin;
38
+
39
+ const title = $('meta[property="og:title"]').attr('content');
40
+
41
+ const resultList = await Promise.all(
42
+ data.toArray().map((item) => {
43
+ const $item = $(item);
44
+ const link = baseUrl + $item.attr('href');
45
+
46
+ return cache.tryGet(link, async () => {
47
+ const response = await ofetch.raw(origin + $item.attr('href'));
48
+ let $1 = load(response._data);
49
+
50
+ let title, pubDate, category, images;
51
+ const jsonText = $1('head script[type="application/ld+json"]')
52
+ .text()
53
+ .replaceAll(/[\u0000-\u001F\u007F-\u009F]/g, '');
54
+ const ldJson = JSON.parse(jsonText);
55
+
56
+ const isSingapore = response.url.startsWith('https://www.zaobao.com.sg/');
57
+ if (isSingapore) {
58
+ const hydrationData = JSON.parse(
59
+ JSON.parse(
60
+ `"${
61
+ $1('script:contains("__staticRouterHydrationData")')
62
+ .text()
63
+ .match(/__staticRouterHydrationData = JSON.parse\("(.*)"\);/)?.[1]
64
+ }"`
65
+ )
66
+ );
67
+ const article = hydrationData.loaderData['0-0'].context.payload.article;
68
+ title = article.headline;
69
+ pubDate = parseDate(article.create_time, 'X');
70
+ category = article.tags.map((t) => t.name);
71
+ $1 = load(article.body_cn, null, false);
72
+ images = article.images;
73
+ } else {
74
+ title = ldJson.headline;
75
+ pubDate = parseDate(ldJson.datePublished);
76
+ category = ldJson.keywords.split(',');
77
+ }
78
+
79
+ // $1('.overlay-microtransaction').remove();
80
+ // $1('#video-freemium-player').remove();
81
+ $1('.bff-google-ad, .bff-recommend-article').remove(); // SG
82
+ $1('button.cursor-pointer').remove(); // SG
83
+ $1('.bff-inline-image-expand-icon').remove(); // SG
84
+ $1('img[alt="expend icon"]').remove(); // HK
85
+
86
+ let articleBodyNode;
87
+ if (isSingapore) {
88
+ articleBodyNode = $1;
89
+ } else {
90
+ // for HK version
91
+ $1('astro-island, .further-reading, .read-on-app-cover').remove();
92
+ articleBodyNode = $1('.article-body');
93
+ }
94
+
95
+ const articleBody = articleBodyNode.html();
96
+ const imageDataArray = processImageData(isSingapore, images, $1);
97
+
98
+ // use JSX as template
99
+ const dom = <Zaobao imageDataArray={imageDataArray} articleBody={articleBody}></Zaobao>;
100
+ const description = renderToString(dom);
101
+
102
+ return {
103
+ title,
104
+ description,
105
+ pubDate,
106
+ link,
107
+ category,
108
+ };
109
+ });
110
+ })
111
+ );
112
+ return {
113
+ title,
114
+ resultList,
115
+ };
116
+ };
117
+
118
+ export const orderContent = (parent) => {
119
+ for (const [i, e] of parent
120
+ .children()
121
+ .toArray()
122
+ .toSorted((a, b) => {
123
+ const index = Buffer.from(base32.parse('GM======')).toString(); // substring(3)
124
+ a = Buffer.from(
125
+ base32.parse(
126
+ parent
127
+ .find((element) => a(element))
128
+ .data('s')
129
+ .slice(index)
130
+ )
131
+ ).toString();
132
+ b = Buffer.from(
133
+ base32.parse(
134
+ parent
135
+ .find((element) => b(element))
136
+ .data('s')
137
+ .slice(index)
138
+ )
139
+ ).toString();
140
+ return a - b;
141
+ })
142
+ .entries()) {
143
+ parent.find((element) => e(element)).attr('s', i);
144
+ parent.append(e);
145
+ }
146
+ };
147
+
148
+ export interface ImageData {
149
+ type: string;
150
+ html: string;
151
+ src?: string;
152
+ title?: string;
153
+ }
154
+
155
+ const processImageData = (isSg, images, $1) => {
156
+ if (isSg && images) {
157
+ return images.map((img) => ({
158
+ type: 'data',
159
+ html: '',
160
+ src: img.url
161
+ .replaceAll(/\/\/.*\.com\/s3fs-public/g, '//static.zaobao.com/s3fs-public')
162
+ .replaceAll('s3/files', 's3fs-public')
163
+ .split('?')[0],
164
+ title: img.caption,
165
+ })) as ImageData[];
166
+ }
167
+
168
+ const hkImg = $1('[data-testid="article-banner"] img');
169
+ if (hkImg.length) {
170
+ return [
171
+ {
172
+ type: 'data',
173
+ html: hkImg.prop('outerHTML'),
174
+ src: hkImg
175
+ .attr('src')
176
+ .replaceAll(/\/\/.*\.com\/s3fs-public/g, '//static.zaobao.com/s3fs-public')
177
+ .replaceAll('s3/files', 's3fs-public')
178
+ .split('?')[0],
179
+ title: hkImg.attr('title'),
180
+ },
181
+ ] as ImageData[];
182
+ }
183
+
184
+ return [];
185
+ };
@@ -0,0 +1,23 @@
1
+ import { ImageData } from './util';
2
+
3
+ function zaobao({ imageDataArray, articleBody }) {
4
+ return (
5
+ <>
6
+ {imageDataArray.map((imageData: ImageData) =>
7
+ imageData.type === 'normalHTML' ? (
8
+ <div dangerouslySetInnerHTML={{ __html: imageData.html }} />
9
+ ) : (
10
+ imageData.type === 'data' && (
11
+ <figure>
12
+ <img src={imageData.src} />
13
+ <figcaption>{imageData.title}</figcaption>
14
+ </figure>
15
+ )
16
+ )
17
+ )}
18
+ {articleBody && <div dangerouslySetInnerHTML={{ __html: articleBody }} />}
19
+ </>
20
+ );
21
+ }
22
+
23
+ export default zaobao;