diff --git a/DOCS-add-photo.md b/DOCS-add-photo.md index 1cf447c..c17aa08 100644 --- a/DOCS-add-photo.md +++ b/DOCS-add-photo.md @@ -1,24 +1,48 @@ # Adding a photo from the chat: `!add_photo` -> **Unwired since 2026-09-04.** `!dodaj` is not a command until photos land -> in an approval queue instead of straight in the album. Everything below -> describes the ingest path as it was wired, and as the queue will reuse it. +How a member of an album's group proposes a photo for it by sending one, +how the operator approves it, and what keeps that path from leaking anything. +The album itself — where it lives, what its manifests look like, how several +albums coexist — is `DOCS-photo-pack.md`. -How a member of an album's group puts a photo into it by sending one, and -what keeps that path from leaking anything. The album itself — where it -lives, what its manifests look like, how several albums coexist — is -`DOCS-photo-pack.md`. +Send a photo captioned `!dodaj alfa wakacje2019` in one of the album's groups. +`alfa` resolves to that person's key; `wakacje2019` matches nobody and travels +as a *proposed* label. The photo is stripped and lands in the album's +`pending/`, and the reply is `czeka na zatwierdzenie: 1`. It is **not in the +album yet**: `!alfa` and `!losowe wakacje2019` cannot find it until somebody +approves it. -Send a photo captioned `!add_photo alfa wakacje2019` in one of the album's -groups. `alfa` resolves to that person's key; `wakacje2019` matches nobody and -is minted as a custom tag. The photo is retrievable through `!alfa` and -`!random wakacje2019` **immediately**, with no restart. +### Approval: the management page, and nothing else + +`./web.sh` shows a **Waiting for approval** section above the tagger whenever +`pending/` is not empty (`web/src/views/pending.tsx`). Each queued photo is a +card with the album's people as checkboxes — the ones the sender named are +pre-ticked — and one checkbox per proposed label. **Approve** moves the file +into `media/`, writes `tags.json`, mints any ticked label in `custom.json`, +records the sender in `adds.json` (with `approved_at`), and asks the bot for +`POST /_reload`. From that moment `!losowe wakacje2019` answers with it, with +no restart. **Reject** deletes the file and its sidecar; nothing else to undo, +because nothing else was written. + +The queue is the only door in. The bot's own process never approves anything; +it writes `pending/.` plus a `.json` sidecar (`keys`, +`labels`, `by`, `at`, `group`) and reads the result back at the reload. The +page can only approve a label the sender actually proposed (anything else is +`400`), and it can keep fewer — an unticked label is simply not minted. If the +bot then refuses the reload (a stale alias, say) the page **reports** it and +does not roll back: the photo is in the album with the tags the approver +chose, and the bot's reason is about something else. What holds: - **No typed label travels.** Labels become keys in the registry's `arg_resolver`, at the dispatcher, *before* the line that logs the command - name. The reply is counts and pseudonyms — `dodano · p1: 4 · t1: 1`. + name; a new word becomes a per-message token (`new1`, `new2`) and is claimed + back by its own handler, so concurrent frames cannot swap labels. The reply + is counts only — `czeka na zatwierdzenie: 2 · 1 już było` — and the proposed + word exists in exactly one place, the sidecar inside the album's `0700`. + `scripts/check_personal_data.sh` reads those sidecars too, so a proposed + name is on the forbidden list before it is ever approved. - **No cap.** No cooldown, no size limit, no daily limit. `!random` takes the ordinary pack cooldown; `!add_photo` takes no decorator at all. - **At most six labels**, the same ceiling `!alfa beta …` has. @@ -26,15 +50,19 @@ What holds: carries the *quoted* message's media in `quote.attachments`, and taking it would let anyone re-file somebody else's photo. - **A duplicate changes nothing.** The stored name is a content hash, so a - photo already in the album is reported and skipped; a mixed batch stores the - new files and says how many were already there. -- **Nothing is written until it works.** Media, `tags.json`, `custom.json` and - `adds.json` 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 album that was already working. + photo already in the album is `już było`, one already waiting is `już + czeka` — the same photo twice in one message counts as waiting too — and a + mixed batch queues the new files and says how many were which. +- **Approval is whole or not at all.** Media, `tags.json`, `custom.json` and + `adds.json` are written, the album is loaded again, and only a load that + succeeds keeps them. A load that fails rolls all four back and leaves the + photo in the queue with a `400` naming the reason. - **The second copy goes.** signal-cli keeps its own copy of every inbound attachment outside the album's `0700`. `!add_photo` deletes it once the file - is stored, and also when the upload was refused. + is queued, and also when the upload was refused. +- **Stripping happens before the wait.** The file in `pending/` is already + the stripped one; `scripts/e2e.sh` re-reads the queue with exiftool before + approving anything, and the album after. ### Stripping: exiftool, with a positive check diff --git a/DOCS-commands.md b/DOCS-commands.md index d955229..c5b05ee 100644 --- a/DOCS-commands.md +++ b/DOCS-commands.md @@ -31,12 +31,13 @@ This file is the trigger reference — every spelling that resolves, which | command_stats | `!analityka` `!command_stats` `!commandstats` `!analytics` `!uzycie` `!użycie` `!ranking` | | help | `!pomoc` `!pomocy` `!help` `!komendy` `!commands` | | random | `!losowe` `!losuj` `!random` `!rand` | +| add_photo | `!dodaj` `!dodaj_zdjecie` `!dodajzdjecie` `!add_photo` `!addphoto` | | suggest | `!zaproponuj` `!propozycja` `!sugestia` `!suggest` `!pomysl` | | good_bot | `!good bot` `!dobry bot` `!goodbot` `!dobrybot` `!good_bot` `!dobry_bot` | | food_today | `!co_jadł` `!co_jadl` `!cojadł` `!cojadl` `!jedzenie` `!dieta` + the operator's own in `aliases_custom.json` | | workout_today | `!co_ćwiczył` `!co_cwiczyl` `!coćwiczył` `!cocwiczyl` `!trening` `!workout` + the operator's own in `aliases_custom.json` | | mute | `!cisza` `!cicho` `!mute` | -| unmute | `!odcisz` `!gadaj` `!wracaj` | +| unmute | `!odcisz` `!wracaj` | Aliases live in `signal_bot/aliases.json`, not in source, because they are data: adding one should never mean touching Python. Two constraints on a new @@ -116,10 +117,12 @@ for `TRUSTED_INVITERS`; anyone else is refused without a reply, because a refusal is a reply. With `TRUSTED_INVITERS` unset, anybody may. There is no `!unmute` alias: it sits one edit from `!mute`, inside the near-miss budget. -`add_photo` (`!dodaj`) is **unwired since 2026-09-04**: a photo posted to a -group must not reach the album without somebody approving it, and no approval -queue exists yet. It comes back when one does; `DOCS-add-photo.md` describes -the path as it was. +`add_photo` (`!dodaj alfa wakacje` + an attachment) exists in the same groups +as `random` and under the same silence elsewhere. It does **not** put the +photo in the album: it strips it and queues it for approval on the management +page (`./web.sh`), and answers with a count — `czeka na zatwierdzenie: 1`. +Only an approved photo is retrievable, and it is from the moment it is +approved. No cooldown, no size cap, no daily limit. `DOCS-add-photo.md`. Any command can be **switched off** from the management page (Commands tab), outright or until a moment, and given its own cooldown. The bot reads the diff --git a/DOCS-photo-pack.md b/DOCS-photo-pack.md index 0e787bd..26cd523 100644 --- a/DOCS-photo-pack.md +++ b/DOCS-photo-pack.md @@ -15,18 +15,18 @@ gamma — so the mechanism can be public while the album stays private. ~/.local/share/signal-bot/pack/ people.json # the commands, and the only place names exist tags.json # which file shows whom - custom.json # labels !add_photo minted (optional) + custom.json # labels approved from !add_photo (optional) adds.json # who added what, read by nothing (optional) media/ # the photos and videos themselves + pending/ # !add_photo sends waiting for approval (optional) ``` 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. +`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 keeps a not-yet-filled-in pack from turning `Restart=always` into a loop. Two absences, deliberately different: @@ -130,14 +130,14 @@ startup rather than shadowing the command it collides with. ## adds.json ```json -{"a3ae52c7….jpg": {"by": "", "at": 1756600000}} +{"a3ae52c7….jpg": {"by": "", "at": 1756600000, "approved_at": 1756700000}} ``` -Who added which file. **Nothing reads it** — `pack_load` ignores the file -entirely — and it exists only so `!undo_photo` can be built later without a -gap in its history; the adder has to be recorded at add time or not at all. -It lives inside the album, which is `0700` and outside this repository, so it -is handled exactly like the photos. +Who added which file, and when it was approved. **Nothing reads it** — +`pack_load` ignores it — and it exists only so `!undo_photo` can be built +later without a gap in its history; the adder is recorded at add time or not +at all. It lives inside the album (`0700`, outside this repository), so it is +handled exactly like the photos. ## sources.json @@ -169,11 +169,14 @@ at startup would be a restart loop under `Restart=always`. ## Adding from the chat: !add_photo -A member of one of the album's groups can send a photo captioned -`!add_photo alfa wakacje` and have it land in that album, tagged, stripped -and retrievable immediately. That path has its own document, because the -metadata handling and the privacy invariants it has to keep are longer than -the album format itself: **`DOCS-add-photo.md`**. +A member of one of the album's groups sends a photo captioned +`!add_photo alfa wakacje`. It is stripped and lands in **`pending/`** — under +its content hash, beside a `.json` sidecar with the proposed `keys`, +`labels`, sender and group — until the operator approves it on `./web.sh`, +which is when it is tagged, moved into `media/` and retrievable. `pack_load` +never reads `pending/`. The path has its own document, because its metadata +handling and privacy invariants are longer than the album format itself: +**`DOCS-add-photo.md`**. ## Filling it in: ./web.sh @@ -242,5 +245,6 @@ the group tries it. 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. It also drives `!add_photo` against the real exiftool, on media it -generates at run time, so no photo enters this repository. +sends. It also drives `!dodaj` against the real exiftool on media generated at +run time (no photo enters this repository), approves the queue through the +management page it boots beside the bot, and has `!losowe` answer, no restart. diff --git a/DOCS-web-ui.md b/DOCS-web-ui.md index df3098e..cc27453 100644 --- a/DOCS-web-ui.md +++ b/DOCS-web-ui.md @@ -77,13 +77,12 @@ account's number and is edited from a tab that can be closed mid-save. ### Group settings need a group listing -`GROUP_IDS` and `SELFTEST_GROUPS` are editable, but only while `/api/groups` -has actually returned something. `parse_group_ids` checks *syntax*; startup -additionally requires each listed group to be one the bot really joined, -judged against a listing that was really read. So with the container down, a -syntactically valid write can name a group the next startup rejects — turning -a scoping mistake into a unit that will not start. The page says so and leaves -the other keys editable. +`GROUP_IDS` and `SELFTEST_GROUPS` are editable only while `/api/groups` has +actually returned something. `parse_group_ids` checks *syntax*; startup also +requires each listed group to be one the bot really joined, judged against a +listing that was really read. With the container down, a syntactically valid +write can name a group the next startup rejects — a scoping mistake turned +into a unit that will not start. The page says so and leaves the rest editable. Group ids are **picked, never typed** — `GROUP_IDS` and `SELFTEST_GROUPS` render as a ` { + setKeys((current) => flip(current, person.key)); + }} + type="checkbox" + /> + {person.triggers[0] ?? person.key} + + + ); + })} + {props.item.labels.map((label) => { + return ( +
  • + +
  • + ); + })} + + + + {error !== "" &&

    {error}

    } + + ); +}