signal-bot/signal_bot/endpoints.py
Krzysztof kuhy Rudnicki 08599e6056 feat: two commands that answer with somebody else's work
!sztuka posts a work of art, skewed 60% Polish; !wiersz posts one short
public-domain Polish poem, whole. Both follow the rule the rest of the bot
already keeps: the repository commits identifiers, and the words arrive over
the wire when somebody asks.

!wiersz was written the other way first, and the gate caught it. A pack of
short public-domain poems was harvested, name-filtered and committed, and
check_personal_data.sh refused it: three lines carried a real group member's
given name, and two still did after a filter that had discarded 31% of the
corpus. That is structural, not a tuning problem -- Polish verse capitalises
every line, so a line-initial capital carries no signal, and that is exactly
where a name sits. The pack now holds Wolne Lektury slugs; the poem, its
title and its author are fetched at post time and appear nowhere in git.
It is also the better version: with no name filter there are no false
positives, so the pool is larger and the attribution is a full name again.

The gate then refused the art category list too, over a painter whose given
name is somebody's. That is the popes.json case exactly, so it takes the
same answer: a file-scoped allowlist entry, still refused everywhere else.

The Commons harvest is now three modules by what they are, not by where the
line count fell: commons_api is the conversation with Commons, commons_harvest
turns categories into a pool of licensed page ids, commons_cache makes an
interrupted run resumable. Along the way it gained licences in the same
request that lists a category (the second pass was 1,500 requests at 2.3s
each), maxlag instead of a guessed sleep, 5xx retries because one 504 used to
destroy a whole run, and Wikidata for works by creators of Polish
citizenship: the Polish pool grew 15,221 -> 20,092.

Batching categories through CirrusSearch looked like a further 6x and was
measured out again: 2,404 files where the authoritative enumeration returns
2,429, a different ~1% missing on every run.

The offline run is 19s where it was four minutes, almost all of it the
deafness gate waiting out three thirty-second liveness probes to prove
something provable in three; the tick is configuration now, and e2e enforces
a 40s budget on itself so that never silently comes back.
2026-08-31 21:32:07 +02:00

39 lines
2.0 KiB
Python

# Copyright (c) 2026 Krzysztof Rudnicki. MIT licence.
"""Default upstream endpoints for the image commands.
Split out of config.py, which had reached the 250-line cap. Like the groups.py
and trust.py splits before it this is a filing change, not an interface change:
config re-exports every name here, so nothing that imported them has to move.
"""
from __future__ import annotations
from typing import Final
DEFAULT_CAT_API_URL: Final = "https://api.thecatapi.com/v1/images/search"
DEFAULT_DOG_API_URL: Final = "https://dog.ceo/api/breeds/image/random"
# taxon_id 371057 is Gekkota, the gecko infraorder. The licence filter is not
# decoration: without it the API happily returns all-rights-reserved photos,
# and this bot reposts whatever it is handed into a group chat.
# Deliberately no order_by=random -- see animals.paged_url for why it does
# not work and what supplies the randomness instead.
DEFAULT_GECKO_API_URL: Final = (
"https://api.inaturalist.org/v1/observations"
"?taxon_id=371057&photos=true&quality_grade=research"
"&photo_license=cc-by,cc-by-sa,cc0&per_page=1"
)
# Commons resolves a file -- named by title for !papiez, by page id for
# !pulapka -- to a real upload.wikimedia.org URL. Special:FilePath would be
# shorter but answers 302, and this repo's httpx client does not follow
# redirects; imageinfo hands back the final URL and a thumbnail in one hop,
# which is also what keeps a 4000px original off Signal.
DEFAULT_COMMONS_API_URL: Final = "https://commons.wikimedia.org/w/api.php"
# Wolne Lektury serves each book's plain text at a URL derived from its slug,
# so this is a template rather than an address. Overridable like the rest, so
# scripts/e2e.sh can point !wiersz at the fake and the offline run stays
# offline -- it is the only command whose upstream is not an image API.
DEFAULT_POEM_TEXT_URL: Final = "https://wolnelektury.pl/media/book/txt/{slug}.txt"
# Wide enough to look like a photograph rather than an avatar, small enough
# that the whole 267-portrait cache stays well under 100 MB.
THUMB_WIDTH: Final = 640