signal-bot/DOCS-photo-pack.md
Krzysztof kuhy Rudnicki 80c0fa8e45 fix: take real people, numbers and a member's uuid out of a public repository
This repository is public, and five things in it were not invented.

The worst was mine, today: the tagger's help text used the group's actual
first names and nicknames as its worked example, in a file that is on
GitHub. It uses ala/basia/celina now, like every other example here.

The rest predate this work. DOCS-signal-envelopes.md carried the operator's
own phone number and the bot's, captured from a live envelope; conftest.py
had the same number as its trusted inviter; and test_trust.py pinned a group
member's full Signal uuid as a fixture. Numbers keep their shape so the
documents still show what a real envelope looks like, but no digit of them
belongs to anybody.

Comments that reached for a real first name to illustrate the diacritic case
now use an invented one.

None of this removes them from the history, which is a separate decision.

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

110 lines
4.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# The photo pack
A pack is a private album the bot posts from: `!alfa` returns a random photo
of alfa, and `!alfa beta` returns one with both of them on it.
**Nothing about a real pack is in this repository, and nothing ever will be.**
Who the people are, what somebody types to get their photo and which file
shows whom live in one directory outside the repo. This document, the tests
and the offline end-to-end run all use the same invented people — alfa, beta,
gamma — so the mechanism can be public while the album stays private.
## Where it lives
```
~/.local/share/signal-bot/pack/
people.json # the commands, and the only place names exist
tags.json # which file shows whom
media/ # the photos and videos themselves
```
Found in this order: `PHOTO_PACK_DIR`, then `$STATE_DIRECTORY/pack` (what
systemd hands a **system** unit), then `$XDG_DATA_HOME/signal-bot/pack`.
`StateDirectory=` is deliberately **not** in the unit this repo installs:
for a *user* unit systemd resolves it below `$XDG_STATE_HOME`, which is not
where the album is. Discovery finds the same directory, and leaving
`PHOTO_PACK_DIR` unset is what keeps a not-yet-filled-in pack from turning
`Restart=always` into a restart loop.
Two absences, deliberately different:
- a pack **nobody named** that is not there, or that has no `people.json`
yet, is an installation without an album: the commands do not exist, and
that is the normal case on every machine and in the whole test suite;
- a pack named in `PHOTO_PACK_DIR` that is missing, or whose manifest cannot
be honoured, **fails startup**. `load_config` raising is an answer, and a
bot that came up healthy and said "no such photo" forever would not be.
## people.json
```json
{
"groups": ["group.WlRKbExYZGhkR05vWldRdA=="],
"people": [
{"key": "p1", "triggers": ["!alfa", "!alfka"]},
{"key": "p2", "triggers": ["!beta"]}
],
"combos": [
{"key": "c1", "triggers": ["!oboje"], "all_of": ["p1", "p2"]},
{"key": "c2", "triggers": ["!ktokolwiek"], "any_of": ["p1", "p2"]}
]
}
```
- **`groups` is the whole privacy guarantee.** In any other group these are
not commands at all: not answered, not counted as a command, not named in a
log line. A handler that quietly returned would still leak all three, which
is why the *dispatcher* enforces it — `registry.resolve_with_args` takes the
group and gives back nothing outside it.
- **`key` is a pseudonym**, and the only identifier that reaches the journal
or the `command_uses` table. A command is `pack:p1`; reading either says
somebody asked for a photo and nothing about whose.
- **`all_of` means one photo with all of them; `any_of` means either**, and
where a choice exists the bot picks among the readings that actually have a
photo, so a combination that could be answered is not refused.
- Triggers may not contain a digit — `folding.py` maps `0134578` onto letters,
so `!alfa7` is not what you typed. Two spellings that fold the same are fine
*within one entry* (`!celina`/`!celiną` is one person written twice) and
refused across two.
## tags.json
```json
{"alfa.png": ["p1"], "both.png": ["p1", "p2"], "clip.mp4": ["p1"]}
```
A tag naming a file that is not in `media/` fails startup: a renamed file
would otherwise drop out of the album in silence. An empty list means "seen,
nobody in it", which is how the tagger records a photo you skipped.
Videos work. The attachment goes out as
`data:video/mp4;filename=<name>;base64,<data>` — the spelling is the
container's own, from its `/swagger/doc.json` — because told it is a photo,
Signal renders a clip as a file nobody can play.
## Filling it in: tool/tagger.py
```sh
python tool/tagger.py --import ~/photos # copy the files in, then tag
python tool/tagger.py # carry on where you left off
```
It serves one page on `127.0.0.1:8140`: the item, a numbered button per
person, `1`–`9` to toggle, `→`/`←` to move. Every toggle rewrites `tags.json`
atomically, so closing the tab loses nothing. Names are typed **into the
page**, never as arguments — an argument would land in the shell history —
and the group is picked from the live `/v1/groups` listing rather than pasted.
When you stop it, it prints how many items were never opened and how many
photos each command would have. **A command showing `0` is dead on arrival**;
that is the number to check before restarting the bot, not after somebody in
the group tries it.
## Running the mechanism without an album
`scripts/e2e.sh` writes a synthetic pack with `tool/e2e_pack.py` and proves,
offline and with no Signal account, that one name posts a photo, two names
intersect, an impossible combination gets one line and no attachment, a video
leaves as a video, and the same command in another group produces **zero**
sends.