Anonymous one-on-one stranger chat, inside Telegram.
Whisper is a self-hosted Telegram bot for Omegle-style anonymous chat. Two people press one button, get matched in seconds, and talk — with no names, no usernames, and no way to find each other afterwards. Every piece of infrastructure you need is in this repository: the bot, the matchmaking engine, the moderation layer, the database schema, the migrations, and the deployment stack.
It is a complete rewrite of the original single-file bot, rebuilt for real production traffic: atomic matchmaking under concurrency, proper rate limiting, abuse handling, and a privacy model that stores no message content at all.
- Demo
- Features
- Architecture
- Privacy model
- Quickstart
- Configuration
- Commands
- How it works
- Testing
- Deployment & operations
- Contributing
- Security
- Roadmap
- License
- Credits
Coming soon. A ~30 second capture of two accounts being matched, exchanging a message and a photo, then leaving with
/next.Live instance: Telegram: @your_whisper_bot
| Instant matching | FIFO queue with an atomic Lua-scripted match claim — no double-pairing, even under concurrent load. |
| Full media relay | Text, photos, videos, voice notes, stickers, GIFs and documents, forwarded in both directions. |
| Queue transparency | Live position in the queue and a one-tap retry when no partner shows up in time. |
| One-tap exits | /next skips a partner and re-queues you; /stop ends the conversation. |
| Anti-poaching filter | Links, @usernames and configurable forbidden words are blocked before they reach your partner. |
| Abuse handling | Per-user rate limits, /report with automatic ban thresholds, and a permanent /block list. |
| Owner tooling | /admin_stats, /admin_ban and /admin_unban for moderation at scale. |
| Self-healing sessions | Active pairs survive a bot restart (Redis TTLs); a Redis flush fails safe with a user-facing notice. |
| Zero message retention | Only metadata is persisted. No message body, caption, or media ever touches the database. |
| Battery included | Docker Compose stack with Redis 7, PostgreSQL 16, healthchecks, Alembic migrations and CI. |
flowchart LR
A["User A<br/>(Telegram client)"] -->|Update| B
C["User B<br/>(Telegram client)"] -->|Update| B
B["Whisper<br/>aiogram 3 dispatcher<br/>middlewares: DI · DB · ban"]
B -->|"queue · sessions · rate limits<br/>atomic Lua matchmaking"| R[("Redis 7")]
B -->|"metadata only<br/>users · reports · bans · stats"| P[("PostgreSQL 16")]
B -->|"relayed messages"| T["Telegram Bot API"]
R -.->|"TTL expiry"| B
- Redis is the hot path: the matchmaking queue, live sessions, rate-limit counters and block lists. The match claim runs inside a Lua script, so pairing is atomic across every bot replica.
- PostgreSQL is the durable path and holds metadata only. Message content is never written, logged, or queued anywhere.
- The bot is stateless between requests, so it can be scaled horizontally behind a single Redis instance.
Whisper's design goal is that a conversation is ephemeral by construction, not by policy.
- No conversation content is ever stored. The relay forwards a message to the partner and forgets it. No message body, caption, filename, or media is written to PostgreSQL, Redis, or the logs.
- Identity is stored, conversations are not. The bot records your Telegram ID, username, display name and language code, plus a match counter and activity timestamps — needed for abuse handling. None of it is ever linked to what was said: that link is never created.
- Short-lived partner links. A live chat is two numeric IDs in a Redis session with a TTL, so it expires on its own. A report stores the reporter's ID, the reported ID, and the free-text reason the reporter typed.
- No discovery. A partner can never learn who you are, and you cannot find
a partner again once the session ends.
/blockmakes that explicit and permanent. - Transparency for users.
/privacyand/rulesstate what is stored and why, in the bot itself.
git clone https://github.com/mramitdas/Whisper.git
cd whisper
cp .env.example .env # set BOT_TOKEN and OWNER_ID
docker compose up -d --build
docker compose logs -f whisperGet a token from @BotFather; OWNER_ID is your
numeric Telegram user ID. The stack brings up the bot, Redis and PostgreSQL with
healthchecks — no other services required. The container applies pending
database migrations on start, so there is no separate migration step.
Requires Python 3.12+ and a reachable Redis and PostgreSQL.
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
export BOT_TOKEN=... # or put them in .env
export OWNER_ID=...
alembic upgrade head
python -m whisper.main # or: whisperwhisper # console script
python -m whisper.main # equivalent
docker compose up -d whisper # containerisedAll configuration is environment-driven (.env or your orchestrator). Every
variable has a working default except the two marked as required.
| Variable | Default | Purpose |
|---|---|---|
BOT_TOKEN |
required | Telegram bot token from @BotFather. |
OWNER_ID |
required | Numeric Telegram ID of the bot owner (admin commands). |
REDIS_URL |
redis://localhost:6379/0 |
Redis connection string. |
DATABASE_URL |
postgresql+asyncpg://bot:bot@localhost:5432/anonbot |
Async SQLAlchemy DSN. |
MATCH_TIMEOUT_SEC |
60 |
How long a user waits in the queue before being told no one is free. |
IDLE_TIMEOUT_SEC |
1800 |
Inactivity window after which a chat is closed automatically. |
SESSION_TTL_SEC |
86400 |
Lifetime of an active chat session in Redis. |
LASTPARTNER_TTL_SEC |
3600 |
How long a recent partner is remembered for /report and /block. |
MAX_REPORTS_BAN |
5 |
Unique reports from different users before an automatic ban. |
MSG_RATE_MAX |
5 |
Messages allowed per MSG_RATE_WINDOW seconds. |
MSG_RATE_WINDOW |
2.0 |
Message flood-control window, in seconds. |
SEARCH_RATE_MAX |
1 |
Searches allowed per SEARCH_RATE_WINDOW seconds. |
SEARCH_RATE_WINDOW |
3.0 |
Search rate-limit window, in seconds. |
MATCH_WINDOW |
50 |
How many queued users are scanned for a potential match. |
FORBIDDEN_WORDS |
[] |
Blocked words — comma-separated (spam,scam) or a JSON list. |
FORBIDDEN_WORDSis parsed by a custom validator, so bothFORBIDDEN_WORDS=spam,scamandFORBIDDEN_WORDS=["spam","scam"]work.
| Command | Description |
|---|---|
/start |
Welcome message and main menu. |
/search |
Join the matchmaking queue. |
/next |
Skip the current partner and search again. |
/stop |
Leave the queue or end the current chat. |
/report [reason] |
Report your recent partner (optional reason is stored as metadata). |
/block |
Block your partner permanently — you will never be matched again. |
/rules |
Community rules. |
/privacy |
What is stored, and what is not. |
/help |
Command reference. |
| Command | Description |
|---|---|
/admin_stats |
Users, matches, reports, queue depth and active bans. |
/admin_ban <user_id> |
Ban a user and tear down any live session. |
/admin_unban <user_id> |
Lift a ban. |
Matching. A user joining the queue is pushed onto a Redis sorted set scored by join time. Matchmaking runs as a single Lua script: it pops the oldest waiting user, creates a session with a TTL, and returns the pairing. Because the whole operation is atomic, two users can never be handed to the same partner, and a bot crash mid-match cannot leave a half-created session.
Sessions. A live chat is two user IDs plus a small metadata blob in Redis
with SESSION_TTL_SEC. The bot keeps no state in memory, so restarts and
deploys are transparent to users.
Moderation. Every outgoing message passes through the moderation filter
(links, @usernames, forbidden words) and a sliding-window rate limiter before
it is relayed. Reports are counted per unique reporter; crossing
MAX_REPORTS_BAN applies an automatic ban, tears down the reported user's live
session, and notifies their partner that the chat ended.
Idle sweep. A background task closes conversations that have been silent
for longer than IDLE_TIMEOUT_SEC and notifies both sides.
pytest # 58 unit/integration tests
ruff check . # linting
mypy whisper # type checkingThe test suite needs no external services — Redis is emulated with
fakeredis (including Lua execution) and the database with in-memory SQLite.
CI runs the same three gates on every push and pull request.
Full E2E requires a real bot token and two Telegram accounts. Verify:
-
/start,/help,/rules,/privacyrespond correctly -
/searchwith no partner times out afterMATCH_TIMEOUT_SECand offers a retry - Two accounts searching simultaneously are matched
- Text and every media type relay in both directions
-
/nextre-queues and re-matches;/stopnotifies the partner - Rapid messages trip the flood control
- Links and
@usernamesare blocked -
/blockprevents future matches -
/admin_stats,/admin_ban,/admin_unbanbehave correctly - Restarting the bot mid-chat preserves the pair
- The database contains metadata only — no message content
- Migrations are managed by Alembic. The container entrypoint runs
alembic upgrade headbefore the bot starts, so deployments are always on the current schema. For manual (non-Docker) deployments, runalembic upgrade headyourself;alembic revision --autogeneratecreates a new migration. - Healthchecks are wired for Redis and PostgreSQL in
docker-compose.yml; the bot waits for both to be healthy before starting. - Scaling: the bot is stateless, so you can run several replicas against one Redis and one PostgreSQL. The matchmaking Lua script uses dynamic keys and therefore targets standalone Redis, not Redis Cluster.
- Backups: only the PostgreSQL volume holds anything worth backing up. Message content does not exist anywhere, by design.
- Logs contain startup information and unhandled errors only — never message content.
Contributions are welcome. Please read CONTRIBUTING.md for the development setup, the quality gates every pull request must pass, and the privacy expectations we hold to. Participation is governed by the Code of Conduct.
Please do not report vulnerabilities through public issues. See SECURITY.md for private disclosure instructions.
- Web admin dashboard for moderation and analytics
- Optional locale-based matchmaking preferences
- Prometheus metrics endpoint
- End-to-end test harness against the Telegram Bot API
Whisper is open source software licensed under the Apache License 2.0.
Copyright 2026 Amit Das
Licensed under the Apache License, Version 2.0.
The original concept and the legacy implementation of this bot are by mramitdas. Whisper is a ground-up rewrite of that project, released under the Apache 2.0 license.