mirror of https://github.com/openclaw/openclaw.git
feat(telegram): add topic-edit action
This commit is contained in:
parent
0c9428a865
commit
a516141bda
|
|
@ -115,6 +115,9 @@ export const telegramMessageActions: ChannelMessageActionAdapter = {
|
|||
if (isEnabled("createForumTopic")) {
|
||||
actions.add("topic-create");
|
||||
}
|
||||
if (isEnabled("editForumTopic")) {
|
||||
actions.add("topic-edit");
|
||||
}
|
||||
return Array.from(actions);
|
||||
},
|
||||
supportsButtons: ({ cfg }) => {
|
||||
|
|
@ -290,6 +293,30 @@ export const telegramMessageActions: ChannelMessageActionAdapter = {
|
|||
);
|
||||
}
|
||||
|
||||
if (action === "topic-edit") {
|
||||
const chatId = readTelegramChatIdParam(params);
|
||||
const messageThreadId =
|
||||
readNumberParam(params, "messageThreadId", { integer: true }) ??
|
||||
readNumberParam(params, "threadId", { integer: true });
|
||||
if (typeof messageThreadId !== "number") {
|
||||
throw new Error("messageThreadId or threadId is required.");
|
||||
}
|
||||
const name = readStringParam(params, "name");
|
||||
const iconCustomEmojiId = readStringParam(params, "iconCustomEmojiId");
|
||||
return await handleTelegramAction(
|
||||
{
|
||||
action: "editForumTopic",
|
||||
chatId,
|
||||
messageThreadId,
|
||||
name: name ?? undefined,
|
||||
iconCustomEmojiId: iconCustomEmojiId ?? undefined,
|
||||
accountId: accountId ?? undefined,
|
||||
},
|
||||
cfg,
|
||||
{ mediaLocalRoots },
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(`Action ${action} is not supported for provider ${providerId}.`);
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ const { botApi, botCtorSpy, loadConfig, loadWebMedia, maybePersistResolvedTelegr
|
|||
const {
|
||||
buildInlineKeyboard,
|
||||
createForumTopicTelegram,
|
||||
editForumTopicTelegram,
|
||||
editMessageTelegram,
|
||||
pinMessageTelegram,
|
||||
reactMessageTelegram,
|
||||
|
|
@ -257,6 +258,42 @@ describe("sendMessageTelegram", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("edits a Telegram forum topic name and icon via the shared helper", async () => {
|
||||
loadConfig.mockReturnValue({
|
||||
channels: {
|
||||
telegram: {
|
||||
botToken: "tok",
|
||||
},
|
||||
},
|
||||
});
|
||||
botApi.editForumTopic.mockResolvedValue(true);
|
||||
|
||||
await editForumTopicTelegram("-1001234567890", 271, {
|
||||
accountId: "default",
|
||||
name: "Codex Thread",
|
||||
iconCustomEmojiId: "emoji-123",
|
||||
});
|
||||
|
||||
expect(botApi.editForumTopic).toHaveBeenCalledWith("-1001234567890", 271, {
|
||||
name: "Codex Thread",
|
||||
icon_custom_emoji_id: "emoji-123",
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects empty topic edits", async () => {
|
||||
await expect(
|
||||
editForumTopicTelegram("-1001234567890", 271, {
|
||||
accountId: "default",
|
||||
}),
|
||||
).rejects.toThrow("Telegram forum topic update requires a name or iconCustomEmojiId");
|
||||
await expect(
|
||||
editForumTopicTelegram("-1001234567890", 271, {
|
||||
accountId: "default",
|
||||
iconCustomEmojiId: " ",
|
||||
}),
|
||||
).rejects.toThrow("Telegram forum topic icon custom emoji ID is required");
|
||||
});
|
||||
|
||||
it("applies timeoutSeconds config precedence", async () => {
|
||||
const cases = [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1128,19 +1128,39 @@ export async function unpinMessageTelegram(
|
|||
};
|
||||
}
|
||||
|
||||
export async function renameForumTopicTelegram(
|
||||
type TelegramEditForumTopicOpts = TelegramDeleteOpts & {
|
||||
name?: string;
|
||||
iconCustomEmojiId?: string;
|
||||
};
|
||||
|
||||
export async function editForumTopicTelegram(
|
||||
chatIdInput: string | number,
|
||||
messageThreadIdInput: string | number,
|
||||
name: string,
|
||||
opts: TelegramDeleteOpts = {},
|
||||
): Promise<{ ok: true; chatId: string; messageThreadId: number; name: string }> {
|
||||
const trimmedName = name.trim();
|
||||
if (!trimmedName) {
|
||||
opts: TelegramEditForumTopicOpts = {},
|
||||
): Promise<{
|
||||
ok: true;
|
||||
chatId: string;
|
||||
messageThreadId: number;
|
||||
name?: string;
|
||||
iconCustomEmojiId?: string;
|
||||
}> {
|
||||
const nameProvided = opts.name !== undefined;
|
||||
const trimmedName = opts.name?.trim();
|
||||
if (nameProvided && !trimmedName) {
|
||||
throw new Error("Telegram forum topic name is required");
|
||||
}
|
||||
if (trimmedName.length > 128) {
|
||||
if (trimmedName && trimmedName.length > 128) {
|
||||
throw new Error("Telegram forum topic name must be 128 characters or fewer");
|
||||
}
|
||||
const iconProvided = opts.iconCustomEmojiId !== undefined;
|
||||
const trimmedIconCustomEmojiId = opts.iconCustomEmojiId?.trim();
|
||||
if (iconProvided && !trimmedIconCustomEmojiId) {
|
||||
throw new Error("Telegram forum topic icon custom emoji ID is required");
|
||||
}
|
||||
if (!trimmedName && !trimmedIconCustomEmojiId) {
|
||||
throw new Error("Telegram forum topic update requires a name or iconCustomEmojiId");
|
||||
}
|
||||
|
||||
const { cfg, account, api } = resolveTelegramApiContext(opts);
|
||||
const rawTarget = String(chatIdInput);
|
||||
const chatId = await resolveAndPersistChatId({
|
||||
|
|
@ -1157,16 +1177,39 @@ export async function renameForumTopicTelegram(
|
|||
retry: opts.retry,
|
||||
verbose: opts.verbose,
|
||||
});
|
||||
const payload = {
|
||||
...(trimmedName ? { name: trimmedName } : {}),
|
||||
...(trimmedIconCustomEmojiId ? { icon_custom_emoji_id: trimmedIconCustomEmojiId } : {}),
|
||||
};
|
||||
await requestWithDiag(
|
||||
() => api.editForumTopic(chatId, messageThreadId, { name: trimmedName }),
|
||||
() => api.editForumTopic(chatId, messageThreadId, payload),
|
||||
"editForumTopic",
|
||||
);
|
||||
logVerbose(`[telegram] Renamed forum topic ${messageThreadId} in chat ${chatId}`);
|
||||
logVerbose(`[telegram] Edited forum topic ${messageThreadId} in chat ${chatId}`);
|
||||
return {
|
||||
ok: true,
|
||||
chatId,
|
||||
messageThreadId,
|
||||
name: trimmedName,
|
||||
...(trimmedName ? { name: trimmedName } : {}),
|
||||
...(trimmedIconCustomEmojiId ? { iconCustomEmojiId: trimmedIconCustomEmojiId } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export async function renameForumTopicTelegram(
|
||||
chatIdInput: string | number,
|
||||
messageThreadIdInput: string | number,
|
||||
name: string,
|
||||
opts: TelegramDeleteOpts = {},
|
||||
): Promise<{ ok: true; chatId: string; messageThreadId: number; name: string }> {
|
||||
const result = await editForumTopicTelegram(chatIdInput, messageThreadIdInput, {
|
||||
...opts,
|
||||
name,
|
||||
});
|
||||
return {
|
||||
ok: true,
|
||||
chatId: result.chatId,
|
||||
messageThreadId: result.messageThreadId,
|
||||
name: result.name ?? name.trim(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,12 @@ const editMessageTelegram = vi.fn(async () => ({
|
|||
messageId: "456",
|
||||
chatId: "123",
|
||||
}));
|
||||
const editForumTopicTelegram = vi.fn(async () => ({
|
||||
ok: true,
|
||||
chatId: "123",
|
||||
messageThreadId: 42,
|
||||
name: "Renamed",
|
||||
}));
|
||||
const createForumTopicTelegram = vi.fn(async () => ({
|
||||
topicId: 99,
|
||||
name: "Topic",
|
||||
|
|
@ -42,6 +48,8 @@ vi.mock("../../../extensions/telegram/src/send.js", () => ({
|
|||
deleteMessageTelegram(...args),
|
||||
editMessageTelegram: (...args: Parameters<typeof editMessageTelegram>) =>
|
||||
editMessageTelegram(...args),
|
||||
editForumTopicTelegram: (...args: Parameters<typeof editForumTopicTelegram>) =>
|
||||
editForumTopicTelegram(...args),
|
||||
createForumTopicTelegram: (...args: Parameters<typeof createForumTopicTelegram>) =>
|
||||
createForumTopicTelegram(...args),
|
||||
}));
|
||||
|
|
@ -105,6 +113,7 @@ describe("handleTelegramAction", () => {
|
|||
sendStickerTelegram.mockClear();
|
||||
deleteMessageTelegram.mockClear();
|
||||
editMessageTelegram.mockClear();
|
||||
editForumTopicTelegram.mockClear();
|
||||
createForumTopicTelegram.mockClear();
|
||||
process.env.TELEGRAM_BOT_TOKEN = "tok";
|
||||
});
|
||||
|
|
@ -457,6 +466,14 @@ describe("handleTelegramAction", () => {
|
|||
readCallOpts: (calls: unknown[][], argIndex: number) => Record<string, unknown>,
|
||||
) => readCallOpts(createForumTopicTelegram.mock.calls as unknown[][], 2),
|
||||
},
|
||||
{
|
||||
name: "editForumTopic",
|
||||
params: { action: "editForumTopic", chatId: "123", messageThreadId: 42, name: "New" },
|
||||
cfg: telegramConfig({ actions: { editForumTopic: true } }),
|
||||
assertCall: (
|
||||
readCallOpts: (calls: unknown[][], argIndex: number) => Record<string, unknown>,
|
||||
) => readCallOpts(editForumTopicTelegram.mock.calls as unknown[][], 2),
|
||||
},
|
||||
])("forwards resolved cfg for $name action", async ({ params, cfg, assertCall }) => {
|
||||
const readCallOpts = (calls: unknown[][], argIndex: number): Record<string, unknown> => {
|
||||
const args = calls[0];
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { resolveTelegramReactionLevel } from "../../../extensions/telegram/src/r
|
|||
import {
|
||||
createForumTopicTelegram,
|
||||
deleteMessageTelegram,
|
||||
editForumTopicTelegram,
|
||||
editMessageTelegram,
|
||||
reactMessageTelegram,
|
||||
sendMessageTelegram,
|
||||
|
|
@ -478,5 +479,36 @@ export async function handleTelegramAction(
|
|||
});
|
||||
}
|
||||
|
||||
if (action === "editForumTopic") {
|
||||
if (!isActionEnabled("editForumTopic")) {
|
||||
throw new Error("Telegram editForumTopic is disabled.");
|
||||
}
|
||||
const chatId = readStringOrNumberParam(params, "chatId", {
|
||||
required: true,
|
||||
});
|
||||
const messageThreadId =
|
||||
readNumberParam(params, "messageThreadId", { integer: true }) ??
|
||||
readNumberParam(params, "threadId", { integer: true });
|
||||
if (typeof messageThreadId !== "number") {
|
||||
throw new Error("messageThreadId or threadId is required.");
|
||||
}
|
||||
const name = readStringParam(params, "name");
|
||||
const iconCustomEmojiId = readStringParam(params, "iconCustomEmojiId");
|
||||
const token = resolveTelegramToken(cfg, { accountId }).token;
|
||||
if (!token) {
|
||||
throw new Error(
|
||||
"Telegram bot token missing. Set TELEGRAM_BOT_TOKEN or channels.telegram.botToken.",
|
||||
);
|
||||
}
|
||||
const result = await editForumTopicTelegram(chatId ?? "", messageThreadId, {
|
||||
cfg,
|
||||
token,
|
||||
accountId: accountId ?? undefined,
|
||||
name: name ?? undefined,
|
||||
iconCustomEmojiId: iconCustomEmojiId ?? undefined,
|
||||
});
|
||||
return jsonResult(result);
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported Telegram action: ${action}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -540,6 +540,21 @@ describe("telegramMessageActions", () => {
|
|||
expect(actions).toContain("poll");
|
||||
});
|
||||
|
||||
it("lists topic-edit when telegram topic edits are enabled", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
telegram: {
|
||||
botToken: "tok",
|
||||
actions: { editForumTopic: true },
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const actions = telegramMessageActions.listActions?.({ cfg }) ?? [];
|
||||
|
||||
expect(actions).toContain("topic-edit");
|
||||
});
|
||||
|
||||
it("omits poll when sendMessage is disabled", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
|
|
@ -793,6 +808,24 @@ describe("telegramMessageActions", () => {
|
|||
accountId: undefined,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "topic-edit maps to editForumTopic",
|
||||
action: "topic-edit" as const,
|
||||
params: {
|
||||
to: "telegram:group:-1001234567890:topic:271",
|
||||
threadId: 271,
|
||||
name: "Build Updates",
|
||||
iconCustomEmojiId: "emoji-123",
|
||||
},
|
||||
expectedPayload: {
|
||||
action: "editForumTopic",
|
||||
chatId: "telegram:group:-1001234567890:topic:271",
|
||||
messageThreadId: 271,
|
||||
name: "Build Updates",
|
||||
iconCustomEmojiId: "emoji-123",
|
||||
accountId: undefined,
|
||||
},
|
||||
},
|
||||
] as const;
|
||||
|
||||
for (const testCase of cases) {
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ export const CHANNEL_MESSAGE_ACTION_NAMES = [
|
|||
"category-edit",
|
||||
"category-delete",
|
||||
"topic-create",
|
||||
"topic-edit",
|
||||
"voice-status",
|
||||
"event-list",
|
||||
"event-create",
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ export type TelegramActionConfig = {
|
|||
sticker?: boolean;
|
||||
/** Enable forum topic creation. */
|
||||
createForumTopic?: boolean;
|
||||
/** Enable forum topic editing (rename / change icon). */
|
||||
editForumTopic?: boolean;
|
||||
};
|
||||
|
||||
export type TelegramNetworkConfig = {
|
||||
|
|
|
|||
|
|
@ -258,6 +258,7 @@ export const TelegramAccountSchemaBase = z
|
|||
editMessage: z.boolean().optional(),
|
||||
sticker: z.boolean().optional(),
|
||||
createForumTopic: z.boolean().optional(),
|
||||
editForumTopic: z.boolean().optional(),
|
||||
})
|
||||
.strict()
|
||||
.optional(),
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ export const MESSAGE_ACTION_TARGET_MODE: Record<ChannelMessageActionName, Messag
|
|||
"category-edit": "none",
|
||||
"category-delete": "none",
|
||||
"topic-create": "to",
|
||||
"topic-edit": "to",
|
||||
"voice-status": "none",
|
||||
"event-list": "none",
|
||||
"event-create": "none",
|
||||
|
|
|
|||
Loading…
Reference in New Issue