fix: force telegram native fetch under bun
This commit is contained in:
@@ -51,7 +51,6 @@
|
|||||||
- Web UI: allow reconnect + password URL auth for the control UI and always scrub auth params from the URL. Thanks @oswalpalash for PR #414.
|
- Web UI: allow reconnect + password URL auth for the control UI and always scrub auth params from the URL. Thanks @oswalpalash for PR #414.
|
||||||
- Web UI: add Connect button on Overview to apply connection changes. Thanks @wizaj for PR #385.
|
- Web UI: add Connect button on Overview to apply connection changes. Thanks @wizaj for PR #385.
|
||||||
- Web UI: keep Focus toggle on the top bar (swap with theme toggle) so it stays visible. Thanks @RobOK2050 for reporting. (#440)
|
- Web UI: keep Focus toggle on the top bar (swap with theme toggle) so it stays visible. Thanks @RobOK2050 for reporting. (#440)
|
||||||
- Web UI: add Logs tab for gateway file logs with filtering, auto-follow, and export.
|
|
||||||
- ClawdbotKit: fix SwiftPM resource bundling path for `tool-display.json`. Thanks @fcatuhe for PR #398.
|
- ClawdbotKit: fix SwiftPM resource bundling path for `tool-display.json`. Thanks @fcatuhe for PR #398.
|
||||||
- Tools: add Telegram/WhatsApp reaction tools (with per-provider gating). Thanks @zats for PR #353.
|
- Tools: add Telegram/WhatsApp reaction tools (with per-provider gating). Thanks @zats for PR #353.
|
||||||
- Tools: flatten literal-union schemas for Claude on Vertex AI. Thanks @carlulsoe for PR #409.
|
- Tools: flatten literal-union schemas for Claude on Vertex AI. Thanks @carlulsoe for PR #409.
|
||||||
@@ -70,7 +69,6 @@
|
|||||||
- Agent: deliver final replies for non-streaming models when block chunking is enabled. Thank you @mneves75 for PR #369!
|
- Agent: deliver final replies for non-streaming models when block chunking is enabled. Thank you @mneves75 for PR #369!
|
||||||
- Agent: trim bootstrap context injections and keep group guidance concise (emoji reactions allowed). Thanks @tobiasbischoff for PR #370.
|
- Agent: trim bootstrap context injections and keep group guidance concise (emoji reactions allowed). Thanks @tobiasbischoff for PR #370.
|
||||||
- Agent: return a friendly context overflow response (413/request_too_large). Thanks @alejandroOPI for PR #395.
|
- Agent: return a friendly context overflow response (413/request_too_large). Thanks @alejandroOPI for PR #395.
|
||||||
- Agent: auto-enable Z.AI GLM-4.x thinking params (preserved for 4.7, interleaved for 4.5/4.6) unless disabled. Thanks @mneves75 for PR #443.
|
|
||||||
- Sub-agents: allow `sessions_spawn` model overrides and error on invalid models. Thanks @azade-c for PR #298.
|
- Sub-agents: allow `sessions_spawn` model overrides and error on invalid models. Thanks @azade-c for PR #298.
|
||||||
- Sub-agents: skip invalid model overrides with a warning and keep the run alive; tool exceptions now return tool errors instead of crashing the agent.
|
- Sub-agents: skip invalid model overrides with a warning and keep the run alive; tool exceptions now return tool errors instead of crashing the agent.
|
||||||
- Sessions: forward explicit sessionKey through gateway/chat/node bridge to avoid sub-agent sessionId mixups.
|
- Sessions: forward explicit sessionKey through gateway/chat/node bridge to avoid sub-agent sessionId mixups.
|
||||||
@@ -96,7 +94,7 @@
|
|||||||
- Telegram: honor `/activation` session mode for group mention gating and clarify group activation docs. Thanks @julianengel for PR #377.
|
- Telegram: honor `/activation` session mode for group mention gating and clarify group activation docs. Thanks @julianengel for PR #377.
|
||||||
- Telegram: isolate forum topic transcripts per thread and validate Gemini turn ordering in multi-topic sessions. Thanks @hsrvc for PR #407.
|
- Telegram: isolate forum topic transcripts per thread and validate Gemini turn ordering in multi-topic sessions. Thanks @hsrvc for PR #407.
|
||||||
- Telegram: render Telegram-safe HTML for outbound formatting and fall back to plain text on parse errors. Thanks @RandyVentures for PR #435.
|
- Telegram: render Telegram-safe HTML for outbound formatting and fall back to plain text on parse errors. Thanks @RandyVentures for PR #435.
|
||||||
- Telegram: add `[[audio_as_voice]]` tag to send audio as voice notes (audio files remain default); docs updated. Thanks @manmal for PR #188.
|
- Telegram: force grammY to use native fetch under Bun for BAN compatibility (avoids TLS chain errors).
|
||||||
- iMessage: ignore disconnect errors during shutdown (avoid unhandled promise rejections). Thanks @antons for PR #359.
|
- iMessage: ignore disconnect errors during shutdown (avoid unhandled promise rejections). Thanks @antons for PR #359.
|
||||||
- Messages: stop defaulting ack reactions to 👀 when identity emoji is missing.
|
- Messages: stop defaulting ack reactions to 👀 when identity emoji is missing.
|
||||||
- Auto-reply: require slash for control commands to avoid false triggers in normal text.
|
- Auto-reply: require slash for control commands to avoid false triggers in normal text.
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ const middlewareUseSpy = vi.fn();
|
|||||||
const onSpy = vi.fn();
|
const onSpy = vi.fn();
|
||||||
const stopSpy = vi.fn();
|
const stopSpy = vi.fn();
|
||||||
const commandSpy = vi.fn();
|
const commandSpy = vi.fn();
|
||||||
|
const botCtorSpy = vi.fn();
|
||||||
const sendChatActionSpy = vi.fn();
|
const sendChatActionSpy = vi.fn();
|
||||||
const setMessageReactionSpy = vi.fn(async () => undefined);
|
const setMessageReactionSpy = vi.fn(async () => undefined);
|
||||||
const setMyCommandsSpy = vi.fn(async () => undefined);
|
const setMyCommandsSpy = vi.fn(async () => undefined);
|
||||||
@@ -76,7 +77,12 @@ vi.mock("grammy", () => ({
|
|||||||
on = onSpy;
|
on = onSpy;
|
||||||
stop = stopSpy;
|
stop = stopSpy;
|
||||||
command = commandSpy;
|
command = commandSpy;
|
||||||
constructor(public token: string) {}
|
constructor(
|
||||||
|
public token: string,
|
||||||
|
public options?: { client?: { fetch?: typeof fetch } },
|
||||||
|
) {
|
||||||
|
botCtorSpy(token, options);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
InputFile: class {},
|
InputFile: class {},
|
||||||
webhookCallback: vi.fn(),
|
webhookCallback: vi.fn(),
|
||||||
@@ -118,6 +124,7 @@ describe("createTelegramBot", () => {
|
|||||||
setMyCommandsSpy.mockReset();
|
setMyCommandsSpy.mockReset();
|
||||||
middlewareUseSpy.mockReset();
|
middlewareUseSpy.mockReset();
|
||||||
sequentializeSpy.mockReset();
|
sequentializeSpy.mockReset();
|
||||||
|
botCtorSpy.mockReset();
|
||||||
sequentializeKey = undefined;
|
sequentializeKey = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -127,6 +134,23 @@ describe("createTelegramBot", () => {
|
|||||||
expect(useSpy).toHaveBeenCalledWith("throttler");
|
expect(useSpy).toHaveBeenCalledWith("throttler");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("forces native fetch for BAN compatibility", () => {
|
||||||
|
const originalFetch = globalThis.fetch;
|
||||||
|
const fetchSpy = vi.fn() as unknown as typeof fetch;
|
||||||
|
globalThis.fetch = fetchSpy;
|
||||||
|
try {
|
||||||
|
createTelegramBot({ token: "tok" });
|
||||||
|
expect(botCtorSpy).toHaveBeenCalledWith(
|
||||||
|
"tok",
|
||||||
|
expect.objectContaining({
|
||||||
|
client: expect.objectContaining({ fetch: fetchSpy }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
globalThis.fetch = originalFetch;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("sequentializes updates by chat and thread", () => {
|
it("sequentializes updates by chat and thread", () => {
|
||||||
createTelegramBot({ token: "tok" });
|
createTelegramBot({ token: "tok" });
|
||||||
expect(sequentializeSpy).toHaveBeenCalledTimes(1);
|
expect(sequentializeSpy).toHaveBeenCalledTimes(1);
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ import type { RuntimeEnv } from "../runtime.js";
|
|||||||
import { loadWebMedia } from "../web/media.js";
|
import { loadWebMedia } from "../web/media.js";
|
||||||
import { resolveTelegramAccount } from "./accounts.js";
|
import { resolveTelegramAccount } from "./accounts.js";
|
||||||
import { createTelegramDraftStream } from "./draft-stream.js";
|
import { createTelegramDraftStream } from "./draft-stream.js";
|
||||||
|
import { resolveTelegramFetch } from "./fetch.js";
|
||||||
import { markdownToTelegramHtml } from "./format.js";
|
import { markdownToTelegramHtml } from "./format.js";
|
||||||
import {
|
import {
|
||||||
readTelegramAllowFromStore,
|
readTelegramAllowFromStore,
|
||||||
@@ -150,9 +151,10 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
|||||||
throw new Error(`exit ${code}`);
|
throw new Error(`exit ${code}`);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const client: ApiClientOptions | undefined = opts.proxyFetch
|
const fetchImpl = resolveTelegramFetch(opts.proxyFetch);
|
||||||
? { fetch: opts.proxyFetch as unknown as ApiClientOptions["fetch"] }
|
const client: ApiClientOptions = {
|
||||||
: undefined;
|
fetch: fetchImpl as unknown as ApiClientOptions["fetch"],
|
||||||
|
};
|
||||||
|
|
||||||
const bot = new Bot(opts.token, { client });
|
const bot = new Bot(opts.token, { client });
|
||||||
bot.api.config.use(apiThrottler());
|
bot.api.config.use(apiThrottler());
|
||||||
|
|||||||
8
src/telegram/fetch.ts
Normal file
8
src/telegram/fetch.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// BAN compatibility: force native fetch to avoid grammY's node-fetch shim under Bun.
|
||||||
|
export function resolveTelegramFetch(proxyFetch?: typeof fetch): typeof fetch {
|
||||||
|
const fetchImpl = proxyFetch ?? globalThis.fetch;
|
||||||
|
if (!fetchImpl) {
|
||||||
|
throw new Error("fetch is not available; set telegram.proxy in config");
|
||||||
|
}
|
||||||
|
return fetchImpl;
|
||||||
|
}
|
||||||
@@ -1,5 +1,13 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
const { botApi, botCtorSpy } = vi.hoisted(() => ({
|
||||||
|
botApi: {
|
||||||
|
sendMessage: vi.fn(),
|
||||||
|
setMessageReaction: vi.fn(),
|
||||||
|
},
|
||||||
|
botCtorSpy: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
const { loadWebMedia } = vi.hoisted(() => ({
|
const { loadWebMedia } = vi.hoisted(() => ({
|
||||||
loadWebMedia: vi.fn(),
|
loadWebMedia: vi.fn(),
|
||||||
}));
|
}));
|
||||||
@@ -8,11 +16,26 @@ vi.mock("../web/media.js", () => ({
|
|||||||
loadWebMedia,
|
loadWebMedia,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock("grammy", () => ({
|
||||||
|
Bot: class {
|
||||||
|
api = botApi;
|
||||||
|
constructor(
|
||||||
|
public token: string,
|
||||||
|
public options?: { client?: { fetch?: typeof fetch } },
|
||||||
|
) {
|
||||||
|
botCtorSpy(token, options);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
InputFile: class {},
|
||||||
|
}));
|
||||||
|
|
||||||
import { reactMessageTelegram, sendMessageTelegram } from "./send.js";
|
import { reactMessageTelegram, sendMessageTelegram } from "./send.js";
|
||||||
|
|
||||||
describe("sendMessageTelegram", () => {
|
describe("sendMessageTelegram", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
loadWebMedia.mockReset();
|
loadWebMedia.mockReset();
|
||||||
|
botApi.sendMessage.mockReset();
|
||||||
|
botCtorSpy.mockReset();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("falls back to plain text when Telegram rejects HTML", async () => {
|
it("falls back to plain text when Telegram rejects HTML", async () => {
|
||||||
@@ -45,6 +68,27 @@ describe("sendMessageTelegram", () => {
|
|||||||
expect(res.messageId).toBe("42");
|
expect(res.messageId).toBe("42");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses native fetch for BAN compatibility when api is omitted", async () => {
|
||||||
|
const originalFetch = globalThis.fetch;
|
||||||
|
const fetchSpy = vi.fn() as unknown as typeof fetch;
|
||||||
|
globalThis.fetch = fetchSpy;
|
||||||
|
botApi.sendMessage.mockResolvedValue({
|
||||||
|
message_id: 1,
|
||||||
|
chat: { id: "123" },
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await sendMessageTelegram("123", "hi", { token: "tok" });
|
||||||
|
expect(botCtorSpy).toHaveBeenCalledWith(
|
||||||
|
"tok",
|
||||||
|
expect.objectContaining({
|
||||||
|
client: expect.objectContaining({ fetch: fetchSpy }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
globalThis.fetch = originalFetch;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("normalizes chat ids with internal prefixes", async () => {
|
it("normalizes chat ids with internal prefixes", async () => {
|
||||||
const sendMessage = vi.fn().mockResolvedValue({
|
const sendMessage = vi.fn().mockResolvedValue({
|
||||||
message_id: 1,
|
message_id: 1,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { mediaKindFromMime } from "../media/constants.js";
|
|||||||
import { isGifMedia } from "../media/mime.js";
|
import { isGifMedia } from "../media/mime.js";
|
||||||
import { loadWebMedia } from "../web/media.js";
|
import { loadWebMedia } from "../web/media.js";
|
||||||
import { resolveTelegramAccount } from "./accounts.js";
|
import { resolveTelegramAccount } from "./accounts.js";
|
||||||
|
import { resolveTelegramFetch } from "./fetch.js";
|
||||||
import { markdownToTelegramHtml } from "./format.js";
|
import { markdownToTelegramHtml } from "./format.js";
|
||||||
|
|
||||||
type TelegramSendOpts = {
|
type TelegramSendOpts = {
|
||||||
@@ -111,7 +112,11 @@ export async function sendMessageTelegram(
|
|||||||
const chatId = normalizeChatId(to);
|
const chatId = normalizeChatId(to);
|
||||||
// Use provided api or create a new Bot instance. The nullish coalescing
|
// Use provided api or create a new Bot instance. The nullish coalescing
|
||||||
// operator ensures api is always defined (Bot.api is always non-null).
|
// operator ensures api is always defined (Bot.api is always non-null).
|
||||||
const api = opts.api ?? new Bot(token).api;
|
const api =
|
||||||
|
opts.api ??
|
||||||
|
new Bot(token, {
|
||||||
|
client: { fetch: resolveTelegramFetch() },
|
||||||
|
}).api;
|
||||||
const mediaUrl = opts.mediaUrl?.trim();
|
const mediaUrl = opts.mediaUrl?.trim();
|
||||||
|
|
||||||
// Build optional params for forum topics and reply threading.
|
// Build optional params for forum topics and reply threading.
|
||||||
@@ -265,7 +270,11 @@ export async function reactMessageTelegram(
|
|||||||
const token = resolveToken(opts.token, account);
|
const token = resolveToken(opts.token, account);
|
||||||
const chatId = normalizeChatId(String(chatIdInput));
|
const chatId = normalizeChatId(String(chatIdInput));
|
||||||
const messageId = normalizeMessageId(messageIdInput);
|
const messageId = normalizeMessageId(messageIdInput);
|
||||||
const api = opts.api ?? new Bot(token).api;
|
const api =
|
||||||
|
opts.api ??
|
||||||
|
new Bot(token, {
|
||||||
|
client: { fetch: resolveTelegramFetch() },
|
||||||
|
}).api;
|
||||||
const request = createTelegramRetryRunner({
|
const request = createTelegramRetryRunner({
|
||||||
retry: opts.retry,
|
retry: opts.retry,
|
||||||
configRetry: account.config.retry,
|
configRetry: account.config.retry,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Bot } from "grammy";
|
import { Bot } from "grammy";
|
||||||
|
import { resolveTelegramFetch } from "./fetch.js";
|
||||||
|
|
||||||
export async function setTelegramWebhook(opts: {
|
export async function setTelegramWebhook(opts: {
|
||||||
token: string;
|
token: string;
|
||||||
@@ -6,7 +7,9 @@ export async function setTelegramWebhook(opts: {
|
|||||||
secret?: string;
|
secret?: string;
|
||||||
dropPendingUpdates?: boolean;
|
dropPendingUpdates?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const bot = new Bot(opts.token);
|
const bot = new Bot(opts.token, {
|
||||||
|
client: { fetch: resolveTelegramFetch() },
|
||||||
|
});
|
||||||
await bot.api.setWebhook(opts.url, {
|
await bot.api.setWebhook(opts.url, {
|
||||||
secret_token: opts.secret,
|
secret_token: opts.secret,
|
||||||
drop_pending_updates: opts.dropPendingUpdates ?? false,
|
drop_pending_updates: opts.dropPendingUpdates ?? false,
|
||||||
@@ -14,6 +17,8 @@ export async function setTelegramWebhook(opts: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteTelegramWebhook(opts: { token: string }) {
|
export async function deleteTelegramWebhook(opts: { token: string }) {
|
||||||
const bot = new Bot(opts.token);
|
const bot = new Bot(opts.token, {
|
||||||
|
client: { fetch: resolveTelegramFetch() },
|
||||||
|
});
|
||||||
await bot.api.deleteWebhook();
|
await bot.api.deleteWebhook();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user