mirror of
https://github.com/tiennm99/tiennm99bot.git
synced 2026-10-16 16:15:59 +00:00
feat(alias): add a shared alias dictionary invocable as a bare command
/alias <name> saves a replied message under a name and /insert <name> sends it back; /aliases lists every name and /unalias deletes one. Every Telegram format is supported — sticker, photo, GIF, video, video note, audio, voice, document, plain text — and each is kept as the file_id Telegram already issued, so nothing is downloaded and an alias survives redeploys. The namespace is global and the last assignment wins, matching the shared sticker pack; /unalias is open to anyone for the same reason. A saved name also works as its own command: /cheer rather than /insert cheer. This needs two new seams in the module contract. Module.Fallback handles a /command no module registered, and the dispatcher installs it after every Command — the bot library returns the first matching handler, so code always beats a name resolved at runtime, including an alias that shares a command added in a later build. /alias refuses a name already in the registry for the same reason, since such an alias would only reach /insert. An unknown command stays silent: the fallback sees every unrecognised /foo in every chat, so replying would make typos noisy and would confirm which names exist. Module.Inline answers inline-mode queries — "@botname <prefix>" from any chat, filtered by prefix and capped at Telegram's 50 results. Each result is a cached inline type carrying the stored file_id, so the picker renders real previews without an upload. Video notes are omitted because Telegram defines no InlineQueryResultCachedVideoNote and substituting a plain video would change what was saved. Inline mode must be enabled in BotFather before Telegram delivers these updates. Both slots are single-occupancy with conflict detection at Build. Auth.Permits learns the inline sender so a gated inline handler would not deny everyone. Build's command indexing and slot claiming move into addCommands/addSingletons, keeping it under the project's cyclomatic cap. Also restores the sticker module: /addsticker moves back out of util, which has no store, into internal/modules/sticker as its only command.
This commit is contained in:
1 parent
822bcdbca8
commit
be91d2eb41
33 files changed
+1882
-52
No files matched your search
+156
@@ -0,0 +1,156 @@
|
||||
# Aliases
|
||||
|
||||
The `alias` module lets anyone give a short name to a message and send it back
|
||||
later by that name.
|
||||
|
||||
| Command | Parameters | Reply to | What it does |
|
||||
|---|---|---|---|
|
||||
| `/alias` | `<name>` | any supported message | Saves it under that name |
|
||||
| `/insert` | `<name>` | — | Sends back whatever is saved under it |
|
||||
| `/aliases` | — | — | Lists every saved name |
|
||||
| `/unalias` | `<name>` | — | Deletes a saved name |
|
||||
| `/<name>` | — | — | Same as `/insert <name>` |
|
||||
| `@botname <prefix>` | — | — | Inline picker, in any chat |
|
||||
|
||||
All are public and single-shot.
|
||||
|
||||
## The namespace is global
|
||||
|
||||
A name assigned in any chat works in **every** chat, for **everyone** — the same
|
||||
way the sticker pack `/addsticker` writes to is shared. The store is a plain map
|
||||
from name to content, with no chat or user in the key.
|
||||
|
||||
The consequence is worth stating plainly: anyone can reassign anyone's name.
|
||||
`/alias` overwrites rather than refusing, and its reply says what it replaced —
|
||||
|
||||
> Replaced /insert cheer — it was a sticker, now it is a GIF.
|
||||
|
||||
Overwriting is deliberate: refusing would make a mistyped alias awkward to
|
||||
correct. `/unalias <name>` deletes one, and is open to anyone for the same
|
||||
reason overwriting is — the namespace is shared, so the permission model is too.
|
||||
A per-owner restriction would leave an alias whose assigner has left the chat
|
||||
permanently unremovable.
|
||||
|
||||
## Invoking an alias
|
||||
|
||||
Three ways, same binding:
|
||||
|
||||
1. **`/<name>`** — a saved name works as its own command. `/cheer` is `/insert cheer`.
|
||||
2. **`/insert <name>`** — always works, including when inline mode is off.
|
||||
3. **`@botname <prefix>`** — an inline picker with previews, usable in any chat,
|
||||
even ones the bot is not a member of.
|
||||
|
||||
**Code always beats an alias.** The dispatcher registers every real command
|
||||
before the alias fallback, and the bot library returns the *first* handler whose
|
||||
matcher accepts an update — so a command defined in code can never be shadowed
|
||||
by a name resolved at runtime. `/alias` refuses a name that is already a
|
||||
command for the same reason, since such an alias would only ever be reachable
|
||||
through `/insert`.
|
||||
|
||||
If a future build adds a command whose name an alias already uses, the command
|
||||
silently wins and the alias stays reachable via `/insert`. That is the intended
|
||||
precedence, not a bug to fix.
|
||||
|
||||
**An unknown `/command` is answered with silence.** The fallback sees every
|
||||
unrecognised command in every chat the bot is in, so replying would turn a typo
|
||||
like `/pign` into noise, and would confirm to anyone probing which names exist.
|
||||
|
||||
## Inline mode
|
||||
|
||||
`@botname` with no query lists everything; with a query it filters by name
|
||||
prefix, case-insensitively, sorted, capped at Telegram's 50 results per answer.
|
||||
|
||||
Each result is a **cached** inline type — it carries the `file_id` Telegram
|
||||
already holds, so nothing is uploaded and the picker shows real previews. This
|
||||
is the payoff for storing a `file_id` rather than bytes.
|
||||
|
||||
**Video-note aliases do not appear inline.** Telegram defines no
|
||||
`InlineQueryResultCachedVideoNote`, and substituting a plain video would change
|
||||
what was saved. They stay reachable through `/insert` and `/<name>`.
|
||||
|
||||
**Inline mode must be enabled in BotFather** (`/setinline`) or Telegram never
|
||||
delivers these updates and the handler is never called. This is a one-time
|
||||
operational step that cannot be done from code.
|
||||
|
||||
## Names
|
||||
|
||||
One word, username-shaped: starts with a letter, then letters, digits and
|
||||
underscores, up to 32 characters. A leading `@` is stripped rather than
|
||||
rejected, since these names imitate usernames and typing the sigil is a natural
|
||||
slip.
|
||||
|
||||
Lookups fold case — `/insert LOUD` and `/insert loud` find the same entry — and
|
||||
the spelling the assigner used is what gets echoed back.
|
||||
|
||||
Telegram's own username minimum is 5 characters; this allows 1 on purpose. The
|
||||
point of an alias is to be shorter than what it replaces, and `gg` is a good
|
||||
name for a sticker.
|
||||
|
||||
## What can be saved
|
||||
|
||||
| Replied message | Sent back with |
|
||||
|---|---|
|
||||
| Sticker | `sendSticker` |
|
||||
| Photo | `sendPhoto` |
|
||||
| GIF / animation | `sendAnimation` |
|
||||
| Video | `sendVideo` |
|
||||
| Video note | `sendVideoNote` |
|
||||
| Audio | `sendAudio` |
|
||||
| Voice message | `sendVoice` |
|
||||
| File / document | `sendDocument` |
|
||||
| Plain text | `sendMessage` |
|
||||
|
||||
Anything else — a location, a poll, a contact — is refused with the list above.
|
||||
|
||||
**Nothing is downloaded.** Every media kind is kept as the `file_id` Telegram
|
||||
already issued, and `/insert` hands that same id straight back to a send call.
|
||||
The module stores bytes for nothing but the name and a caption. A `file_id`
|
||||
refers to a file on Telegram's servers, so an alias survives restarts and
|
||||
redeploys.
|
||||
|
||||
The kind is stored alongside the id because a bare `file_id` does not say which
|
||||
send method will accept it.
|
||||
|
||||
**Order matters when Telegram fills more than one field.** A GIF arrives as an
|
||||
`Animation` *and* a `Document`, and the more specific kind is claimed first —
|
||||
otherwise `/insert` would hand back a plain file instead of a looping GIF.
|
||||
|
||||
**Captions come back too**, for the kinds that can carry one. Stickers and video
|
||||
notes cannot, and Telegram's send methods for them have no caption field at all.
|
||||
|
||||
## Listing
|
||||
|
||||
`/aliases` prints the count and every name, sorted, in one message.
|
||||
|
||||
**Names only, not what each holds.** The store answers "which keys exist" in a
|
||||
single call, while naming each kind would cost one read per alias — a round trip
|
||||
each against MongoDB, on a dispatcher that serves one update at a time. To find
|
||||
out what a name holds, `/insert` it.
|
||||
|
||||
Names list in their folded (lowercase) form, which is exactly what `/insert`
|
||||
takes, and each is wrapped in a `<code>` span so tapping one copies just that
|
||||
name.
|
||||
|
||||
The list is trimmed to fit Telegram's 4096-character message limit and ends
|
||||
with `…and N more.` when it does not fit; the count at the top is always the
|
||||
true total. The trim budget counts the markup, not only the names — at 13 bytes
|
||||
a pair the tags outweigh a short name.
|
||||
|
||||
## Behaviour worth knowing
|
||||
|
||||
**Text loses its formatting.** Bold, links and mentions are stored as plain
|
||||
text: re-sending entities means carrying offsets that no longer line up once the
|
||||
text is repeated in a different message. A bare URL still auto-links.
|
||||
|
||||
**A `file_id` can stop working** — the original file was deleted, or Telegram
|
||||
rejects it. `/insert` answers with something actionable rather than a generic
|
||||
failure:
|
||||
|
||||
> "gone" can no longer be sent. Save it again with /alias gone.
|
||||
|
||||
**Replies keep their forum topic.** Every send forwards `MessageThreadID`, for
|
||||
the reason `chathelper.Reply` documents: without it Telegram routes the message
|
||||
to a supergroup's General topic instead of the topic the command was typed in.
|
||||
|
||||
Both handlers run under a 10-second deadline. The bot processes updates one at a
|
||||
time, so that bound is what keeps a slow store from stalling other users.
|
||||
@@ -1,8 +1,8 @@
|
||||
# Sticker pack
|
||||
|
||||
`/addsticker` appends a sticker to **one shared pack** that every user of the
|
||||
bot contributes to. It lives in the `util` module — there is no `sticker`
|
||||
module any more, and no per-user packs.
|
||||
bot contributes to. It is the whole of the `sticker` module — one command, no
|
||||
storage, and no per-user packs.
|
||||
|
||||
| Command | Parameters | Reply to | What it does |
|
||||
|---|---|---|---|
|
||||
|
||||
Reference in new issue
Block a user