mirror of
https://github.com/tiennm99/tiennm99bot.git
synced 2026-10-11 03:13:46 +00:00
188 lines
9.6 KiB
Markdown
188 lines
9.6 KiB
Markdown
# Sticker pack
|
||
|
||
`/addsticker` appends a sticker to **one shared pack** that every user of the
|
||
bot contributes to. It is the whole of the `sticker` module — one command, no
|
||
storage, and no per-user packs.
|
||
|
||
**The module stores nothing.** Its factory ignores the collection it is handed:
|
||
the pack comes from the environment and the set owner from `OWNER_ID`, so there
|
||
is nothing per-user to key. A one-time startup cleanup
|
||
(`migration:sticker-drop-legacy-packs-v1`) removes the records the retired
|
||
per-user pack commands left in the `sticker` collection — pack documents keyed
|
||
by owner ID, `slug:` name reservations, and `pending-delete:` confirmations.
|
||
It is marker-guarded, so it scans once per database and never touches anything
|
||
written afterwards.
|
||
|
||
| Command | Parameters | Reply to | What it does |
|
||
|---|---|---|---|
|
||
| `/addsticker` | `[emoji...]` | sticker, photo, image document, video, GIF, or video note | Adds it to the shared pack and replies with the link |
|
||
|
||
Single-shot: one message replying to the media to add. No conversation state.
|
||
[What it accepts](#what-it-accepts) lists every supported kind.
|
||
|
||
## Configuration
|
||
|
||
| Env | Default | Meaning |
|
||
|---|---|---|
|
||
| `STICKER_PACK_NAME` | `miti99_by_miti99bot` | The Telegram set to write to |
|
||
| `OWNER_ID` | — | Must be the account that **owns** that set |
|
||
|
||
`OWNER_ID` is reused rather than given a sticker-specific twin because
|
||
`addStickerToSet` takes the **set owner's** user ID, not the caller's, and the
|
||
default pack belongs to the bot owner. Point `OWNER_ID` at the owning account if
|
||
the configured pack belongs to someone else.
|
||
|
||
The caller's identity is used nowhere. That is what makes the command stateless:
|
||
no records, no keys, no per-user locks, and no ownership checks. It is also why
|
||
`/addsticker` needs no per-user storage.
|
||
|
||
`STICKER_PACK_NAME` **must end in `_by_<this bot's username>`** — Telegram
|
||
requires that suffix on every set a bot creates, and refuses to let a bot edit
|
||
any set it did not create. A name without the suffix therefore cannot be this
|
||
bot's, which makes it a configuration fault the command can prove offline,
|
||
before any download or API call:
|
||
|
||
> The shared pack is not one this bot can manage. Ask the bot owner to check its
|
||
> configuration.
|
||
|
||
## Creating the pack
|
||
|
||
**The pack creates itself on first use.** If the set does not exist, the first
|
||
`/addsticker` creates it — owned by `OWNER_ID`, titled with the slug half of the
|
||
name (`miti99_by_miti99bot` → *miti99*), and seeded with the sticker that
|
||
triggered it, because Telegram cannot create an empty set. The reply says
|
||
*"Created the shared pack with this sticker."*
|
||
|
||
Rename the title afterwards through `@Stickers` if the derived one is not wanted.
|
||
The name itself is permanent — Telegram has no method to rename a set's short
|
||
name.
|
||
|
||
The order is add-first, create-on-missing, not probe-then-add: probing with
|
||
`getStickerSet` would cost an extra call on every invocation forever, and return
|
||
the set's entire sticker list each time, to save one call on the single
|
||
invocation that creates the pack.
|
||
|
||
**A name already taken by a set this bot cannot write to is an error, not a
|
||
takeover.** If the add reports the set missing *and* the create reports the name
|
||
occupied, something stands there that this bot cannot manage — a set created by
|
||
another bot, or by this bot for a different owner:
|
||
|
||
> A sticker set with that name already exists and this bot cannot manage it. Ask
|
||
> the bot owner to check it.
|
||
|
||
**The owner cannot be verified up front.** `getStickerSet` returns a set's name,
|
||
title, type, stickers and thumbnail — and no owner or creator ID. So the suffix
|
||
proves *which bot* created a set, but nothing proves *which user* owns it until
|
||
Telegram refuses the write. That refusal is what the message above reports.
|
||
|
||
## No moderation, by design
|
||
|
||
Anyone who can reach the bot can add to the pack. There is no approval step, no
|
||
per-contributor limit, and no way to remove a sticker through the bot —
|
||
`/delsticker`, `/editsticker`, `/ordersticker`, `/setpackicon`, `/renamepack`
|
||
and `/delpack` were removed along with the per-user model.
|
||
|
||
Cleanup is done by the pack's owner through Telegram's own `@Stickers` bot,
|
||
which can edit any set the owner owns. Two consequences worth accepting on
|
||
purpose before enabling this:
|
||
|
||
- The pack carries the owner's name and sits in the sticker tray of everyone who
|
||
installed it, but its contents are decided by whoever runs the command.
|
||
- The 120-sticker ceiling is shared. One user can fill it.
|
||
|
||
## What it accepts
|
||
|
||
| Replied message | Result |
|
||
|---|---|
|
||
| **Sticker** — static, animated (.TGS), or video (.WEBM) | Copied by `file_id`, no conversion |
|
||
| **Photo** or image **document** (`image/png`, `image/jpeg`, `image/webp`) | Converted to a 512px PNG and uploaded |
|
||
| **Video, GIF, animation, video note** | Transcoded to a WEBM/VP9 video sticker |
|
||
| **Mask or custom-emoji sticker** | Refused — the pack is a `regular` set |
|
||
|
||
**All three sticker formats go in the same pack.** Since Bot API 7.2 the format
|
||
is a property of each sticker, not of the set: `createNewStickerSet` lost its
|
||
`sticker_format` parameter and `StickerSet` lost `is_animated`/`is_video`. So a
|
||
pack seeded with a static sticker takes video stickers later with no migration.
|
||
|
||
**A sticker is copied, never converted.** It already lives in a set Telegram
|
||
accepted, so it satisfies every dimension, duration and size rule for its
|
||
format. That is why animated and video *stickers* work here while an ordinary
|
||
video or GIF *file* does not. Copying a sticker out of someone else's pack is
|
||
normal Telegram behaviour and does not touch that pack.
|
||
|
||
A replied photo or image document is downloaded, resized so its long edge is
|
||
exactly 512px with the aspect ratio preserved, uploaded as a PNG, and then
|
||
added — attributed to the pack owner, matching the set it is about to join.
|
||
Sources above 2 MB, or with either side above 4096px, are rejected.
|
||
|
||
## Video and GIF
|
||
|
||
A replied video, GIF, animation or video note is downloaded and transcoded with
|
||
**ffmpeg** to what Telegram requires of a video sticker: WEBM/VP9, long edge
|
||
exactly 512px, at most 3 seconds, at most 30 FPS, at most 256 KB, and **no
|
||
audio stream**. Documents count too — `image/gif`, `video/mp4`, `video/webm`,
|
||
`video/quicktime`, `video/x-matroska`.
|
||
|
||
Every one of those rules is applied by the filter chain and encoder flags rather
|
||
than checked afterwards, so there is no case where a source slips through
|
||
half-converted:
|
||
|
||
- The long edge is scaled to exactly 512 **in either direction**. A 100×50 GIF
|
||
becomes 512×256 — deliberately not `force_original_aspect_ratio=decrease`,
|
||
which leaves a small source undersized and so fails the "one side must be
|
||
exactly 512" rule.
|
||
- `-t 3` cuts the length; `fps=30` caps the rate.
|
||
- `-an -sn -dn` drops audio, subtitle and data streams. Telegram refuses a video
|
||
sticker carrying audio, so this is not merely tidiness.
|
||
- `-pix_fmt yuva420p` keeps GIF transparency; VP9 carries an alpha plane.
|
||
|
||
Size is the one rule that cannot be known before encoding, so it retries down a
|
||
CRF ladder (32 → 42 → 52) until the output fits. Ordinary footage lands around
|
||
30–70 KB at the first rung, well inside the limit. If even the last rung is too
|
||
big, the smallest attempt is sent and Telegram is left to be the authority.
|
||
|
||
`image/gif` is transcoded rather than reduced to its first frame — a GIF is sent
|
||
to be animated. (Note that a GIF forwarded through Telegram usually arrives as
|
||
an `Animation` in mp4 form, not as `image/gif` at all.)
|
||
|
||
**ffmpeg is a hard runtime dependency.** Telegram accepts no other codec for a
|
||
video sticker, Go's standard library has no VP9 encoder
|
||
(`golang.org/x/image/vp8` decodes only), and the binary is built
|
||
`CGO_ENABLED=0` so a cgo encoder would not link. The runtime image is therefore
|
||
`alpine` with `apk add ffmpeg` rather than `distroless/static`, which cannot
|
||
carry a second binary — the one reason that base was given up, and it takes the
|
||
image from roughly 20 MB to 213 MB.
|
||
|
||
**A transcode holds the whole bot.** Handlers run inline on a single worker, so
|
||
an encode is time no other user is served. A 1280×720 source encodes in about
|
||
0.4 s with these flags, and one encode is capped at 20 s so a pathological input
|
||
fails rather than hangs. The moving path also gets a longer handler deadline
|
||
(45 s, against 10 s for stills) — chosen from the replied message before any API
|
||
call, so a still never pays for the video budget. Source downloads are capped at
|
||
10 MB for video against 2 MB for images, since the ceiling is on the input and
|
||
the 256 KB limit applies to the output.
|
||
|
||
Telegram allows 120 stickers per pack and 1–20 emoji per sticker. Emoji come
|
||
from the command's arguments, else the replied sticker's own emoji, else `⭐`.
|
||
There is no documented file-size limit for static stickers; the code applies its
|
||
own client-side ceiling and never presents it as a Telegram rule.
|
||
|
||
## Behaviour worth knowing
|
||
|
||
**The reply carries no sticker count.** Nothing is stored, and reading the count
|
||
back would mean a `getStickerSet` call that returns the entire set on every add.
|
||
|
||
**Download errors are never echoed.** A Telegram file URL embeds the bot token,
|
||
and every transport failure from the HTTP client formats that URL into its error
|
||
text. The download path replaces all of them with one opaque error and logs only
|
||
a coarse type label, so no failure mode can print the token.
|
||
|
||
**Telegram's refusals are translated, not forwarded.** `STICKERS_TOO_MUCH`,
|
||
`STICKERSET_INVALID` and the emoji errors get sentences a user can act on;
|
||
anything unrecognised becomes a generic line plus an ERROR log.
|
||
|
||
The handler runs under a 10-second deadline, with the download-and-upload leg
|
||
bounded inside it so the reply always has budget left. The bot processes updates
|
||
one at a time, so that bound is what keeps an image conversion from stalling
|
||
other users.
|