diff --git a/src/bot/index.ts b/src/bot/index.ts index 2aee9f05..4b111092 100644 --- a/src/bot/index.ts +++ b/src/bot/index.ts @@ -13,6 +13,7 @@ import { safeBackgroundTask } from "../utils/safe-background-task.js"; import { withTelegramRateLimitRetry } from "../utils/telegram-rate-limit-retry.js"; import { registerCallbackRouter } from "./callbacks/callback-router.js"; import { authMiddleware } from "./middleware/auth.js"; +import { reactionMiddleware } from "./middleware/reaction.js"; import { interactionGuardMiddleware } from "./middleware/interaction-guard.js"; import { ensureCommandsInitialized, @@ -115,6 +116,7 @@ export function createBot(): Bot { }); bot.use(authMiddleware); + bot.use(reactionMiddleware); bot.use(ensureCommandsInitialized); bot.use(interactionGuardMiddleware); diff --git a/src/bot/middleware/reaction.ts b/src/bot/middleware/reaction.ts new file mode 100644 index 00000000..348649bf --- /dev/null +++ b/src/bot/middleware/reaction.ts @@ -0,0 +1,18 @@ +import { Context, NextFunction } from "grammy"; +import { pickReactionEmoji } from "../reactions.js"; +import { logger } from "../../utils/logger.js"; + +export async function reactionMiddleware(ctx: Context, next: NextFunction): Promise { + if (ctx.message && !ctx.message.from?.is_bot) { + const emoji = pickReactionEmoji(); + ctx.api + .setMessageReaction(ctx.chat!.id, ctx.message.message_id, [ + { type: "emoji", emoji }, + ]) + .catch((err) => { + logger.debug(`[Reaction] Failed to react to message ${ctx.message!.message_id}: ${err}`); + }); + } + + await next(); +} diff --git a/src/bot/reactions.ts b/src/bot/reactions.ts new file mode 100644 index 00000000..908bea1b --- /dev/null +++ b/src/bot/reactions.ts @@ -0,0 +1,5 @@ +const ACK_REACTIONS = ["👍", "âĪ", "ðŸ”Ĩ", "🎉", "😁", "ðŸĪĐ", "👏", "⚡", "🙏", "👌", "ðŸ’Ŋ", "🏆", "âĪ‍ðŸ”Ĩ", "🕊", "ðŸĪ“", "👀", "😇", "ðŸĪ", "✍", "ðŸĪ—", "ðŸŦĄ", "ðŸĪŠ", "🆒", "ðŸĶ„", "😎", "ðŸ‘ū"] as const; + +export function pickReactionEmoji(): (typeof ACK_REACTIONS)[number] { + return ACK_REACTIONS[Math.floor(Math.random() * ACK_REACTIONS.length)]; +} diff --git a/tests/bot/reactions.test.ts b/tests/bot/reactions.test.ts new file mode 100644 index 00000000..be9bc7bb --- /dev/null +++ b/tests/bot/reactions.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, it, vi } from "vitest"; +import { pickReactionEmoji } from "../../src/bot/reactions.js"; + +describe("bot/reactions", () => { + it("returns a valid emoji from the pool", () => { + const emoji = pickReactionEmoji(); + const validEmojis = ["👍", "âĪ", "ðŸ”Ĩ", "🎉", "😁", "ðŸĪĐ", "👏", "⚡", "🙏", "👌", "ðŸ’Ŋ", "🏆", "âĪ‍ðŸ”Ĩ", "🕊", "ðŸĪ“", "👀", "😇", "ðŸĪ", "✍", "ðŸĪ—", "ðŸŦĄ", "ðŸĪŠ", "🆒", "ðŸĶ„", "😎", "ðŸ‘ū"]; + expect(validEmojis).toContain(emoji); + }); + + it("returns different emojis over multiple calls", () => { + const results = new Set(Array.from({ length: 100 }, () => pickReactionEmoji())); + expect(results.size).toBeGreaterThan(1); + }); +});