refactor(telegram): unify callback-data byte limit checks

This commit is contained in:
Peter Steinberger
2026-04-03 00:37:49 +09:00
parent 7e2a450e31
commit 047b701859
4 changed files with 32 additions and 8 deletions

View File

@@ -867,6 +867,28 @@ describe("readTelegramButtons", () => {
}),
).toThrow(/style must be one of danger, success, primary/i);
});
it("rejects callback_data over Telegram's 64-byte limit", () => {
expect(() =>
readTelegramButtons({
buttons: [[{ text: "Option A", callback_data: "x".repeat(65) }]],
}),
).toThrow(/callback_data too long/i);
});
it("accepts multibyte callback_data at 64 bytes and rejects 68 bytes", () => {
expect(
readTelegramButtons({
buttons: [[{ text: "Option A", callback_data: "😀".repeat(16) }]],
}),
).toEqual([[{ text: "Option A", callback_data: "😀".repeat(16) }]]);
expect(() =>
readTelegramButtons({
buttons: [[{ text: "Option A", callback_data: "😀".repeat(17) }]],
}),
).toThrow(/callback_data too long/i);
});
});
describe("handleTelegramAction per-account gating", () => {

View File

@@ -13,6 +13,10 @@ import {
type TelegramActionConfig,
} from "openclaw/plugin-sdk/telegram-core";
import { createTelegramActionGate, resolveTelegramPollActionGateState } from "./accounts.js";
import {
fitsTelegramCallbackData,
TELEGRAM_CALLBACK_DATA_MAX_BYTES,
} from "./approval-callback-data.js";
import type { TelegramButtonStyle, TelegramInlineButtons } from "./button-types.js";
import { resolveTelegramInlineButtons } from "./button-types.js";
import {
@@ -131,9 +135,9 @@ export function readTelegramButtons(
if (!text || !callbackData) {
throw new Error(`buttons[${rowIndex}][${buttonIndex}] requires text and callback_data`);
}
if (callbackData.length > 64) {
if (!fitsTelegramCallbackData(callbackData)) {
throw new Error(
`buttons[${rowIndex}][${buttonIndex}] callback_data too long (max 64 chars)`,
`buttons[${rowIndex}][${buttonIndex}] callback_data too long (max ${TELEGRAM_CALLBACK_DATA_MAX_BYTES} bytes)`,
);
}
const styleRaw = rawButton.style;

View File

@@ -1,4 +1,4 @@
const TELEGRAM_CALLBACK_DATA_MAX_BYTES = 64;
export const TELEGRAM_CALLBACK_DATA_MAX_BYTES = 64;
const TELEGRAM_APPROVE_ALLOW_ALWAYS_PATTERN =
/^\/approve(?:@[^\s]+)?\s+[A-Za-z0-9][A-Za-z0-9._:-]*\s+allow-always$/i;

View File

@@ -8,6 +8,7 @@
* - mdl_sel/{model} - select model (compact fallback when standard is >64 bytes)
* - mdl_back - back to providers list
*/
import { fitsTelegramCallbackData } from "./approval-callback-data.js";
export type ButtonRow = Array<{ text: string; callback_data: string }>;
@@ -39,7 +40,6 @@ export type ModelsKeyboardParams = {
};
const MODELS_PAGE_SIZE = 8;
const MAX_CALLBACK_DATA_BYTES = 64;
const CALLBACK_PREFIX = {
providers: "mdl_prov",
back: "mdl_back",
@@ -108,13 +108,11 @@ export function buildModelSelectionCallbackData(params: {
model: string;
}): string | null {
const fullCallbackData = `${CALLBACK_PREFIX.selectStandard}${params.provider}/${params.model}`;
if (Buffer.byteLength(fullCallbackData, "utf8") <= MAX_CALLBACK_DATA_BYTES) {
if (fitsTelegramCallbackData(fullCallbackData)) {
return fullCallbackData;
}
const compactCallbackData = `${CALLBACK_PREFIX.selectCompact}${params.model}`;
return Buffer.byteLength(compactCallbackData, "utf8") <= MAX_CALLBACK_DATA_BYTES
? compactCallbackData
: null;
return fitsTelegramCallbackData(compactCallbackData) ? compactCallbackData : null;
}
export function resolveModelSelection(params: {