A Telegram bot on Cloudflare Workers that takes a video (upload or social link), transcribes the speech, translates it, and burns the translated captions back into the video.
- Worker (
src/index.ts) — handles the Telegram webhook, replies fast, and kicks off the real work in the background. - Workflow (
src/pipeline/workflow.ts) — the durable pipeline: fetch → extract audio → transcribe (chunked) → translate → burn → deliver → offer edit. Each stage can retry on its own without redoing the whole job. - Container (
src/media/container.ts,container/server.js) — runs ffmpeg, one instance per job, so files persist on disk between calls.
Per-video settings (language, transcriber, translation model, caption style, etc.) can be confirmed before a job starts, and a finished job can be re-run at four different depths (full, retranscribe, retranslate, or restyle only) from an edit card in the chat.
See CLAUDE.md for full architecture notes, key files, and known gotchas.
npm install
cp .dev.vars.example .dev.vars # fill in secrets, see below
npm run types # generate worker-configuration.d.ts| Variable | Purpose |
|---|---|
TELEGRAM_BOT_TOKEN |
Bot token from @BotFather |
TELEGRAM_WEBHOOK_SECRET |
Shared secret validating the Telegram webhook |
CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID |
Only for npm run usage |
GROQ_API_KEY / MISTRAL_API_KEY |
Only if STT_PROVIDER in wrangler.jsonc is set away from workers-ai |
DOWNLOAD_API_KEY |
Lets the bot fetch a video from a social link instead of an upload; unset means links are refused, uploads still work |
API_KEY |
Required for the external /api/jobs REST API (x-api-key header); unset closes that surface entirely |
Other settings (language/transcriber/model defaults, ADMIN_CHAT_ID, SUBTITLE_FONT, etc.)
live in wrangler.jsonc. Note ADMIN_CHAT_ID fails open — the bot is public until it's set.
npm run typecheck # tsc --noEmit — the only verification that runs on this machine
npm run dev # wrangler dev
npm run deploy # wrangler deploy (needs Docker for the container image)
npm run types # regenerate worker-configuration.d.ts from wrangler.jsonc
npm run set-webhook # point Telegram at the deployed worker; also publishes the ☰ menu
npm run usage # container usage + projected cost
npm run r2-lifecycle # one-time: expire jobs/ objects after 2 daysAll secret-gated with TELEGRAM_WEBHOOK_SECRET unless noted:
GET /healthGET /debug/fonts?secret=…— what libass can actually seeGET /telegram/commands?secret=…— republish the ☰ menu after changingCOMMANDSPOST /telegram/webhook/api/jobs— external REST API, gated by theAPI_KEYsecret (x-api-keyheader)
Docker isn't required for npm run typecheck, but building/deploying the container
(npm run deploy) does need it. Anything touching ffmpeg, burning, or fonts can't be
exercised without a deploy — see CLAUDE.md for details.