
Я написал бота на Python, используя библиотеки kurigram, yt‑dlp и ffmpeg.Данный бот умеет скачивать что‑то (в зависимости от площадки) из следующих платформ: YouTube, TikTok, Instagram, Pinterest, Rutube, VK, Spotify, Shazam, Yandex Music, SoundCloud, VK Music, Deezer, Apple Music, Tenor, JioSaavn, Twitch (только клипы), Snapchat, Reddit, Kinopoisk.
Функции:
Загрузка видео, аудио и миниатюр со всех поддерживаемых платформ через yt‑dlp.
Полный пользовательский интерфейс Telegram‑бота: встроенные клавиатуры для выбора формата/качества, кнопки отмены и история загрузок для каждого пользователя.
Inline режим — запуск загрузок из любого чата через @your_bot <ссылка или поисковый запрос>.
Резервный вариант поиска музыки: ссылки Deezer/Apple Music обрабатывают метаданные, а затем находят соответствующий трек через поиск YouTube.
Поддержка Кинопоиска для просмотра сезонов, эпизодов и вариантов озвучки/перевода.
Кэширование результатов (Postgres + Redis), поэтому повторные запросы обрабатываются мгновенно, а не загружаются повторно.
Настраиваемые одновременные обработчики загрузок и очередь загрузок с поддержкой отмены.
Встраивание аудиотегов/обложки (ID3, FLAC, MP4) через mutagen.
Встроенный HTTP‑сервер проверки работоспособности для контейнеров.
Готовность к Docker для простого развертывания.
Изначальная цель этого бота заключается в том, чтобы сделать большой комбайн, притом без рекламы и платных подписок, в отличие от множества других подобных ботов. Данный проект работает с апреля 2026 года.
Однако код открыл я только сейчас. Обусловлено это тем, что я хотел допилить его до более‑менее адекватного состояния.

В нем имеется поддержка двух языков (RU, EN).

Красивый, но простой дизайн. Загрузка видео до 4K, а также загрузка аудио от MP3 (192 kbps) до FLAC.

История запросов.

Очередь.

Конечно, без трудностей не обошлось. Пришлось реверсить некоторые платформы, чтобы использовать не веб‑скрейпинг, а конкретно их API. Это сделано для «Яндекс Музыки», «VK Музыки», «Кинопоиска» и Spotify. У последних двух это сделано частично, так как пришлось использовать Playwright.

Специально для Habr
Мне сказали рассказать больше технических подробностей. Чтож... А я и не против;)
1. Общая архитектура системы
Схема движения запроса от сообщения пользователя до выдачи готового медиафайла:

2. Реверс‑инжиниринг API: Spotify и Кинопоиск
Spotify Partner GraphQL без API‑ключей и платных подписок
Стандартный API Spotify требует OAuth‑авторизацию пользователя и имеет жесткие лимиты. Для бота реализован обход через внутренний Partner GraphQL API Spotify (api-partner.spotify.com):
Бот делает асинхронный HTTP‑запрос к публичной странице эмбеда трека/альбома (
open.spotify.com/embed/track/...).Из тега
<script id="__NEXT_DATA__">парсится JSON‑конфиг страницы, откуда вытаскивается анонимныйaccessToken.С этим токеном бот напрямую дергает эндпоинт
pathfinder/v2/queryс SHA256-хешами объявленных операций (getTrack,getAlbum,fetchPlaylistContents).После получения чистых метаданных (название, артисты, обложка HD) трек находится в сопряженных аудиоисточниках через движок поиска.
# Извлечение анонимного accessToken из Spotify Embed m = re.search(r'<script id="__NEXT_DATA__" type="application/json">([^<]+)</script>', resp.text) data = json.loads(m.group(1)) access_token = data["props"]["pageProps"]["state"]["settings"]["session"]["accessToken"] # Запрос к внутреннему GraphQL Spotify payload = { "variables": {"uri": f"spotify:track:{track_id}"}, "operationName": "getTrack", "extensions": { "persistedQuery": { "version": 1, "sha256Hash": "612585ae06ba435ad26369870deaae23b5c8800a256cd8a57e08eddc25a37294", } } }
Кинопоиск: деобфускация ссылок и сигнатур
Для Кинопоиска пришлось разбирать клиентские скрипты плееров и восстанавливать алгоритмы расшифровки потоков. В коде бота реализованы функции маппинга и математических перестановок символов по простым числам и битовым маскам:
def a5(s: str) -> str: """Перестановка символов строки s с использованием модульной арифметики по простым числам.""" ah = len(s) if ah <= 0: return s ax = _next_prime_above(ah) ac, seen, pos = [], [False] * ah, 0 while len(ac) < ah: pos = (pos + 2) % ax if pos < ah and not seen[pos]: ac.append(pos) seen[pos] = True result = [''] * ah for i in range(ah): result[ac[i]] = s[i] return ''.join(result)
И важно понимать! Я не разбирал сам сервис Кинопоиск, а лишь сторонние пиратские сервисы. Кинопоиск лишь выступает посредником для получения названия фильма или сериала. На удивление, да, у пиратских сервисов есть защита плеера.:P
3. Двухуровневое кэширование (PostgreSQL + Redis)
Чтобы не качать один и тот же популярный трек или ролик повторно (что расходует трафик сервера и время пользователя), используется система кэширования Telegram file_id:
Для любого входящего урла и выбранного качества/формата формируется уникальный медиа‑ключ:
media_key = sha256(f"{url}:{media_type}:{fmt}").Перед запуском
yt-dlpпроисходит быстрый чек в PostgreSQL/Redis.Если хэш найден в БД, бот мгновенно отправляет медиафайл пользователю через вызов
send_audio(chat_id, file_id)без повторного скачивания на диск и без повторной загрузки в Telegram. Время выдачи меньше 0.2 секунды.
class MediaCache(Base): __tablename__ = "media_cache" media_key: Mapped[str] = mapped_column(String(64), primary_key=True) file_id: Mapped[str] = mapped_column(Text, nullable=False) media_type: Mapped[str] = mapped_column(String(10), nullable=False) media_format: Mapped[str | None] = mapped_column(String(32)) title: Mapped[str | None] = mapped_column(Text) performer: Mapped[str | None] = mapped_column(Text)
4. Неблокирующий асинхронный пайплайн и защиту от FloodWait
Библиотека yt-dlp по своей природе синхронная и блокирующая. Вызывать её прямо в Telegram‑хэндлере нельзя. Это заблокирует весь event loop бота.
Вынос в ThreadPoolExecutor: Вызовы
YoutubeDL().download()выполняются в пуле потоков черезasyncio.to_thread().Троттлинг прогресс‑бара: При скачивании больших файлов
yt-dlpвызывает hook прогресса сотни раз в секунду. Если на каждый вызов обновлять сообщение в Telegram (edit_message_text), бот моментально поймает ошибку420 FLOOD_WAIT_X. Для решения сделан троттлер: статус в Telegram редактируется не чаще 1 раза в 2–3 секунды с проверкой флага отмены от пользователя (CancelFlag).
async def _download_in_executor(ydl_opts, url): loop = asyncio.get_running_loop() def _sync_download(): with yt_dlp.YoutubeDL(ydl_opts) as ydl: return ydl.extract_info(url, download=True) return await loop.run_in_executor(None, _sync_download)
5. Обработка аудио, FFmpeg и Mutagen
При скачивании музыки важно не просто получить файл, но и сделать так, чтобы в нативном плеере Telegram отображались правильная обложка, имя исполнителя и название альбома:
Транскодинг через FFmpeg: В зависимости от выбора пользователя (FLAC, MP3 320kbps, MP3 192kbps, M4A) запускается оптимизированный пайплайн конвертации.
Вшивание метаданных через Mutagen:
Для MP3: создаются теги ID3v2.3/v2.4 с фреймами
TIT2(Title),TPE1(Artist),TALB(Album) иAPIC(Cover Image).Для FLAC: используется
Picture()с Mime‑typeimage/jpeg.Для M4A / MP4: метаданные пишутся в атомы
©nam,©ART,covr.
# Пример вшивания обложки и тегов в MP3 через mutagen audio = MP3(filepath, ID3=ID3) try: audio.add_tags() except Exception: pass if thumbnail_data: audio.tags.add( APIC( encoding=3, mime='image/jpeg', type=3, # Front cover desc='Cover', data=thumbnail_data ) ) audio.save()
Однако мне нравится, что оно так вышло. Да, конечно, появляются вопросы: «А кому это надо?». Но, судя по всему, кому‑то да надо, раз такие боты существуют в огромном количестве. Лично я использую своего бота для скачивания треков.
Вы тоже можете принять участие в разработке. Я не говорю, что проект завершён, в нём определённо есть что допиливать (много чего...). Так что те, кому понравился бот и кто хочет добавить либо что‑то новое и интересное, либо исправить баги, могут отправлять PR‑запросы, и я их с радостью буду проверять и одобрять.
Тем же, кому всё это чуждо и кто просто хочет поскачивать что‑то, вы, конечно, можете зайти в Telegram и просто использовать бота;)
Github | Telegram Bot | мой тгк

Ну и спасибо вам за прочтение. ❤
Приятно оставить хоть и маленькую, но какую‑то историю.

