A personal Telegram bot built on Telegram's Guest Mode. You can tag the bot anywhere — group, DM, anyone's chat — and it will reply only to you.
Powered by Claude. Pick your backend:
- Claude Code (uses your existing
claude login— no extra API key) - Anthropic API (pay-as-you-go API key)
You tag @yourbot in any chat
↓
Telegram sends `guest_message` update → your server (HTTPS via Caddy)
↓
Bot checks `from.id == OWNER_USER_ID` ← anyone else: silent drop
↓
LLM (Claude) generates reply, optionally with web search
↓
Bot calls `answerGuestQuery` → message appears in the chat
The bot is invisible to anyone except you. Strangers tagging it get nothing.
- A small Linux server (1 vCPU, 1 GB RAM is plenty) with Docker + docker-compose
- A domain you control, with ability to add an A-record
- One of:
- Claude Pro/Max subscription +
claudeCLI installed and logged in on the server, or - Anthropic API key from console.anthropic.com
- Claude Pro/Max subscription +
In Telegram, open @BotFather:
/newbot→ follow prompts → save theBOT_TOKEN/mybots→ choose your bot → Bot Settings → Guest Mode → Enable
DM @userinfobot. It will reply with your numeric id.
Add a DNS A-record:
bot.example.com A <your server's public IP>
git clone https://github.com/scrm77/tg-guest-bot.git
cd tg-guest-bot
./setup.shsetup.sh will ask for BOT_TOKEN, your user id, your domain, and which LLM
backend to use, then write a .env (chmod 600) and a random webhook secret.
docker compose up -dThis starts two containers: the bot, and Caddy (which auto-issues a Let's Encrypt certificate). First start may take ~30 s for the SSL handshake.
Sanity check:
curl https://bot.example.com/healthz # → "ok"python3 -m pip install httpx python-dotenv
python3 register_webhook.pyOr, if you'd rather not install anything locally:
docker compose exec bot python register_webhook.pyTag your bot in any chat:
@yourbot what time is it in Tokyo?
You get a reply. Anyone else tagging the bot — silence.
All settings live in .env. See .env.example for the full
list with comments. Highlights:
| Variable | What |
|---|---|
BOT_TOKEN |
From @BotFather |
OWNER_USER_ID |
Only this user's messages are processed |
WEBHOOK_SECRET |
Auto-generated by setup.sh |
PUBLIC_URL, PUBLIC_HOST |
Your domain |
LLM_BACKEND |
claude-code or anthropic-api |
CLAUDE_CONFIG_DIR |
Path to your ~/.claude (claude-code backend only) |
ANTHROPIC_API_KEY |
API key (anthropic-api backend only) |
ANTHROPIC_MODEL |
Default: claude-sonnet-4-6 |
Three layers protect you from someone else triggering your bot:
- Owner filter —
bot.pyrejects anyguest_messagewhosefrom.idisn't yourOWNER_USER_ID. Reply is never generated, Claude is never called, Telegram is never re-contacted. Just a 200 OK with a log line. - Webhook secret — Telegram sends
X-Telegram-Bot-Api-Secret-Tokenon every webhook call; we verify it. Nobody who learns the URL can spoof updates. - HTTPS — Caddy auto-issues Let's Encrypt certs; webhook traffic is end-to-end encrypted.
from.id cannot be forged by other users because the update arrives directly
from Telegram's servers (validated by the secret token).
You can extend the bot beyond plain web search. A few patterns:
- Local wiki / notes: bind-mount a directory into the container and let
the bot read it (the
Readtool is already allowed in the claude-code backend). - Calendar / Notion / Gmail: if you use the claude-code backend, any
Claude connectors you've authenticated on
your account become available to the bot. Add the relevant
mcp__*tool names to the_ALLOWED_TOOLSlist inbot.py. - Personal context: put facts about yourself in
SYSTEM_PROMPTinbot.pyso the bot doesn't have to ask twice.
curl https://your.domain/healthzhangs or fails — DNS hasn't propagated yet, or Caddy hasn't gotten the cert. Checkdocker compose logs caddy.- Telegram says "Webhook was set" but the bot doesn't reply — check
docker compose logs bot. Common causes: wrongOWNER_USER_ID, wrongBOT_TOKEN, or Guest Mode isn't enabled in BotFather. claude-codebackend errors about credentials — make sure yourCLAUDE_CONFIG_DIRactually contains.credentials.jsonand that the bind-mount path indocker-compose.ymlis correct.
MIT — see LICENSE.