# telegram-sticker-downloader Download Telegram sticker packs to your disk — one folder per pack, in the file formats you ask for. - Downloads static (`.webp`), animated (`.tgs`), video (`.webm`) stickers and PNG thumbnails, filtered to whichever types you want. - Downloads files **in parallel**, with retries and Telegram rate-limit handling. - **Resumable**: files already on disk are skipped without spending API calls. - Writes a `pack.json` per pack with each sticker's emoji, size and file names. - Works as a scriptable CLI *and* as the original interactive prompt. ## Requirements - Python 3.10 or newer - A Telegram **bot token** (free, takes a minute — see below) ## Install ```bash git clone https://git.hiddenden.cafe/Hiddenden/telegram-sticker-downloader cd telegram-sticker-downloader python3 -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate pip install -e . # installs the `tg-stickers` command # or, without installing the package: pip install -r requirements.txt ``` ## Get a bot token 1. Open Telegram and message [@BotFather](https://t.me/BotFather). 2. Send `/newbot` and follow the prompts. 3. Copy the token it gives you. 4. Save it next to the project: ```bash cp example.env .env # then edit .env: # TELEGRAM_BOT_TOKEN=123456:ABC-DEF... ``` The token can also come from the `TELEGRAM_BOT_TOKEN` environment variable or from `--token`. You do not need to add the bot to any chat — reading public sticker packs is enough. ## Usage ```bash # One pack tg-stickers https://t.me/addstickers/SomePack # Several packs, only the static images and thumbnails tg-stickers SomePack AnotherPack --types webp,png # Everything listed in urls.txt, 16 files at a time tg-stickers --from-file urls.txt --concurrency 16 # From a pipe printf 'PackOne\nPackTwo\n' | tg-stickers --from-file - # See what would happen without writing anything tg-stickers --from-file urls.txt --dry-run # No arguments: the original interactive prompts tg-stickers ``` Without installing the package, the same thing works as `python -m sticker_downloader ...` or `python app.py ...`. ### Pack references Anything recognisable is accepted, so you can paste straight from Telegram: ``` https://t.me/addstickers/SomePack t.me/addstickers/SomePack https://telegram.me/addstickers/Pack tg://addstickers?set=SomePack https://t.me/addemoji/SomePack SomePack ``` In a `--from-file` list, blank lines and `#` comments are ignored, and duplicate packs are downloaded only once. ### Options | Option | Description | | --- | --- | | `PACK ...` | One or more pack URLs / names | | `-f, --from-file PATH` | Read references from a file, one per line (`-` for stdin) | | `-t, --types LIST` | `webp`, `tgs`, `webm`, `png`, comma-separated, or `all` (default: `all`) | | `-o, --output DIR` | Where packs are written (default: `downloads`) | | `-c, --concurrency N` | Files downloaded in parallel per pack (default: `8`) | | `--retries N` | Attempts per network call (default: `3`) | | `--overwrite` | Re-download files that already exist | | `--emoji-names` | Put each sticker's emoji in its file name | | `--no-metadata` | Skip writing `pack.json` | | `--no-pack-thumbnail` | Skip the pack's own cover image | | `--dry-run` | Report what would be downloaded, write nothing | | `--token TOKEN` | Bot token (overrides the environment) | | `--env-file PATH` | dotenv file to read the token from (default: `.env`) | | `-v, --verbose` | Log every file individually | | `-q, --quiet` | Only report fatal errors | | `--version` | Print the version | ### File types | Type | What it is | Notes | | --- | --- | --- | | `webp` | Static stickers | Regular image, opens anywhere | | `tgs` | Animated stickers | Gzipped Lottie JSON, not a video file | | `webm` | Video stickers | VP9 with alpha | | `png` | Thumbnails | Small preview Telegram generates per sticker | A sticker has exactly one primary type — `--types tgs` on a pack of static stickers downloads nothing but the thumbnails you asked for. ## Output ``` downloads/ └── SomePack/ ├── 001.webp ├── 001_thumb.png ├── 002.webp ├── 002_thumb.png ├── _pack_thumbnail.webp └── pack.json ``` Files are numbered in pack order. With `--emoji-names` they become `001_🦊.webp`. `pack.json` records what was downloaded: ```json { "pack": { "reference": "https://t.me/addstickers/SomePack", "name": "SomePack", "title": "Some Pack", "sticker_type": "regular" }, "generated_at": "2026-07-27T12:00:00+00:00", "downloader_version": "1.0.0", "requested_file_types": ["webp", "png"], "sticker_count": 2, "stickers": [ { "index": 1, "emoji": "🦊", "file_unique_id": "AgADBAADwqbcCw", "type": "regular", "width": 512, "height": 512, "is_animated": false, "is_video": false, "files": ["001.webp", "001_thumb.png"] } ] } ``` ## How it works Packs are processed one at a time so progress stays readable; the files inside a pack download concurrently, bounded by `--concurrency`. - **Resume.** Existing files are detected *before* any API call, so re-running a large batch costs almost nothing. Use `--overwrite` to force a refresh. - **Atomic writes.** Files land via a temporary `.part` file, so an interrupted run never leaves a half-written sticker that a later run would skip. - **Retries.** Timeouts, connection errors and HTTP 429/5xx are retried with exponential backoff; Telegram's own `retry_after` hint is respected. Genuine errors (unknown pack, bad token) fail immediately instead of being retried. - **Partial failure is not fatal.** A file that cannot be downloaded is reported and the rest of the pack continues; the run's exit code reflects it. ### Exit codes | Code | Meaning | | --- | --- | | `0` | Everything downloaded | | `1` | One or more packs or files failed | | `2` | Bad arguments, missing/rejected token, or aborted | ## Development ```bash pip install -r requirements-dev.txt pytest # test suite ruff check . # lint ruff format . # format ``` The tests use fakes for the Telegram bot, plus a real local HTTP server for the download layer, so the whole suite runs offline in well under a second. `.gitea/workflows/ci.yml` runs lint and tests on Python 3.10–3.13. It needs Gitea Actions enabled on the repository and a registered `act_runner`; without a runner the file is simply ignored. Layout: | Module | Responsibility | | --- | --- | | `sticker_downloader/cli.py` | Argument parsing, interactive prompts, wiring | | `sticker_downloader/downloader.py` | Planning and downloading a pack | | `sticker_downloader/urls.py` | Pack-reference parsing | | `sticker_downloader/config.py` | Settings and file-type handling | | `sticker_downloader/fetcher.py` | HTTP layer behind a small protocol | | `sticker_downloader/retry.py` | Retry policy and backoff | | `sticker_downloader/progress.py` | Console reporting | | `sticker_downloader/results.py` | Result and totals objects | ## Limitations - The Bot API only serves files up to 20 MB. Stickers are far smaller, so this is not normally a concern. - `.tgs` and `.webm` are downloaded as-is; no conversion to GIF/APNG is done. - Private or deleted packs cannot be read, and are reported as `sticker pack not found`. ## License MIT — see [LICENSE](LICENSE).