Skip to content

Commit f9b76b1

Browse files
author
Mack Ding
committed
Add Telegram API base and proxy support
1 parent d97a701 commit f9b76b1

File tree

12 files changed

+142
-7
lines changed

12 files changed

+142
-7
lines changed

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
BOT_TOKEN=123456789:telegram-token-from-botfather
2+
TELEGRAM_API_BASE=https://api.telegram.org
3+
TELEGRAM_PROXY_URL=
24
TELEGRAM_EXPECTED_USERNAME=
35
TELEGRAM_SMOKE_CHAT_ID=
46
ALLOWED_USER_IDS=123456789,987654321

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,8 @@ CODEX_WORKDIR=.
407407
Common options:
408408

409409
```bash
410+
TELEGRAM_API_BASE=https://api.telegram.org
411+
TELEGRAM_PROXY_URL=
410412
CODEX_COMMAND=codex
411413
CODEX_ARGS=
412414
CODEX_BACKEND=sdk
@@ -537,6 +539,7 @@ Telegram can manage runtime usage of Bot-side MCP and skills, but not install ar
537539
## Troubleshooting
538540

539541
- **Bot not responding**: verify `BOT_TOKEN` and `ALLOWED_USER_IDS`
542+
- **Telegram API blocked**: set `TELEGRAM_PROXY_URL` (HTTP proxy like `http://127.0.0.1:7890`) or run a local Bot API server and set `TELEGRAM_API_BASE`
540543
- **Codex not producing output**: verify `CODEX_BACKEND`, `CODEX_COMMAND`, and `CODEX_WORKDIR`
541544
- **SDK backend cannot resume**: verify the host still has access to `~/.codex/sessions` and that the saved thread id belongs to the same working directory
542545
- **Markdown parse errors**: reduce output size/context; check special characters in tool output

package-lock.json

Lines changed: 34 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,14 @@
4747
"@octokit/rest": "^22.0.0",
4848
"@openai/codex-sdk": "^0.114.0",
4949
"dotenv": "^16.4.7",
50+
"https-proxy-agent": "^8.0.0",
5051
"lodash.throttle": "^4.1.1",
5152
"node-cron": "^4.0.5",
5253
"node-pty": "^1.0.0",
5354
"simple-git": "^3.28.0",
5455
"strip-ansi": "^7.1.0",
55-
"telegraf": "^4.16.3"
56+
"telegraf": "^4.16.3",
57+
"undici": "^7.24.5"
5658
},
5759
"devDependencies": {
5860
"@eslint/js": "^9.17.0",

scripts/telegramSmoke.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import "dotenv/config";
22
import process from "node:process";
3+
import {
4+
buildTelegramApiUrl,
5+
createTelegramFetchDispatcher,
6+
normalizeTelegramApiBase
7+
} from "../src/lib/telegramApi.js";
38

49
interface TelegramBotUser {
510
id: number;
@@ -28,13 +33,20 @@ const expectedUsername = String(process.env.TELEGRAM_EXPECTED_USERNAME || "")
2833
.trim()
2934
.replace(/^@/, "");
3035
const smokeChatId = String(process.env.TELEGRAM_SMOKE_CHAT_ID || "").trim();
36+
const apiBase = normalizeTelegramApiBase(process.env.TELEGRAM_API_BASE);
37+
const dispatcher = createTelegramFetchDispatcher(
38+
process.env.TELEGRAM_PROXY_URL
39+
);
3140

3241
if (!token) {
3342
console.error("Missing BOT_TOKEN.");
3443
process.exit(1);
3544
}
3645

37-
const getMeResponse = await fetch(`https://api.telegram.org/bot${token}/getMe`);
46+
const getMeResponse = await fetch(
47+
buildTelegramApiUrl(apiBase, token, "getMe"),
48+
dispatcher ? { dispatcher } : undefined
49+
);
3850
const getMePayload =
3951
(await getMeResponse.json()) as TelegramApiResponse<TelegramBotUser>;
4052

@@ -57,7 +69,7 @@ if (expectedUsername && botUser.username !== expectedUsername) {
5769
if (smokeChatId) {
5870
const message = `codex-telegram-claws smoke check ${new Date().toISOString()}`;
5971
const sendResponse = await fetch(
60-
`https://api.telegram.org/bot${token}/sendMessage`,
72+
buildTelegramApiUrl(apiBase, token, "sendMessage"),
6173
{
6274
method: "POST",
6375
headers: {
@@ -66,7 +78,8 @@ if (smokeChatId) {
6678
body: JSON.stringify({
6779
chat_id: smokeChatId,
6880
text: message
69-
})
81+
}),
82+
...(dispatcher ? { dispatcher } : {})
7083
}
7184
);
7285
const sendPayload =

src/config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import path from "node:path";
33
import process from "node:process";
44
import dotenv from "dotenv";
55
import { toErrorMessage } from "./lib/errors.js";
6+
import {
7+
normalizeTelegramApiBase,
8+
normalizeTelegramProxyUrl
9+
} from "./lib/telegramApi.js";
610

711
dotenv.config();
812

@@ -49,6 +53,8 @@ export interface AppConfig {
4953
};
5054
telegram: {
5155
botToken: string;
56+
apiBase: string;
57+
proxyUrl?: string;
5258
allowedUserIds: string[];
5359
proactiveUserIds: string[];
5460
};
@@ -374,6 +380,8 @@ export function loadConfig(): AppConfig {
374380
},
375381
telegram: {
376382
botToken: required("BOT_TOKEN"),
383+
apiBase: normalizeTelegramApiBase(process.env.TELEGRAM_API_BASE),
384+
proxyUrl: normalizeTelegramProxyUrl(process.env.TELEGRAM_PROXY_URL),
377385
allowedUserIds,
378386
proactiveUserIds
379387
},

src/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,18 @@ import { ShellManager } from "./runner/shellManager.js";
1515
import { DevServerManager } from "./runner/devServerManager.js";
1616
import { Scheduler } from "./cron/scheduler.js";
1717
import { toErrorMessage } from "./lib/errors.js";
18+
import { createTelegramApiAgent } from "./lib/telegramApi.js";
1819

1920
const config = loadConfig();
21+
const telegramApiAgent = createTelegramApiAgent(config.telegram.proxyUrl);
2022
const bot = new Telegraf(config.telegram.botToken, {
21-
handlerTimeout: 120000
23+
handlerTimeout: 120000,
24+
telegram: {
25+
apiRoot: config.telegram.apiBase,
26+
...(telegramApiAgent
27+
? { agent: telegramApiAgent, attachmentAgent: telegramApiAgent }
28+
: {})
29+
}
2230
});
2331
const stateStore = new RuntimeStateStore({ config });
2432
let mcpClient: McpClient | null = null;

src/lib/telegramApi.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import type { Agent as HttpAgent } from "node:http";
2+
import type { Dispatcher } from "undici";
3+
import { ProxyAgent } from "undici";
4+
import { HttpsProxyAgent } from "https-proxy-agent";
5+
6+
const DEFAULT_TELEGRAM_API_BASE = "https://api.telegram.org";
7+
8+
export function normalizeTelegramApiBase(value?: string): string {
9+
const trimmed = String(value || "").trim();
10+
if (!trimmed) return DEFAULT_TELEGRAM_API_BASE;
11+
return trimmed.replace(/\/+$/, "");
12+
}
13+
14+
export function normalizeTelegramProxyUrl(value?: string): string | undefined {
15+
const trimmed = String(value || "").trim();
16+
return trimmed ? trimmed : undefined;
17+
}
18+
19+
export function buildTelegramApiUrl(
20+
apiBase: string,
21+
token: string,
22+
method: string
23+
): string {
24+
const normalized = normalizeTelegramApiBase(apiBase);
25+
const baseUrl = new URL(`${normalized}/`);
26+
return new URL(`bot${token}/${method}`, baseUrl).toString();
27+
}
28+
29+
export function createTelegramApiAgent(
30+
proxyUrl?: string
31+
): HttpAgent | undefined {
32+
const normalized = normalizeTelegramProxyUrl(proxyUrl);
33+
if (!normalized) return undefined;
34+
return new HttpsProxyAgent(normalized);
35+
}
36+
37+
export function createTelegramFetchDispatcher(
38+
proxyUrl?: string
39+
): Dispatcher | undefined {
40+
const normalized = normalizeTelegramProxyUrl(proxyUrl);
41+
if (!normalized) return undefined;
42+
return new ProxyAgent(normalized);
43+
}

src/ops/healthcheck.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import type { AppConfig } from "../config.js";
66
import { repairNodePtySpawnHelperPermissions } from "../runner/ptyPreflight.js";
77
import { extractCodexExecResponse } from "../bot/formatter.js";
88
import { toErrorMessage } from "../lib/errors.js";
9+
import {
10+
buildTelegramApiUrl,
11+
createTelegramFetchDispatcher
12+
} from "../lib/telegramApi.js";
913

1014
export type HealthcheckStatus = "pass" | "warn" | "fail";
1115

@@ -291,8 +295,17 @@ export async function runHealthcheck(
291295
const liveTelegramCheck = Boolean(options.telegramLiveCheck);
292296
if (liveTelegramCheck) {
293297
try {
298+
const url = buildTelegramApiUrl(
299+
config.telegram.apiBase,
300+
config.telegram.botToken,
301+
"getMe"
302+
);
303+
const dispatcher = createTelegramFetchDispatcher(
304+
config.telegram.proxyUrl
305+
);
294306
const response = await fetch(
295-
`https://api.telegram.org/bot${config.telegram.botToken}/getMe`
307+
url,
308+
dispatcher ? { dispatcher } : undefined
296309
);
297310
const payload = (await response.json()) as TelegramGetMeResponse;
298311
if (response.ok && payload?.ok && payload?.result?.username) {

tests/config.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { loadConfig } from "../src/config.js";
77

88
const ENV_KEYS = [
99
"BOT_TOKEN",
10+
"TELEGRAM_API_BASE",
11+
"TELEGRAM_PROXY_URL",
1012
"ALLOWED_USER_IDS",
1113
"PROACTIVE_USER_IDS",
1214
"STATE_FILE",
@@ -95,6 +97,8 @@ test("loadConfig parses env values into runtime config", () => {
9597
const config = withEnv(
9698
{
9799
BOT_TOKEN: "telegram-token",
100+
TELEGRAM_API_BASE: "https://telegram.example/api/",
101+
TELEGRAM_PROXY_URL: "http://127.0.0.1:7890",
98102
ALLOWED_USER_IDS: "1, 2",
99103
PROACTIVE_USER_IDS: "2",
100104
STATE_FILE: stateFile,
@@ -134,6 +138,8 @@ test("loadConfig parses env values into runtime config", () => {
134138
);
135139

136140
assert.equal(config.telegram.botToken, "telegram-token");
141+
assert.equal(config.telegram.apiBase, "https://telegram.example/api");
142+
assert.equal(config.telegram.proxyUrl, "http://127.0.0.1:7890");
137143
assert.equal(config.app.stateFile, stateFile);
138144
assert.deepEqual(config.telegram.allowedUserIds, ["1", "2"]);
139145
assert.deepEqual(config.telegram.proactiveUserIds, ["2"]);

0 commit comments

Comments
 (0)