mirror of
https://github.com/tiennm99/tiennm99bot.git
synced 2026-10-11 03:13:46 +00:00
feat(sticker): add sticker pack module
Nine commands mirroring the names @Stickers uses: /newpack, /mypack, /addsticker, /delsticker, /editsticker, /ordersticker, /setpackicon, /renamepack and /delpack, plus a confirm callback for the destructive one. Sources are replied stickers, photos or image documents; photos are downloaded, resampled to 512px and re-uploaded. One pack per user, keyed by owner id. Creating a pack is the only operation here that makes a durable, publicly linkable object on a user's behalf, so it is built around proving ownership rather than assuming it: - A name is claimed globally and create-only before Telegram is called. A pending record alone proves only that a caller *asked* for a name, which is exactly what someone naming a victim's public slug also does. - Adopting an existing set additionally requires that the claim predates this invocation. The claim lives in our store and the pack lives at Telegram, so a wiped store would otherwise make every pack adoptable. - Names are released only on positive evidence that no pack stands behind them, never on a generic failure, so a transient error cannot hand a live name to the next caller. - Ownership refusals are byte-identical across failure modes, so they cannot be used to probe which sets exist. Error classification is positive-only throughout: "the set is gone" and "nothing was created" are each proven from a specific Telegram response, never inferred from an error. Post-action commits run on a context detached from the request so a shutdown mid-handler cannot lose the record of something Telegram already did. Enabled explicitly via MODULES rather than by default.
This commit is contained in:
1 parent
e1bf7f1ddb
commit
3751010b8e
35 files changed
+5178
-5
No files matched your search
@@ -15,6 +15,7 @@ remain responsible for parsing and validation.
|
||||
|---|---|---|
|
||||
| Required value | `<name>` | `<ticker>` |
|
||||
| Required comma-separated values | `<name,...>` | `<option,...>` |
|
||||
| Required remaining text | `<name...>` | `<title...>` |
|
||||
| Optional value | `[name]` | `[date]` |
|
||||
| Optional remaining text | `[name...]` | `[target...]` |
|
||||
| Alternatives in an optional group | `[literal | literal <name>]` | `[users | user <username>]` |
|
||||
@@ -40,6 +41,7 @@ language or extra punctuation without a user-facing need.
|
||||
|
||||
```text
|
||||
/stock_buy <quantity> <ticker>
|
||||
/renamepack <title...>
|
||||
/lol [date]
|
||||
/trongtruonghop [target...]
|
||||
/stats [users | user <username> | cmd <command_name>]
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
# Sticker packs
|
||||
|
||||
The `sticker` module lets any user create and manage **one** personal Telegram
|
||||
sticker pack through the bot. The pack is created on behalf of the calling user,
|
||||
so it appears under their account, and it stays bot-manageable because the bot
|
||||
created it.
|
||||
|
||||
Every command is single-shot: one message carrying its arguments, optionally
|
||||
replying to a sticker or photo. There is no conversation state and no `/cancel`.
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Parameters | Reply to | What it does |
|
||||
|---|---|---|---|
|
||||
| `/newpack` | `<pack> <title...>` | sticker or photo | Creates your pack and returns its share link |
|
||||
| `/mypack` | — | — | Shows your pack: name, title, sticker count, link |
|
||||
| `/addsticker` | `[emoji...]` | sticker or photo | Adds it to your pack |
|
||||
| `/delsticker` | — | a sticker in your pack | Removes it |
|
||||
| `/editsticker` | `<emoji...>` | a sticker in your pack | Replaces that sticker's emoji |
|
||||
| `/ordersticker` | `<position>` | a sticker in your pack | Moves it; positions start at 0 |
|
||||
| `/setpackicon` | — | a sticker in your pack | Uses it as the pack icon |
|
||||
| `/renamepack` | `<title...>` | — | Changes the displayed title |
|
||||
| `/delpack` | — | — | Deletes the pack, after an inline confirmation |
|
||||
|
||||
Only `/newpack` names a pack. Every other command resolves your single pack from
|
||||
storage, or from the replied sticker's set.
|
||||
|
||||
## The pack name is permanent
|
||||
|
||||
`/newpack mypack My Pack` creates `t.me/addstickers/mypack_by_<botusername>`.
|
||||
|
||||
**Telegram has no method to rename a sticker set's short name.** That link is
|
||||
fixed for the life of the pack. `/renamepack` changes only the displayed title.
|
||||
|
||||
The only way to a different link is `/delpack` followed by `/newpack` under a new
|
||||
name — and the stickers do not come along. `/delpack`'s confirmation states the
|
||||
title, the number of stickers it destroys, the exact link being surrendered, and
|
||||
that both are permanent, because that prompt is the last point at which someone
|
||||
wanting "a rename" learns what it actually costs.
|
||||
|
||||
Pack-name rules: 3–40 characters, lowercase letters, digits and underscores,
|
||||
starting with a letter, no two underscores in a row, no trailing underscore.
|
||||
|
||||
## Images
|
||||
|
||||
Static stickers only. Animated, video, mask, and custom-emoji stickers are
|
||||
rejected.
|
||||
|
||||
- A replied **sticker** is added directly.
|
||||
- A replied **photo** or image **document** (`image/png`, `image/jpeg`,
|
||||
`image/webp`) is downloaded, resized so its long edge is exactly 512px with
|
||||
the aspect ratio preserved, and uploaded as a PNG.
|
||||
- Pack icons are resized to exactly 100×100, padded transparently.
|
||||
- Sources above 2 MB, or with either side above 4096px, are rejected.
|
||||
|
||||
Telegram allows 120 stickers per pack and 1–20 emoji per sticker. There is no
|
||||
documented file-size limit for static stickers; the module applies its own
|
||||
client-side ceiling and never presents it as a Telegram rule.
|
||||
|
||||
## Who can use it
|
||||
|
||||
Public — every user manages their own pack.
|
||||
|
||||
**Anonymous group admins are refused.** Telegram substitutes a single global
|
||||
`GroupAnonymousBot` user for every anonymous admin message, so without this
|
||||
refusal all anonymous admins across all groups would share one pack. Turn off
|
||||
anonymous posting for the message and try again.
|
||||
|
||||
## Deliberate omissions
|
||||
|
||||
- **Usage statistics** (`/stats`, `/top`, `/packstats`, …) — the Bot API does
|
||||
not expose sticker usage counts, and `/stats` belongs to the `stats` module.
|
||||
- **Animated, video, emoji, and mask packs** — out of scope; this module is
|
||||
static-only.
|
||||
- **More than one pack per user** — a deliberate simplification. It is what lets
|
||||
every command but `/newpack` drop its pack argument.
|
||||
- **`/cancel`** — meaningless without conversation state.
|
||||
- **A `/repack` migration command** — copying a full pack is up to ~121
|
||||
sequential API calls, which would stall the bot for every user.
|
||||
|
||||
## Behaviour worth knowing
|
||||
|
||||
**Sticker counts are advisory.** `/mypack` reads the count from storage and
|
||||
makes no API calls at all. Editing your pack through @Stickers changes the real
|
||||
count without the bot seeing it; the number re-syncs whenever a command already
|
||||
has a fresh view of the set.
|
||||
|
||||
**Pack names are claimed first-come and held permanently.** The bot records who
|
||||
claimed each name before it creates anything on Telegram, and only that user can
|
||||
ever manage a pack under it. This is what stops someone from reading a pack's
|
||||
name off its public link and taking it over.
|
||||
|
||||
`/newpack` therefore reports when a name is taken, which reveals that some user
|
||||
of this bot holds it. That is accepted: `t.me/addstickers/<name>_by_<bot>` is
|
||||
publicly probeable without the bot, so the command discloses nothing new. It
|
||||
never says *who* holds a name. Refusals about *managing* a pack are deliberately
|
||||
uniform for the opposite reason — see below.
|
||||
|
||||
A name is claimed *before* the bot calls Telegram, not after the pack exists.
|
||||
That ordering is the point: the claim is what proves, on a later re-run, that an
|
||||
existing set under that name is yours to finish rather than someone else's to
|
||||
take.
|
||||
|
||||
The claim is given up again whenever the bot has positive evidence that no pack
|
||||
stands behind it — Telegram refusing the creation outright, `/delpack`, or a
|
||||
later command finding the set already gone. A `/newpack` that never got as far
|
||||
as claiming, or that is refused before Telegram is contacted, leaves nothing
|
||||
behind.
|
||||
|
||||
Telegram may keep a deleted short name reserved on its own side, so a freed name
|
||||
is not guaranteed to be usable again by anyone, including its previous owner.
|
||||
|
||||
**Ownership refusals are identical by design.** "You don't have a pack" and
|
||||
"that sticker isn't from your pack" produce the exact same reply. Distinct
|
||||
wording would let anyone probe which sets exist under this bot.
|
||||
|
||||
**An interrupted `/newpack` can be finished.** The bot records your intent
|
||||
before calling Telegram, so if a deploy or crash lands mid-creation, re-running
|
||||
the same `/newpack` command completes it instead of reporting the name taken.
|
||||
`/mypack` marks an unfinished attempt so it is visible rather than mysterious.
|
||||
|
||||
This depends on the bot still holding your claim to the name. If its storage has
|
||||
been wiped since — which is what a restart does when no database is configured —
|
||||
the claim is gone while the pack at Telegram is not, and `/newpack` reports the
|
||||
name as taken rather than adopting a set it can no longer prove is yours.
|
||||
Recovering a pack in that state needs operator help. Run this module against a
|
||||
real database, not the in-memory backend.
|
||||
|
||||
**Deleting the last sticker may delete the pack.** Telegram's behaviour here is
|
||||
undocumented, so the bot does not guess: it will not remove your pack record on
|
||||
anything less than a positive "this set no longer exists" from Telegram. If a
|
||||
command reports the pack is gone, `/delpack` clears the stale record and
|
||||
`/newpack` works again.
|
||||
|
||||
## Operations
|
||||
|
||||
The module is enabled by listing `sticker` in `MODULES` (an empty `MODULES`
|
||||
loads every module). It stores one record per user, keyed by Telegram user ID,
|
||||
plus at most one pending `/delpack` confirmation per user — running `/delpack`
|
||||
again supersedes the previous prompt, and a confirmation stops working after 10
|
||||
minutes.
|
||||
|
||||
Every handler runs under a 10-second deadline. The bot processes updates one at
|
||||
a time, so this bound is what keeps an image conversion from stalling other
|
||||
users.
|
||||
Reference in new issue
Block a user