mirror of
https://github.com/kuhyx/signal-bot.git
synced 2026-11-10 20:27:17 +01:00
A compliment gets a reaction drawn from a small warm set (❤️ 🥰 😊 👍 🎉 🙏 😸) put on the compliment itself, and nothing in the group -- the one answer that adds no line. Same cooldown as every other command. The transport learns reactions: send_reaction on the Protocol, POST /v1/reactions/<number> on the REST adapter with the four fields api.SendReactionRequest requires (204 back), a recorded list on the fake. CommandContext.react() names the target the way a quote does, and falls back to the emoji as text for an envelope with no timestamp, so a shape this bot has never seen is visible rather than swallowed. The fake container gains the endpoint, strict about the schema, and records reactions in order with the sends; a scenario can now expect {kind: reaction, quotes: true}. First two-word trigger: fold() keeps the inner space and the registry matches whole text first, so "!good bot" resolves and "!good" does not; one-word spellings cover a double space. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
70 lines
2.6 KiB
Python
70 lines
2.6 KiB
Python
# Copyright (c) 2026 Krzysztof Rudnicki. MIT licence.
|
|
"""!good bot: the bot says thank you the one way that adds no noise.
|
|
|
|
A reaction rather than a reply. "good bot" is a compliment, and answering a
|
|
compliment with a line of text is the kind of thing that gets a bot muted;
|
|
an emoji on the compliment itself is seen by the whole group without taking
|
|
up a line. Which emoji is a draw from a small warm set, so the second
|
|
thank-you of the evening does not look like a canned copy of the first.
|
|
|
|
Rate-limited like every other command: a heart costs a POST to the
|
|
container, and somebody typing it in a loop is the same problem as somebody
|
|
typing !cat in a loop.
|
|
|
|
The trigger is two words, which no other command has. That works because
|
|
``folding.fold`` keeps inner spaces and the registry matches the whole text
|
|
before it looks at the first word -- so "!good bot" resolves, "!good" alone
|
|
resolves to nothing, and "!good bot" typed with two spaces resolves to
|
|
nothing either. The one-word spellings in aliases.json are for that last
|
|
case.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import secrets
|
|
from typing import TYPE_CHECKING, Final
|
|
|
|
from signal_bot.animals import load_aliases
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Sequence
|
|
|
|
from signal_bot.context import CommandContext
|
|
from signal_bot.decorators import Decorator
|
|
from signal_bot.registry import CommandRegistry, Handler
|
|
|
|
COMMAND_NAME: Final = "good_bot"
|
|
DESCRIPTION: Final = "bot dziękuje za komplement reakcją, bez gadania"
|
|
|
|
# Warm and unambiguous, nothing that reads as sarcasm in a group chat.
|
|
EMOJI: Final = ("❤️", "🥰", "😊", "👍", "🎉", "🙏", "😸")
|
|
|
|
|
|
def pick(choices: Sequence[str] = EMOJI) -> str:
|
|
"""One emoji, uniformly at random.
|
|
|
|
secrets rather than random for the reason animals.paged_url gives: the
|
|
choice is not security-sensitive, but ruff's ALL rule set flags `random`
|
|
and this repository forbids the noqa that would silence it.
|
|
"""
|
|
return choices[secrets.randbelow(len(choices))]
|
|
|
|
|
|
async def good_bot_handler(ctx: CommandContext) -> None:
|
|
"""Thank the asker on their own message; say nothing in the group."""
|
|
await ctx.react(pick())
|
|
|
|
|
|
def register_good_bot_command(
|
|
registry: CommandRegistry,
|
|
*,
|
|
decorate: Decorator | None = None,
|
|
aliases: dict[str, list[str]] | None = None,
|
|
) -> None:
|
|
"""Register !good bot under every spelling aliases.json gives it."""
|
|
table = load_aliases() if aliases is None else aliases
|
|
handler: Handler = good_bot_handler
|
|
if decorate is not None:
|
|
handler = decorate(handler)
|
|
registry.register(COMMAND_NAME, table[COMMAND_NAME], handler, DESCRIPTION)
|