A Discord bot that transcribes voice messages using Faster Whisper (LARGEV3 turbo) via Runpod.
- Auto-transcription — automatically transcribes Discord native voice messages in guilds that have it enabled
- Context menu commands — "Transcribe" (public) and "Transcribe Privately" (ephemeral) on any message
- User-installable — context menu commands work in any server, even without bot presence
- Long transcription handling — threads for threadable channels,
.txtfile attachment fallback elsewhere - Opt-out — users can toggle themselves out of transcription globally via
/yapper ignore
- discord.js + @hijuno/botkit for command handling
- Drizzle ORM + PostgreSQL for persistence
- Hono for the webhook HTTP server
- Pino for logging
- OpenTelemetry (OTLP) for metrics
- Biome for linting and formatting
| Command | Description |
|---|---|
/yapper ping |
Latency check |
/yapper ignore |
Toggle opt-out of transcription (global) |
/yapper config auto-transcript-voice-messages |
Enable/disable auto-transcription for the guild (requires Manage Guild) |
| "Transcribe" (message menu) | Transcribe a voice message publicly |
| "Transcribe Privately" (message menu) | Transcribe a voice message ephemerally |
- Node.js 20+
- PostgreSQL database
- Discord bot token + application
- Runpod account with a Faster Whisper async endpoint
Create a .env file based on .env.example:
NODE_ENV=development
TOKEN= # Discord bot token
CLIENT_ID= # Discord application ID
DATABASE_URL= # PostgreSQL connection string
PORT=3000 # Hono server port
# Runpod
RUNPOD_ENDPOINT_ID= # Async endpoint ID (LARGEV3/turbo)
RUNPOD_API_KEY= # Runpod API key
CALLBACK_URL= # Publicly reachable base URL of this server (used for Runpod webhooks)
# Observability (optional)
OTEL_EXPORTER_OTLP_ENDPOINT= # OTLP collector endpoint
GUILD_LOG_WEBHOOK_URL= # Discord webhook for guild join/leave logspnpm install
pnpm migrate
pnpm run devpnpm build
pnpm startpnpm devdocker run --name mydb -e POSTGRES_PASSWORD=mysecretpassword -p 5432:5432 -d postgresThen run migrations:
pnpm migrateRunpod needs a publicly reachable URL to POST transcription results back to. Use a tunnel for local development:
Set CALLBACK_URL in your .env to the tunnel's base URL (e.g. https://<tunnel-subdomain>.trycloudflare.com).