Send social-media videos inline in any Telegram chat.
No files on disk, no ffmpeg, no yt-dlp — just forward the link.
Type a social-media URL into Telegram's inline mode in any chat → the bot replies with a playable inline card. Tap it — media is fetched through a pool of cobalt servers and the message becomes the actual video, photo carousel, or audio.
All media stays in RAM. Nothing is written to disk on the bot host.
Videos, photo carousels, GIFs, and audio tracks are all supported. Carousel items have ⬅️ ➡️ navigation, a 🎵 toggle when there's a separate audio track, and a 🔄 retry if delivery fails.
YouTube is explicitly blocked. Cobalt needs logged-in YouTube cookies
or a residential proxy to bypass bot-check, and we don't ship either.
Posting a youtube.com or youtu.be link returns
Link not supported 😩 instead of silently failing.
Everything else that isn't in the list above (Reddit, Facebook, random sites, etc.) gets the same rejection at parse time — no wasted cobalt calls.
Telegram user
│ inline query: https://tiktok.com/…
▼
spiw-bot ──► metadata resolver (tiktok / instagram / x / threads)
│
│ on tap
▼
Cobalt pool ──► self-hosted cobalt (fast, private)
│ ↳ if rate-limited, fall through to
│ community instances from cobalt.directory
▼
RAM buffers ──► Telegram edit-message
- Inline query hits our resolver whitelist; unsupported URLs are rejected immediately.
- Tap starts the real work: cobalt resolves the URL to a media
tunnel, we stream the bytes into RAM (hard cap, default 2 GB), probe
them with
mediainfo.js, and edit the inline message to the actual media. - Cobalt pool with fallback — our primary instance is self-hosted.
When TikTok/Instagram rate-limit our data-center IP, the bot
auto-falls through to community cobalt instances discovered via
cobalt.directory. Endpoints
that return
auth.jwt.missingor HTTP 403 are auto-banned until the next hourly refresh.
Unlike 99% of Telegram bots this one does not use the HTTPS Bot API.
It talks native MTProto through mtcute — the
same protocol the official Telegram apps use. That matters:
- Big files. Bot API caps uploads at 50 MB. MTProto lets us push up to 2 GB per file (4 GB for Premium). No "file too large" when forwarding a 100 MB TikTok.
- Faster inline. Updates flow over a persistent MTProto connection, not long-polled HTTPS — inline cards appear and edit with minimum round-trip latency.
- Direct data-center upload. Media goes straight to Telegram's file DCs, bypassing the Bot API proxy layer entirely. Less copying, less bandwidth, less lag.
- Full Telegram capability. Features the Bot API exposes slowly or not at all (stable inline edits, large-file chunking, rich message operations) are available natively.
In short: it's not a toy wrapper — it's a real Telegram client that happens to answer as a bot.
# 1. clone + install
git clone https://github.com/CrashSystemZ/spiw-bot
cd spiw-bot
npm install
# 2. configure
cp .env.example .env
# fill in BOT_TOKEN, TG_API_ID, TG_API_HASH
# 3. run
docker compose up -dFor local dev without Docker:
npm run dev # tsx watch, hot-reloadThe bot reads env via Zod; unset optional vars get sane defaults.
| Variable | Required | Default | What it does |
|---|---|---|---|
BOT_TOKEN |
✅ | — | Telegram bot token |
TG_API_ID / TG_API_HASH |
✅ | — | MTProto app credentials |
COBALT_BASE_URL |
✅ | — | Primary cobalt instance URL |
COBALT_DISCOVERY_ENABLED |
true |
Enable community fallback pool | |
COBALT_DISCOVERY_SERVICES |
tiktok,instagram |
Which service categories to pick from cobalt.directory | |
COBALT_DISCOVERY_MAX |
5 |
Max dynamic endpoints to keep | |
MEDIA_BUFFER_BUDGET_BYTES |
2 GB |
LRU session cache size | |
MAX_CONCURRENT_JOBS |
32 |
Parallel session builds |
All user-facing strings live in
resources/messages.json — edit them without
rebuilding.
src/
├── telegram/ mtcute dispatcher, handlers, UI builders
├── use-cases/ request-flow, session-flow, details-flow
├── core/ runtime, cobalt client & pool, errors, logger
│ ├── metadata/ platform resolvers (whitelist lives here)
│ └── db/ drizzle schema + SQLite client
└── adapters/ metadata gateway, media session builder, repos
Layered, dependency-injected, testable. SpiwRuntime is the DI
root; nothing reads globals except the validated env object.
- MTProto:
mtcute— Telegram client+dispatcher - DB: SQLite (WAL) +
drizzle-orm - Media resolver:
cobaltwith a dynamic pool fromcobalt.directory - Cache:
lru-cachewith byte-budget eviction - Concurrency:
p-limitat two tiers - Config:
zodvalidation at startup
- Platforms can break overnight. TikTok and Instagram actively fight scrapers. If the whole cobalt ecosystem is down, so are we.
- Community cobalt endpoints are unreliable. They can go offline, enable Turnstile, or hit rate limits. The bot auto-bans failing ones until the next refresh, but can't make them work.
- Files > 2 GB won't be sent. Hard cap to protect RAM. Telegram itself also has upload limits (50 MB via Bot API; we use user-client, so higher, but not infinite).