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.
- package/lib/config.ts +10 -0
- package/lib/middleware/cache.ts +4 -0
- package/lib/middleware/parameter.ts +1 -1
- package/lib/registry.ts +6 -2
- package/lib/routes/51cto/utils.ts +1 -1
- package/lib/routes/abc/index.ts +1 -1
- package/lib/routes/acgvinyl/namespace.ts +6 -0
- package/lib/routes/acgvinyl/news.ts +86 -0
- package/lib/routes/ally/rail.ts +1 -1
- package/lib/routes/anthropic/news.ts +13 -11
- package/lib/routes/apnews/mobile-api.ts +1 -1
- package/lib/routes/apnews/sitemap.ts +1 -1
- package/lib/routes/apple/apps.ts +2 -2
- package/lib/routes/apple/podcast.ts +58 -25
- package/lib/routes/bangumi.tv/group/reply.ts +1 -1
- package/lib/routes/bilibili/cache.ts +18 -2
- package/lib/routes/bilibili/dynamic.ts +31 -7
- package/lib/routes/bilibili/page.ts +1 -1
- package/lib/routes/bilibili/ranking.ts +23 -17
- package/lib/routes/bilibili/wasm-exec.ts +1 -1
- package/lib/routes/bilibili/weekly-recommend.ts +22 -6
- package/lib/routes/bjp/apod.ts +1 -1
- package/lib/routes/buaa/jiaowu.ts +1 -1
- package/lib/routes/bullionvault/gold-news.ts +3 -3
- package/lib/routes/cast/index.ts +1 -1
- package/lib/routes/coolapk/utils.ts +5 -4
- package/lib/routes/coolbuy/index.ts +106 -0
- package/lib/routes/{xinhuanet → coolbuy}/namespace.ts +3 -3
- package/lib/routes/coolbuy/templates/description.art +48 -0
- package/lib/routes/coolidge/film-guide.ts +60 -0
- package/lib/routes/coolidge/namespace.ts +7 -0
- package/lib/routes/coolidge/news.ts +65 -0
- package/lib/routes/coolidge/templates/description.art +4 -0
- package/lib/routes/copymanga/comic.ts +1 -1
- package/lib/routes/cpta/handler.ts +1 -1
- package/lib/routes/creative-comic/book.ts +1 -1
- package/lib/routes/daum/potplayer.ts +1 -1
- package/lib/routes/dockerhub/utils.ts +1 -1
- package/lib/routes/dora-world/article.ts +2 -2
- package/lib/routes/ehentai/ehapi.ts +3 -3
- package/lib/routes/eventbrite/events.ts +152 -0
- package/lib/routes/eventbrite/namespace.ts +7 -0
- package/lib/routes/github/activity.ts +1 -1
- package/lib/routes/google/jules.ts +63 -0
- package/lib/routes/gov/mem/namespace.ts +7 -0
- package/lib/routes/gov/mem/zfxxgkpt.ts +96 -0
- package/lib/routes/gov/mot/index.ts +158 -53
- package/lib/routes/hameln/chapter.ts +1 -1
- package/lib/routes/hpoi/banner-item.ts +28 -11
- package/lib/routes/hpoi/info.ts +1 -1
- package/lib/routes/huggingface/daily-papers.ts +1 -1
- package/lib/routes/hupu/index.ts +158 -74
- package/lib/routes/hupu/types.ts +163 -0
- package/lib/routes/hust/gs.ts +1 -1
- package/lib/routes/hust/mse.ts +1 -1
- package/lib/routes/huxiu/util.ts +2 -2
- package/lib/routes/infoq/presentations.ts +1 -1
- package/lib/routes/instagram/common-utils.ts +3 -3
- package/lib/routes/itch/devlog.ts +7 -3
- package/lib/routes/javbus/index.ts +1 -1
- package/lib/routes/javlibrary/utils.ts +1 -1
- package/lib/routes/jbma/namespace.ts +17 -0
- package/lib/routes/jbma/report.ts +473 -0
- package/lib/routes/jetbrains/comments.ts +1 -1
- package/lib/routes/jingzhengu/utils.ts +1 -1
- package/lib/routes/juejin/aicoding.ts +102 -0
- package/lib/routes/juejin/utils.ts +36 -51
- package/lib/routes/kakuyomu/works.ts +1 -1
- package/lib/routes/kemono/index.ts +2 -2
- package/lib/routes/komiic/comic.ts +1 -1
- package/lib/routes/koyso/index.ts +338 -0
- package/lib/routes/koyso/namespace.ts +9 -0
- package/lib/routes/koyso/templates/description.art +13 -0
- package/lib/routes/letterboxd/index.ts +65 -0
- package/lib/routes/letterboxd/namespace.ts +8 -0
- package/lib/routes/maccms/index.ts +1 -1
- package/lib/routes/mercari/util.ts +1 -1
- package/lib/routes/mingpao/index.ts +1 -1
- package/lib/routes/nankai/ai-notice.ts +142 -0
- package/lib/routes/nankai/graduate-notice.ts +162 -0
- package/lib/routes/natgeo/natgeo.ts +1 -0
- package/lib/routes/nhk/news-web-easy.ts +1 -1
- package/lib/routes/nicovideo/mylist.ts +39 -0
- package/lib/routes/nicovideo/types.ts +27 -0
- package/lib/routes/nicovideo/utils.ts +27 -1
- package/lib/routes/nikkei/cn/index.ts +1 -4
- package/lib/routes/now/news.ts +1 -1
- package/lib/routes/nytimes/index.ts +1 -1
- package/lib/routes/pixiv/novel-api/user-novels/sfw.ts +1 -1
- package/lib/routes/pixivision/utils.ts +1 -1
- package/lib/routes/pku/hr.ts +1 -1
- package/lib/routes/pku/scc/recruit.ts +1 -1
- package/lib/routes/producthunt/templates/description.art +2 -2
- package/lib/routes/producthunt/today.ts +17 -8
- package/lib/routes/ps/trophy.ts +1 -1
- package/lib/routes/pubscholar/utils.ts +14 -1
- package/lib/routes/qweather/3days.ts +14 -14
- package/lib/routes/qweather/now.ts +12 -10
- package/lib/routes/qweather/util.tsx +89 -0
- package/lib/routes/qwenlm/blog.ts +75 -0
- package/lib/routes/qwenlm/namespace.ts +6 -0
- package/lib/routes/radio-canada/latest.ts +30 -17
- package/lib/routes/ruc/ai.ts +1 -1
- package/lib/routes/ruc/hr.ts +1 -1
- package/lib/routes/samrdprc/index.ts +241 -0
- package/lib/routes/samrdprc/namespace.ts +1 -1
- package/lib/routes/sdo/ff14risingstones/api.ts +78 -0
- package/lib/routes/sdo/ff14risingstones/constant.ts +338 -0
- package/lib/routes/sdo/ff14risingstones/posts.ts +80 -0
- package/lib/routes/sdo/ff14risingstones/strats.ts +75 -0
- package/lib/routes/sdo/ff14risingstones/templates/duties-party.art +41 -0
- package/lib/routes/sdo/ff14risingstones/templates/fc-party.art +26 -0
- package/lib/routes/sdo/ff14risingstones/templates/novice-network-party.art +9 -0
- package/lib/routes/sdo/ff14risingstones/templates/rp-party.art +15 -0
- package/lib/routes/sdo/ff14risingstones/timeline.ts +31 -0
- package/lib/routes/sdo/ff14risingstones/types/dynamic.ts +50 -0
- package/lib/routes/sdo/ff14risingstones/types/index.ts +3 -0
- package/lib/routes/sdo/ff14risingstones/types/other.ts +57 -0
- package/lib/routes/sdo/ff14risingstones/types/party.ts +111 -0
- package/lib/routes/sdo/ff14risingstones/user-dynamics.ts +32 -0
- package/lib/routes/sdo/ff14risingstones/user-posts.ts +32 -0
- package/lib/routes/sdo/ff14risingstones/user-resently.ts +38 -0
- package/lib/routes/sdo/ff14risingstones/user-strats.ts +32 -0
- package/lib/routes/sdo/ff14risingstones/utils.ts +215 -0
- package/lib/routes/sdo/namespace.ts +7 -0
- package/lib/routes/showstart/utils.ts +1 -1
- package/lib/routes/sohu/mp.ts +1 -1
- package/lib/routes/sotwe/user.ts +1 -1
- package/lib/routes/surfshark/blog.ts +273 -77
- package/lib/routes/surfshark/templates/description.art +16 -4
- package/lib/routes/sustainabilitymag/articles.ts +1 -1
- package/lib/routes/syosetu/dev.ts +1 -1
- package/lib/routes/syosetu/ranking-isekai.ts +1 -1
- package/lib/routes/szse/disclosure/listed-notice.ts +44 -6
- package/lib/routes/telegram/channel-media.ts +249 -0
- package/lib/routes/telegram/channel.ts +5 -4
- package/lib/routes/telegram/stories.ts +130 -0
- package/lib/routes/telegram/tglib/channel.ts +136 -118
- package/lib/routes/telegram/tglib/client.ts +37 -139
- package/lib/routes/tesla/cx.ts +1 -1
- package/lib/routes/theverge/index.ts +20 -6
- package/lib/routes/threads/utils.ts +7 -3
- package/lib/routes/tidb/blog.ts +1 -1
- package/lib/routes/toutiao/user.ts +2 -2
- package/lib/routes/twitter/api/mobile-api/api.ts +1 -1
- package/lib/routes/txrjy/fornumtopic.ts +2 -2
- package/lib/routes/typst/universe.ts +1 -1
- package/lib/routes/uber/blog.ts +87 -46
- package/lib/routes/weibo/utils.ts +17 -9
- package/lib/routes/xiaohongshu/user.ts +1 -1
- package/lib/routes/xjtu/ee-jzxx.ts +1 -1
- package/lib/routes/yahoo/news/utils.ts +1 -1
- package/lib/routes/ymgal/article.ts +1 -1
- package/lib/routes/yoasobi-music/media.ts +1 -1
- package/lib/routes/youtube/api/youtubei.ts +1 -1
- package/lib/routes/youtube/community.ts +4 -4
- package/lib/routes/zaker/utils.ts +1 -1
- package/lib/routes/zaobao/util.tsx +1 -1
- package/lib/server.ts +1 -1
- package/lib/types.ts +1 -1
- package/lib/utils/puppeteer-utils.test.ts +2 -2
- package/lib/views/index.tsx +4 -4
- package/package.json +40 -40
- package/lib/routes/qweather/templates/3days.art +0 -22
- package/lib/routes/qweather/templates/now.art +0 -16
- 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
|
+
}
|