rsshub
Version:
Make RSS Great Again!
794 lines (792 loc) • 10.1 kB
JavaScript
import { t as config } from "./config-C37vj7VH.mjs";
import { t as cache_default } from "./cache-Bo__VnGm.mjs";
import { t as invalid_parameter_default } from "./invalid-parameter-rr4AgGpp.mjs";
import { a as unwrapMedia, n as getDocument, r as getFilename, t as getClient } from "./client-DvJvDTER.mjs";
import { stream } from "hono/streaming";
import { Api } from "telegram";
import { returnBigInt } from "telegram/Helpers.js";
import { getAppropriatedPartSize } from "telegram/Utils.js";
//#region lib/routes/telegram/channel-media.ts
/**
* https://core.telegram.org/api/files#stripped-thumbnails
* @param bytes Buffer
* @returns Buffer jpeg
*/
function ExpandInlineBytes(bytes) {
if (bytes.length < 3 || bytes[0] !== 1) throw new Error("cannot inflate a stripped jpeg");
const header = Buffer.from([
255,
216,
255,
224,
0,
16,
74,
70,
73,
70,
0,
1,
1,
0,
0,
1,
0,
1,
0,
0,
255,
219,
0,
67,
0,
40,
28,
30,
35,
30,
25,
40,
35,
33,
35,
45,
43,
40,
48,
60,
100,
65,
60,
55,
55,
60,
123,
88,
93,
73,
100,
145,
128,
153,
150,
143,
128,
140,
138,
160,
180,
230,
195,
160,
170,
218,
173,
138,
140,
200,
255,
203,
218,
238,
245,
255,
255,
255,
155,
193,
255,
255,
255,
250,
255,
230,
253,
255,
248,
255,
219,
0,
67,
1,
43,
45,
45,
60,
53,
60,
118,
65,
65,
118,
248,
165,
140,
165,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
248,
255,
192,
0,
17,
8,
0,
0,
0,
0,
3,
1,
34,
0,
2,
17,
1,
3,
17,
1,
255,
196,
0,
31,
0,
0,
1,
5,
1,
1,
1,
1,
1,
1,
0,
0,
0,
0,
0,
0,
0,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
255,
196,
0,
181,
16,
0,
2,
1,
3,
3,
2,
4,
3,
5,
5,
4,
4,
0,
0,
1,
125,
1,
2,
3,
0,
4,
17,
5,
18,
33,
49,
65,
6,
19,
81,
97,
7,
34,
113,
20,
50,
129,
145,
161,
8,
35,
66,
177,
193,
21,
82,
209,
240,
36,
51,
98,
114,
130,
9,
10,
22,
23,
24,
25,
26,
37,
38,
39,
40,
41,
42,
52,
53,
54,
55,
56,
57,
58,
67,
68,
69,
70,
71,
72,
73,
74,
83,
84,
85,
86,
87,
88,
89,
90,
99,
100,
101,
102,
103,
104,
105,
106,
115,
116,
117,
118,
119,
120,
121,
122,
131,
132,
133,
134,
135,
136,
137,
138,
146,
147,
148,
149,
150,
151,
152,
153,
154,
162,
163,
164,
165,
166,
167,
168,
169,
170,
178,
179,
180,
181,
182,
183,
184,
185,
186,
194,
195,
196,
197,
198,
199,
200,
201,
202,
210,
211,
212,
213,
214,
215,
216,
217,
218,
225,
226,
227,
228,
229,
230,
231,
232,
233,
234,
241,
242,
243,
244,
245,
246,
247,
248,
249,
250,
255,
196,
0,
31,
1,
0,
3,
1,
1,
1,
1,
1,
1,
1,
1,
1,
0,
0,
0,
0,
0,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
255,
196,
0,
181,
17,
0,
2,
1,
2,
4,
4,
3,
4,
7,
5,
4,
4,
0,
1,
2,
119,
0,
1,
2,
3,
17,
4,
5,
33,
49,
6,
18,
65,
81,
7,
97,
113,
19,
34,
50,
129,
8,
20,
66,
145,
161,
177,
193,
9,
35,
51,
82,
240,
21,
98,
114,
209,
10,
22,
36,
52,
225,
37,
241,
23,
24,
25,
26,
38,
39,
40,
41,
42,
53,
54,
55,
56,
57,
58,
67,
68,
69,
70,
71,
72,
73,
74,
83,
84,
85,
86,
87,
88,
89,
90,
99,
100,
101,
102,
103,
104,
105,
106,
115,
116,
117,
118,
119,
120,
121,
122,
130,
131,
132,
133,
134,
135,
136,
137,
138,
146,
147,
148,
149,
150,
151,
152,
153,
154,
162,
163,
164,
165,
166,
167,
168,
169,
170,
178,
179,
180,
181,
182,
183,
184,
185,
186,
194,
195,
196,
197,
198,
199,
200,
201,
202,
210,
211,
212,
213,
214,
215,
216,
217,
218,
226,
227,
228,
229,
230,
231,
232,
233,
234,
242,
243,
244,
245,
246,
247,
248,
249,
250,
255,
218,
0,
12,
3,
1,
0,
2,
17,
3,
17,
0,
63,
0
]);
const footer = Buffer.from([255, 217]);
const real = Buffer.alloc(header.length + bytes.length + footer.length);
header.copy(real);
bytes.copy(real, header.length, 3);
bytes.copy(real, 164, 1, 2);
bytes.copy(real, 166, 2, 3);
footer.copy(real, header.length + bytes.length, 0);
return real;
}
function sortThumb(thumb) {
if (thumb instanceof Api.PhotoStrippedSize) return thumb.bytes.length;
if (thumb instanceof Api.PhotoCachedSize) return thumb.bytes.length;
if (thumb instanceof Api.PhotoSize) return thumb.size;
if (thumb instanceof Api.PhotoSizeProgressive) return Math.max(...thumb.sizes);
return 0;
}
function chooseLargestThumb(thumbs) {
thumbs = [...thumbs].sort((a, b) => sortThumb(a) - sortThumb(b));
return thumbs.pop();
}
async function* streamThumbnail(client, doc) {
if (doc.thumbs?.length ?? false) {
const size = chooseLargestThumb(doc.thumbs);
if (size instanceof Api.PhotoCachedSize || size instanceof Api.PhotoStrippedSize) yield ExpandInlineBytes(size.bytes);
else yield* streamDocument(client, doc, size && "type" in size ? size.type : "");
return;
}
throw new Error("no thumbnails available");
}
async function* streamDocument(client, obj, thumbSize = "", offset, limit) {
const chunkSize = (obj.size ? getAppropriatedPartSize(obj.size) : 64) * 1024;
const iterFileParams = {
file: new Api.InputDocumentFileLocation({
id: obj.id,
accessHash: obj.accessHash,
fileReference: obj.fileReference,
thumbSize
}),
chunkSize,
requestSize: 512 * 1024,
dcId: obj.dcId,
offset: void 0,
limit: void 0
};
if (offset) iterFileParams.offset = offset;
if (limit) iterFileParams.limit = limit.valueOf();
const stream$1 = client.iterDownload(iterFileParams);
yield* stream$1;
await stream$1.close();
}
function parseRange(range, length) {
if (!range) return [];
const [typ, segstr] = range.split("=");
if (typ !== "bytes") throw new invalid_parameter_default(`unsupported range: ${typ}`);
const segs = segstr.split(",").map((s) => s.trim());
const parsedSegs = [];
for (const seg of segs) {
const range$1 = seg.split("-", 2).filter((v) => !!v).map((v) => returnBigInt(v));
if (range$1.length < 2) if (seg.startsWith("-")) range$1.unshift(returnBigInt(0));
else range$1.push(length.subtract(returnBigInt(1)));
parsedSegs.push(range$1);
}
return parsedSegs;
}
async function configureMiddlewares(ctx) {
await cache_default.set(ctx.get("cacheControlKey"), "0", config.cache.requestTimeout);
ctx.req.raw.headers.delete("Accept-Encoding");
}
function streamResponse(c, bodyIter) {
return stream(c, async (stream$1) => {
let aborted = false;
stream$1.onAbort(() => {
aborted = true;
});
for await (const chunk of bodyIter) {
if (aborted) break;
await stream$1.write(chunk);
}
});
}
const route = {
path: "/media/:entityName/:messageId",
categories: ["social-media"],
example: "/telegram/media/telegram/1233",
parameters: {
entityName: "entity name",
messageId: "message id"
},
features: {
requireConfig: [{
name: "TELEGRAM_SESSION",
optional: false,
description: "Telegram API Authentication"
}],
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false
},
radar: [],
name: "Channel Media",
maintainers: ["synchrone"],
handler,
description: `
::: tip
Serves telegram media like pictures, video or files.
:::
`
};
async function handleMedia(media, client, ctx) {
if (media instanceof Api.MessageMediaPhoto) {
const buf = await client.downloadMedia(media);
return new Response(buf, { headers: { "Content-Type": "image/jpeg" } });
}
const doc = getDocument(media);
if (doc) {
if ("thumb" in ctx.req.query()) {
ctx.header("Content-Type", "image/jpeg");
return streamResponse(ctx, streamThumbnail(client, doc));
}
ctx.header("Content-Type", doc.mimeType);
ctx.header("Accept-Ranges", "bytes");
ctx.header("Content-Security-Policy", "default-src 'self'; script-src 'none'");
const range = parseRange(ctx.req.header("Range") ?? "", doc.size);
if (range.length > 1) return ctx.text("Not Satisfiable", 416);
if (range.length === 0) {
ctx.header("Content-Length", doc.size.toString());
if (!doc.mimeType.startsWith("video/") && !doc.mimeType.startsWith("audio/") && !doc.mimeType.startsWith("image/")) ctx.header("Content-Disposition", `attachment; filename="${encodeURIComponent(getFilename(media))}"`);
return streamResponse(ctx, streamDocument(client, doc));
} else {
const [offset, limit] = range[0];
ctx.status(206);
ctx.header("Content-Length", limit.subtract(offset).add(1).toString());
ctx.header("Content-Range", `bytes ${offset}-${limit}/${doc.size}`);
return streamResponse(ctx, streamDocument(client, doc, "", offset, limit));
}
}
return ctx.text(media.className, 415);
}
async function handler(ctx) {
await configureMiddlewares(ctx);
const client = await getClient();
const { entityName, messageId } = ctx.req.param();
const entity = await client.getInputEntity(entityName);
const media = await unwrapMedia((await client.getMessages(entity, { ids: [Number(messageId)] }))[0]?.media);
if (!media) return ctx.text("Unknown media", 404);
return await handleMedia(media, client, ctx);
}
//#endregion
export { streamDocument as a, route as i, handleMedia as n, streamThumbnail as o, handler as r, configureMiddlewares as t };