From 781fc7b2dddc359b325e32d49931f02e8d14320a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=9D=E7=81=B5=E4=BA=91?= Date: Thu, 29 Jan 2026 13:35:53 +0800 Subject: [PATCH] feat(telegram): add TelegramDownloadError and proxy support for downloads - Add TelegramDownloadError class with error codes (fetch_failed, http_error, api_error) - Add TelegramDownloadOptions type with optional fetchImpl for proxy configuration - Wrap fetch calls in try-catch for better error handling - This helps users in regions where Telegram API is blocked to configure proxies Co-Authored-By: Warp --- src/telegram/download.ts | 100 +++++++++++++++++++++++++++++++++------ 1 file changed, 86 insertions(+), 14 deletions(-) diff --git a/src/telegram/download.ts b/src/telegram/download.ts index 31f431db0..35d8affa4 100644 --- a/src/telegram/download.ts +++ b/src/telegram/download.ts @@ -8,16 +8,62 @@ export type TelegramFileInfo = { file_path?: string; }; -export async function getTelegramFile(token: string, fileId: string): Promise { - const res = await fetch( - `https://api.telegram.org/bot${token}/getFile?file_id=${encodeURIComponent(fileId)}`, - ); - if (!res.ok) { - throw new Error(`getFile failed: ${res.status} ${res.statusText}`); +export type TelegramDownloadOptions = { + fetchImpl?: typeof fetch; +}; + +export class TelegramDownloadError extends Error { + readonly code: "fetch_failed" | "http_error" | "api_error"; + + constructor(code: "fetch_failed" | "http_error" | "api_error", message: string) { + super(message); + this.code = code; + this.name = "TelegramDownloadError"; } - const json = (await res.json()) as { ok: boolean; result?: TelegramFileInfo }; +} + +export async function getTelegramFile( + token: string, + fileId: string, + options?: TelegramDownloadOptions, +): Promise { + const fetcher = options?.fetchImpl ?? globalThis.fetch; + if (!fetcher) { + throw new TelegramDownloadError( + "fetch_failed", + "fetch is not available; set channels.telegram.proxy in config", + ); + } + + let res: Response; + try { + res = await fetcher( + `https://api.telegram.org/bot${token}/getFile?file_id=${encodeURIComponent(fileId)}`, + ); + } catch (err) { + throw new TelegramDownloadError( + "fetch_failed", + `Failed to fetch Telegram file info: ${err instanceof Error ? err.message : String(err)}`, + ); + } + + if (!res.ok) { + throw new TelegramDownloadError( + "http_error", + `getFile failed: ${res.status} ${res.statusText}`, + ); + } + + const json = (await res.json()) as { + ok: boolean; + result?: TelegramFileInfo; + description?: string; + }; if (!json.ok || !json.result?.file_path) { - throw new Error("getFile returned no file_path"); + throw new TelegramDownloadError( + "api_error", + json.description ?? "getFile returned no file_path", + ); } return json.result; } @@ -26,13 +72,39 @@ export async function downloadTelegramFile( token: string, info: TelegramFileInfo, maxBytes?: number, + options?: TelegramDownloadOptions, ): Promise { - if (!info.file_path) throw new Error("file_path missing"); - const url = `https://api.telegram.org/file/bot${token}/${info.file_path}`; - const res = await fetch(url); - if (!res.ok || !res.body) { - throw new Error(`Failed to download telegram file: HTTP ${res.status}`); + if (!info.file_path) { + throw new TelegramDownloadError("api_error", "file_path missing"); } + + const fetcher = options?.fetchImpl ?? globalThis.fetch; + if (!fetcher) { + throw new TelegramDownloadError( + "fetch_failed", + "fetch is not available; set channels.telegram.proxy in config", + ); + } + + const url = `https://api.telegram.org/file/bot${token}/${info.file_path}`; + + let res: Response; + try { + res = await fetcher(url); + } catch (err) { + throw new TelegramDownloadError( + "fetch_failed", + `Failed to download Telegram file: ${err instanceof Error ? err.message : String(err)}`, + ); + } + + if (!res.ok || !res.body) { + throw new TelegramDownloadError( + "http_error", + `Failed to download telegram file: HTTP ${res.status}`, + ); + } + const array = Buffer.from(await res.arrayBuffer()); const mime = await detectMime({ buffer: array, @@ -40,7 +112,7 @@ export async function downloadTelegramFile( filePath: info.file_path, }); // save with inbound subdir - const saved = await saveMediaBuffer(array, mime, "inbound", maxBytes, info.file_path); + const saved = await saveMediaBuffer(array, mime, "inbound", maxBytes); // Ensure extension matches mime if possible if (!saved.contentType && mime) saved.contentType = mime; return saved;