Files
2026-07-27 15:16:01 +02:00

228 lines
7.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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).