Skip to content

nokturnal3301/mirai-bot

Repository files navigation

Mirai-bot

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.

mirai-bot

Supported platforms

  • 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)

Supported media

  • 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.)

Highlights

  • 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 Media union (video | photo | audio | carousel) with exhaustive dispatch
  • Strategy pattern with automatic fallback per platform
  • Runs on Bun, stays lightweight

Setup

bun install

Create .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 redis

Run:

bun run dev

Requirements

  • Bun 1.0+
  • FFmpeg (for muxing video/audio streams)

How it works

The 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.

Instagram

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.

YouTube

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_vrtv_simplyiosmweb. 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:

  1. Fetches a BotGuard challenge from Google's WAA API (google.internal.waa.v1.Waa/Create)
  2. Evaluates the BotGuard VM script inside a JSDOM sandbox
  3. Runs the challenge program to produce a snapshot + a webPoSignalOutput callback
  4. Exchanges the snapshot for an integrity token via Waa/GenerateIT
  5. Uses the callback to mint a content-bound PO token per video ID
  6. Sends the token in serviceIntegrityDimensions.poToken on 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.

TikTok

Three strategies tried in order: webssrshort-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_token cookie 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:

  1. Fetches the post page — gets a WAF challenge instead of content
  2. Parses the challenge from the HTML (#wci cookie name, #cs base64-encoded challenge data)
  3. Solves the SHA-256 proof-of-work (finds i where SHA256(prefix + i) == target)
  4. Re-fetches with the solution cookie — gets the full SSR page with __UNIVERSAL_DATA_FOR_REHYDRATION__
  5. Extracts media from the hydration data:
    • imagePost.images[].imageURL.urlList for photo slideshows (path matches /photo/...)
    • video.bitrateInfo[].PlayAddr.UrlList for videos (non-kmoat CDN paths)
    • music.{playUrl, title, authorName, duration} for the audio track on photo slideshows
  6. Downloads with tt_chain_token cookies from the SSR response

All server-side, no headless browser, no third-party APIs, solves in ~1ms.

Threads

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 the code of the requested post, then walk the JSON to find the matching post object.
  • Caption is taken from caption.text + user.username; carousels from carousel_media[].

Returns video, photo, carousel, or text (for text-only posts).

Twitter / X

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.

Reddit

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 .json to the URL (Reddit's public JSON endpoint, no auth, no token):
    • is_gallery + gallery_data + media_metadata → carousel of photos
    • is_video + media.reddit_video.fallback_url → DASH MP4, muxed with DASH_AUDIO_128.mp4 (or 64/audio fallbacks). If no audio track exists, ships video-only.
    • url ∈ i.redd.it → single photo
    • url ∈ v.redd.it → same as video case
    • Otherwise (external link, self post, etc.) → falls back to text media with title + selftext + external URL.

Short URLs (redd.it/<id>) are resolved via the redirect Location header before extraction.

Project structure

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

License

MIT

About

The bot extracts video URLs from platform APIs and CDNs, downloads them, and sends back to Telegram. No third-party services, no tracking, no bullshit.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors