rsshub 1.0.0-master.f9b85f5 → 1.0.0-master.f9c381a

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 (166) hide show
  1. package/lib/config.ts +10 -0
  2. package/lib/middleware/cache.ts +4 -0
  3. package/lib/middleware/parameter.ts +1 -1
  4. package/lib/registry.ts +6 -2
  5. package/lib/routes/51cto/utils.ts +1 -1
  6. package/lib/routes/abc/index.ts +1 -1
  7. package/lib/routes/acgvinyl/namespace.ts +6 -0
  8. package/lib/routes/acgvinyl/news.ts +86 -0
  9. package/lib/routes/ally/rail.ts +1 -1
  10. package/lib/routes/anthropic/news.ts +13 -11
  11. package/lib/routes/apnews/mobile-api.ts +1 -1
  12. package/lib/routes/apnews/sitemap.ts +1 -1
  13. package/lib/routes/apple/apps.ts +2 -2
  14. package/lib/routes/apple/podcast.ts +58 -25
  15. package/lib/routes/bangumi.tv/group/reply.ts +1 -1
  16. package/lib/routes/bilibili/cache.ts +18 -2
  17. package/lib/routes/bilibili/dynamic.ts +31 -7
  18. package/lib/routes/bilibili/page.ts +1 -1
  19. package/lib/routes/bilibili/ranking.ts +23 -17
  20. package/lib/routes/bilibili/wasm-exec.ts +1 -1
  21. package/lib/routes/bilibili/weekly-recommend.ts +22 -6
  22. package/lib/routes/bjp/apod.ts +1 -1
  23. package/lib/routes/buaa/jiaowu.ts +1 -1
  24. package/lib/routes/bullionvault/gold-news.ts +3 -3
  25. package/lib/routes/cast/index.ts +1 -1
  26. package/lib/routes/coolapk/utils.ts +5 -4
  27. package/lib/routes/coolbuy/index.ts +106 -0
  28. package/lib/routes/{xinhuanet → coolbuy}/namespace.ts +3 -3
  29. package/lib/routes/coolbuy/templates/description.art +48 -0
  30. package/lib/routes/coolidge/film-guide.ts +60 -0
  31. package/lib/routes/coolidge/namespace.ts +7 -0
  32. package/lib/routes/coolidge/news.ts +65 -0
  33. package/lib/routes/coolidge/templates/description.art +4 -0
  34. package/lib/routes/copymanga/comic.ts +1 -1
  35. package/lib/routes/cpta/handler.ts +1 -1
  36. package/lib/routes/creative-comic/book.ts +1 -1
  37. package/lib/routes/daum/potplayer.ts +1 -1
  38. package/lib/routes/dockerhub/utils.ts +1 -1
  39. package/lib/routes/dora-world/article.ts +2 -2
  40. package/lib/routes/ehentai/ehapi.ts +3 -3
  41. package/lib/routes/eventbrite/events.ts +152 -0
  42. package/lib/routes/eventbrite/namespace.ts +7 -0
  43. package/lib/routes/github/activity.ts +1 -1
  44. package/lib/routes/google/jules.ts +63 -0
  45. package/lib/routes/gov/mem/namespace.ts +7 -0
  46. package/lib/routes/gov/mem/zfxxgkpt.ts +96 -0
  47. package/lib/routes/gov/mot/index.ts +158 -53
  48. package/lib/routes/hameln/chapter.ts +1 -1
  49. package/lib/routes/hpoi/banner-item.ts +28 -11
  50. package/lib/routes/hpoi/info.ts +1 -1
  51. package/lib/routes/huggingface/daily-papers.ts +1 -1
  52. package/lib/routes/hupu/index.ts +158 -74
  53. package/lib/routes/hupu/types.ts +163 -0
  54. package/lib/routes/hust/gs.ts +1 -1
  55. package/lib/routes/hust/mse.ts +1 -1
  56. package/lib/routes/huxiu/util.ts +2 -2
  57. package/lib/routes/infoq/presentations.ts +1 -1
  58. package/lib/routes/instagram/common-utils.ts +3 -3
  59. package/lib/routes/itch/devlog.ts +7 -3
  60. package/lib/routes/javbus/index.ts +1 -1
  61. package/lib/routes/javlibrary/utils.ts +1 -1
  62. package/lib/routes/jbma/namespace.ts +17 -0
  63. package/lib/routes/jbma/report.ts +473 -0
  64. package/lib/routes/jetbrains/comments.ts +1 -1
  65. package/lib/routes/jingzhengu/utils.ts +1 -1
  66. package/lib/routes/juejin/aicoding.ts +102 -0
  67. package/lib/routes/juejin/utils.ts +36 -51
  68. package/lib/routes/kakuyomu/works.ts +1 -1
  69. package/lib/routes/kemono/index.ts +2 -2
  70. package/lib/routes/komiic/comic.ts +1 -1
  71. package/lib/routes/koyso/index.ts +338 -0
  72. package/lib/routes/koyso/namespace.ts +9 -0
  73. package/lib/routes/koyso/templates/description.art +13 -0
  74. package/lib/routes/letterboxd/index.ts +65 -0
  75. package/lib/routes/letterboxd/namespace.ts +8 -0
  76. package/lib/routes/maccms/index.ts +1 -1
  77. package/lib/routes/mercari/util.ts +1 -1
  78. package/lib/routes/mingpao/index.ts +1 -1
  79. package/lib/routes/nankai/ai-notice.ts +142 -0
  80. package/lib/routes/nankai/graduate-notice.ts +162 -0
  81. package/lib/routes/natgeo/natgeo.ts +1 -0
  82. package/lib/routes/nhk/news-web-easy.ts +1 -1
  83. package/lib/routes/nicovideo/mylist.ts +39 -0
  84. package/lib/routes/nicovideo/types.ts +27 -0
  85. package/lib/routes/nicovideo/utils.ts +27 -1
  86. package/lib/routes/nikkei/cn/index.ts +1 -4
  87. package/lib/routes/now/news.ts +1 -1
  88. package/lib/routes/nytimes/index.ts +1 -1
  89. package/lib/routes/pixiv/novel-api/user-novels/sfw.ts +1 -1
  90. package/lib/routes/pixivision/utils.ts +1 -1
  91. package/lib/routes/pku/hr.ts +1 -1
  92. package/lib/routes/pku/scc/recruit.ts +1 -1
  93. package/lib/routes/producthunt/templates/description.art +2 -2
  94. package/lib/routes/producthunt/today.ts +17 -8
  95. package/lib/routes/ps/trophy.ts +1 -1
  96. package/lib/routes/pubscholar/utils.ts +14 -1
  97. package/lib/routes/qweather/3days.ts +14 -14
  98. package/lib/routes/qweather/now.ts +12 -10
  99. package/lib/routes/qweather/util.tsx +89 -0
  100. package/lib/routes/qwenlm/blog.ts +75 -0
  101. package/lib/routes/qwenlm/namespace.ts +6 -0
  102. package/lib/routes/radio-canada/latest.ts +30 -17
  103. package/lib/routes/ruc/ai.ts +1 -1
  104. package/lib/routes/ruc/hr.ts +1 -1
  105. package/lib/routes/samrdprc/index.ts +241 -0
  106. package/lib/routes/samrdprc/namespace.ts +1 -1
  107. package/lib/routes/sdo/ff14risingstones/api.ts +78 -0
  108. package/lib/routes/sdo/ff14risingstones/constant.ts +338 -0
  109. package/lib/routes/sdo/ff14risingstones/posts.ts +80 -0
  110. package/lib/routes/sdo/ff14risingstones/strats.ts +75 -0
  111. package/lib/routes/sdo/ff14risingstones/templates/duties-party.art +41 -0
  112. package/lib/routes/sdo/ff14risingstones/templates/fc-party.art +26 -0
  113. package/lib/routes/sdo/ff14risingstones/templates/novice-network-party.art +9 -0
  114. package/lib/routes/sdo/ff14risingstones/templates/rp-party.art +15 -0
  115. package/lib/routes/sdo/ff14risingstones/timeline.ts +31 -0
  116. package/lib/routes/sdo/ff14risingstones/types/dynamic.ts +50 -0
  117. package/lib/routes/sdo/ff14risingstones/types/index.ts +3 -0
  118. package/lib/routes/sdo/ff14risingstones/types/other.ts +57 -0
  119. package/lib/routes/sdo/ff14risingstones/types/party.ts +111 -0
  120. package/lib/routes/sdo/ff14risingstones/user-dynamics.ts +32 -0
  121. package/lib/routes/sdo/ff14risingstones/user-posts.ts +32 -0
  122. package/lib/routes/sdo/ff14risingstones/user-resently.ts +38 -0
  123. package/lib/routes/sdo/ff14risingstones/user-strats.ts +32 -0
  124. package/lib/routes/sdo/ff14risingstones/utils.ts +215 -0
  125. package/lib/routes/sdo/namespace.ts +7 -0
  126. package/lib/routes/showstart/utils.ts +1 -1
  127. package/lib/routes/sohu/mp.ts +1 -1
  128. package/lib/routes/sotwe/user.ts +1 -1
  129. package/lib/routes/surfshark/blog.ts +273 -77
  130. package/lib/routes/surfshark/templates/description.art +16 -4
  131. package/lib/routes/sustainabilitymag/articles.ts +1 -1
  132. package/lib/routes/syosetu/dev.ts +1 -1
  133. package/lib/routes/syosetu/ranking-isekai.ts +1 -1
  134. package/lib/routes/szse/disclosure/listed-notice.ts +44 -6
  135. package/lib/routes/telegram/channel-media.ts +249 -0
  136. package/lib/routes/telegram/channel.ts +5 -4
  137. package/lib/routes/telegram/stories.ts +130 -0
  138. package/lib/routes/telegram/tglib/channel.ts +136 -118
  139. package/lib/routes/telegram/tglib/client.ts +37 -139
  140. package/lib/routes/tesla/cx.ts +1 -1
  141. package/lib/routes/theverge/index.ts +20 -6
  142. package/lib/routes/threads/utils.ts +7 -3
  143. package/lib/routes/tidb/blog.ts +1 -1
  144. package/lib/routes/toutiao/user.ts +2 -2
  145. package/lib/routes/twitter/api/mobile-api/api.ts +1 -1
  146. package/lib/routes/txrjy/fornumtopic.ts +2 -2
  147. package/lib/routes/typst/universe.ts +1 -1
  148. package/lib/routes/uber/blog.ts +87 -46
  149. package/lib/routes/weibo/utils.ts +17 -9
  150. package/lib/routes/xiaohongshu/user.ts +1 -1
  151. package/lib/routes/xjtu/ee-jzxx.ts +1 -1
  152. package/lib/routes/yahoo/news/utils.ts +1 -1
  153. package/lib/routes/ymgal/article.ts +1 -1
  154. package/lib/routes/yoasobi-music/media.ts +1 -1
  155. package/lib/routes/youtube/api/youtubei.ts +1 -1
  156. package/lib/routes/youtube/community.ts +4 -4
  157. package/lib/routes/zaker/utils.ts +1 -1
  158. package/lib/routes/zaobao/util.tsx +1 -1
  159. package/lib/server.ts +1 -1
  160. package/lib/types.ts +1 -1
  161. package/lib/utils/puppeteer-utils.test.ts +2 -2
  162. package/lib/views/index.tsx +4 -4
  163. package/package.json +40 -40
  164. package/lib/routes/qweather/templates/3days.art +0 -22
  165. package/lib/routes/qweather/templates/now.art +0 -16
  166. package/lib/routes/xinhuanet/app.ts +0 -109
@@ -0,0 +1,249 @@
1
+ import InvalidParameterError from '@/errors/types/invalid-parameter';
2
+ import { Route } from '@/types';
3
+ import { Context } from 'hono';
4
+ import { stream } from 'hono/streaming';
5
+ import { Api, TelegramClient } from 'telegram';
6
+ import { IterDownloadFunction } from 'telegram/client/downloads.js';
7
+ import { getAppropriatedPartSize } from 'telegram/Utils.js';
8
+ import { config } from '@/config';
9
+ import cacheModule from '@/utils/cache/index';
10
+ import { getClient, getDocument, getFilename, unwrapMedia } from './tglib/client';
11
+ import { returnBigInt as bigInt } from 'telegram/Helpers.js';
12
+
13
+ /**
14
+ * https://core.telegram.org/api/files#stripped-thumbnails
15
+ * @param bytes Buffer
16
+ * @returns Buffer jpeg
17
+ */
18
+ function ExpandInlineBytes(bytes: Buffer) {
19
+ if (bytes.length < 3 || bytes[0] !== 0x1) {
20
+ throw new Error('cannot inflate a stripped jpeg');
21
+ }
22
+ const header = Buffer.from([
23
+ 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,
24
+ 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,
25
+ 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,
26
+ 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,
27
+ 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,
28
+ 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,
29
+ 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,
30
+ 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,
31
+ 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,
32
+ 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,
33
+ 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,
34
+ 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,
35
+ 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,
36
+ 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,
37
+ 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,
38
+ 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,
39
+ 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,
40
+ ]);
41
+ const footer = Buffer.from([0xFF, 0xD9]);
42
+ const real = Buffer.alloc(header.length + bytes.length + footer.length);
43
+ header.copy(real);
44
+ bytes.copy(real, header.length, 3);
45
+ bytes.copy(real, 164, 1, 2);
46
+ bytes.copy(real, 166, 2, 3);
47
+ footer.copy(real, header.length + bytes.length, 0);
48
+ return real;
49
+ }
50
+
51
+ function sortThumb(thumb: Api.TypePhotoSize) {
52
+ if (thumb instanceof Api.PhotoStrippedSize) {
53
+ return thumb.bytes.length;
54
+ }
55
+ if (thumb instanceof Api.PhotoCachedSize) {
56
+ return thumb.bytes.length;
57
+ }
58
+ if (thumb instanceof Api.PhotoSize) {
59
+ return thumb.size;
60
+ }
61
+ if (thumb instanceof Api.PhotoSizeProgressive) {
62
+ return Math.max(...thumb.sizes);
63
+ }
64
+ return 0;
65
+ }
66
+
67
+ function chooseLargestThumb(thumbs: Api.TypePhotoSize[]) {
68
+ thumbs = [...thumbs].sort((a, b) => sortThumb(a) - sortThumb(b));
69
+ return thumbs.pop();
70
+ }
71
+
72
+ export async function* streamThumbnail(client: TelegramClient, doc: Api.Document) {
73
+ if (doc.thumbs?.length ?? 0 > 0) {
74
+ const size = chooseLargestThumb(doc.thumbs!);
75
+ if (size instanceof Api.PhotoCachedSize || size instanceof Api.PhotoStrippedSize) {
76
+ yield ExpandInlineBytes(size.bytes);
77
+ } else {
78
+ yield* streamDocument(client, doc, size && 'type' in size ? size.type : '');
79
+ }
80
+ return;
81
+ }
82
+ throw new Error('no thumbnails available');
83
+ }
84
+
85
+ export async function* streamDocument(client: TelegramClient, obj: Api.Document, thumbSize = '', offset?: bigInt.BigInteger, limit?: bigInt.BigInteger) {
86
+ const chunkSize = (obj.size ? getAppropriatedPartSize(obj.size) : 64) * 1024;
87
+ const iterFileParams: IterDownloadFunction = {
88
+ file: new Api.InputDocumentFileLocation({
89
+ id: obj.id,
90
+ accessHash: obj.accessHash,
91
+ fileReference: obj.fileReference,
92
+ thumbSize,
93
+ }),
94
+ chunkSize,
95
+ requestSize: 512 * 1024, // MAX_CHUNK_SIZE
96
+ dcId: obj.dcId,
97
+ offset: undefined,
98
+ limit: undefined,
99
+ };
100
+ if (offset) {
101
+ iterFileParams.offset = offset;
102
+ }
103
+ if (limit) {
104
+ iterFileParams.limit = limit.valueOf();
105
+ }
106
+ // console.log('starting iterDownload');
107
+ const stream = client.iterDownload(iterFileParams);
108
+ yield* stream;
109
+ await stream.close();
110
+ }
111
+
112
+ function parseRange(range: string, length: bigInt.BigInteger) {
113
+ if (!range) {
114
+ return [];
115
+ }
116
+ const [typ, segstr] = range.split('=');
117
+ if (typ !== 'bytes') {
118
+ throw new InvalidParameterError(`unsupported range: ${typ}`);
119
+ }
120
+ const segs = segstr.split(',').map((s) => s.trim());
121
+ const parsedSegs: bigInt.BigInteger[][] = [];
122
+ for (const seg of segs) {
123
+ const range = seg
124
+ .split('-', 2)
125
+ .filter((v) => !!v)
126
+ .map((v) => bigInt(v));
127
+ if (range.length < 2) {
128
+ if (seg.startsWith('-')) {
129
+ range.unshift(bigInt(0));
130
+ } else {
131
+ range.push(length.subtract(bigInt(1)));
132
+ }
133
+ }
134
+ parsedSegs.push(range);
135
+ }
136
+ return parsedSegs;
137
+ }
138
+
139
+ export async function configureMiddlewares(ctx: Context) {
140
+ // media is too heavy to cache in memory or redis, and lock-up is not needed
141
+ await cacheModule.set(ctx.get('cacheControlKey'), '0', config.cache.requestTimeout);
142
+ ctx.req.raw.headers.delete('Accept-Encoding'); // avoid hono compress() middleware detecting Accept-Encoding on req
143
+ }
144
+
145
+ function streamResponse(c: Context, bodyIter: AsyncGenerator<Buffer>) {
146
+ return stream(c, async (stream) => {
147
+ let aborted = false;
148
+ stream.onAbort(() => {
149
+ // console.log(`stream aborted`);
150
+ aborted = true;
151
+ });
152
+ for await (const chunk of bodyIter) {
153
+ if (aborted) {
154
+ break;
155
+ }
156
+ // console.log(`writing ${chunk.length / 1024}kB`);
157
+ await stream.write(chunk);
158
+ }
159
+ // console.log(`done streamResponse`);
160
+ });
161
+ }
162
+
163
+ export const route: Route = {
164
+ path: '/media/:entityName/:messageId',
165
+ categories: ['social-media'],
166
+ example: '/telegram/media/telegram/1233',
167
+ parameters: { entityName: 'entity name', messageId: 'message id' },
168
+ features: {
169
+ requireConfig: [
170
+ {
171
+ name: 'TELEGRAM_SESSION',
172
+ optional: false,
173
+ description: 'Telegram API Authentication',
174
+ },
175
+ ],
176
+ requirePuppeteer: false,
177
+ antiCrawler: false,
178
+ supportBT: false,
179
+ supportPodcast: false,
180
+ supportScihub: false,
181
+ },
182
+ radar: [],
183
+ name: 'Channel Media',
184
+ maintainers: ['synchrone'],
185
+ handler,
186
+ description: `
187
+ ::: tip
188
+ Serves telegram media like pictures, video or files.
189
+ :::
190
+ `,
191
+ };
192
+
193
+ export async function handleMedia(media: Api.TypeMessageMedia, client: TelegramClient, ctx: Context) {
194
+ if (media instanceof Api.MessageMediaPhoto) {
195
+ const buf = await client.downloadMedia(media);
196
+ return new Response(buf, { headers: { 'Content-Type': 'image/jpeg' } });
197
+ }
198
+
199
+ const doc = getDocument(media);
200
+ if (doc) {
201
+ if ('thumb' in ctx.req.query()) {
202
+ ctx.header('Content-Type', 'image/jpeg');
203
+ return streamResponse(ctx, streamThumbnail(client, doc));
204
+ }
205
+ ctx.header('Content-Type', doc.mimeType);
206
+ ctx.header('Accept-Ranges', 'bytes');
207
+ ctx.header('Content-Security-Policy', "default-src 'self'; script-src 'none'");
208
+
209
+ const rangeHeader = ctx.req.header('Range') ?? '';
210
+ const range = parseRange(rangeHeader, doc.size);
211
+ if (range.length > 1) {
212
+ return ctx.text('Not Satisfiable', 416);
213
+ }
214
+
215
+ if (range.length === 0) {
216
+ ctx.header('Content-Length', doc.size.toString());
217
+ if (!doc.mimeType.startsWith('video/') && !doc.mimeType.startsWith('audio/') && !doc.mimeType.startsWith('image/')) {
218
+ ctx.header('Content-Disposition', `attachment; filename="${encodeURIComponent(getFilename(media))}"`);
219
+ }
220
+ return streamResponse(ctx, streamDocument(client, doc));
221
+ } else {
222
+ const [offset, limit] = range[0];
223
+ // console.log(`Range: ${rangeHeader}`);
224
+ ctx.status(206); // partial content
225
+ ctx.header('Content-Length', limit.subtract(offset).add(1).toString());
226
+ ctx.header('Content-Range', `bytes ${offset}-${limit}/${doc.size}`);
227
+ return streamResponse(ctx, streamDocument(client, doc, '', offset, limit));
228
+ }
229
+ }
230
+
231
+ return ctx.text(media.className, 415);
232
+ }
233
+
234
+ export default async function handler(ctx: Context) {
235
+ await configureMiddlewares(ctx);
236
+ const client = await getClient();
237
+
238
+ const { entityName, messageId } = ctx.req.param();
239
+ const entity = await client.getInputEntity(entityName);
240
+ const msgs = await client.getMessages(entity, {
241
+ ids: [Number(messageId)],
242
+ });
243
+ const media = await unwrapMedia(msgs[0]?.media);
244
+ if (!media) {
245
+ return ctx.text('Unknown media', 404);
246
+ }
247
+
248
+ return await handleMedia(media, client, ctx);
249
+ }
@@ -148,6 +148,11 @@ For backward compatibility reasons, invalid \`routeParams\` will be treated as \
148
148
  };
149
149
 
150
150
  async function handler(ctx) {
151
+ const useWeb = ctx.req.param('routeParams') || !config.telegram.session;
152
+ if (!useWeb) {
153
+ return tglibchannel(ctx);
154
+ }
155
+
151
156
  const username = ctx.req.param('username');
152
157
  let routeParams = ctx.req.param('routeParams');
153
158
  let showLinkPreview = true;
@@ -219,10 +224,6 @@ async function handler(ctx) {
219
224
  : $('.tgme_widget_message_wrap:not(.tgme_widget_message_wrap:has(.service_message,.tme_no_messages_found))'); // also exclude service messages
220
225
 
221
226
  if (list.length === 0 && $('.tgme_channel_history').length === 0) {
222
- if (config.telegram.session) {
223
- return tglibchannel(ctx);
224
- }
225
-
226
227
  throw new Error(`Unable to fetch message feed from this channel. Please check this URL to see if you can view the message preview: ${resourceUrl}`);
227
228
  }
228
229
 
@@ -0,0 +1,130 @@
1
+ /* eslint-disable no-await-in-loop */
2
+ import NotFoundError from '@/errors/types/not-found';
3
+ import { configureMiddlewares, handleMedia } from '@/routes/telegram/channel-media';
4
+ import { Data, DataItem, Route } from '@/types';
5
+ import { Context } from 'hono';
6
+ import { Api } from 'telegram';
7
+ import { getClient, getStory, unwrapMedia } from './tglib/client';
8
+ import { getGeoLink, getMediaLink } from './tglib/channel';
9
+
10
+ export const route: Route = {
11
+ path: '/stories/:username/:story?',
12
+ categories: ['social-media'],
13
+ example: '/telegram/stories/telegram',
14
+ parameters: { username: 'entity name', story: 'story' },
15
+ features: {
16
+ requireConfig: [
17
+ {
18
+ name: 'TELEGRAM_SESSION',
19
+ optional: false,
20
+ description: 'Telegram API Authentication',
21
+ },
22
+ {
23
+ name: 'TELEGRAM_API_ID',
24
+ optional: true,
25
+ description: 'Telegram API ID',
26
+ },
27
+ {
28
+ name: 'TELEGRAM_API_HASH',
29
+ optional: true,
30
+ description: 'Telegram API Hash',
31
+ },
32
+ {
33
+ name: 'TELEGRAM_MAX_CONCURRENT_DOWNLOADS',
34
+ optional: true,
35
+ description: 'Telegram Max Concurrent Downloads',
36
+ },
37
+ {
38
+ name: 'TELEGRAM_PROXY_HOST',
39
+ optional: true,
40
+ description: 'Telegram Proxy Host',
41
+ },
42
+ {
43
+ name: 'TELEGRAM_PROXY_PORT',
44
+ optional: true,
45
+ description: 'Telegram Proxy Port',
46
+ },
47
+ {
48
+ name: 'TELEGRAM_PROXY_SECRET',
49
+ optional: true,
50
+ description: 'Telegram Proxy Secret',
51
+ },
52
+ ],
53
+ requirePuppeteer: false,
54
+ antiCrawler: false,
55
+ supportBT: false,
56
+ supportPodcast: false,
57
+ supportScihub: false,
58
+ },
59
+ radar: [],
60
+ name: 'Stories',
61
+ maintainers: ['synchrone'],
62
+ handler,
63
+ description: ``,
64
+ };
65
+
66
+ function getMediaAreas(mediaAreas?: Api.TypeMediaArea[]) {
67
+ let description = '';
68
+ for (const area of mediaAreas ?? []) {
69
+ if (area instanceof Api.MediaAreaChannelPost) {
70
+ // TODO: fetch area.msgId and display inline
71
+ } else if ((area instanceof Api.MediaAreaGeoPoint || area instanceof Api.MediaAreaVenue) && area.geo instanceof Api.GeoPoint) {
72
+ description += getGeoLink(area.geo);
73
+ } else if (area instanceof Api.MediaAreaSuggestedReaction) {
74
+ if (area.reaction instanceof Api.ReactionEmoji) {
75
+ description += area.reaction.emoticon;
76
+ } else if (area.reaction instanceof Api.ReactionCustomEmoji) {
77
+ // TODO: fetch area.reaction.documentId and display inline
78
+ }
79
+ }
80
+ }
81
+ return description;
82
+ }
83
+
84
+ export default async function handler(ctx: Context) {
85
+ const c = await getClient();
86
+ const { username, story } = ctx.req.param();
87
+ if (!username) {
88
+ throw new NotFoundError();
89
+ }
90
+ const peer = await c.getInputEntity(username);
91
+ if (story) {
92
+ const storyItem = await getStory(peer, Number(story));
93
+ await configureMiddlewares(ctx);
94
+ return await handleMedia(storyItem.media, c, ctx);
95
+ }
96
+
97
+ const storiesRes = await c.invoke(new Api.stories.GetPeerStories({ peer }));
98
+
99
+ const item: DataItem[] = [];
100
+ for (const story of storiesRes.stories.stories) {
101
+ if (!(story instanceof Api.StoryItem)) {
102
+ // story is deleted (archived) or skipped
103
+ continue;
104
+ }
105
+ const src = `${new URL(ctx.req.url).origin}/telegram/stories/${username}/${story.id}`;
106
+ const pubDate = new Date(story.date * 1000).toUTCString();
107
+ const media = await unwrapMedia(story.media);
108
+ if (!media) {
109
+ // cannot load the story
110
+ continue;
111
+ }
112
+
113
+ const description = getMediaLink(src, media) + getMediaAreas(story.mediaAreas);
114
+ item.push({
115
+ title: story.caption ?? pubDate,
116
+ description,
117
+ pubDate,
118
+ link: `https://t.me/${username}/s/${story.id}`,
119
+ author: username,
120
+ });
121
+ }
122
+
123
+ return {
124
+ title: `Stories of @${username}`,
125
+ link: `https://t.me/${username}`,
126
+ item,
127
+ allowEmpty: ctx.req.param('id') === 'allow_empty',
128
+ description: `Stories of @${username} on Telegram`,
129
+ } as Data;
130
+ }