feat: unify provider reaction tools

This commit is contained in:
Peter Steinberger
2026-01-07 04:10:13 +01:00
parent 551a8d5683
commit 3afef2d504
41 changed files with 1169 additions and 82 deletions

View File

@@ -1,4 +1,4 @@
export { createTelegramBot, createTelegramWebhookCallback } from "./bot.js";
export { monitorTelegramProvider } from "./monitor.js";
export { sendMessageTelegram } from "./send.js";
export { reactMessageTelegram, sendMessageTelegram } from "./send.js";
export { startTelegramWebhook } from "./webhook.js";

View File

@@ -8,7 +8,7 @@ vi.mock("../web/media.js", () => ({
loadWebMedia,
}));
import { sendMessageTelegram } from "./send.js";
import { reactMessageTelegram, sendMessageTelegram } from "./send.js";
describe("sendMessageTelegram", () => {
beforeEach(() => {
@@ -108,3 +108,50 @@ describe("sendMessageTelegram", () => {
expect(res.messageId).toBe("9");
});
});
describe("reactMessageTelegram", () => {
it("sends emoji reactions", async () => {
const setMessageReaction = vi.fn().mockResolvedValue(undefined);
const api = { setMessageReaction } as unknown as {
setMessageReaction: typeof setMessageReaction;
};
await reactMessageTelegram("telegram:123", "456", "✅", {
token: "tok",
api,
});
expect(setMessageReaction).toHaveBeenCalledWith("123", 456, [
{ type: "emoji", emoji: "✅" },
]);
});
it("removes reactions when emoji is empty", async () => {
const setMessageReaction = vi.fn().mockResolvedValue(undefined);
const api = { setMessageReaction } as unknown as {
setMessageReaction: typeof setMessageReaction;
};
await reactMessageTelegram("123", 456, "", {
token: "tok",
api,
});
expect(setMessageReaction).toHaveBeenCalledWith("123", 456, []);
});
it("removes reactions when remove flag is set", async () => {
const setMessageReaction = vi.fn().mockResolvedValue(undefined);
const api = { setMessageReaction } as unknown as {
setMessageReaction: typeof setMessageReaction;
};
await reactMessageTelegram("123", 456, "✅", {
token: "tok",
api,
remove: true,
});
expect(setMessageReaction).toHaveBeenCalledWith("123", 456, []);
});
});

View File

@@ -18,6 +18,12 @@ type TelegramSendResult = {
chatId: string;
};
type TelegramReactionOpts = {
token?: string;
api?: Bot["api"];
remove?: boolean;
};
const PARSE_ERR_RE =
/can't parse entities|parse entities|find end of the entity/i;
@@ -57,6 +63,21 @@ function normalizeChatId(to: string): string {
return normalized;
}
function normalizeMessageId(raw: string | number): number {
if (typeof raw === "number" && Number.isFinite(raw)) {
return Math.trunc(raw);
}
if (typeof raw === "string") {
const value = raw.trim();
if (!value) {
throw new Error("Message id is required for Telegram reactions");
}
const parsed = Number.parseInt(value, 10);
if (Number.isFinite(parsed)) return parsed;
}
throw new Error("Message id is required for Telegram reactions");
}
export async function sendMessageTelegram(
to: string,
text: string,
@@ -196,6 +217,28 @@ export async function sendMessageTelegram(
return { messageId, chatId: String(res?.chat?.id ?? chatId) };
}
export async function reactMessageTelegram(
chatIdInput: string | number,
messageIdInput: string | number,
emoji: string,
opts: TelegramReactionOpts = {},
): Promise<{ ok: true }> {
const token = resolveToken(opts.token);
const chatId = normalizeChatId(String(chatIdInput));
const messageId = normalizeMessageId(messageIdInput);
const bot = opts.api ? null : new Bot(token);
const api = opts.api ?? bot?.api;
const remove = opts.remove === true;
const trimmedEmoji = emoji.trim();
const reactions =
remove || !trimmedEmoji ? [] : [{ type: "emoji", emoji: trimmedEmoji }];
if (typeof api.setMessageReaction !== "function") {
throw new Error("Telegram reactions are unavailable in this bot API.");
}
await api.setMessageReaction(chatId, messageId, reactions);
return { ok: true };
}
function inferFilename(kind: ReturnType<typeof mediaKindFromMime>) {
switch (kind) {
case "image":