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
@@ -1,160 +1,191 @@
1
- import InvalidParameterError from '@/errors/types/invalid-parameter';
2
- import { client, decodeMedia, getClient, getFilename, getMediaLink, streamDocument, streamThumbnail } from './client';
3
- import { returnBigInt } from 'telegram/Helpers.js';
4
- import { HTMLParser } from 'telegram/extensions/html.js';
1
+ /* eslint-disable no-await-in-loop */
5
2
  import { DataItem } from '@/types';
6
- import type { Api } from 'telegram';
3
+ import { Context } from 'hono';
4
+ import { Api } from 'telegram';
5
+ import { HTMLParser } from 'telegram/extensions/html.js';
6
+ import { getClient, getDocument, getFilename, unwrapMedia } from './client';
7
+ import { getDisplayName } from 'telegram/Utils.js';
8
+ import cache from '@/utils/cache';
9
+ import { returnBigInt } from 'telegram/Helpers.js';
7
10
 
8
- function parseRange(range, length) {
9
- if (!range) {
10
- return [];
11
+ export function getGeoLink(geo: Api.GeoPoint) {
12
+ return `<a href="https://www.google.com/maps/search/?api=1&query=${geo.lat}%2C${geo.long}" target="_blank">Geo LatLon: ${geo.lat}, ${geo.long}</a>`;
13
+ }
14
+
15
+ export async function getPollResults(client, message, m: Api.MessageMediaPoll) {
16
+ const resultsUpdateResponse = await client.invoke(new Api.messages.GetPollResults({ peer: message.peerId, msgId: message.id }));
17
+ let results: Api.PollResults;
18
+ if (resultsUpdateResponse?.updates[0] instanceof Api.UpdateMessagePoll) {
19
+ results = resultsUpdateResponse.updates[0].results as Api.PollResults;
11
20
  }
12
- const [typ, segstr] = range.split('=');
13
- if (typ !== 'bytes') {
14
- throw new InvalidParameterError(`unsupported range: ${typ}`);
21
+ const txt = `<h4>${m.poll.quiz ? 'Quiz' : 'Poll'}: ${m.poll.question.text}</h4>
22
+ <div><ul>${m.poll.answers
23
+ .map((a) => {
24
+ let answerTxt = a.text.text;
25
+ const result = results.results?.find((r) => r.option.buffer === a.option.buffer);
26
+ if (result && results.totalVoters) {
27
+ answerTxt = `<strong>${Math.round((result.voters / results.totalVoters) * 100)}%</strong>: ${answerTxt}`;
28
+ }
29
+ return `<li>${answerTxt}</li>`;
30
+ })
31
+ .join('')}</ul></div>
32
+ `;
33
+ return txt;
34
+ }
35
+
36
+ export function getMediaLink(src: string, m: Api.TypeMessageMedia) {
37
+ const doc = getDocument(m);
38
+ const mime = doc ? doc.mimeType : '';
39
+
40
+ if (m instanceof Api.MessageMediaPhoto || mime.startsWith('image/')) {
41
+ return `<img src="${src}" alt=""/>`;
15
42
  }
16
- const segs = segstr.split(',').map((s) => s.trim());
17
- const parsedSegs = [];
18
- for (const seg of segs) {
19
- const range = seg
20
- .split('-', 2)
21
- .filter((v) => !!v)
22
- .map((elem) => returnBigInt(elem));
23
- if (range.length < 2) {
24
- if (seg.startsWith('-')) {
25
- range.unshift(0);
26
- } else {
27
- range.push(length);
28
- }
29
- }
30
- parsedSegs.push(range);
43
+ if (doc && mime.startsWith('video/')) {
44
+ const vid = (doc.attributes.find((t) => t instanceof Api.DocumentAttributeVideo) ?? { w: 1080, h: 720 }) as { w: number; h: number };
45
+ return `<video controls preload="metadata" poster="${src}?thumb" width="${vid.w / 2}" height="${vid.h / 2}"><source src="${src}" type="${mime}"></video>`;
46
+ }
47
+ if (doc && mime.startsWith('audio/')) {
48
+ return `<div>${getAudioTitle(m)}</div><div><audio src="${src}"></audio></div>`;
31
49
  }
32
- return parsedSegs;
33
- }
34
50
 
35
- async function getMedia(ctx) {
36
- const media = await decodeMedia(ctx.req.param('username'), ctx.req.param('media'));
37
- if (!media) {
38
- ctx.status = 500;
39
- return ctx.res.end();
51
+ if (doc && mime.startsWith('application/')) {
52
+ let linkText = `${getFilename(m)} (${humanFileSize(doc.size.valueOf())})`;
53
+ if (mime.endsWith('x-tgsticker')) {
54
+ linkText = ''; // remove filename, it's only an animated sticker
55
+ }
56
+ if ((doc.thumbs?.length ?? 0) > 0) {
57
+ linkText = `<div><img src="${src}?thumb" alt=""/></div><div>${linkText}</div>`;
58
+ }
59
+ return `<a href="${src}" target="_blank">${linkText}</a>`;
60
+ }
61
+ if ((m instanceof Api.MessageMediaGeo || m instanceof Api.MessageMediaGeoLive) && m.geo instanceof Api.GeoPoint) {
62
+ return getGeoLink(m.geo);
63
+ }
64
+ if (m instanceof Api.MessageMediaWebPage) {
65
+ return ''; // a link without a document attach, usually is in the message text, so we can skip here
66
+ }
67
+ if (m instanceof Api.MessageMediaContact) {
68
+ return `Contact: <a href="tel:${m.phoneNumber}" target="_blank">${m.firstName} ${m.lastName} ${m.phoneNumber}</a>`;
69
+ // TODO: download vCard as media ?
40
70
  }
41
- if (ctx.res.closed) {
42
- // console.log(`prematurely closed ${ctx.req.param('media')}`);
43
- return;
71
+ if (m instanceof Api.MessageMediaInvoice) {
72
+ let description = m.description;
73
+ if (m.photo?.url) {
74
+ description = `<img src="${m.photo?.url}" /><br />${description}`;
75
+ }
76
+ return `<h4>${m.test ? 'TEST ' : ''}Invoice: ${m.title}</h4><div>${description}</div>`;
44
77
  }
45
78
 
46
- if (media.document) {
47
- ctx.status = 200;
48
- let stream;
49
- if ('thumb' in ctx.req.query()) {
50
- try {
51
- stream = streamThumbnail(media);
52
- ctx.set('Content-Type', 'image/jpeg');
53
- } catch {
54
- ctx.status = 404;
55
- return ctx.res.end();
56
- }
57
- } else {
58
- ctx.set('Content-Type', media.document.mimeType);
59
-
60
- ctx.set('Accept-Ranges', 'bytes');
61
- const range = parseRange(ctx.get('Range'), media.document.size - 1);
62
- if (range.length > 1) {
63
- ctx.status = 416; // range not satisfiable
64
- return ctx.res.end();
65
- }
66
- if (range.length === 1) {
67
- // console.log(`${ctx.method} ${ctx.req.url} Range: ${ctx.get('Range')}`);
68
- ctx.status = 206; // partial content
69
- const [offset, limit] = range[0];
70
- ctx.set('Content-Length', limit - offset + 1);
71
- ctx.set('Content-Range', `bytes ${offset}-${limit}/${media.document.size}`);
72
-
73
- const stream = streamDocument(media.document, '', offset, limit);
74
- for await (const chunk of stream) {
75
- ctx.res.write(chunk);
76
- if (ctx.res.closed) {
77
- break;
78
- }
79
- }
80
- return ctx.res.end();
81
- }
79
+ return m.className;
80
+ }
82
81
 
83
- ctx.set('Content-Length', media.document.size);
84
- if (media.document.mimeType.startsWith('application/')) {
85
- ctx.set('Content-Disposition', `attachment; filename="${encodeURIComponent(getFilename(media))}"`);
86
- }
87
- stream = streamDocument(media.document);
88
- }
89
- // const addr = JSON.stringify(ctx.res.socket.address());
90
- // console.log(`streaming ${ctx.req.param('media')} to ${addr}`);
82
+ function humanFileSize(size: number) {
83
+ const i = size === 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
84
+ return (size / Math.pow(1024, i)).toFixed(2) + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
85
+ }
91
86
 
92
- for await (const chunk of stream) {
93
- if (ctx.res.closed) {
94
- // console.log(`closed ${addr}`);
95
- break;
96
- }
97
- // console.log(`writing ${chunk.length / 1024} to ${addr}`);
98
- ctx.res.write(chunk);
99
- }
100
- if ('close' in stream) {
101
- stream.close();
87
+ export function getAudioTitle(x: Api.TypeMessageMedia) {
88
+ if (x instanceof Api.MessageMediaDocument && x.document instanceof Api.Document) {
89
+ const attr = x.document.attributes.find((x) => x instanceof Api.DocumentAttributeAudio);
90
+ if (attr) {
91
+ return `${attr.performer} - ${attr.title} (${humanDuration(attr.duration)})`;
102
92
  }
103
- } else if (media.photo) {
104
- ctx.status = 200;
105
- ctx.set('Content-Type', 'image/jpeg');
106
- const buf = await client.downloadMedia(media);
107
- ctx.res.write(buf);
93
+ }
94
+ return getFilename(x);
95
+ }
96
+
97
+ export function humanDuration(seconds: number) {
98
+ const hours = Math.floor(seconds / 3600);
99
+ const minutes = Math.floor((seconds % 3600) / 60);
100
+ const remainingSeconds = seconds % 60;
101
+
102
+ // Format time components with leading zeros if necessary
103
+ const paddedMinutes = String(minutes).padStart(2, '0');
104
+ const paddedSeconds = String(remainingSeconds).padStart(2, '0');
105
+
106
+ // Construct the time string conditionally
107
+ if (hours > 0) {
108
+ return `${hours}:${paddedMinutes}:${paddedSeconds}`; // Show hours, minutes, and seconds
109
+ } else if (minutes > 0) {
110
+ return `${minutes}:${paddedSeconds}`; // Show minutes and seconds
108
111
  } else {
109
- ctx.status = 415;
110
- ctx.write(media.className);
112
+ return `0:${paddedSeconds}`; // Show only seconds
111
113
  }
112
- return ctx.res.end();
113
114
  }
114
115
 
115
- export default async function handler(ctx) {
116
- const { username } = ctx.req.param();
116
+ export default async function handler(ctx: Context) {
117
117
  const client = await getClient();
118
+ const username = ctx.req.param('username');
118
119
 
119
- const item: DataItem[] = [];
120
- const chat = (await client.getInputEntity(username)) as Api.InputPeerChannel;
121
- const channelInfo = await client.getEntity(chat);
122
-
123
- if (channelInfo.className !== 'Channel') {
124
- throw new Error(`${username} is not a channel`);
120
+ let peerCache = await cache.get(`telegram:inputEntity:${username}`);
121
+ if (!peerCache) {
122
+ const p = await client.getInputEntity(username);
123
+ peerCache = JSON.stringify(p.toJSON());
124
+ await cache.set(`telegram:inputEntity:${username}`, peerCache);
125
125
  }
126
+ const peerData = JSON.parse(peerCache, (k, v) => (k === 'channelId' || k === 'accessHash' ? returnBigInt(v) : v));
127
+ const peer = new Api.InputPeerChannel(peerData);
128
+
129
+ const entity = await client.getEntity(peer);
126
130
 
127
131
  let attachments: string[] = [];
128
- const messages = await client.getMessages(chat, { limit: 50 });
132
+ const messages = await client.getMessages(peer, { limit: 50 });
129
133
 
134
+ let i = 0;
135
+ const item: DataItem[] = [];
130
136
  for (const message of messages) {
131
- if (message.media) {
137
+ let text = message.text; // must not be HTML
138
+
139
+ if (message.fwdFrom?.fromId) {
140
+ const fwdFrom = await client.getEntity(message.fwdFrom.fromId);
141
+ text = `Forwarded From: ${getDisplayName(fwdFrom)}: ${text}`;
142
+ }
143
+ const media = await unwrapMedia(message.media, message.peerId);
144
+ if (message.media instanceof Api.MessageMediaStory && media) {
145
+ // if successfully loaded the story
146
+ const storyFrom = await client.getEntity(message.media.peer);
147
+ text = `Story From: ${getDisplayName(storyFrom)}: ${text}`;
148
+ }
149
+ if (media) {
150
+ if (media instanceof Api.MessageMediaPoll) {
151
+ attachments.push(await getPollResults(client, message, media));
152
+ continue;
153
+ }
132
154
  // messages that have no text are shown as if they're one post
133
155
  // because in TG only 1 attachment per message is possible
134
- attachments.push(getMediaLink(ctx, chat, username, message));
156
+ const src = `${new URL(ctx.req.url).origin}/telegram/media/${username}/${message.id}`;
157
+ attachments.push(getMediaLink(src, media));
135
158
  }
136
- if (message.text !== '') {
137
- let description = attachments.join('\n');
159
+ if (message.replyMarkup instanceof Api.ReplyInlineMarkup) {
160
+ for (const buttonRow of message.replyMarkup.rows) {
161
+ for (const button of buttonRow.buttons) {
162
+ if (button instanceof Api.KeyboardButtonUrl) {
163
+ attachments.push(`<div><a href="${button.url}" target="_blank">${button.text}</a></div>`);
164
+ }
165
+ }
166
+ }
167
+ }
168
+ if (text !== '' || ++i === messages.length - 1) {
169
+ let description = attachments.join('<br/>\n');
138
170
  attachments = []; // emitting these, buffer other ones
139
171
 
140
- if (message.text) {
172
+ if (text) {
141
173
  description += `<p>${HTMLParser.unparse(message.message, message.entities).replaceAll('\n', '<br/>')}</p>`;
142
174
  }
143
175
 
144
176
  const title = message.text ? message.text.slice(0, 80) + (message.text.length > 80 ? '...' : '') : new Date(message.date * 1000).toUTCString();
145
-
146
177
  item.push({
147
178
  title,
148
179
  description,
149
180
  pubDate: new Date(message.date * 1000).toUTCString(),
150
181
  link: `https://t.me/s/${username}/${message.id}`,
151
- author: `${channelInfo.title} (@${username})`,
182
+ author: getDisplayName(message.sender ?? entity),
152
183
  });
153
184
  }
154
185
  }
155
186
 
156
187
  return {
157
- title: channelInfo.title,
188
+ title: getDisplayName(entity),
158
189
  language: null,
159
190
  link: `https://t.me/${username}`,
160
191
  item,
@@ -162,5 +193,3 @@ export default async function handler(ctx) {
162
193
  description: `@${username} on Telegram`,
163
194
  };
164
195
  }
165
-
166
- export { getMedia };
@@ -1,7 +1,6 @@
1
1
  import { Api, TelegramClient } from 'telegram';
2
2
  import { UserAuthParams } from 'telegram/client/auth';
3
3
  import { StringSession } from 'telegram/sessions/index.js';
4
- import { getAppropriatedPartSize } from 'telegram/Utils.js';
5
4
 
6
5
  import { config } from '@/config';
7
6
  import ConfigNotFoundError from '@/errors/types/config-not-found';
@@ -34,160 +33,59 @@ export async function getClient(authParams?: UserAuthParams, session?: string) {
34
33
  : undefined,
35
34
  });
36
35
 
37
- await client.connect();
36
+ await client.start(
37
+ Object.assign(authParams ?? {}, {
38
+ onError: (err: Error) => {
39
+ throw new Error('Cannot start TG: ' + err);
40
+ },
41
+ }) as any
42
+ );
38
43
  return client;
39
44
  }
40
45
 
41
- function humanFileSize(size) {
42
- const i = size === 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
43
- return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
44
- }
45
-
46
- /**
47
- * https://core.telegram.org/api/files#stripped-thumbnails
48
- * @param bytes Buffer
49
- * @returns Buffer jpeg
50
- */
51
- function ExpandInlineBytes(bytes) {
52
- if (bytes.length < 3 || bytes[0] !== 0x1) {
53
- return [];
54
- }
55
- const header = Buffer.from([
56
- 0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xDB, 0x00, 0x43, 0x00, 0x28, 0x1C, 0x1E, 0x23, 0x1E, 0x19, 0x28, 0x23, 0x21, 0x23, 0x2D, 0x2B,
57
- 0x28, 0x30, 0x3C, 0x64, 0x41, 0x3C, 0x37, 0x37, 0x3C, 0x7B, 0x58, 0x5D, 0x49, 0x64, 0x91, 0x80, 0x99, 0x96, 0x8F, 0x80, 0x8C, 0x8A, 0xA0, 0xB4, 0xE6, 0xC3, 0xA0, 0xAA, 0xDA, 0xAD, 0x8A, 0x8C, 0xC8, 0xFF, 0xCB, 0xDA, 0xEE,
58
- 0xF5, 0xFF, 0xFF, 0xFF, 0x9B, 0xC1, 0xFF, 0xFF, 0xFF, 0xFA, 0xFF, 0xE6, 0xFD, 0xFF, 0xF8, 0xFF, 0xDB, 0x00, 0x43, 0x01, 0x2B, 0x2D, 0x2D, 0x3C, 0x35, 0x3C, 0x76, 0x41, 0x41, 0x76, 0xF8, 0xA5, 0x8C, 0xA5, 0xF8, 0xF8, 0xF8,
59
- 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8,
60
- 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xC4, 0x00, 0x1F, 0x00, 0x00, 0x01, 0x05,
61
- 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0xFF, 0xC4, 0x00, 0xB5, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04,
62
- 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1,
63
- 0x15, 0x52, 0xD1, 0xF0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
64
- 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96,
65
- 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7,
66
- 0xD8, 0xD9, 0xDA, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFF, 0xC4, 0x00, 0x1F, 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
67
- 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0xFF, 0xC4, 0x00, 0xB5, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00,
68
- 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33, 0x52, 0xF0, 0x15, 0x62,
69
- 0x72, 0xD1, 0x0A, 0x16, 0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57,
70
- 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A,
71
- 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE2,
72
- 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFF, 0xDA, 0x00, 0x0C, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3F, 0x00,
73
- ]);
74
- const footer = Buffer.from([0xFF, 0xD9]);
75
- const real = Buffer.alloc(header.length + bytes.length + footer.length);
76
- header.copy(real);
77
- bytes.copy(real, header.length, 3);
78
- bytes.copy(real, 164, 1, 2);
79
- bytes.copy(real, 166, 2, 3);
80
- footer.copy(real, header.length + bytes.length, 0);
81
- return real;
82
- }
83
-
84
- function getMediaLink(ctx, channel: Api.InputPeerChannel, channelName: string, message: Api.Message) {
85
- const base = `${ctx.protocol}://${ctx.host}/telegram/channel/${channelName}`;
86
- const src = base + `${channel.channelId}_${message.id}`;
87
-
88
- const x = message.media;
89
- if (x instanceof Api.MessageMediaPhoto || (x instanceof Api.MessageMediaDocument && x.document.mimeType.startsWith('image/'))) {
90
- return `<img src="${src}" alt=""/>`;
91
- }
92
- if (x instanceof Api.MessageMediaDocument && x.document.mimeType.startsWith('video/')) {
93
- const vid = x.document.attributes.find((t) => t.className === 'DocumentAttributeVideo') ?? { w: 1080, h: 720 };
94
- return `<video controls preload="metadata" poster="${src}?thumb" width="${vid.w / 2}" height="${vid.h / 2}"><source src="${src}" type="${x.document.mimeType}"></video>`;
95
- }
96
- if (x instanceof Api.MessageMediaDocument && x.document.mimeType.startsWith('audio/')) {
97
- return `<audio src="${src}"></audio>`;
98
- }
99
-
100
- let linkText = getFilename(x);
101
- if (x instanceof Api.MessageMediaDocument) {
102
- linkText += ` (${humanFileSize(x.document.size)})`;
103
- return `<a href="${src}" target="_blank"><img src="${src}?thumb" alt=""/><br/>${linkText}</a>`;
104
- }
105
- return '';
106
- }
107
- function getFilename(x) {
46
+ export function getFilename(x: Api.TypeMessageMedia) {
108
47
  if (x instanceof Api.MessageMediaDocument) {
109
- const docFilename = x.document.attributes.find((a) => a.className === 'DocumentAttributeFilename');
110
- if (docFilename) {
111
- return docFilename.fileName;
48
+ for (const a of (x.document as Api.Document).attributes) {
49
+ if (a instanceof Api.DocumentAttributeFilename) {
50
+ return a.fileName;
51
+ }
112
52
  }
113
53
  }
114
54
  return x.className;
115
55
  }
116
56
 
117
- function sortThumb(thumb) {
118
- if (thumb instanceof Api.PhotoStrippedSize) {
119
- return thumb.bytes.length;
57
+ export function getDocument(m: Api.TypeMessageMedia) {
58
+ if (m instanceof Api.MessageMediaDocument && m.document && !(m.document instanceof Api.DocumentEmpty)) {
59
+ return m.document;
120
60
  }
121
- if (thumb instanceof Api.PhotoCachedSize) {
122
- return thumb.bytes.length;
61
+ if (m instanceof Api.MessageMediaWebPage && m.webpage instanceof Api.WebPage && m.webpage.document instanceof Api.Document) {
62
+ return m.webpage.document;
123
63
  }
124
- if (thumb instanceof Api.PhotoSize) {
125
- return thumb.size;
126
- }
127
- if (thumb instanceof Api.PhotoSizeProgressive) {
128
- return Math.max(...thumb.sizes);
129
- }
130
- if (thumb instanceof Api.VideoSize) {
131
- return thumb.size;
132
- }
133
- return 0;
134
64
  }
135
65
 
136
- function chooseLargestThumb(thumbs) {
137
- thumbs = [...thumbs].sort((a, b) => sortThumb(a) - sortThumb(b));
138
- return thumbs.pop();
66
+ export async function getStory(entity: Api.TypeEntityLike, id: number) {
67
+ const result = await (
68
+ await getClient()
69
+ ).invoke(
70
+ new Api.stories.GetStoriesByID({
71
+ id: [id],
72
+ peer: entity,
73
+ })
74
+ );
75
+ return result.stories[0] as Api.StoryItem;
139
76
  }
140
77
 
141
- function streamThumbnail(x) {
142
- if (x instanceof Api.MessageMediaDocument && x.document.thumbs.length > 0) {
143
- const size = chooseLargestThumb(x.document.thumbs);
144
- if (size instanceof Api.PhotoCachedSize || size instanceof Api.PhotoStrippedSize) {
145
- return (function* () {
146
- yield ExpandInlineBytes(size.bytes);
147
- })();
78
+ export async function unwrapMedia(media: Api.TypeMessageMedia | undefined, backupPeerId?: Api.TypePeer) {
79
+ if (media instanceof Api.MessageMediaStory) {
80
+ if (media.story instanceof Api.StoryItem && media.story.media) {
81
+ return media.story.media;
148
82
  }
149
- return streamDocument(x.document, size && 'type' in size ? size.type : '');
150
- }
151
- throw 'not supported';
152
- }
153
-
154
- async function decodeMedia(channelName, x, retry = false) {
155
- const [channel, msg] = x.split('_');
156
-
157
- try {
158
- const msgs = await client.getMessages(channel, {
159
- ids: [Number(msg)],
160
- });
161
- return msgs[0]?.media;
162
- } catch (error) {
163
- if (!retry) {
164
- // channel likely not seen before, we need to resolve ID and retry
165
- await client.getInputEntity(channelName);
166
- return decodeMedia(channelName, x, true);
83
+ let storyItem = await getStory(media.peer, media.id);
84
+ if (!storyItem?.media && backupPeerId) {
85
+ // it's possible the story got hidden by the original user, but we've saved it into Saved Messages - we can still get it
86
+ storyItem = await getStory(backupPeerId, media.id);
167
87
  }
168
- throw error;
169
- }
170
- }
171
-
172
- function streamDocument(obj, thumbSize = '', offset, limit) {
173
- const chunkSize = (obj.size ? getAppropriatedPartSize(obj.size) : 64) * 1024;
174
- const iterFileParams = {
175
- file: new Api.InputDocumentFileLocation({
176
- id: obj.id,
177
- accessHash: obj.accessHash,
178
- fileReference: obj.fileReference,
179
- thumbSize,
180
- }),
181
- chunkSize,
182
- dcId: obj.dcId,
183
- };
184
- if (offset) {
185
- iterFileParams.offset = offset;
186
- }
187
- if (limit) {
188
- iterFileParams.limit = limit;
88
+ return storyItem?.media;
189
89
  }
190
- return client.iterDownload(iterFileParams);
90
+ return media;
191
91
  }
192
-
193
- export { client, getMediaLink, decodeMedia, getFilename, streamDocument, streamThumbnail };
@@ -147,7 +147,7 @@ async function handler(ctx) {
147
147
  alt: item.venueName ?? item.title,
148
148
  }
149
149
  : undefined,
150
- description: item.description?.replace(/\["|"]/g, '') ?? undefined,
150
+ description: item.description?.replaceAll(/\["|"]/g, '') ?? undefined,
151
151
  data: item.parkingLocationId
152
152
  ? {
153
153
  title: item.venueName ?? item.title,
@@ -6,6 +6,7 @@ import cache from '@/utils/cache';
6
6
  import { fetchArticle } from '@/utils/wechat-mp';
7
7
  import ConfigNotFoundError from '@/errors/types/config-not-found';
8
8
  import InvalidParameterError from '@/errors/types/invalid-parameter';
9
+ import CaptchaError from '@/errors/types/captcha';
9
10
 
10
11
  let cacheIndex = 0;
11
12
 
@@ -32,6 +33,9 @@ async function handler(ctx) {
32
33
  if (ctx.req.param('id') === 'invalid-parameter-error') {
33
34
  throw new InvalidParameterError('Test invalid parameter error');
34
35
  }
36
+ if (ctx.req.param('id') === 'captcha-error') {
37
+ throw new CaptchaError('Test captcha error');
38
+ }
35
39
  if (ctx.req.param('id') === 'redirect') {
36
40
  ctx.set('redirect', '/test/1');
37
41
  return;
@@ -0,0 +1,7 @@
1
+ import type { Namespace } from '@/types';
2
+
3
+ export const namespace: Namespace = {
4
+ name: 'Gadget Flow',
5
+ url: 'thegadgetflow.com',
6
+ lang: 'en',
7
+ };
@@ -0,0 +1,99 @@
1
+ import { Route } from '@/types';
2
+ import got from '@/utils/got';
3
+ import { parseDate } from '@/utils/parse-date';
4
+ import { art } from '@/utils/render';
5
+ import path from 'node:path';
6
+ import { load } from 'cheerio';
7
+ import cache from '@/utils/cache';
8
+
9
+ export const route: Route = {
10
+ path: '/:category?',
11
+ categories: ['shopping'],
12
+ example: '/thegadgetflow/cool-gadgets-gifts',
13
+ parameters: { category: 'category name, can be found in url' },
14
+ features: {
15
+ requireConfig: false,
16
+ requirePuppeteer: false,
17
+ antiCrawler: false,
18
+ supportBT: false,
19
+ supportPodcast: false,
20
+ supportScihub: false,
21
+ },
22
+ radar: [
23
+ {
24
+ source: ['thegadgetflow.com/categories/:category'],
25
+ target: '/:category',
26
+ },
27
+ ],
28
+ name: 'Category',
29
+ maintainers: ['EthanWng97'],
30
+ handler,
31
+ };
32
+
33
+ async function handler(ctx) {
34
+ const baseUrl = 'https://thegadgetflow.com';
35
+ const categoryApiPath = '/wp-json/wp/v2/categories';
36
+ const postApiPath = '/wp-json/wp/v2/posts';
37
+
38
+ // get category number
39
+ const categorySlug = ctx.req.param('category') || '';
40
+
41
+ let category;
42
+ if (categorySlug) {
43
+ category = await cache.tryGet(`${baseUrl}${categoryApiPath}`, async () => {
44
+ const { data } = await got(`${baseUrl}${categoryApiPath}`, {
45
+ searchParams: { slug: categorySlug },
46
+ });
47
+ if (!data || data.length === 0) {
48
+ throw new Error(`Category "${categorySlug}" not found`);
49
+ }
50
+ return data[0];
51
+ });
52
+ }
53
+
54
+ const categoryId = category?.id;
55
+ const categoryName = category?.name;
56
+ const categoryLink = category?.link;
57
+
58
+ // get posts
59
+ const postsUrl = `${baseUrl}${postApiPath}`;
60
+ const postsResponse = await got(postsUrl, {
61
+ searchParams: {
62
+ per_page: ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 10,
63
+ _embed: '',
64
+ ...(categoryId && { categories: categoryId }),
65
+ },
66
+ });
67
+
68
+ const items = postsResponse.data.map((item) => {
69
+ const featuredMedia = item._embedded?.['wp:featuredmedia']?.find((v) => v.id === item.featured_media);
70
+ const image = featuredMedia?.source_url;
71
+ const altText = featuredMedia?.alt_text || featuredMedia?.title?.rendered;
72
+ let caption;
73
+ if (featuredMedia?.caption?.rendered) {
74
+ caption = load(featuredMedia?.caption?.rendered);
75
+ }
76
+
77
+ const single = {
78
+ title: item.title.rendered,
79
+ description: art(path.join(__dirname, 'templates/description.art'), {
80
+ content: item.content.rendered,
81
+ image,
82
+ altText,
83
+ caption: caption?.text() || '',
84
+ }),
85
+ link: item.link,
86
+ pubDate: parseDate(item.date_gmt),
87
+ updated: parseDate(item.modified_gmt),
88
+ // image,
89
+ author: item._embedded.author[0].name,
90
+ };
91
+ return single;
92
+ });
93
+
94
+ return {
95
+ title: categoryName ? `Gadget Flow - ${categoryName}` : 'Gadget Flow',
96
+ link: categoryLink || `${baseUrl}/${categorySlug}`,
97
+ item: items,
98
+ };
99
+ }