Send a voice or a video circle. Get the text.
A small Go Telegram bot that transcribes with Deepgram.
Quick Start · Using Voicy · How It Works · Configuration · Security · Docs
Voice notes and video circles are fast to send and slow to read later. Pasting them into a separate transcriber breaks the chat, and a bot that talks on every group voice becomes noise.
Voicy keeps the first alpha deliberately narrow:
| Need | Voicy approach |
|---|---|
| One action | Send a voice or circle, get the text back |
| Quiet groups | Transcribes only on /v or /vp as a reply |
| Private ask | /vp is ephemeral and visible only to the requester |
| Cheap repeats | Caches by Telegram file_id; audio stays on Telegram |
| Familiar stack | Go, first-party Bot API HTTP, shared Asterfield core |
Result: one small service that turns speech already in Telegram into readable text without becoming a group chatterbot.
| Channel | Version | Meaning |
|---|---|---|
| Latest | v0.0.1-beta.1 |
Beta: 16 interface languages, audio files and video, streamed media |
| Stable | — | Not yet. This line is pre-release until v0.0.1 |
The bot is live for limited testing as @voicyin_bot. Alpha means the Deepgram and Telegram paths work, but the public contract is still allowed to change.
You need Docker, a Telegram bot token from BotFather, and a Deepgram API key.
# 1. Copy local configuration
cp .env.example .env
# 2. Fill TELEGRAM_BOT_TOKEN and DEEPGRAM_API_KEY
$EDITOR .env
# 3. Start Voicy and PostgreSQL
docker compose up --buildVoicy runs startup migrations from migrations/ and records completed versions
in schema_migrations. Keep AUTO_MIGRATE=true for local development.
The bundled docker compose is for local development only. It runs a local
PostgreSQL and seeds a minimal shared core schema (deploy/core-init.sql) so
migrations that reference core.person / core.chat boot cleanly. In the
shared production deployment Voicy instead connects to the existing
core-postgres; use the production notes in
docs/releases.md.
- Open @voicyin_bot and send
/start. - In a private chat, send a voice message or a video circle.
- Wait for the transcript.
- In a group, reply to a voice or circle with
/v(everyone) or/vp(only you).
/start in a group is an ephemeral Bot API 10.2 command. Its prompt is visible
only to the user who invoked it. Bare voices in a group are ignored.
A file that Voicy has already transcribed is served from cache. The audio file
stays on Telegram; Voicy stores the file_id and the text. Long transcripts
use Bot API rich messages of up to 32,768 characters each and split into
multiple readable messages when needed. Voicy never sends transcript files.
Voicy is one Go service with PostgreSQL as its only durable store. Domain tables
— transcript cache, jobs, user stats, poll offset — live in a voicy
schema. Telegram identity and presence are delegated to a shared core schema
(core.person, core.chat), which Voicy upserts via core.touch before any
dependent write. In production that schema lives in the shared core-postgres
database; local docker compose seeds a minimal core schema so development
boots the same way.
telegram poller -> worker pool -> decide -> cache or Deepgram
http server -> /healthz
One batch of updates is fanned out across MAX_CONCURRENT_JOBS workers, so a
long recording no longer holds up everyone else. Updates from the same person
stay on one worker, in order.
Transcription path:
getFile -> bounded download
-> Deepgram prerecorded POST /v1/listen
-> persist file_id + text
-> send rich HTML message
| Included | Excluded |
|---|---|
| Voice notes, circles, audio files, video | Files with no sound track |
Deepgram nova-3 prerecorded Listen |
Whisper or another STT |
DM implicit for speech; /v for files and video |
Auto-transcribe every group voice |
file_id cache |
Stored audio bytes |
/healthz |
Public metrics surface in this alpha |
| Variable | Required | Default | Description |
|---|---|---|---|
TELEGRAM_BOT_TOKEN |
yes | — | Bot token from BotFather |
TELEGRAM_API_BASE |
no | https://api.telegram.org |
Bot API server; a self-hosted one lifts the 20 MB media ceiling |
BOT_API_FILES_DIR |
with a self-hosted server | — | That server's media directory as Voicy sees it (its --files-dir, or its --dir on a server without one). It runs with --local, so it hands over files by absolute path and serves none over HTTP; mount only <dir>/<token>, and create that directory owned by uid 101 first |
TELEGRAM_READY_WAIT |
no | 30s |
How long startup waits for a self-hosted server that is still booting |
DEEPGRAM_API_KEY |
yes | — | Deepgram project API key |
DATABASE_URL |
yes | — | PostgreSQL URL for voicy_core; the service enforces search_path=voicy |
HTTP_ADDR |
no | :8080 |
HTTP listen address |
MIGRATIONS_DIR |
no | ./migrations |
Migration directory |
AUTO_MIGRATE |
no | true |
Run migrations on startup |
LOG_LEVEL |
no | info |
debug, info, warn, or error |
TRANSCRIPT_RETENTION |
no | 2160h |
Retain terminal jobs and cached transcripts since last use |
MAX_MEDIA_BYTES |
no | 20971520 |
Maximum Telegram media download size |
MAX_MEDIA_DURATION |
no | 1h |
Maximum voice or video-circle duration |
JOB_STALE_AFTER |
no | 30m |
Age at which an unfinished job is failed and reported as stuck |
FFMPEG_PATH |
no | ffmpeg |
Audio extraction binary; empty disables extraction |
EXTRACT_ABOVE_BYTES |
no | 20971520 |
Extract an audio track from non-video media above this size |
MEDIA_TMP_DIR |
no | system temp | Where media is staged while it is transcribed |
MAX_CONCURRENT_JOBS |
no | 4 |
Updates handled at once; one user is always served in order |
STATS_CACHE_TTL |
no | 5m |
How long a statistics snapshot is served before refreshing |
STATS_TIMEZONE |
no | Europe/Kyiv |
Zone the peak-hour statistic is bucketed in |
- Telegram is called over HTTPS with a first-party client. Transport errors redact the bot token.
- Deepgram requests use
Authorization: Token …andmip_opt_out=true. Telegram file URLs are never sent to Deepgram. - Empty and failed transcriptions are logged with
file_idandupdate_id, not audio bytes or full transcripts. - Logs avoid bot tokens, Deepgram keys, and full Bot API URLs.
Local Compose is not the production source. Production runs from
/opt/stacks/voicy on the shared core_net and the voicy_core
role. See docs/releases.md.
/healthz reports database status, Telegram initialization, polling freshness,
stuck jobs, job counts, and build metadata without exposing secrets. /metrics
serves process counters in Prometheus text format: polling failures, cache hits
and misses, Deepgram retries, delivery errors, and job failures by stage.
docker run --rm -v "$PWD":/src -w /src golang:1.26.6-alpine go test ./...
docker run --rm -v "$PWD":/src -w /src golang:1.26.6-alpine go vet ./...
cp .env.example .env
docker compose config| Document | Purpose |
|---|---|
| Architecture | Service structure and core decisions |
| Telegram behavior | Commands, privacy, rich messages, cache |
| Versioning | Pre-release and stable version line |
| Release process | Changelog and GitHub Release rules |
Releases · Changelog · Apache-2.0 · NOTICE
Voicy is open source software by Asterfield.
Copyright 2026 Asterfield.