Fast Telegram bot for downloading videos, photos, carousels, and audio from Instagram, TikTok, YouTube, Threads, Twitter/X, and Reddit.
Just drop a link in the chat — the bot will figure out the rest.
- Instagram — reels, posts (single video/photo), carousels
- TikTok — videos, photo slideshows with music, short links (vm.tiktok.com, vt.tiktok.com)
- YouTube — Shorts, regular videos, and youtu.be links
- Threads — posts (video/photo/carousel) from threads.com and threads.net
- Twitter / X — tweets with photos, multi-photo, videos, and animated GIFs
- Reddit — native images (
i.redd.it), galleries, native videos (v.redd.it, muxed with audio), short links (redd.it)
- Videos — sent as a video message with width/height/duration
- Photos — sent as a photo
- Carousels — sent as a media group (mixed photos and videos)
- Audio tracks — for TikTok photo slideshows, the original music is sent as a separate audio message with title/performer metadata; extension picked from the source mime (
mp3,m4a,aac,opus, etc.)
- Zero third-party APIs — talks directly to platform CDNs
- Cracks YouTube's BotGuard VM to mint PO tokens — works from datacenter IPs without login
- Solves TikTok's WAF challenge (SHA-256 proof-of-work) on the fly to bypass kmoat DRM
- Handles Instagram's
XDTGraph{Sidecar,Image,Video}typenames for carousels and photo posts - Parses DASH manifests and muxes video+audio via FFmpeg when platforms split streams
- Optional Redis cache of Telegram
file_ids — repeat requests deliver instantly with zero download (videos, photos, and audio all cached) - Railway-oriented pipeline with a custom Flow monad — no try/catch spaghetti
- Discriminated
Mediaunion (video | photo | audio | carousel) with exhaustive dispatch - Strategy pattern with automatic fallback per platform
- Runs on Bun, stays lightweight
bun installCreate .env:
TELEGRAM_BOT_TOKEN=your_token_here
# Optional: residential proxy for YouTube (datacenter IPs are aggressively blocked)
PROXY_URL=http://user:pass@host:port
# Optional: Redis for instant re-delivery of previously sent videos
REDIS_URL=redis://localhost:6379
Redis (optional) via docker-compose:
docker compose up -d redisRun:
bun run devThe bot extracts media URLs from platform APIs and CDNs, downloads them, and sends back to Telegram. No third-party services, no tracking, no bullshit.
Each platform has multiple extraction strategies that run in order — if one fails, the next picks up automatically.
Two strategies: embed and graphql. The embed page is faster but doesn't always have everything. GraphQL hits Instagram's internal API with generated CSRF tokens and device IDs — no login needed.
The GraphQL strategy dispatches on __typename (handling Instagram's newer XDT prefix as well as legacy values):
*GraphSidecar→ carousel; each child node is fetched and classified independently as photo or video*GraphImage→ single photo*GraphVideo→ single video (or DASH-muxed if video and audio are split)
Some reels split video and audio into separate DASH streams. When that happens, the bot parses the DASH manifest, picks the highest quality video track plus the audio track, and muxes them together with FFmpeg.
Talks directly to the InnerTube API (the same one the YouTube apps use). Two strategies: combined grabs a single stream with video+audio baked in. adaptive picks the best video and audio tracks separately and muxes via FFmpeg — kicks in when adaptive quality is higher than combined.
Each request cascades through a list of InnerTube clients ordered by reliability on datacenter IPs: android_vr → tv_simply → ios → mweb. On mux or CDN failure, the failed client is skipped and the next one runs — the player response is cached per video ID with in-flight deduplication.
YouTube blocks unauthenticated InnerTube requests from datacenter IPs with LOGIN_REQUIRED, and clients like ios/mweb require a PO (Proof of Origin) token. This bot mints one natively:
- Fetches a BotGuard challenge from Google's WAA API (
google.internal.waa.v1.Waa/Create) - Evaluates the BotGuard VM script inside a JSDOM sandbox
- Runs the challenge program to produce a snapshot + a
webPoSignalOutputcallback - Exchanges the snapshot for an integrity token via
Waa/GenerateIT - Uses the callback to mint a content-bound PO token per video ID
- Sends the token in
serviceIntegrityDimensions.poTokenon every InnerTube player request
No bgutils-js, no headless browser, no third-party APIs. The minter is cached for 11 hours and reused across requests.
Three strategies tried in order: web → ssr → short-url.
- web — fetches the full page with a desktop User-Agent; works for many videos without solving the WAF (returns non-kmoat CDN URLs +
tt_chain_tokencookie in one round-trip) - ssr — for posts gated by the WAF (most photo carousels and some videos); solves the challenge first
- short-url — last resort via the legacy embed endpoint
TikTok protects their videos behind two layers: a WAF challenge and kmoat DRM cookies. Most downloaders give up here or shell out to yt-dlp with device credentials.
The SSR strategy handles it natively:
- Fetches the post page — gets a WAF challenge instead of content
- Parses the challenge from the HTML (
#wcicookie name,#csbase64-encoded challenge data) - Solves the SHA-256 proof-of-work (finds
iwhereSHA256(prefix + i) == target) - Re-fetches with the solution cookie — gets the full SSR page with
__UNIVERSAL_DATA_FOR_REHYDRATION__ - Extracts media from the hydration data:
imagePost.images[].imageURL.urlListfor photo slideshows (path matches/photo/...)video.bitrateInfo[].PlayAddr.UrlListfor videos (non-kmoat CDN paths)music.{playUrl, title, authorName, duration}for the audio track on photo slideshows
- Downloads with
tt_chain_tokencookies from the SSR response
All server-side, no headless browser, no third-party APIs, solves in ~1ms.
Single strategy: ssr. Meta has fully gated their public GraphQL and /embed endpoints behind login walls, so the only reliable anonymous path is the SEO-exposed HTML.
- Fetch the post page (
/t/<code>) with a Googlebot User-Agent — Threads serves the full hydration JSON to search-engine crawlers for indexing, but a login-walled shell to anonymous browsers. - Scan
<script type="application/json" data-sjs>blocks for thecodeof the requested post, then walk the JSON to find the matching post object. - Caption is taken from
caption.text+user.username; carousels fromcarousel_media[].
Returns video, photo, carousel, or text (for text-only posts).
Single strategy: syndication. Twitter's /embed page no longer ships hydration data and the guest-token API requires headers Twitter has been progressively rejecting — both have stopped paying off in practice.
https://cdn.syndication.twitter.com/tweet-result?id=<ID>&token=<TOKEN>— the public embed endpoint used by the official tweet widget.- The token is deterministic from the tweet ID — derived via
((Number(id) / 1e15) * Math.PI).toString(36).replace(/(0+|\.)/g, ""), exactly what the JS widget computes. - Response has
mediaDetails[]with photo URLs and video variants (the highest-bitrate MP4 variant under 50 MB is HEAD-probed and picked).
Multi-photo tweets become carousels. Animated GIFs are treated as silent videos. Text-only tweets ship as text.
Two strategies: vredd for bare video URLs, json for everything else.
- vredd — for
v.redd.it/<id>URLs with no post context. Fetches the DASH manifest, picks the highest-bandwidth video track, finds the audio track, muxes via FFmpeg. - json (primary) — appends
.jsonto the URL (Reddit's public JSON endpoint, no auth, no token):is_gallery + gallery_data + media_metadata→ carousel of photosis_video + media.reddit_video.fallback_url→ DASH MP4, muxed withDASH_AUDIO_128.mp4(or64/audiofallbacks). If no audio track exists, ships video-only.url ∈ i.redd.it→ single photourl ∈ v.redd.it→ same as video case- Otherwise (external link, self post, etc.) → falls back to
textmedia with title + selftext + external URL.
Short URLs (redd.it/<id>) are resolved via the redirect Location header before extraction.
src/
├── bot/ # telegram handlers, pipeline transforms, sendMedia dispatcher
├── config/ # env validation (zod) and app constants
├── extractors/ # platform-specific extraction logic
│ ├── instagram/ # embed + graphql strategies (carousels, photos, videos)
│ ├── tiktok/ # web + ssr + short-url strategies (videos, photo slideshows + audio)
│ ├── youtube/ # combined + adaptive strategies, BotGuard PO token minter
│ ├── threads/ # ssr strategy (Googlebot UA bypass)
│ ├── twitter/ # syndication strategy
│ └── reddit/ # vredd + json strategies
└── lib/ # flow monad, http client, ffmpeg, cache, utilities
MIT
