feat(telegram): add deleteMessage action
Add ability to delete messages in Telegram chats via the message tool. Changes: - Add deleteMessageTelegram function in send.ts - Add deleteMessage action handler in telegram-actions.ts - Add delete action support in telegram message plugin adapter - Add deleteMessage to TelegramActionConfig type - Update message tool description to mention delete action Usage: - Via message tool: action="delete", chatId, messageId - Can be disabled via channels.telegram.actions.deleteMessage=false Limitations (Telegram API): - Bot can delete its own messages in any chat - Bot can delete others' messages only if admin with "Delete Messages" - Messages older than 48h in groups may fail to delete
This commit is contained in:
committed by
Peter Steinberger
parent
9b7df414e6
commit
83a25d26fc
@@ -148,7 +148,7 @@ export function createMessageTool(options?: MessageToolOptions): AnyAgentTool {
|
|||||||
label: "Message",
|
label: "Message",
|
||||||
name: "message",
|
name: "message",
|
||||||
description:
|
description:
|
||||||
"Send messages and channel actions (polls, reactions, pins, threads, etc.) via configured channel plugins.",
|
"Send, delete, and manage messages via channel plugins. Supports actions: send, delete, react, poll, pin, threads, and more.",
|
||||||
parameters: schema,
|
parameters: schema,
|
||||||
execute: async (_toolCallId, args) => {
|
execute: async (_toolCallId, args) => {
|
||||||
const params = args as Record<string, unknown>;
|
const params = args as Record<string, unknown>;
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||||
import { resolveChannelCapabilities } from "../../config/channel-capabilities.js";
|
import { resolveChannelCapabilities } from "../../config/channel-capabilities.js";
|
||||||
import type { ClawdbotConfig } from "../../config/config.js";
|
import type { ClawdbotConfig } from "../../config/config.js";
|
||||||
import { reactMessageTelegram, sendMessageTelegram } from "../../telegram/send.js";
|
import {
|
||||||
|
deleteMessageTelegram,
|
||||||
|
reactMessageTelegram,
|
||||||
|
sendMessageTelegram,
|
||||||
|
} from "../../telegram/send.js";
|
||||||
import { resolveTelegramToken } from "../../telegram/token.js";
|
import { resolveTelegramToken } from "../../telegram/token.js";
|
||||||
import {
|
import {
|
||||||
createActionGate,
|
createActionGate,
|
||||||
@@ -149,5 +153,29 @@ export async function handleTelegramAction(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action === "deleteMessage") {
|
||||||
|
if (!isActionEnabled("deleteMessage")) {
|
||||||
|
throw new Error("Telegram deleteMessage is disabled.");
|
||||||
|
}
|
||||||
|
const chatId = readStringOrNumberParam(params, "chatId", {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
const messageId = readNumberParam(params, "messageId", {
|
||||||
|
required: true,
|
||||||
|
integer: true,
|
||||||
|
});
|
||||||
|
const token = resolveTelegramToken(cfg, { accountId }).token;
|
||||||
|
if (!token) {
|
||||||
|
throw new Error(
|
||||||
|
"Telegram bot token missing. Set TELEGRAM_BOT_TOKEN or channels.telegram.botToken.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await deleteMessageTelegram(chatId ?? "", messageId ?? 0, {
|
||||||
|
token,
|
||||||
|
accountId: accountId ?? undefined,
|
||||||
|
});
|
||||||
|
return jsonResult({ ok: true, deleted: true });
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error(`Unsupported Telegram action: ${action}`);
|
throw new Error(`Unsupported Telegram action: ${action}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export const telegramMessageActions: ChannelMessageActionAdapter = {
|
|||||||
const gate = createActionGate(cfg.channels?.telegram?.actions);
|
const gate = createActionGate(cfg.channels?.telegram?.actions);
|
||||||
const actions = new Set<ChannelMessageActionName>(["send"]);
|
const actions = new Set<ChannelMessageActionName>(["send"]);
|
||||||
if (gate("reactions")) actions.add("react");
|
if (gate("reactions")) actions.add("react");
|
||||||
|
if (gate("deleteMessage")) actions.add("delete");
|
||||||
return Array.from(actions);
|
return Array.from(actions);
|
||||||
},
|
},
|
||||||
supportsButtons: ({ cfg }) => hasTelegramInlineButtons(cfg),
|
supportsButtons: ({ cfg }) => hasTelegramInlineButtons(cfg),
|
||||||
@@ -92,6 +93,22 @@ export const telegramMessageActions: ChannelMessageActionAdapter = {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action === "delete") {
|
||||||
|
const chatId = readStringParam(params, "chatId", { required: true });
|
||||||
|
const messageId = readStringParam(params, "messageId", {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
return await handleTelegramAction(
|
||||||
|
{
|
||||||
|
action: "deleteMessage",
|
||||||
|
chatId,
|
||||||
|
messageId: Number(messageId),
|
||||||
|
accountId: accountId ?? undefined,
|
||||||
|
},
|
||||||
|
cfg,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error(`Action ${action} is not supported for provider ${providerId}.`);
|
throw new Error(`Action ${action} is not supported for provider ${providerId}.`);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type { DmConfig, ProviderCommandsConfig } from "./types.messages.js";
|
|||||||
export type TelegramActionConfig = {
|
export type TelegramActionConfig = {
|
||||||
reactions?: boolean;
|
reactions?: boolean;
|
||||||
sendMessage?: boolean;
|
sendMessage?: boolean;
|
||||||
|
deleteMessage?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TelegramAccountConfig = {
|
export type TelegramAccountConfig = {
|
||||||
|
|||||||
@@ -396,6 +396,44 @@ export async function reactMessageTelegram(
|
|||||||
return { ok: true };
|
return { ok: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TelegramDeleteOpts = {
|
||||||
|
token?: string;
|
||||||
|
accountId?: string;
|
||||||
|
verbose?: boolean;
|
||||||
|
api?: Bot["api"];
|
||||||
|
retry?: RetryConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function deleteMessageTelegram(
|
||||||
|
chatIdInput: string | number,
|
||||||
|
messageIdInput: string | number,
|
||||||
|
opts: TelegramDeleteOpts = {},
|
||||||
|
): Promise<{ ok: true }> {
|
||||||
|
const cfg = loadConfig();
|
||||||
|
const account = resolveTelegramAccount({
|
||||||
|
cfg,
|
||||||
|
accountId: opts.accountId,
|
||||||
|
});
|
||||||
|
const token = resolveToken(opts.token, account);
|
||||||
|
const chatId = normalizeChatId(String(chatIdInput));
|
||||||
|
const messageId = normalizeMessageId(messageIdInput);
|
||||||
|
const fetchImpl = resolveTelegramFetch();
|
||||||
|
const client: ApiClientOptions | undefined = fetchImpl
|
||||||
|
? { fetch: fetchImpl as unknown as ApiClientOptions["fetch"] }
|
||||||
|
: undefined;
|
||||||
|
const api = opts.api ?? new Bot(token, client ? { client } : undefined).api;
|
||||||
|
const request = createTelegramRetryRunner({
|
||||||
|
retry: opts.retry,
|
||||||
|
configRetry: account.config.retry,
|
||||||
|
verbose: opts.verbose,
|
||||||
|
});
|
||||||
|
await request(() => api.deleteMessage(chatId, messageId), "deleteMessage");
|
||||||
|
logVerbose(
|
||||||
|
`[telegram] Deleted message ${messageId} from chat ${chatId}`,
|
||||||
|
);
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
function inferFilename(kind: ReturnType<typeof mediaKindFromMime>) {
|
function inferFilename(kind: ReturnType<typeof mediaKindFromMime>) {
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case "image":
|
case "image":
|
||||||
|
|||||||
Reference in New Issue
Block a user