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 <agent@warp.dev>
This commit is contained in:
九灵云 2026-01-29 13:35:53 +08:00
parent 34291321b4
commit 781fc7b2dd

View File

@ -8,16 +8,62 @@ export type TelegramFileInfo = {
file_path?: string;
};
export async function getTelegramFile(token: string, fileId: string): Promise<TelegramFileInfo> {
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<TelegramFileInfo> {
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<SavedMedia> {
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;