mirror of
https://github.com/kuhyx/signal-bot.git
synced 2026-11-10 20:37:03 +01:00
d3d89c890e
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 48957a105d |
feat: serve several photo albums at once, each scoped to its own groups
Some checks are pending
CI / check (push) Waiting to run
You asked for !add_photo in testbot without the real album being reachable there. One album cannot do that: its people.json "groups" list *is* the privacy guarantee, so making the commands exist in a second group necessarily makes the real photos postable there. So the bot now loads several albums. PHOTO_PACK_DIR takes a comma-separated list, the spelling GROUP_IDS and TRUSTED_INVITERS already use. Each album carries its own group scope, its own people, its own tags and its own custom labels; they share nothing but the process they run in. That makes trigger collisions between albums legal for the first time, and the registry had to learn it. Two albums are two different sets of people and both may reasonably call somebody "!alfa" -- no message can ever mean both, because a command a group is not scoped to is not a command there at all. So a trigger may be claimed more than once, and DuplicateTriggerError now fires only where the group sets *overlap*. Overlapping albums are refused at startup rather than resolved by registration order: a coin toss about which private album receives a photo is the outcome this whole design exists to prevent. Two consequences worth naming: - registry.resolve() takes an optional group. Without one it still answers "does anything claim this word", which is what pack_add's mint check wants; with one it answers "what runs here", which is what the dispatcher wants. resolve_with_args no longer resolves-then-checks, because with two claimants the first one found may be the wrong group's. - CommandContext moved to signal_bot/context.py to keep registry.py under the 250-line cap. A filing change: that file is "which word runs what", this one is "what the thing that runs may do". DOCS-add-photo.md is likewise a split, not new prose -- DOCS-photo-pack.md had grown past the cap and the ingest path is its own subject now. Verified live: the unit serves both albums, the real group resolves its 13 album commands and testbot its 7, and !losowe / !alfa / !dodaj all fire in testbot through the live dispatcher. 997 tests, 100% branch coverage, 28 e2e scenarios. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JqqE22jp94B1ypwxppdr1o |
|||
| aefffb2fd9 |
feat: add a photo to the album from the chat, tagged, stripped and verified
Some checks are pending
CI / check (push) Waiting to run
Send a photo captioned "!add_photo alfa wakacje" in one of the album's own groups and it lands in the pack tagged with both, retrievable through !alfa and the new !losowe immediately -- no restart, and no tagger session on the PC. A label matching nobody becomes a custom tag, and !losowe is the only door to one, which is what keeps a live swap a *data* swap: it can never register a trigger or widen a command's groups. No typed label travels. Labels become pseudonym keys in a new registry arg_resolver, at the dispatcher, before the line that logs the command name -- so no handler, journal entry, command_uses row or reply body ever holds a word somebody typed. The reply is counts and pseudonyms: "dodano · p1: 4 · t1: 1". Stripping is exiftool with a *positive* check on the output, never an exit code. ffmpeg was tried first and fails silently on photos: given a JPEG with a real APP1 Exif segment it returned a byte-identical file, GPS intact, exit 0. The surviving-tag allowlist is per tag *name* rather than per group, because measurement showed a group-level "must be empty" rule rejects every format except JPEG -- a valid PNG always reports its IHDR, an MP4 some fifty QuickTime tags that are the container itself. Codec configuration (CompressorName, VendorID, the HEVC parameter set) is on the list by decision: CompressorName lives in the avc1 sample description and exiftool cannot write it at all, so the alternative was refusing every video and every iPhone HEIC. Location and identity tags are still refused, and the e2e run re-reads the stored files to prove a QuickTime geotag did not survive. .webm and .svg stay on the format allowlist and are stored *unstripped*: exiftool 13.55 can write neither, and its SVG reader does not even surface <metadata>. That is a deliberate, documented hole, not an oversight. Two things this had to defuse along the way: - personal_data_sources now reads custom.json, so "!add_photo kot <photo>" would have outlawed the word "kot" and refused every future commit, fixable only by hand-editing an album the committer cannot see. Minting now refuses a word that is already a command, and the gate subtracts the repo's own alias vocabulary from custom labels only -- curated people.json names are never excused. - signal_bot/external.py is now the single subprocess site in the repository, so ruff's S603 needs exactly one scoped per-file-ignore instead of one per module that shells out. A test asserts nothing else imports subprocess. A failed add changes nothing: media and the three manifests are written, the album is reloaded, and only a load that succeeds is published; a load that fails rolls all four back and keeps serving the pack that was already working, without raising -- Restart=always would turn a ConfigError into a loop. 977 tests, 100% branch coverage, no omits and no suppressions. scripts/e2e.sh gains 8 scenarios driven against the real exiftool on media generated at run time, and asserts the invariants a reply cannot: that a refused upload is not stored, that a message with no attachment leaves custom.json byte-identical, and that signal-cli's own copy of every ingested attachment is gone. Closes TODO-add-photo.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JqqE22jp94B1ypwxppdr1o |
|||
| ca38147576 |
release: 0.1.4 «Eleuteriusz» — the list of commands lives in the chat now
Thirteen commands, and the only list of them was a README table nobody in a Signal group can read — which had itself gone stale two releases earlier, still advertising four commands after nine more had shipped. 0.1.1 gave each command an output that says what the command is; that only helps once you already know to type it. !pomoc (also !pomocy, !help, !komendy, !commands) prints all fourteen, grouped, one Polish line each. The interesting part is that it cannot go stale. CommandRegistry.register now takes the description as a REQUIRED argument, so a command that does not explain itself does not compile; and render_help prints any command that help_command.GROUPS forgot to file, under no heading, rather than dropping it. Two independent ways to fail closed, because the README proved that a hand-maintained second list is a list that drifts. Command also gained `primary`, the first raw alias. The trigger set could not supply it: those are folded skeletons in an unordered frozenset, so all that survives of "!papież" there is "!papiez", and the styled Unicode aliases come out as zalgo. aliases.json already lists the canonical Polish spelling first. The full trigger table moves to DOCS-commands.md. It did not fit in README — that file was already sitting exactly on the shared 250-line cap, and the gate caught the overflow on the first commit attempt. Exercised in testbot at the unbumped version before this bump: the live dispatcher ran it and posted the list, journal clean. pytest 708 green at 100% branch coverage, e2e green including a new scenario that asserts !pomoc mentions a command registered by a different module. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017w1HZ3cZ7neL3rFdQ6VMBA |