A fast, stateless Telegram bot that turns raw media links into native, Telegram-friendly media.
Twitter/X Β· Pixiv Β· Bluesky Β· TikTok Β· Instagram Β· Bilibili Β· YouTube β pasted as a link, delivered as playable media.
- Why Daphne
- Features
- Supported Platforms
- Commands & Modes
- Quick Start
- Configuration
- Access Control (RBAC)
- Development
- Architecture
- License
Most link-downloader bots pipe every URL through a single blind yt-dlp call. Daphne instead uses purpose-built, login-wall-bypassing handlers per platform for clean native rendering, and falls back to a multi-engine downloader (yt-dlp β you-get β lux) only where it helps. It keeps zero persistent state by default β no database, secrets in the environment, everything else in a single config.toml. An optional Valkey connection can back live RBAC edits (see RBAC.md); everything else stays stateless.
- π― Per-platform extractors β bespoke handlers render photos, GIFs, videos, and carousels natively instead of dumping a link.
- π¬ Multi-engine video downloads β
yt-dlpβyou-getβluxfallback with automatic dimension/duration probing and truncation detection (a partial download is retried on the next engine rather than accepted). - π§ Audio extraction β
/audio <link>pulls the audio track and encodes it to MP3 with performer/title metadata. - πΌοΈ Image galleries β
/gallery <link>fetches full galleries viagallery-dland posts them as chunked media groups. - β‘ Inline mode β
@daphne <link>converts Twitter/X, Instagram, and YouTube/Bilibili links from any chat (user-allowlisted). - π Role-based access control β multi-tenant RBAC by user and chat ID, configured in
config.tomlor, optionally, live-edited via Valkey and the admin-only/grant,/revoke,/rolescommands. See RBAC.md. - π¦ Concurrency guard β per-user and global download semaphores so one large transfer never starves the others; users see a Queuedβ¦ notice.
- π Live feedback β message reactions (π working β π done / π’ failed) plus
upload_video/upload_photo/upload_audiochat actions. - π¨ Rich HTML captions β title, uploader, duration, source link, platform tag, and requester attribution.
- π Safety-first β the original message is deleted only after a successful conversion and upload, so links are never lost on error.
| Platform | Method | Media | Inline |
|---|---|---|---|
| Twitter / X | FxTwitter API | Photos, GIFs, videos | β |
parth-dl (GraphQL) |
Images, carousels, reels | β | |
| YouTube Β· Bilibili Β· b23 | yt-dlp / you-get / lux |
Video downloads | β |
| Pixiv | Artwork/gallery resolver | Photo / media groups | β |
| Bluesky | XRPC identity + HLS parsing | Image carousels, videos | β |
| TikTok / Douyin | TikWM API (+ yt-dlp fallback) |
Direct video | β |
| Image galleries | gallery-dl (/gallery) |
Batched photo groups | β |
Pixiv is intentionally excluded from inline: its CDN rejects hotlinking (requires a
Refererheader), and inline results hand Telegram a bare URL to fetch β so it works in-chat only.
| Command | Description |
|---|---|
| Paste a link | Auto-detect the platform and convert it in the chat |
/start |
Welcome message; answers even before RBAC has whitelisted you |
/audio <link> |
Extract the audio track as MP3 |
/gallery <link> |
Download an image gallery and send it as media group(s) |
/help |
Show usage |
@daphne <link> |
Inline mode β convert from any chat (requires inline_convert) |
Admins get three additional commands (/grant, /revoke, /roles) β deliberately left out of Telegram's / picker for everyone else. See RBAC.md.
Daphne uses uv for dependencies and runs cleanly in containers (Podman / Docker) with an optional local Telegram Bot API sidecar for 2 GB uploads.
# 1. Generate local config + env templates (untracked)
make init-local
# 2. Fill in your bot credentials and allowed user/chat IDs
# .daphne.local.env and .daphne.config.local.toml
# 3. Spin up the stack (bot + local Bot API sidecar)
make up
# 4. Tear it down
make downFor inline mode, enable it once with @BotFather β /setinline.
| Variable | Purpose |
|---|---|
DAPHNE_BOT_TOKEN |
Bot token from @BotFather |
TELEGRAM_API_ID / TELEGRAM_API_HASH |
Credentials for the local Bot API sidecar |
Non-secret runtime settings live in config.toml:
[app]
# telegram_api_url = "http://localhost:8081"
video_upload_limit_mb = 256
# Heavy-download concurrency guard (yt-dlp / gallery-dl):
# max_concurrent_downloads = 3 # across the whole bot
# max_user_concurrent_downloads = 1 # per user
[rbac]
public_commands = ["help"]
[rbac.roles.admin]
permissions = ["*"]
# Example non-admin roles, graduated from minimal to full. Available
# permissions: convert_link, fetch_metadata, preview_video, download_video,
# extract_audio, download_gallery, inline_convert.
# [rbac.roles.default]
# permissions = ["convert_link"]
# [rbac.roles.power_user]
# permissions = ["convert_link", "preview_video", "fetch_metadata", "extract_audio", "download_video"]
[rbac.users]
# 111111111 = "admin" # Replace with your Telegram user ID
[rbac.chats]
# -1001111111111 = "power_user" # Replace with your chat IDIf a detected video exceeds video_upload_limit_mb, Daphne replies with a decorated HTML info card (with a direct-download button) instead of uploading the file.
Daphne resolves access in order: admin bypass β public commands β chat-level role β user-level role. Full authorization flows and fallback mechanics are documented in RBAC.md.
Important
Inline mode is user-level only. Telegram provides no chat_id for inline queries, so the chat tier of RBAC can never apply. Grant inline_convert via [rbac.users] (or admin), not [rbac.chats]. This is intentional β inline runs from anywhere in Telegram, outside any group boundary.
make fmt # Format code (ruff)
make lint # Lint check (ruff)
make test # Run unit tests
make ready # fmt + lint + test + container smoke build- Nix-first tooling from the devShell;
uvfor the Python runtime. - Every
subprocesscall to an external downloader (yt-dlp,you-get,lux,gallery-dl,ffprobe) carries an explicit timeout to protect the executor pool.
- Python 3.12+,
python-telegram-bot, polling-based. - Stateless: no database; secrets in env, everything else in
config.toml. - Deployed as a systemd user service or a Podman/Docker stack with a local Bot API sidecar (TZ
Asia/Tokyo, no token-bearing HTTP logs).
Released under the MIT License.