openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
780 lines (779 loc) • 28.8 kB
JavaScript
import { c as isRecord } from "./record-coerce-DItp3I4t.js";
import { g as readStringValue, l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { r as logVerbose } from "./globals-CTaGxEqj.js";
import "./runtime-env-0Q-gCyUb.js";
import "./string-coerce-runtime-GQa0ehRA.js";
import { t as getOptionalTelegramRuntime } from "./runtime-D4cq5Nic.js";
import { n as resolveTelegramBotUserIdFromToken, t as fingerprintTelegramBotToken } from "./token-fingerprint-z983D2R-.js";
import { r as normalizeTelegramCommandName, t as TELEGRAM_COMMAND_NAME_PATTERN } from "./command-config-BipFmZLM.js";
import { t as withTelegramApiErrorLogging } from "./api-logging-aN8dtVY1.js";
import { createHash } from "node:crypto";
//#region extensions/telegram/src/bot-native-command-menu-state.ts
const TELEGRAM_MENU_LOCALE_LEDGER_VERSION = 1;
const TELEGRAM_MENU_LOCALE_LEDGER_NAMESPACE = "telegram.command-menu-locales";
const TELEGRAM_MENU_LOCALE_LEDGER_MAX_ENTRIES = 1e3;
const TELEGRAM_MENU_LEDGER_DIAGNOSTIC_MAX_CODES = 8;
const TELEGRAM_MENU_LEDGER_DIAGNOSTIC_CODE_MAX_LENGTH = 32;
const TELEGRAM_MENU_LANGUAGE_CODES = new Set(Object.keys({
ab: true,
aa: true,
af: true,
ak: true,
sq: true,
am: true,
ar: true,
an: true,
hy: true,
as: true,
av: true,
ae: true,
ay: true,
az: true,
bm: true,
ba: true,
eu: true,
be: true,
bn: true,
bi: true,
bs: true,
br: true,
bg: true,
my: true,
ca: true,
ch: true,
ce: true,
ny: true,
zh: true,
cu: true,
cv: true,
kw: true,
co: true,
cr: true,
hr: true,
cs: true,
da: true,
dv: true,
nl: true,
dz: true,
en: true,
eo: true,
et: true,
ee: true,
fo: true,
fj: true,
fi: true,
fr: true,
fy: true,
ff: true,
gd: true,
gl: true,
lg: true,
ka: true,
de: true,
el: true,
kl: true,
gn: true,
gu: true,
ht: true,
ha: true,
he: true,
hz: true,
hi: true,
ho: true,
hu: true,
is: true,
io: true,
ig: true,
id: true,
ia: true,
ie: true,
iu: true,
ik: true,
ga: true,
it: true,
ja: true,
jv: true,
kn: true,
kr: true,
ks: true,
kk: true,
km: true,
ki: true,
rw: true,
ky: true,
kv: true,
kg: true,
ko: true,
kj: true,
ku: true,
lo: true,
la: true,
lv: true,
li: true,
ln: true,
lt: true,
lu: true,
lb: true,
mk: true,
mg: true,
ms: true,
ml: true,
mt: true,
gv: true,
mi: true,
mr: true,
mh: true,
mn: true,
na: true,
nv: true,
nd: true,
nr: true,
ng: true,
ne: true,
no: true,
nb: true,
nn: true,
ii: true,
oc: true,
oj: true,
or: true,
om: true,
os: true,
pi: true,
ps: true,
fa: true,
pl: true,
pt: true,
pa: true,
qu: true,
ro: true,
rm: true,
rn: true,
ru: true,
se: true,
sm: true,
sg: true,
sa: true,
sc: true,
sr: true,
sn: true,
sd: true,
si: true,
sk: true,
sl: true,
so: true,
st: true,
es: true,
su: true,
sw: true,
ss: true,
sv: true,
tl: true,
ty: true,
tg: true,
ta: true,
tt: true,
te: true,
th: true,
bo: true,
ti: true,
to: true,
ts: true,
tn: true,
tr: true,
tk: true,
tw: true,
ug: true,
uk: true,
ur: true,
uz: true,
ve: true,
vi: true,
vo: true,
wa: true,
cy: true,
wo: true,
xh: true,
yi: true,
yo: true,
za: true,
zu: true
}));
const syncTails = /* @__PURE__ */ new Map();
const syncedCommandHashes = /* @__PURE__ */ new Map();
const knownLanguageCodes = /* @__PURE__ */ new Map();
function resolveTelegramMenuRemoteOwner(params) {
const token = params.botToken?.trim();
const tokenBotId = resolveTelegramBotUserIdFromToken(token);
const botId = params.botId ?? tokenBotId;
const tokenFingerprint = token ? fingerprintTelegramBotToken(token) : void 0;
const fallbackKey = `${params.accountId ?? "default"}:${tokenFingerprint ?? "unknown"}`;
const queueKey = botId === void 0 ? `fallback:${fallbackKey}` : `bot:${botId}`;
return {
queueKey,
hashKey: `${queueKey}:${tokenFingerprint ?? ""}`,
...botId === void 0 ? {} : { botId: String(botId) }
};
}
function enqueueTelegramMenuSync(params) {
const next = (syncTails.get(params.ownerKey) ?? Promise.resolve()).then(params.sync).catch((error) => {
try {
params.onError(error);
} catch {}
});
syncTails.set(params.ownerKey, next);
next.then(() => {
if (syncTails.get(params.ownerKey) === next) syncTails.delete(params.ownerKey);
});
}
function readTelegramMenuCommandHash(key) {
return syncedCommandHashes.get(key) ?? null;
}
function writeTelegramMenuCommandHash(key, hash) {
syncedCommandHashes.set(key, hash);
}
function getProcessKnownTelegramMenuLocales(ownerKey) {
let locales = knownLanguageCodes.get(ownerKey);
if (!locales) {
locales = /* @__PURE__ */ new Set();
knownLanguageCodes.set(ownerKey, locales);
}
return locales;
}
function isTelegramMenuLanguageCode(languageCode) {
return TELEGRAM_MENU_LANGUAGE_CODES.has(languageCode);
}
function normalizeTelegramMenuLanguageCode(languageCode) {
const normalized = languageCode.trim().toLowerCase();
return isTelegramMenuLanguageCode(normalized) ? normalized : null;
}
function formatTelegramMenuLedgerUnsupportedCode(languageCode) {
const readable = languageCode.trim().replace(/\s+/g, " ") || "(empty)";
if (readable.length <= TELEGRAM_MENU_LEDGER_DIAGNOSTIC_CODE_MAX_LENGTH) return readable;
return `${readable.slice(0, 29)}...`;
}
function normalizeTelegramMenuLocaleLedger(stored) {
const candidate = isRecord(stored) ? stored : void 0;
const rawLanguageCodes = Array.isArray(candidate?.languageCodes) ? candidate.languageCodes : [];
const languageCodes = /* @__PURE__ */ new Set();
const unsupportedLanguageCodes = /* @__PURE__ */ new Set();
let malformedEntryCount = 0;
for (const rawLanguageCode of rawLanguageCodes) {
if (typeof rawLanguageCode !== "string") {
malformedEntryCount += 1;
continue;
}
const languageCode = normalizeTelegramMenuLanguageCode(rawLanguageCode);
if (languageCode) languageCodes.add(languageCode);
else unsupportedLanguageCodes.add(formatTelegramMenuLedgerUnsupportedCode(rawLanguageCode));
}
if (!candidate || candidate.version !== TELEGRAM_MENU_LOCALE_LEDGER_VERSION) malformedEntryCount += 1;
if (!Array.isArray(candidate?.languageCodes)) malformedEntryCount += 1;
if (candidate && Object.keys(candidate).toSorted().join(",") !== "languageCodes,version") malformedEntryCount += 1;
const canonicalLanguageCodes = [...languageCodes].toSorted();
const isCanonical = canonicalLanguageCodes.length > 0 && malformedEntryCount === 0 && unsupportedLanguageCodes.size === 0 && rawLanguageCodes.length === canonicalLanguageCodes.length && rawLanguageCodes.every((languageCode, index) => languageCode === canonicalLanguageCodes[index]);
return {
...canonicalLanguageCodes.length > 0 ? { value: {
version: TELEGRAM_MENU_LOCALE_LEDGER_VERSION,
languageCodes: canonicalLanguageCodes
} } : {},
isCanonical,
unsupportedLanguageCodes: [...unsupportedLanguageCodes].toSorted(),
malformedEntryCount
};
}
function formatTelegramMenuLocaleLedgerRepairDiagnostic(params) {
const action = params.normalization.value ? "repaired" : "reset";
const details = [];
const unsupportedCodes = params.normalization.unsupportedLanguageCodes;
if (unsupportedCodes.length > 0) {
const visibleCodes = unsupportedCodes.slice(0, TELEGRAM_MENU_LEDGER_DIAGNOSTIC_MAX_CODES);
const hiddenCount = unsupportedCodes.length - visibleCodes.length;
details.push(`discarded unsupported language codes: ${visibleCodes.join(", ")}${hiddenCount > 0 ? ` (+${hiddenCount} more)` : ""}`);
}
if (params.normalization.malformedEntryCount > 0) details.push(`discarded ${params.normalization.malformedEntryCount} malformed ledger field(s) or entry(ies)`);
const detail = details.length > 0 ? ` (${details.join("; ")})` : "";
return `Telegram command menu locale ledger for bot ${params.botId} was ${action}; the unshipped ledger contained non-canonical data${detail}.`;
}
async function readTelegramMenuLocaleLedger(params) {
const telegramRuntime = getOptionalTelegramRuntime();
if (!telegramRuntime) {
params.runtime.error?.(`Telegram command menu locale ledger unavailable for bot ${params.botId}: runtime not initialized`);
return null;
}
try {
const store = telegramRuntime.state.openKeyedStore({
namespace: TELEGRAM_MENU_LOCALE_LEDGER_NAMESPACE,
maxEntries: TELEGRAM_MENU_LOCALE_LEDGER_MAX_ENTRIES,
overflowPolicy: "reject-new"
});
const stored = await store.lookup(params.botId);
if (stored === void 0) return { store };
const storedVersion = (isRecord(stored) ? stored : void 0)?.version;
if (typeof storedVersion === "number" && Number.isInteger(storedVersion) && storedVersion > TELEGRAM_MENU_LOCALE_LEDGER_VERSION) {
params.runtime.error?.(`Telegram command menu locale ledger for bot ${params.botId} uses unsupported future version ${storedVersion}; preserving it unchanged.`);
return null;
}
const normalization = normalizeTelegramMenuLocaleLedger(stored);
if (normalization.isCanonical) return {
store,
value: normalization.value
};
try {
if (normalization.value) await store.register(params.botId, normalization.value);
else await store.delete(params.botId);
} catch (error) {
params.runtime.error?.(`Telegram command menu locale ledger repair failed for bot ${params.botId}: ${String(error)}`);
return null;
}
params.runtime.error?.(formatTelegramMenuLocaleLedgerRepairDiagnostic({
botId: params.botId,
normalization
}));
return {
store,
...normalization.value ? { value: normalization.value } : {}
};
} catch (error) {
params.runtime.error?.(`Telegram command menu locale ledger unavailable for bot ${params.botId}: ${String(error)}`);
return null;
}
}
async function persistTelegramMenuLocaleLedger(params) {
const current = params.read.value?.languageCodes ?? [];
if (current.length === params.languageCodes.length && current.every((languageCode, index) => languageCode === params.languageCodes[index])) return;
if (params.languageCodes.length === 0) {
await params.read.store.delete(params.botId);
return;
}
await params.read.store.register(params.botId, {
version: TELEGRAM_MENU_LOCALE_LEDGER_VERSION,
languageCodes: params.languageCodes
});
}
//#endregion
//#region extensions/telegram/src/bot-native-command-menu.ts
const TELEGRAM_MAX_COMMANDS = 100;
const TELEGRAM_TOTAL_COMMAND_TEXT_BUDGET = 5700;
const TELEGRAM_COMMAND_RETRY_RATIO = .8;
const TELEGRAM_MIN_COMMAND_DESCRIPTION_LENGTH = 1;
const TELEGRAM_MAX_COMMAND_DESCRIPTION_LENGTH = 256;
const TELEGRAM_MENU_RESULT_CACHE_MAX = 128;
const TELEGRAM_COMMAND_MENU_SCOPES = [{ label: "default" }, {
label: "all_group_chats",
options: { scope: { type: "all_group_chats" } }
}];
const cappedTelegramMenuCache = /* @__PURE__ */ new Map();
function countTelegramCommandText(value) {
let count = 0;
for (let index = 0; index < value.length;) {
const codePoint = value.codePointAt(index);
index += codePoint && codePoint > 65535 ? 2 : 1;
count += 1;
}
return count;
}
function truncateTelegramCommandText(value, maxLength) {
if (maxLength <= 0) return "";
const suffix = maxLength > 1 ? "…" : "";
const prefixLimit = maxLength - countTelegramCommandText(suffix);
let count = 0;
let prefixEnd = 0;
for (const char of value) {
count += 1;
if (count <= prefixLimit) prefixEnd += char.length;
if (count > maxLength) return `${value.slice(0, prefixEnd)}${suffix}`;
}
return value;
}
function fitTelegramCommandsWithinTextBudget(commands, maxTotalChars) {
let candidateCommands = [...commands];
while (candidateCommands.length > 0) {
const descriptionBudget = maxTotalChars - candidateCommands.reduce((total, command) => total + countTelegramCommandText(command.command), 0);
if (descriptionBudget < candidateCommands.length * TELEGRAM_MIN_COMMAND_DESCRIPTION_LENGTH) {
candidateCommands = candidateCommands.slice(0, -1);
continue;
}
const descriptionCap = Math.max(TELEGRAM_MIN_COMMAND_DESCRIPTION_LENGTH, Math.floor(descriptionBudget / candidateCommands.length));
let descriptionTrimmed = false;
const fittedCommands = candidateCommands.map((command) => {
const description = truncateTelegramCommandText(command.description, Math.min(descriptionCap, TELEGRAM_MAX_COMMAND_DESCRIPTION_LENGTH));
if (description !== command.description) {
descriptionTrimmed = true;
return Object.assign({}, command, { description });
}
return command;
});
return {
commands: fittedCommands,
descriptionTrimmed,
textBudgetDropCount: commands.length - fittedCommands.length
};
}
return {
commands: [],
descriptionTrimmed: false,
textBudgetDropCount: commands.length
};
}
function readErrorTextField(value, key) {
if (!value || typeof value !== "object" || !(key in value)) return;
return readStringValue(value[key]);
}
function isBotCommandsTooMuchError(err) {
if (!err) return false;
const pattern = /\bBOT_COMMANDS_TOO_MUCH\b/i;
if (typeof err === "string") return pattern.test(err);
if (err instanceof Error) {
if (pattern.test(err.message)) return true;
}
const description = readErrorTextField(err, "description");
if (description && pattern.test(description)) return true;
const message = readErrorTextField(err, "message");
if (message && pattern.test(message)) return true;
return false;
}
function formatTelegramCommandRetrySuccessLog(params) {
const omittedCount = Math.max(0, params.initialCount - params.acceptedCount);
return `Telegram accepted ${params.acceptedCount} commands after BOT_COMMANDS_TOO_MUCH (started with ${params.initialCount}; omitted ${omittedCount}). Reduce plugin/skill/custom commands to expose more menu entries.`;
}
function buildPluginTelegramMenuCommands(params) {
const { specs, existingCommands } = params;
const commands = [];
const selectedCommands = [];
const issues = [];
const pluginCommandNames = /* @__PURE__ */ new Set();
const sortedSpecs = specs.map((spec) => {
const rawName = typeof spec.name === "string" ? spec.name : "";
return {
spec,
rawName,
normalized: normalizeTelegramCommandName(rawName)
};
}).toSorted((a, b) => {
if (a.normalized !== b.normalized) return a.normalized < b.normalized ? -1 : 1;
const aExact = a.rawName.trim().toLowerCase() === a.normalized;
if (aExact !== (b.rawName.trim().toLowerCase() === b.normalized)) return aExact ? -1 : 1;
return a.rawName < b.rawName ? -1 : a.rawName > b.rawName ? 1 : 0;
});
for (const { spec, rawName, normalized } of sortedSpecs) {
if (!normalized || !TELEGRAM_COMMAND_NAME_PATTERN.test(normalized)) {
const invalidName = rawName.trim() ? rawName : "<unknown>";
issues.push(`Plugin command "/${invalidName}" is invalid for Telegram (use a-z, 0-9, underscore; max 32 chars).`);
continue;
}
const description = normalizeOptionalString(spec.description) ?? "";
if (!description) {
issues.push(`Plugin command "/${normalized}" is missing a description.`);
continue;
}
if (existingCommands.has(normalized)) {
if (pluginCommandNames.has(normalized)) issues.push(`Plugin command "/${normalized}" is duplicated.`);
else issues.push(`Plugin command "/${normalized}" conflicts with an existing Telegram command.`);
continue;
}
pluginCommandNames.add(normalized);
existingCommands.add(normalized);
const menuCommand = {
command: normalized,
description,
spec
};
if (spec.descriptionLocalizations) menuCommand.descriptionLocalizations = spec.descriptionLocalizations;
const { spec: _spec, ...displayCommand } = menuCommand;
commands.push(displayCommand);
selectedCommands.push(menuCommand);
}
return {
commands,
selectedCommands,
issues
};
}
function buildCappedTelegramMenuCommands(params) {
const maxCommands = params.maxCommands ?? TELEGRAM_MAX_COMMANDS;
const maxTotalChars = params.maxTotalChars ?? TELEGRAM_TOTAL_COMMAND_TEXT_BUDGET;
const cacheKey = buildTelegramMenuResultCacheKey({
allCommands: params.allCommands,
maxCommands,
maxTotalChars
});
const cached = cappedTelegramMenuCache.get(cacheKey);
if (cached) return cached;
const result = buildUncachedCappedTelegramMenuCommands({
allCommands: params.allCommands,
maxCommands,
maxTotalChars
});
rememberCappedTelegramMenuResult(cacheKey, result);
return result;
}
function buildDirectSkillFallbackCommands(commands) {
const fallback = commands.find((command) => command.command === "skill" && !command.isSkill);
const remaining = commands.filter((command) => !command.isSkill && command !== fallback);
return fallback ? [fallback, ...remaining] : remaining;
}
function buildUncachedCappedTelegramMenuCommands(params) {
const { allCommands, maxCommands, maxTotalChars } = params;
const fitCommands = (commands) => {
const cappedCommands = commands.slice(0, maxCommands);
return cappedCommands.some((command) => countTelegramCommandText(command.description) > TELEGRAM_MAX_COMMAND_DESCRIPTION_LENGTH) || cappedCommands.reduce((total, { command, description }) => total + countTelegramCommandText(command) + countTelegramCommandText(description), 0) > maxTotalChars ? fitTelegramCommandsWithinTextBudget(cappedCommands, maxTotalChars) : {
commands: cappedCommands,
descriptionTrimmed: false,
textBudgetDropCount: 0
};
};
let effectiveCommands = allCommands;
let fitted = fitCommands(allCommands);
const skillCommandCount = allCommands.filter((command) => command.isSkill).length;
const skillCommandsOmitted = skillCommandCount > 0 && fitted.commands.filter((command) => command.isSkill).length < skillCommandCount;
if (skillCommandsOmitted) {
effectiveCommands = buildDirectSkillFallbackCommands(allCommands);
fitted = fitCommands(effectiveCommands);
}
const totalCommands = effectiveCommands.length;
const overflowCount = Math.max(0, totalCommands - maxCommands);
return {
commandsToRegister: fitted.commands,
totalCommands,
maxCommands,
overflowCount,
maxTotalChars,
descriptionTrimmed: fitted.descriptionTrimmed,
textBudgetDropCount: fitted.textBudgetDropCount,
skillCommandsOmitted
};
}
function buildTelegramMenuResultCacheKey(params) {
const digest = createHash("sha256");
updateTelegramCommandDigestField(digest, String(params.maxCommands));
updateTelegramCommandDigestField(digest, String(params.maxTotalChars));
for (const command of params.allCommands) {
updateTelegramCommandDigestField(digest, command.command);
updateTelegramCommandDigestField(digest, command.description);
updateTelegramCommandDigestField(digest, command.isAlias ? "1" : "0");
updateTelegramCommandDigestField(digest, command.isSkill ? "1" : "0");
updateTelegramCommandLocalizationDigest(digest, command.descriptionLocalizations);
}
return digest.digest("hex").slice(0, 16);
}
function updateTelegramCommandDigestField(digest, value) {
digest.update(String(value.length));
digest.update(":");
digest.update(value);
}
function updateTelegramCommandLocalizationDigest(digest, localizations) {
const entries = buildEffectiveTelegramCommandLocalizations(localizations);
updateTelegramCommandDigestField(digest, String(entries.length));
for (const [locale, description] of entries) {
updateTelegramCommandDigestField(digest, locale);
updateTelegramCommandDigestField(digest, description);
}
}
function rememberCappedTelegramMenuResult(key, result) {
cappedTelegramMenuCache.set(key, result);
if (cappedTelegramMenuCache.size <= TELEGRAM_MENU_RESULT_CACHE_MAX) return;
const oldestKey = cappedTelegramMenuCache.keys().next().value;
if (oldestKey) cappedTelegramMenuCache.delete(oldestKey);
}
function hashCommandList(commands) {
const digest = createHash("sha256");
updateTelegramCommandDigestField(digest, String(commands.length));
for (const command of commands) {
updateTelegramCommandDigestField(digest, command.command);
updateTelegramCommandDigestField(digest, command.description);
updateTelegramCommandLocalizationDigest(digest, command.descriptionLocalizations);
}
return digest.digest("hex").slice(0, 16);
}
function reduceTelegramMenuCommands(commands, maxCommands) {
const reduced = commands.slice(0, maxCommands);
const skillCommandCount = commands.filter((command) => command.isSkill).length;
return reduced.filter((command) => command.isSkill).length < skillCommandCount ? buildDirectSkillFallbackCommands(commands).slice(0, maxCommands) : reduced;
}
function buildEffectiveTelegramCommandLocalizations(localizations) {
const effective = /* @__PURE__ */ new Map();
for (const [rawLanguageCode, rawDescription] of Object.entries(localizations ?? {})) {
const languageCode = normalizeTelegramMenuLanguageCode(rawLanguageCode);
const description = normalizeOptionalString(rawDescription);
if (languageCode && description && !effective.has(languageCode)) effective.set(languageCode, description);
}
return [...effective.entries()].toSorted(([a], [b]) => a.localeCompare(b));
}
function readLocalizedDescription(localizations, languageCode) {
return localizations.find(([effectiveLanguageCode]) => effectiveLanguageCode === languageCode)?.[1];
}
function toTelegramBotCommands(commands) {
return commands.map((command) => ({
command: command.command,
description: command.description
}));
}
function buildLocalizedCommandVariants(commands) {
const locales = /* @__PURE__ */ new Set();
const unsupportedLanguageCodes = /* @__PURE__ */ new Set();
const commandsWithLocalizations = commands.map((command) => ({
command,
localizations: buildEffectiveTelegramCommandLocalizations(command.descriptionLocalizations)
}));
for (const { command, localizations } of commandsWithLocalizations) {
for (const [languageCode] of localizations) locales.add(languageCode);
for (const [rawLanguageCode, rawDescription] of Object.entries(command.descriptionLocalizations ?? {})) if (!normalizeTelegramMenuLanguageCode(rawLanguageCode) && normalizeOptionalString(rawDescription)) unsupportedLanguageCodes.add(rawLanguageCode);
}
return {
variants: [...locales].toSorted().map((languageCode) => {
return {
languageCode,
commands: buildCappedTelegramMenuCommands({ allCommands: commandsWithLocalizations.map(({ command, localizations }) => Object.assign({}, command, { description: readLocalizedDescription(localizations, languageCode) ?? command.description })) }).commandsToRegister
};
}),
unsupportedLanguageCodes: [...unsupportedLanguageCodes].toSorted()
};
}
function formatTelegramCommandScopeOperation(operation, scope, languageCode) {
const base = scope.label === "default" ? operation : `${operation}(${scope.label})`;
return languageCode ? `${base}(${languageCode})` : base;
}
function buildTelegramCommandScopeOptions(scope, languageCode) {
return scope.options || languageCode ? {
...scope.options,
...languageCode ? { language_code: languageCode } : {}
} : void 0;
}
async function clearTelegramMenuCommandsForScopes(params) {
const { bot, runtime, languageCode } = params;
let allCleared = true;
for (const scope of TELEGRAM_COMMAND_MENU_SCOPES) {
const options = buildTelegramCommandScopeOptions(scope, languageCode);
const operation = typeof bot.api.deleteMyCommands === "function" ? "deleteMyCommands" : "setMyCommands";
const cleared = await withTelegramApiErrorLogging({
operation: formatTelegramCommandScopeOperation(operation, scope, languageCode),
runtime,
fn: () => {
if (typeof bot.api.deleteMyCommands === "function") return options ? bot.api.deleteMyCommands(options) : bot.api.deleteMyCommands();
return options ? bot.api.setMyCommands([], options) : bot.api.setMyCommands([]);
}
}).then(() => true).catch(() => false);
allCleared &&= cleared;
}
return allCleared;
}
async function setTelegramMenuCommandsForScopes(params) {
const { bot, runtime, commands, languageCode, shouldLog } = params;
const botCommands = toTelegramBotCommands(commands);
for (const scope of TELEGRAM_COMMAND_MENU_SCOPES) await withTelegramApiErrorLogging({
operation: formatTelegramCommandScopeOperation("setMyCommands", scope, languageCode),
runtime,
shouldLog,
fn: () => {
const opts = buildTelegramCommandScopeOptions(scope, languageCode);
return opts ? bot.api.setMyCommands(botCommands, opts) : bot.api.setMyCommands(botCommands);
}
});
}
function syncTelegramMenuCommands(params) {
const { bot, runtime, commandsToRegister } = params;
const owner = resolveTelegramMenuRemoteOwner(params);
const sync = async () => {
const currentHash = hashCommandList(commandsToRegister);
if (readTelegramMenuCommandHash(owner.hashKey) === currentHash) {
logVerbose("telegram: command menu unchanged; skipping sync");
return;
}
const processLocales = getProcessKnownTelegramMenuLocales(owner.queueKey);
const ledgerRead = owner.botId ? await readTelegramMenuLocaleLedger({
botId: owner.botId,
runtime
}) : null;
if (owner.botId && !ledgerRead) return;
const trackedLocales = /* @__PURE__ */ new Set([...processLocales, ...ledgerRead?.value?.languageCodes ?? []]);
const neutralCleared = await clearTelegramMenuCommandsForScopes({
bot,
runtime
});
const unclearedLocales = /* @__PURE__ */ new Set();
for (const languageCode of [...trackedLocales].toSorted()) if (!await clearTelegramMenuCommandsForScopes({
bot,
runtime,
languageCode
})) unclearedLocales.add(languageCode);
processLocales.clear();
for (const languageCode of unclearedLocales) processLocales.add(languageCode);
const persistLocales = async (desiredLocales) => {
const knownLocales = [.../* @__PURE__ */ new Set([...unclearedLocales, ...desiredLocales])].toSorted();
processLocales.clear();
for (const languageCode of knownLocales) processLocales.add(languageCode);
if (!owner.botId) return true;
if (!ledgerRead) return false;
try {
await persistTelegramMenuLocaleLedger({
botId: owner.botId,
read: ledgerRead,
languageCodes: knownLocales
});
return true;
} catch (error) {
runtime.error?.(`Telegram command menu locale ledger write failed for bot ${owner.botId}: ${String(error)}`);
return false;
}
};
if (commandsToRegister.length === 0) {
const ledgerComplete = await persistLocales([]);
if (neutralCleared && unclearedLocales.size === 0 && ledgerComplete) writeTelegramMenuCommandHash(owner.hashKey, currentHash);
else runtime.log?.("telegram: command menu cleanup incomplete; skipping success hash cache write");
return;
}
let retryCommands = commandsToRegister;
let acceptedCommands = null;
const initialCommandCount = commandsToRegister.length;
while (retryCommands.length > 0) try {
await setTelegramMenuCommandsForScopes({
bot,
runtime,
commands: retryCommands,
shouldLog: (err) => !isBotCommandsTooMuchError(err)
});
if (retryCommands.length < initialCommandCount) runtime.log?.(formatTelegramCommandRetrySuccessLog({
initialCount: initialCommandCount,
acceptedCount: retryCommands.length
}));
acceptedCommands = retryCommands;
break;
} catch (err) {
if (!isBotCommandsTooMuchError(err)) throw err;
const nextCount = Math.floor(retryCommands.length * TELEGRAM_COMMAND_RETRY_RATIO);
const reducedCount = nextCount < retryCommands.length ? nextCount : retryCommands.length - 1;
const nextCommands = reduceTelegramMenuCommands(commandsToRegister, reducedCount);
if (reducedCount <= 0 || nextCommands.length === 0) {
runtime.error?.("Telegram rejected native command registration (BOT_COMMANDS_TOO_MUCH); leaving menu empty. Reduce commands or disable channels.telegram.commands.native.");
return;
}
runtime.log?.(`Telegram rejected ${retryCommands.length} commands (BOT_COMMANDS_TOO_MUCH); retrying with ${nextCommands.length}.`);
retryCommands = nextCommands;
}
if (!acceptedCommands) return;
const { variants, unsupportedLanguageCodes } = buildLocalizedCommandVariants(acceptedCommands);
if (unsupportedLanguageCodes.length > 0) runtime.log?.(`Telegram command menu ignored unsupported description localization codes: ${unsupportedLanguageCodes.join(", ")}.`);
if (!await persistLocales(variants.map((variant) => variant.languageCode))) {
runtime.log?.("telegram: localized command menu skipped because locale intent was not durably recorded");
return;
}
for (const variant of variants) await setTelegramMenuCommandsForScopes({
bot,
runtime,
commands: variant.commands,
languageCode: variant.languageCode
});
if (neutralCleared && unclearedLocales.size === 0) writeTelegramMenuCommandHash(owner.hashKey, currentHash);
else runtime.log?.("telegram: command menu cleanup incomplete; skipping success hash cache write");
};
enqueueTelegramMenuSync({
ownerKey: owner.queueKey,
sync,
onError: (error) => {
runtime.error?.(`Telegram command sync failed: ${String(error)}`);
}
});
}
//#endregion
export { buildPluginTelegramMenuCommands as n, syncTelegramMenuCommands as r, buildCappedTelegramMenuCommands as t };