feat(telegram): add topic-edit action

This commit is contained in:
Ayaan Zaidi
2026-03-16 07:49:13 +05:30
parent 0c9428a865
commit a516141bda
10 changed files with 204 additions and 10 deletions

View File

@@ -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}.`);
},
};

View File

@@ -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 = [
{

View File

@@ -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(),
};
}