Pahlavon Numonjonov Case study
downloader
← All work

downloader

Send a link, get the media back. Instagram, YouTube, Pinterest and roughly a thousand other sites.

Role
Solo — design, build, run
Timeline
2026 — present
Stack
Python · yt-dlp · gallery-dl
Status
Running

The problem

Someone sends you a Reel. You want to keep it. Between those two facts sit a dozen sites that would rather you did not, each with its own player, its own markup and its own reasons to change both next week.

The bot collapses that into one move: send a link, get the file back. Instagram reels, posts and carousels, YouTube video, Shorts and audio, Pinterest, and roughly a thousand other sites through yt-dlp.

Constraints and tradeoffs

Two tools, one decision

yt-dlp handles video platforms, gallery-dl handles image galleries. They overlap badly and fail differently. All the “what should download this?” logic lives in one module, so adding a site means editing one function rather than hunting through handlers.

The tools go stale, and that is normal

When a download breaks, the first move is to update yt-dlp and gallery-dl — not to read my own code. Platforms change their markup and their APIs on their own schedule, and the extractors follow days later. Treating breakage as an operations task instead of a bug saved me a lot of pointless debugging.

Downloading must not block the bot

A download is a subprocess that can run for minutes. Run it inside the update handler and the bot stops answering everyone — the same mistake I had already made in tekstlybot, which is why this one was built the other way round from the first version.

What I traded away

  • Telegram's upload limit stays. Large videos cannot be sent by a bot at all. Saying so plainly, with the actual number, beats a generic failure — and beats running a local Bot API server just for the rare big file.
  • No accounts, no cookies. Private and age-gated content needs a logged-in session. Storing someone's session is a liability I have no interest in holding.
  • No queue of pending links. Unlike tekstlybot this one is thin: a download either starts now or the user is told to wait. Adding a queue would be the next step, not the first.

Architecture

handlers.py a link arrives downloader.py what downloads this? one place, one rule yt-dlp video, audio gallery-dl galleries downloads/ sent, then deleted
One dispatcher decides the tool; everything downstream is the same path.

Screens and states

https://instagram.com/reel/… → instagram · gallery-dl → downloading… → sent (8.4 MB, 0:31)

Normal. The tool that was chosen is shown — when something breaks, that line is the first clue.

https://youtu.be/… [ video ] [ audio only ] → audio → mp3, 192 kbps → sent (4.1 MB)

YouTube. Two buttons instead of a settings menu — video or just the sound.

✗ 214 MB — Telegram does not let bots upload files this large. Try a shorter clip, or open the original link.

Too large. The platform limit named out loud, with something to do next.

$ ./bot.sh status running, pid 4821, up 6d $ ./bot.sh logs extractor failed: update yt-dlp

Operating it. One script for start, stop, status and logs, and a log that names the real cause.

Results and lessons

~1000
sites reachable, through two extractors
1
module decides what downloads what
0
downloads run in the update handler

What I would tell myself at the start

  • Put the routing decision in one place immediately. It started spread across handlers and every new site made it worse.
  • Update before you debug. Most “bugs” here are an extractor that has not caught up with a site yet.
  • Delete temporary files in a finally. The disk fills long before anything else complains.
  • Name the platform limit in the error. “Failed” generates a question; “Telegram does not allow bots to upload files this large” does not.