Let each chat choose how long the bot's messages live in it - #165
Merged
Conversation
The delays of the self-destruction were the operator's alone, and issue #49 was merged with that as an open question: if a chat turns out not to want the mechanic, or to want it sooner, it had nothing to say about it. Now it has /cleanup, an admins-only picker of two levels. The first lists the four message groups with what each one costs in this chat; pressing a group opens the delays the bot suggests, plus the two answers it always takes — keep them for good, or let the bot decide again. Those two are added by the picker itself rather than by the list, so no configuration can leave a chat with a choice it can't take back. The choice is a number of minutes, stored under a `cleanup` key of the `Chats.settings` jsonb next to the chat language and the allowed topics. A group is therefore in one of three states, and all three are distinct: a number is the delay the chat picked, a zero is the chat asking for that group to be kept, and an absent key is the chat leaving the decision to the bot. The last two look alike until the operator changes his mind, which is exactly when they part. No migration: the column has been there since migration 25. The numbers on offer are MSG_SELFDESTRUCT_DELAY_OPTIONS_MINUTES, sorted and deduped, with zeroes and anything past the 47-hour limit dropped as it is read. A stored value that is no longer in the list still applies — the list is a suggestion for the next press, not a rule about what may already be stored — which is why the cap is applied on the way out and not only on the way in. The list also counts towards `enabled()`, the worker's spawn gate, since a chat can now be the only reason a row is ever written; with nothing offered, a chat can only keep messages, and the gate says so. Choosing a non-zero delay is the one press that asks Telegram anything. Where the mode deletes commands the bot may not be allowed to, and then the answers would go while the commands stayed, so the press turns into a warning that has to be confirmed — a stronger one under ONLY_WITH_COMMAND, where nothing at all would be deleted — carrying the chosen number through the detour. The answer lands in the rights cache, which every later message reads. `SelfDestructionService::may_delete_here` is that unconditional check, next to the mode-dependent `may_delete_commands` the answering path uses. The setting is cached in this process, like the chat language and the allowed topics, not in the Redis cache: that one is flag-shaped and is never a source of truth. Inline messages keep the bot's own settings, since an InlineQuery carries no chat to look the setting up by — the same half of #76 that /topics runs into. Two things came along on the way. `MessageGroup` moved out of `config` into `src/domain/enums.rs`: the new domain object had to name it, and domain never imports config. `config` re-exports it, so every call site stays as it was. And `commands.support.disabled` is gone. It was word for word the top-level `errors.feature_disabled`, which is what /support and /cleanup both use now. While translating the new block it turned out that the `topics` one and the two forbidden-topic errors had been left in English for it, fa, zh and zh-TW since #102; they are translated too. The two new variables are in .env.example, docker-compose.yml, the Dockerfile and the docs, but not in the server-configs repository — that one is separate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
/cleanup governed only what the bot sends into a chat. Inline answers went past it on the operator's numbers, because an inline update names no chat. That last part is only half true, and the testing of #128 is what made it worth checking. Two of the three places that schedule a placeholder already hold a chat: `inline_chosen_handler` works solely for anchored groups — it filters on a row carrying both the id and the instance — and `inline_callback_handler` resolves one for the command itself. Only the battle offer has to decode the id out of the `inline_message_id`, and only where the chats are merged. Which is the point: the settings can't be changed without adding the bot to the chat anyway, so the chats this can serve are exactly the anchored ones. An inline message can never be deleted, only rewritten into the placeholder, and that is a different promise from "this will be gone in five minutes". So the chat gets a switch of its own rather than quietly inheriting the delays — `MSG_SELFDESTRUCT_INLINE_GROUPS` becomes the default for a chat that said nothing instead of the last word for everyone. The flag lives in the `cleanup` object beside the groups, under the one key a group can never be called. It is a tri-state like the delays: absent means the operator's list decides which groups are touched, `true` means every group the chat cleans up, `false` means none. `inline_delay_for_chat` is where the two meet, and the delay is the chat's either way. Where no chat can be named — a legacy group without an anchor, an id that encodes none — the bot's own settings apply, which is the half of #76 that /topics runs into as well. Inline messages are also stretched by the reading time now. Its absence was mechanical rather than considered: `schedule` is handed a `Message` and reads `sent.text()`, while `schedule_inline` was handed only an id. The text is right there at both call sites, one line above the request that sends it. The battle offer stays at zero — Telegram builds that text out of the article, so the bot never sees it, and it is one line long. Two parameters of `schedule_inline` became the domain types they always were: `InlineMessageId`, which the service used to build out of a `&str` a line later, and a new `CharCount`, counting characters rather than bytes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #128.
The self-destruction of messages (#49, #127) was configured once, for the whole bot. This gives
every chat a say of its own — the follow-up the issue was created for, in case a chat turned out not
to want the mechanic, or to want it sooner.
What a chat gets
/cleanup, admins-only, a picker of two levels. The first lists the four message groups with whateach one costs in this chat; pressing a group opens the delays the bot suggests, plus the two
answers it always takes — keep them for good, or let the bot decide again. Those two are added by
the picker itself rather than by the configured list, so no list can leave a chat with a choice it
can't take back.
The mode stays the operator's: it is about the bot's rights rather than about taste, and
MSG_SELFDESTRUCT_MODE=DISABLEDhides the command entirely.How it is stored
A
cleanupkey in theChats.settingsjsonb, next to the chat language and the allowed topics —no migration. A group is in one of three states, and all three are distinct: a number is the delay
the chat picked, a zero is the chat asking for that group to be kept, and an absent key is the chat
leaving the decision to the bot. The last two look alike until the operator changes his mind, which
is exactly when they part.
Two things worth a look in review
The rights warning. Choosing a non-zero delay is the one press that asks Telegram anything:
where the mode deletes commands the bot may not be allowed to, and then the answers would go while
the commands stayed. The press turns into a warning that has to be confirmed — a stronger one under
ONLY_WITH_COMMAND, where nothing at all would be deleted — carrying the chosen number through thedetour. The answer lands in the rights cache, which every later message reads.
Inline messages now obey the chat as well, where the chat can be named at all: two of the three
places that schedule a placeholder already hold one, and
inline_chosen_handlerworks solely foranchored groups. Since the settings can't be changed without adding the bot to the chat, those are
exactly the chats this can serve. An inline message can only be rewritten, never deleted, so the
chat gets a switch of its own rather than inheriting the delays quietly, and
MSG_SELFDESTRUCT_INLINE_GROUPSbecomes the default for a chat that said nothing. Where no chat canbe named — a legacy group without an anchor, an id that encodes none — the bot's own settings apply,
which is the half of #76 that
/topicsruns into as well.Inline messages are also stretched by the reading time now; its absence was mechanical rather than
considered.
Configuration
Two new variables, both optional and both defaulted:
They are in
.env.example,docker-compose.yml, theDockerfileand the docs — but not in theserver-configs repository yet.
Along the way
MessageGroupmoved out ofconfigintosrc/domain/enums.rs: the new domain object had to nameit, and domain never imports config. Re-exported, so no call site changed.
commands.support.disabledis gone — it was word for word the top-levelerrors.feature_disabled,which
/supportand/cleanupboth use now.topicsblock and the two forbidden-topicerrors had been left in English for it, fa, zh and zh-TW since Support enforcing of the bot use in a single chat topic only #102. They are translated too.
Verification
cargo build --all-targets,cargo clippy --all-targetsclean;cargo test— 240 passed.Covered by tests: the delay matrix (a chosen delay beats the bot's, a zero keeps, a value past the
cap is cut down,
DISABLEDbeats everything), the inline matrix over the three states of the switch,the option-list parsing, both keyboard levels and their wire format, and the jsonb round trip
alongside the language and the topics in the same column.
🤖 Generated with Claude Code