A production-oriented Telegram bot for downloading media from major social platforms, with inline delivery, PostgreSQL-backed state, structured logging, and a queue designed for real-world load.
The project is built on aiogram 3, uses SQLAlchemy + Alembic for persistence, and supports both local development and Docker-based deployment.
/start to initialize the bot/settings to configure delivery behavior/stats to view usage statistics/admin/perf/sessionThe codebase is split into a few clear layers:
handlers contains Telegram message, callback, and inline-query flowsservices/platforms contains platform-specific media extraction and download logicservices/download contains the shared queue and worker logicservices/storage contains the database models, repositories, caching, and schema bootstrapservices/runtime contains transient runtime state such as dedupe, pending requests, and live statsmiddlewares contains antiflood, chat tracking, ban checks, and private-chat guard behaviortests covers handlers, runtime services, startup, storage, queue behavior, and deployment helpersgit clone https://github.com/Mak5er/Downloader-Bot.git
cd Downloader-Bot
pip install -r requirements.txt
For development and tests:
pip install -r requirements-dev.txt
Create a .env file in the project root:
BOT_TOKEN=your_telegram_bot_token
DATABASE_URL=postgresql://user:password@host:5432/dbname
ADMIN_ID=123456789
CUSTOM_API_URL=http://your-telegram-bot-api:8081
# Optional but recommended depending on your deployment/features
MEASUREMENT_ID=
API_SECRET=
CHANNEL_ID=
COBALT_API_URL=
COBALT_API_KEY=
# Optional YouTube access helpers for yt-dlp
YTDLP_YOUTUBE_COOKIES_FILE=cookies/youtube.txt
# Alternative to a cookie file, useful only when running on the same machine as the browser:
# YTDLP_YOUTUBE_COOKIES_FROM_BROWSER=firefox:Profile 1
# YTDLP_YOUTUBE_REMOTE_COMPONENTS=ejs:github
# YTDLP_YOUTUBE_PLAYER_CLIENT=web,android
# YTDLP_YOUTUBE_PO_TOKEN=web.gvs+your_token
# Performance tuning
BOT_POLLING_TASKS_CONCURRENCY_LIMIT=256
BOT_SESSION_CONNECTION_LIMIT=400
DB_POOL_SIZE=32
DB_MAX_OVERFLOW=64
DB_POOL_TIMEOUT=30
# Anti-flood and dedupe
ANTIFLOOD_MESSAGE_LIMIT=4
ANTIFLOOD_MESSAGE_WINDOW_SECONDS=2
ANTIFLOOD_CALLBACK_LIMIT=6
ANTIFLOOD_CALLBACK_WINDOW_SECONDS=2
ANTIFLOOD_INLINE_LIMIT=4
ANTIFLOOD_INLINE_WINDOW_SECONDS=3
ANTIFLOOD_GLOBAL_LIMIT=8
ANTIFLOOD_GLOBAL_WINDOW_SECONDS=3
ANTIFLOOD_COOLDOWN_SECONDS=6
ANTIFLOOD_USER_TTL_SECONDS=180
ANTIFLOOD_MAX_TRACKED_USERS=50000
REQUEST_DEDUPE_ACTIVE_TTL_SECONDS=900
REQUEST_DEDUPE_COMPLETED_TTL_SECONDS=12
REQUEST_DEDUPE_MAX_ENTRIES=50000
# Download queue tuning
DOWNLOAD_QUEUE_MIN_WORKERS=3
DOWNLOAD_QUEUE_MAX_WORKERS=7
DOWNLOAD_QUEUE_MAX_SIZE=250
DOWNLOAD_QUEUE_PER_USER_RATE_LIMIT=4
DOWNLOAD_QUEUE_PER_USER_WINDOW_SECONDS=10
DOWNLOAD_QUEUE_PER_USER_MAX_PENDING=3
DOWNLOAD_QUEUE_PER_USER_PENDING_TIMEOUT_SECONDS=0
DOWNLOAD_QUEUE_SCALE_COOLDOWN_SECONDS=8
DOWNLOAD_QUEUE_IDLE_SCALE_DOWN_SECONDS=35
DOWNLOAD_MAX_WORKERS_CAP=6
# Batch link tuning
BATCH_LINKS_MAX_ITEMS=6
BATCH_LINKS_MIN_CONCURRENCY=1
BATCH_LINKS_MAX_CONCURRENCY=2
BATCH_LINKS_PARALLEL_QUEUE_DEPTH_THRESHOLD=2
BATCH_LINKS_PARALLEL_ACTIVE_JOBS_THRESHOLD=3
python main.py
On startup the bot initializes the database schema via Alembic migrations automatically.
Some YouTube videos need an authenticated browser session for yt-dlp to work correctly. The bot automatically uses cookies/youtube.txt when that file exists. You can also override the path with YTDLP_YOUTUBE_COOKIES_FILE.
Recommended setup:
youtube.com in the browser profile that has a normal signed-in YouTube session..youtube.com / youtube.com in Netscape cookies.txt format.cookies/youtube.txt.YTDLP_YOUTUBE_COOKIES_FILE=cookies/youtube.txt in .env, or omit it and let the default path be used.yt-dlp logs n challenge solving failed or only shows storyboard/image formats, set YTDLP_YOUTUBE_REMOTE_COMPONENTS=ejs:github.yt-dlp options.The cookies directory is kept in git with cookies/.gitkeep, but real cookie files are ignored by git and Docker builds. Treat cookies/youtube.txt like a password: do not commit it, paste it in chats, or bake it into images.
For Docker Compose, the repository mounts ./cookies into the container as /app/cookies, so the same cookies/youtube.txt path works inside the container. The bot startup fixes ownership on that mount because yt-dlp may need to update the cookie jar.
The repository includes a production-friendly multi-stage Dockerfile and a minimal docker-compose.yml.
Run with the published image:
docker compose pull
docker compose up -d
Or build locally:
docker compose up -d --build
Notes:
ffmpeg is installed in the runtime imageFor a 4-core i5-7500, 16 GB RAM, and gigabit network, start with:
BOT_POLLING_TASKS_CONCURRENCY_LIMIT=192
BOT_SESSION_CONNECTION_LIMIT=300
DB_POOL_SIZE=24
DB_MAX_OVERFLOW=48
DB_POOL_TIMEOUT=30
DOWNLOAD_QUEUE_MIN_WORKERS=3
DOWNLOAD_QUEUE_MAX_WORKERS=7
DOWNLOAD_QUEUE_MAX_SIZE=250
DOWNLOAD_QUEUE_PER_USER_RATE_LIMIT=4
DOWNLOAD_QUEUE_PER_USER_WINDOW_SECONDS=10
DOWNLOAD_QUEUE_PER_USER_MAX_PENDING=3
DOWNLOAD_QUEUE_PER_USER_PENDING_TIMEOUT_SECONDS=0
DOWNLOAD_QUEUE_SCALE_COOLDOWN_SECONDS=8
DOWNLOAD_QUEUE_IDLE_SCALE_DOWN_SECONDS=35
DOWNLOAD_MAX_WORKERS_CAP=6
BATCH_LINKS_MAX_ITEMS=6
BATCH_LINKS_MIN_CONCURRENCY=1
BATCH_LINKS_MAX_CONCURRENCY=2
BATCH_LINKS_PARALLEL_QUEUE_DEPTH_THRESHOLD=2
BATCH_LINKS_PARALLEL_ACTIVE_JOBS_THRESHOLD=3
This keeps CPU-heavy downloader work bounded while still letting the queue scale up under real demand. Batch links run with limited parallelism only when the queue is healthy; under load they fall back to sequential processing automatically.
The bot writes both human-readable and structured logs:
logs/bot_log.loglogs/error_log.loglogs/events_log.jsonllogs/perf_log.jsonlThe logging layer adds request-scoped context such as service, flow, and request_id, which makes it easier to trace a single user action across fetch, queue, download, and delivery steps.
CUSTOM_API_URLCOBALT_API_URL and COBALT_API_KEY as neededRun tests with:
pytest
Issues and pull requests are welcome. If you change runtime behavior, settings, platform handling, or deployment flow, please update the README and related tests in the same change.
.
|-- handlers/
|-- services/
| |-- download/
| |-- inline/
| |-- links/
| |-- media/
| |-- platforms/
| |-- runtime/
| `-- storage/
|-- middlewares/
|-- keyboards/
|-- messages/
|-- filters/
|-- tests/
|-- main.py
|-- config.py
|-- Dockerfile
`-- docker-compose.yml
Public bot: @MaxLoadBot
If Downloader-Bot saves you time, you can support ongoing development and infrastructure costs:
TS4Ktovpwz9n2Ws8q9YXC3npW8gXi4QyYi0xE8F613484f84F1B70A777325771d3A3Ca33979Ab8pfgWjfvDUpmeszVXbRzbifFzUDzeNeGWuJf6HCcjAF70x9b38804F07A4ca4381a6Ef7F0022a3F4caBc5b6FUQBm9KPhtMw-XVVjirUoa09wzrlyWsbeZhKfefl1Uw-qNZ-rReleased under the MIT License.
$ claude mcp add Downloader-Bot \
-- python -m otcore.mcp_server <graph>