diff --git a/src/lib/bridge/adapters/telegram-adapter.ts b/src/lib/bridge/adapters/telegram-adapter.ts index 173220b5..e8b2b30c 100644 --- a/src/lib/bridge/adapters/telegram-adapter.ts +++ b/src/lib/bridge/adapters/telegram-adapter.ts @@ -15,7 +15,7 @@ import type { } from '../types'; import type { FileAttachment } from '@/types'; import { BaseChannelAdapter, registerAdapterFactory } from '../channel-adapter'; -import { callTelegramApi, sendMessageDraft } from './telegram-utils'; +import { callTelegramApi, sendMessageDraft, proxyFetch, getProxyUrl } from './telegram-utils'; import { isImageEnabled, downloadPhoto, @@ -103,6 +103,14 @@ export class TelegramAdapter extends BaseChannelAdapter { return; } + // Log proxy configuration + const proxyUrl = getProxyUrl(); + if (proxyUrl) { + console.log('[telegram-adapter] Using proxy:', proxyUrl.replace(/:.*@/, ':***@')); + } else { + console.log('[telegram-adapter] No proxy configured, using direct connection'); + } + // Resolve bot identity via getMe before starting the poll loop. // This provides a stable offset key that survives token rotation. await this.resolveBotIdentity(); @@ -391,7 +399,7 @@ export class TelegramAdapter extends BaseChannelAdapter { try { const url = `${TELEGRAM_API}/bot${token}/getMe`; - const res = await fetch(url, { + const res = await proxyFetch(url, { method: 'GET', signal: AbortSignal.timeout(10_000), }); @@ -475,7 +483,7 @@ export class TelegramAdapter extends BaseChannelAdapter { } const url = `${TELEGRAM_API}/bot${token}/getUpdates`; - const res = await fetch(url, { + const res = await proxyFetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ diff --git a/src/lib/bridge/adapters/telegram-media.ts b/src/lib/bridge/adapters/telegram-media.ts index f8efda8d..e354c07b 100644 --- a/src/lib/bridge/adapters/telegram-media.ts +++ b/src/lib/bridge/adapters/telegram-media.ts @@ -8,6 +8,7 @@ import type { FileAttachment } from '@/types'; import { getSetting } from '../../db'; +import { proxyFetch } from './telegram-utils'; const TELEGRAM_API = 'https://api.telegram.org'; @@ -201,7 +202,7 @@ async function downloadFileById( try { // Step 1: Get file path from Telegram const getFileUrl = `${TELEGRAM_API}/bot${botToken}/getFile`; - const getFileRes = await fetch(getFileUrl, { + const getFileRes = await proxyFetch(getFileUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ file_id: fileId }), @@ -229,7 +230,7 @@ async function downloadFileById( // Step 2: Download the file const downloadUrl = `${TELEGRAM_API}/file/bot${botToken}/${filePath}`; - const downloadRes = await fetch(downloadUrl, { + const downloadRes = await proxyFetch(downloadUrl, { signal: AbortSignal.timeout(60_000), }); diff --git a/src/lib/bridge/adapters/telegram-utils.ts b/src/lib/bridge/adapters/telegram-utils.ts index 8f180abe..ffa3edbd 100644 --- a/src/lib/bridge/adapters/telegram-utils.ts +++ b/src/lib/bridge/adapters/telegram-utils.ts @@ -5,8 +5,32 @@ * Extracted from telegram-bot.ts to avoid duplication. */ +import { ProxyAgent } from 'undici'; + const TELEGRAM_API = 'https://api.telegram.org'; +/** + * Get proxy URL from environment variables. + * Supports HTTP_PROXY, HTTPS_PROXY, and ALL_PROXY. + */ +export function getProxyUrl(): string | undefined { + return process.env.HTTPS_PROXY || process.env.HTTP_PROXY || process.env.ALL_PROXY; +} + +/** + * Proxy-enabled fetch for Telegram API calls. + * Falls back to native fetch if no proxy is configured. + */ +export async function proxyFetch(url: string | URL, init?: RequestInit): Promise { + const proxyUrl = getProxyUrl(); + if (proxyUrl) { + const dispatcher = new ProxyAgent(proxyUrl); + // @ts-expect-error - dispatcher is an undici-specific extension not in standard fetch types + return fetch(url, { ...init, dispatcher }); + } + return fetch(url, init); +} + export interface TelegramSendResult { ok: boolean; messageId?: string; @@ -41,7 +65,7 @@ export async function callTelegramApi( ): Promise { try { const url = `${TELEGRAM_API}/bot${botToken}/${method}`; - const res = await fetch(url, { + const res = await proxyFetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params), diff --git a/src/lib/telegram-bot.ts b/src/lib/telegram-bot.ts index c50e36f0..59d7c733 100644 --- a/src/lib/telegram-bot.ts +++ b/src/lib/telegram-bot.ts @@ -21,6 +21,7 @@ import { escapeHtml, splitMessage, formatSessionHeader, + proxyFetch, } from './bridge/adapters/telegram-utils'; // ── Types ────────────────────────────────────────────────────── @@ -279,7 +280,7 @@ export async function verifyBot( ): Promise<{ ok: boolean; botName?: string; error?: string }> { try { const url = `${TELEGRAM_API}/bot${botToken}/getMe`; - const res = await fetch(url); + const res = await proxyFetch(url); const data: TelegramBotInfo = await res.json(); if (!data.ok || !data.result) { @@ -317,7 +318,7 @@ export async function detectChatId( try { // Try getUpdates first (works when polling hasn't consumed the message) const url = `${TELEGRAM_API}/bot${botToken}/getUpdates`; - const res = await fetch(url, { + const res = await proxyFetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ limit: 100, timeout: 0, allowed_updates: ['message'] }), @@ -493,7 +494,7 @@ async function pollLoop(botToken: string, chatId: string, state: PollerState): P while (state.running) { try { const url = `${TELEGRAM_API}/bot${botToken}/getUpdates`; - const res = await fetch(url, { + const res = await proxyFetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({