fix: scope Telegram pairing code blocks (#52784) (thanks @sumukhj1219)

* Telegram: format pairing challenge for easier copy

* test: restore Telegram pairing chatId assertion

* fix: scope Telegram pairing code blocks (#52784) (thanks @sumukhj1219)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
This commit is contained in:
SUMUKH
2026-03-25 11:03:33 +05:30
committed by GitHub
parent 9ab226d275
commit 149c4683a3
6 changed files with 37 additions and 12 deletions

View File

@@ -51,6 +51,7 @@ Docs: https://docs.openclaw.ai
- Feishu/startup: keep `requireMention` enforcement strict when bot identity startup probes fail, raise the startup bot-info timeout to 30s, and add cancellable background identity recovery so mention-gated groups recover without noisy fallback. (#43788) Thanks @lefarcen.
- Plugins: enforce terminal hook decision semantics for tool/message guards (#54241) Thanks @joshavant.
- Telegram/outbound errors: preserve actionable 403 membership/block/kick details and treat `bot not a member` as a permanent delivery failure so Telegram sends stop retrying doomed chats. (#53635) Thanks @w-sss.
- Telegram/pairing: render pairing codes and approval commands as Telegram-only code blocks while keeping shared pairing replies plain text for other channels. (#52784) Thanks @sumukhj1219.
## 2026.3.23

View File

@@ -500,10 +500,10 @@ describe("createTelegramBot", () => {
const pairingText = String(sendMessageSpy.mock.calls[0]?.[1]);
expect(pairingText, testCase.name).toContain(`Your Telegram user id: ${senderId}`);
expect(pairingText, testCase.name).toContain("Pairing code:");
const code = pairingText.match(/Pairing code:\s*([A-Z2-9]{8})/)?.[1];
expect(code, testCase.name).toBeDefined();
expect(pairingText, testCase.name).toContain(`openclaw pairing approve telegram ${code}`);
expect(pairingText, testCase.name).not.toContain("<code>");
expect(pairingText, testCase.name).toContain("openclaw pairing approve telegram");
expect(sendMessageSpy.mock.calls[0]?.[2], testCase.name).toEqual(
expect.objectContaining({ parse_mode: "HTML" }),
);
}
});
});
@@ -546,7 +546,12 @@ describe("createTelegramBot", () => {
expect(getFileSpy).not.toHaveBeenCalled();
expect(fetchSpy).not.toHaveBeenCalled();
expect(sendMessageSpy).toHaveBeenCalledTimes(1);
expect(String(sendMessageSpy.mock.calls[0]?.[1])).toContain("Pairing code:");
const pairingText = String(sendMessageSpy.mock.calls[0]?.[1]);
expect(pairingText).toContain("Pairing code:");
expect(pairingText).toContain("<pre><code>");
expect(sendMessageSpy.mock.calls[0]?.[2]).toEqual(
expect.objectContaining({ parse_mode: "HTML" }),
);
expect(replySpy).not.toHaveBeenCalled();
} finally {
fetchSpy.mockRestore();
@@ -633,7 +638,12 @@ describe("createTelegramBot", () => {
expect(getFileSpy).not.toHaveBeenCalled();
expect(fetchSpy).not.toHaveBeenCalled();
expect(sendMessageSpy).toHaveBeenCalledTimes(1);
expect(String(sendMessageSpy.mock.calls[0]?.[1])).toContain("Pairing code:");
const pairingText = String(sendMessageSpy.mock.calls[0]?.[1]);
expect(pairingText).toContain("Pairing code:");
expect(pairingText).toContain("<pre><code>");
expect(sendMessageSpy.mock.calls[0]?.[2]).toEqual(
expect.objectContaining({ parse_mode: "HTML" }),
);
expect(replySpy).not.toHaveBeenCalled();
} finally {
fetchSpy.mockRestore();

View File

@@ -119,8 +119,12 @@ describe("enforceTelegramDmAccess", () => {
});
expect(allowed).toBe(false);
expect(createChannelPairingChallengeIssuerMock).toHaveBeenCalledTimes(1);
expect(sendMessage).toHaveBeenCalledWith(42, "Pairing code: 123456");
expect(sendMessage).toHaveBeenCalledTimes(1);
const [firstCall] = sendMessage.mock.calls as Array<unknown[]>;
expect(firstCall?.[0]).toBe(42);
const sentText = String(firstCall?.[1] ?? "");
expect(sentText).toContain("Pairing code:");
expect(firstCall?.[2]).toEqual(expect.objectContaining({ parse_mode: "HTML" }));
expect(logger.info).toHaveBeenCalledWith(
expect.objectContaining({
chatId: "42",

View File

@@ -6,6 +6,7 @@ import { upsertChannelPairingRequest } from "openclaw/plugin-sdk/conversation-ru
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { withTelegramApiErrorLogging } from "./api-logging.js";
import { resolveSenderAllowMatch, type NormalizedAllowFrom } from "./bot-access.js";
import { renderTelegramHtmlText } from "./format.js";
type TelegramDmAccessLogger = {
info: (obj: Record<string, unknown>, msg: string) => void;
@@ -113,9 +114,10 @@ export async function enforceTelegramDmAccess(params: {
);
},
sendPairingReply: async (text) => {
const html = renderTelegramHtmlText(text);
await withTelegramApiErrorLogging({
operation: "sendMessage",
fn: () => bot.api.sendMessage(chatId, text),
fn: () => bot.api.sendMessage(chatId, html, { parse_mode: "HTML" }),
});
},
onReplyError: (err) => {

View File

@@ -52,12 +52,14 @@ describe("buildPairingReply", () => {
it(`formats pairing reply for ${testCase.channel}`, () => {
const text = buildPairingReply(testCase);
expect(text).toContain(testCase.idLine);
expect(text).toContain(`Pairing code: ${testCase.code}`);
expect(text).toContain("Pairing code:");
expect(text).toContain(`\n\`\`\`\n${testCase.code}\n\`\`\`\n`);
// CLI commands should respect OPENCLAW_PROFILE when set (most tests run with isolated profile)
const commandRe = new RegExp(
`(?:openclaw|openclaw) --profile isolated pairing approve ${testCase.channel} ${testCase.code}`,
);
expect(text).toMatch(commandRe);
expect(text).toContain("\n```\n");
});
}
});

View File

@@ -7,14 +7,20 @@ export function buildPairingReply(params: {
code: string;
}): string {
const { channel, idLine, code } = params;
const approveCommand = formatCliCommand(`openclaw pairing approve ${channel} ${code}`);
return [
"OpenClaw: access not configured.",
"",
idLine,
"",
`Pairing code: ${code}`,
"Pairing code:",
"```",
code,
"```",
"",
"Ask the bot owner to approve with:",
formatCliCommand(`openclaw pairing approve ${channel} ${code}`),
"```",
approveCommand,
"```",
].join("\n");
}