signal-bot/signal_bot/popes_command.py
Krzysztof kuhy Rudnicki 9db3687752 release: 0.1.7 «Callistus I» — a version now says which pope, and when
"Wersja 0.1.7 «Callistus I»" told nobody whether Callistus was the
fifteenth pope or the hundredth, and the changelog was the one place the
ladder is actually read. pope_line() derives both facts from popes.json --
the ordinal, and the `reign` string exactly as the table spells it, so the
one reigning pope keeps his single year instead of being padded into a
range he never had.

It appears in all four places a version is named: the group announcement
(header and every block of a backlog), each section of DOCS-changelog.md,
/healthz, and the !papiez caption, which now describes the release's pope
in the same sentence it uses for every other one.

The changelog's copy is checked rather than trusted, like the heading name
already was: a test walks the raw file and compares each line against
popes.json, so a hand-edited ordinal fails here instead of in a group.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017w1HZ3cZ7neL3rFdQ6VMBA
2026-08-30 22:12:09 +02:00

65 lines
2.1 KiB
Python

# Copyright (c) 2026 Krzysztof Rudnicki. MIT licence.
"""The !papież command: a random pope, his portrait, his Latin name, his article.
The list this draws from is the same signal_bot/popes.json the version ladder
is built on, so the bot posts the popes it is named after.
"""
from __future__ import annotations
import secrets
from typing import TYPE_CHECKING, Final
from signal_bot.animals import load_aliases
from signal_bot.versioning import POPES, article_url, pope_line
if TYPE_CHECKING:
from signal_bot.decorators import Decorator
from signal_bot.portraits import PortraitCache
from signal_bot.registry import CommandContext, CommandRegistry, Handler
from signal_bot.versioning import Pope
COMMAND_NAME: Final = "pope"
DESCRIPTION: Final = "losowy papież z portretem i imieniem po łacinie"
def random_pope() -> Pope:
"""Return one pope, 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 repo forbids the noqa that would silence it.
"""
return POPES[secrets.randbelow(len(POPES))]
def caption_for(pope: Pope) -> str:
"""Return the message that rides along with the portrait."""
return f"{pope.name} — {pope.latin}\n{pope_line(pope)}\n{article_url(pope)}"
def pope_handler(cache: PortraitCache) -> Handler:
"""Build the handler that posts a random pope."""
async def handler(ctx: CommandContext) -> None:
pope = random_pope()
image = await cache.get_base64(pope)
await ctx.reply_image(image, caption_for(pope))
return handler
def register_pope_command(
registry: CommandRegistry,
cache: PortraitCache,
*,
decorate: Decorator | None = None,
aliases: dict[str, list[str]] | None = None,
) -> None:
"""Register !papież under every trigger aliases.json gives it."""
table = load_aliases() if aliases is None else aliases
handler = pope_handler(cache)
if decorate is not None:
handler = decorate(handler)
registry.register(COMMAND_NAME, table[COMMAND_NAME], handler, DESCRIPTION)