Merge pull request #821 from gumadeiras/fix-bindings-telegram-webhook
Telegram: fix webhook multi-account routing (respect bindings.accountId)
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
- Onboarding/Configure: refuse to proceed with invalid configs; run `clawdbot doctor` first to avoid wiping custom fields. (#764 — thanks @mukhtharcm)
|
- Onboarding/Configure: refuse to proceed with invalid configs; run `clawdbot doctor` first to avoid wiping custom fields. (#764 — thanks @mukhtharcm)
|
||||||
- Anthropic: merge consecutive user turns (preserve newest metadata) before validation to avoid “Incorrect role information” errors. (#804 — thanks @ThomsenDrake)
|
- Anthropic: merge consecutive user turns (preserve newest metadata) before validation to avoid “Incorrect role information” errors. (#804 — thanks @ThomsenDrake)
|
||||||
- Discord/Slack: centralize reply-thread planning so auto-thread replies stay in the created thread without parent reply refs.
|
- Discord/Slack: centralize reply-thread planning so auto-thread replies stay in the created thread without parent reply refs.
|
||||||
|
- Telegram: respect account-scoped bindings when webhook mode is enabled. (#821 — thanks @gumadeiras)
|
||||||
- Update: run `clawdbot doctor --non-interactive` during updates to avoid TTY hangs. (#781 — thanks @ronyrus)
|
- Update: run `clawdbot doctor --non-interactive` during updates to avoid TTY hangs. (#781 — thanks @ronyrus)
|
||||||
- Browser tools: treat explicit `maxChars: 0` as unlimited while keeping the default limit only when omitted. (#796 — thanks @gabriel-trigo)
|
- Browser tools: treat explicit `maxChars: 0` as unlimited while keeping the default limit only when omitted. (#796 — thanks @gabriel-trigo)
|
||||||
- Tools: allow Claude/Gemini tool param aliases (`file_path`, `old_string`, `new_string`) while enforcing required params at runtime. (#793 — thanks @hsrvc)
|
- Tools: allow Claude/Gemini tool param aliases (`file_path`, `old_string`, `new_string`) while enforcing required params at runtime. (#793 — thanks @hsrvc)
|
||||||
|
|||||||
@@ -887,6 +887,53 @@ describe("createTelegramBot", () => {
|
|||||||
expect(replySpy).toHaveBeenCalledTimes(1);
|
expect(replySpy).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("routes DMs by telegram accountId binding", async () => {
|
||||||
|
onSpy.mockReset();
|
||||||
|
const replySpy = replyModule.__replySpy as unknown as ReturnType<
|
||||||
|
typeof vi.fn
|
||||||
|
>;
|
||||||
|
replySpy.mockReset();
|
||||||
|
|
||||||
|
loadConfig.mockReturnValue({
|
||||||
|
telegram: {
|
||||||
|
accounts: {
|
||||||
|
opie: {
|
||||||
|
botToken: "tok-opie",
|
||||||
|
dmPolicy: "open",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
bindings: [
|
||||||
|
{
|
||||||
|
agentId: "opie",
|
||||||
|
match: { provider: "telegram", accountId: "opie" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
createTelegramBot({ token: "tok", accountId: "opie" });
|
||||||
|
const handler = getOnHandler("message") as (
|
||||||
|
ctx: Record<string, unknown>,
|
||||||
|
) => Promise<void>;
|
||||||
|
|
||||||
|
await handler({
|
||||||
|
message: {
|
||||||
|
chat: { id: 123, type: "private" },
|
||||||
|
from: { id: 999, username: "testuser" },
|
||||||
|
text: "hello",
|
||||||
|
date: 1736380800,
|
||||||
|
message_id: 42,
|
||||||
|
},
|
||||||
|
me: { username: "clawdbot_bot" },
|
||||||
|
getFile: async () => ({ download: async () => new Uint8Array() }),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(replySpy).toHaveBeenCalledTimes(1);
|
||||||
|
const payload = replySpy.mock.calls[0][0];
|
||||||
|
expect(payload.AccountId).toBe("opie");
|
||||||
|
expect(payload.SessionKey).toBe("agent:opie:main");
|
||||||
|
});
|
||||||
|
|
||||||
it("allows per-group requireMention override", async () => {
|
it("allows per-group requireMention override", async () => {
|
||||||
onSpy.mockReset();
|
onSpy.mockReset();
|
||||||
const replySpy = replyModule.__replySpy as unknown as ReturnType<
|
const replySpy = replyModule.__replySpy as unknown as ReturnType<
|
||||||
|
|||||||
@@ -96,6 +96,8 @@ export async function monitorTelegramProvider(opts: MonitorTelegramOpts = {}) {
|
|||||||
if (opts.useWebhook) {
|
if (opts.useWebhook) {
|
||||||
await startTelegramWebhook({
|
await startTelegramWebhook({
|
||||||
token,
|
token,
|
||||||
|
accountId: account.accountId,
|
||||||
|
config: cfg,
|
||||||
path: opts.webhookPath,
|
path: opts.webhookPath,
|
||||||
port: opts.webhookPort,
|
port: opts.webhookPort,
|
||||||
secret: opts.webhookSecret,
|
secret: opts.webhookSecret,
|
||||||
|
|||||||
@@ -14,25 +14,37 @@ const handlerSpy = vi.fn(
|
|||||||
const setWebhookSpy = vi.fn();
|
const setWebhookSpy = vi.fn();
|
||||||
const stopSpy = vi.fn();
|
const stopSpy = vi.fn();
|
||||||
|
|
||||||
|
const createTelegramBotSpy = vi.fn(() => ({
|
||||||
|
api: { setWebhook: setWebhookSpy },
|
||||||
|
stop: stopSpy,
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock("grammy", () => ({
|
vi.mock("grammy", () => ({
|
||||||
webhookCallback: () => handlerSpy,
|
webhookCallback: () => handlerSpy,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("./bot.js", () => ({
|
vi.mock("./bot.js", () => ({
|
||||||
createTelegramBot: () => ({
|
createTelegramBot: (...args: unknown[]) => createTelegramBotSpy(...args),
|
||||||
api: { setWebhook: setWebhookSpy },
|
|
||||||
stop: stopSpy,
|
|
||||||
}),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe("startTelegramWebhook", () => {
|
describe("startTelegramWebhook", () => {
|
||||||
it("starts server, registers webhook, and serves health", async () => {
|
it("starts server, registers webhook, and serves health", async () => {
|
||||||
|
createTelegramBotSpy.mockClear();
|
||||||
const abort = new AbortController();
|
const abort = new AbortController();
|
||||||
|
const cfg = { bindings: [] };
|
||||||
const { server } = await startTelegramWebhook({
|
const { server } = await startTelegramWebhook({
|
||||||
token: "tok",
|
token: "tok",
|
||||||
|
accountId: "opie",
|
||||||
|
config: cfg,
|
||||||
port: 0, // random free port
|
port: 0, // random free port
|
||||||
abortSignal: abort.signal,
|
abortSignal: abort.signal,
|
||||||
});
|
});
|
||||||
|
expect(createTelegramBotSpy).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
accountId: "opie",
|
||||||
|
config: expect.objectContaining({ bindings: [] }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
const address = server.address();
|
const address = server.address();
|
||||||
if (!address || typeof address === "string") throw new Error("no address");
|
if (!address || typeof address === "string") throw new Error("no address");
|
||||||
const url = `http://127.0.0.1:${address.port}`;
|
const url = `http://127.0.0.1:${address.port}`;
|
||||||
@@ -46,13 +58,23 @@ describe("startTelegramWebhook", () => {
|
|||||||
|
|
||||||
it("invokes webhook handler on matching path", async () => {
|
it("invokes webhook handler on matching path", async () => {
|
||||||
handlerSpy.mockClear();
|
handlerSpy.mockClear();
|
||||||
|
createTelegramBotSpy.mockClear();
|
||||||
const abort = new AbortController();
|
const abort = new AbortController();
|
||||||
|
const cfg = { bindings: [] };
|
||||||
const { server } = await startTelegramWebhook({
|
const { server } = await startTelegramWebhook({
|
||||||
token: "tok",
|
token: "tok",
|
||||||
|
accountId: "opie",
|
||||||
|
config: cfg,
|
||||||
port: 0,
|
port: 0,
|
||||||
abortSignal: abort.signal,
|
abortSignal: abort.signal,
|
||||||
path: "/hook",
|
path: "/hook",
|
||||||
});
|
});
|
||||||
|
expect(createTelegramBotSpy).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
accountId: "opie",
|
||||||
|
config: expect.objectContaining({ bindings: [] }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
const addr = server.address();
|
const addr = server.address();
|
||||||
if (!addr || typeof addr === "string") throw new Error("no addr");
|
if (!addr || typeof addr === "string") throw new Error("no addr");
|
||||||
await fetch(`http://127.0.0.1:${addr.port}/hook`, { method: "POST" });
|
await fetch(`http://127.0.0.1:${addr.port}/hook`, { method: "POST" });
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createServer } from "node:http";
|
import { createServer } from "node:http";
|
||||||
|
|
||||||
import { webhookCallback } from "grammy";
|
import { webhookCallback } from "grammy";
|
||||||
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
import { formatErrorMessage } from "../infra/errors.js";
|
import { formatErrorMessage } from "../infra/errors.js";
|
||||||
import type { RuntimeEnv } from "../runtime.js";
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
import { defaultRuntime } from "../runtime.js";
|
import { defaultRuntime } from "../runtime.js";
|
||||||
@@ -8,6 +9,8 @@ import { createTelegramBot } from "./bot.js";
|
|||||||
|
|
||||||
export async function startTelegramWebhook(opts: {
|
export async function startTelegramWebhook(opts: {
|
||||||
token: string;
|
token: string;
|
||||||
|
accountId?: string;
|
||||||
|
config?: ClawdbotConfig;
|
||||||
path?: string;
|
path?: string;
|
||||||
port?: number;
|
port?: number;
|
||||||
host?: string;
|
host?: string;
|
||||||
@@ -27,6 +30,8 @@ export async function startTelegramWebhook(opts: {
|
|||||||
token: opts.token,
|
token: opts.token,
|
||||||
runtime,
|
runtime,
|
||||||
proxyFetch: opts.fetch,
|
proxyFetch: opts.fetch,
|
||||||
|
config: opts.config,
|
||||||
|
accountId: opts.accountId,
|
||||||
});
|
});
|
||||||
const handler = webhookCallback(bot, "http", {
|
const handler = webhookCallback(bot, "http", {
|
||||||
secretToken: opts.secret,
|
secretToken: opts.secret,
|
||||||
|
|||||||
Reference in New Issue
Block a user