feat(telegram): inline keyboard buttons (#491)
Co-authored-by: Azade <azade@hey.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import { resolveProviderCapabilities } from "../../config/provider-capabilities.js";
|
||||
import {
|
||||
reactMessageTelegram,
|
||||
sendMessageTelegram,
|
||||
@@ -15,11 +16,74 @@ import {
|
||||
readStringParam,
|
||||
} from "./common.js";
|
||||
|
||||
type TelegramButton = {
|
||||
text: string;
|
||||
callback_data: string;
|
||||
};
|
||||
|
||||
function hasInlineButtonsCapability(params: {
|
||||
cfg: ClawdbotConfig;
|
||||
accountId?: string | undefined;
|
||||
}): boolean {
|
||||
const caps =
|
||||
resolveProviderCapabilities({
|
||||
cfg: params.cfg,
|
||||
provider: "telegram",
|
||||
accountId: params.accountId,
|
||||
}) ?? [];
|
||||
return caps.some((cap) => cap.toLowerCase() === "inlinebuttons");
|
||||
}
|
||||
|
||||
export function readTelegramButtons(
|
||||
params: Record<string, unknown>,
|
||||
): TelegramButton[][] | undefined {
|
||||
const raw = params.buttons;
|
||||
if (raw == null) return undefined;
|
||||
if (!Array.isArray(raw)) {
|
||||
throw new Error("buttons must be an array of button rows");
|
||||
}
|
||||
const rows = raw.map((row, rowIndex) => {
|
||||
if (!Array.isArray(row)) {
|
||||
throw new Error(`buttons[${rowIndex}] must be an array`);
|
||||
}
|
||||
return row.map((button, buttonIndex) => {
|
||||
if (!button || typeof button !== "object") {
|
||||
throw new Error(
|
||||
`buttons[${rowIndex}][${buttonIndex}] must be an object`,
|
||||
);
|
||||
}
|
||||
const text =
|
||||
typeof (button as { text?: unknown }).text === "string"
|
||||
? (button as { text: string }).text.trim()
|
||||
: "";
|
||||
const callbackData =
|
||||
typeof (button as { callback_data?: unknown }).callback_data ===
|
||||
"string"
|
||||
? (button as { callback_data: string }).callback_data.trim()
|
||||
: "";
|
||||
if (!text || !callbackData) {
|
||||
throw new Error(
|
||||
`buttons[${rowIndex}][${buttonIndex}] requires text and callback_data`,
|
||||
);
|
||||
}
|
||||
if (callbackData.length > 64) {
|
||||
throw new Error(
|
||||
`buttons[${rowIndex}][${buttonIndex}] callback_data too long (max 64 chars)`,
|
||||
);
|
||||
}
|
||||
return { text, callback_data: callbackData };
|
||||
});
|
||||
});
|
||||
const filtered = rows.filter((row) => row.length > 0);
|
||||
return filtered.length > 0 ? filtered : undefined;
|
||||
}
|
||||
|
||||
export async function handleTelegramAction(
|
||||
params: Record<string, unknown>,
|
||||
cfg: ClawdbotConfig,
|
||||
): Promise<AgentToolResult<unknown>> {
|
||||
const action = readStringParam(params, "action", { required: true });
|
||||
const accountId = readStringParam(params, "accountId");
|
||||
const isActionEnabled = createActionGate(cfg.telegram?.actions);
|
||||
|
||||
if (action === "react") {
|
||||
@@ -36,7 +100,7 @@ export async function handleTelegramAction(
|
||||
const { emoji, remove, isEmpty } = readReactionParams(params, {
|
||||
removeErrorMessage: "Emoji is required to remove a Telegram reaction.",
|
||||
});
|
||||
const token = resolveTelegramToken(cfg).token;
|
||||
const token = resolveTelegramToken(cfg, { accountId }).token;
|
||||
if (!token) {
|
||||
throw new Error(
|
||||
"Telegram bot token missing. Set TELEGRAM_BOT_TOKEN or telegram.botToken.",
|
||||
@@ -45,6 +109,7 @@ export async function handleTelegramAction(
|
||||
await reactMessageTelegram(chatId ?? "", messageId ?? 0, emoji ?? "", {
|
||||
token,
|
||||
remove,
|
||||
accountId: accountId ?? undefined,
|
||||
});
|
||||
if (!remove && !isEmpty) {
|
||||
return jsonResult({ ok: true, added: emoji });
|
||||
@@ -59,6 +124,15 @@ export async function handleTelegramAction(
|
||||
const to = readStringParam(params, "to", { required: true });
|
||||
const content = readStringParam(params, "content", { required: true });
|
||||
const mediaUrl = readStringParam(params, "mediaUrl");
|
||||
const buttons = readTelegramButtons(params);
|
||||
if (
|
||||
buttons &&
|
||||
!hasInlineButtonsCapability({ cfg, accountId: accountId ?? undefined })
|
||||
) {
|
||||
throw new Error(
|
||||
'Telegram inline buttons requested but not enabled. Add "inlineButtons" to telegram.capabilities (or telegram.accounts.<id>.capabilities).',
|
||||
);
|
||||
}
|
||||
// Optional threading parameters for forum topics and reply chains
|
||||
const replyToMessageId = readNumberParam(params, "replyToMessageId", {
|
||||
integer: true,
|
||||
@@ -66,7 +140,7 @@ export async function handleTelegramAction(
|
||||
const messageThreadId = readNumberParam(params, "messageThreadId", {
|
||||
integer: true,
|
||||
});
|
||||
const token = resolveTelegramToken(cfg).token;
|
||||
const token = resolveTelegramToken(cfg, { accountId }).token;
|
||||
if (!token) {
|
||||
throw new Error(
|
||||
"Telegram bot token missing. Set TELEGRAM_BOT_TOKEN or telegram.botToken.",
|
||||
@@ -74,7 +148,9 @@ export async function handleTelegramAction(
|
||||
}
|
||||
const result = await sendMessageTelegram(to, content, {
|
||||
token,
|
||||
accountId: accountId ?? undefined,
|
||||
mediaUrl: mediaUrl || undefined,
|
||||
buttons,
|
||||
replyToMessageId: replyToMessageId ?? undefined,
|
||||
messageThreadId: messageThreadId ?? undefined,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user