Telegram two-way chat bot · Web admin panel · Multi-storage backend · Multiple verification methods
Users DM the bot → auto-creates a forum topic → admin replies → message forwarded back to user.
Deploy via Cloudflare Pages or Docker. Storage supports KV / D1 / Hyperdrive (PostgreSQL & MySQL) with seamless switching.
Features · Quick Start · Deployment · Configuration · Verification · Message Filtering · Storage · Security · Local Dev · Troubleshooting
📨 Messaging
- Two-way message forwarding (user ↔ admin forum topics)
- Per-user forum topics
- Message edit sync
- Message deduplication (prevents duplicate forwarding)
- Supports all message types (text/photo/video/audio/file/sticker/location/poll etc.)
- Admin replies auto-forwarded back to user
🔐 Verification System (8 verification methods)
| Type | Key Required | Site URL Required | Description |
|---|---|---|---|
| Math | ❌ | ❌ | Random arithmetic, button selection |
| Image Numeric | ❌ | ✅ | 4-digit image captcha |
| Image Alphanumeric | ❌ | ✅ | 5-digit alphanumeric image captcha |
| Cloudflare Turnstile | ✅ | ✅ | Cloudflare invisible verification |
| Google reCAPTCHA v2 | ✅ | ✅ | "I'm not a robot" checkbox |
| Google reCAPTCHA v3 | ✅ | ✅ | Invisible score-based verification |
| hCaptcha | ✅ | ✅ | hCaptcha verification widget |
All verification methods support:
- Auto-timeout with notification and cleanup
- Max attempts limit
- Whitelist bypass
🛡️ Moderation
- Ban / unban / permanent ban
- User appeal flow (admin approval)
- Slash command filter (
/xxxcommands not forwarded to admin) - Zalgo abnormal text filter
- Keyword filtering (text and regex rules, ReDoS protection)
🖥️ Web Admin Panel
- Dashboard (total users, message statistics, bot info)
- Conversation history
- User management (ban/unblock/whitelist/delete)
- Whitelist management
- Full settings page (Bot/Webhook/Verification/Features/Filters/Welcome/Storage)
- Profile (change password/username/2FA)
🗄️ Storage System
- KV — Zero config, suitable for small-medium scale
- D1 — SQLite, supports SQL queries
- Hyperdrive — PostgreSQL / MySQL, suitable for production
- One-click switching with automatic data sync
- SQL import/export (plain / Base64 / AES-256-GCM encryption)
🌍 Internationalization
- Simplified Chinese / Traditional Chinese / English
- Bot message language and WebUI language configured independently
- Switch bot language via settings page
git clone https://github.com/milangree/Telegram_ChatBot.git
cd Telegram_ChatBot
docker compose up -dVisit http://localhost:3000. On first boot a temporary admin admin is created; the random password is printed in the server logs (log in immediately and complete first-time registration to disable the default account).
To set a specific admin password on first boot (avoid checking logs), add ADMIN_USERNAME and ADMIN_PASSWORD environment variables in docker-compose.yml — see the "Environment Variables" table below for details.
- Login to WebUI → Change password → Enable 2FA
- Set Bot Token (from @BotFather)
- Set Forum Group ID (supergroup with topics enabled, system provides chat resolver)
- Set Admin Telegram IDs
- Set Webhook URL:
https://your-domain/webhook - Choose and configure verification method
- Test: user sends message → topic created → admin replies → user receives
☁️ Cloudflare Pages Deployment
Required — KV namespace: Cloudflare Dashboard → Workers & Pages → KV → Create a namespace
Optional — D1 database:
npx wrangler d1 create tg-chatbot-d1Optional — Hyperdrive:
Dashboard → Hyperdrive → Create connection (postgres:// / mysql://)
Fork this repo → Workers & Pages → Create → Pages → Connect GitHub repo
| Setting | Value |
|---|---|
| Framework preset | Vue |
| Build command | npm run build |
| Output directory | dist |
Settings → Bindings → Add binding:
| Variable | Binding type | Required |
|---|---|---|
KV |
KV namespace | ✅ |
D1 |
D1 database | ❌ |
HYPERDRIVE |
Hyperdrive | ❌ |
For Hyperdrive: run
npm install pg mysql2and addnodejs_compatin Settings → Compatibility flags.
Push code to trigger auto-deploy. You'll get https://your-project.pages.dev.
Passwords are stored as hashes only. Plaintext passwords cannot be recovered; you can only reset them.
Find usernames (Dashboard):
- Cloudflare Dashboard → Workers & Pages → KV
- Open the KV namespace bound to this project
- Search these prefixes:
webuser:→ key suffix is the username (e.g.webuser:ops)webuser_id:→ full account JSON (username/id/totp_enabled)auth:bootstrap:v2→ bootstrap admin state (defaultAdminUsername/state)
If D1 is bound:
npx wrangler d1 execute <your-d1-name> --remote --command "SELECT id, username, is_admin, totp_enabled, created_at FROM web_users;"Reset password (manual Cloudflare steps):
- Generate a new password hash locally:
npm run admin -- hash-password 'NewPassw0rd!'
# Example output: pbkdf2:100000:<salt>:<hash>- Update the target account:
- KV: edit
password_hashin bothwebuser_id:<id>andwebuser:<username> - D1:
- KV: edit
npx wrangler d1 execute <your-d1-name> --remote --command "UPDATE web_users SET password_hash='pbkdf2:...' WHERE username='ops';"-
Revoke existing sessions (strongly recommended):
- Put/overwrite KV key
auth:session_epoch:<userId>with any random string - Optionally delete
sess_user:<userId>:keys and matchingsess:keys
- Put/overwrite KV key
-
If 2FA is also lost, set
totp_enabledto0and cleartotp_secret.
🐳 Docker Deployment
git clone https://github.com/milangree/Telegram_ChatBot.git
cd Telegram_ChatBot
docker compose up -ddocker pull kakuwari/tg-chatbot:latest
docker run -d \
-p 3000:3000 \
-v telegram-data:/app/data \
--name telegram-chatbot \
kakuwari/tg-chatbot:latestdocker build -t telegram-chatbot .
docker run -d -p 3000:3000 -v data:/app/data telegram-chatbot| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
Server port |
KV_FILE |
/app/data/kv-store.db |
KV storage SQLite path |
D1_FILE |
/app/data/d1-store.db |
D1 storage SQLite path |
DATABASE_URL |
— | PostgreSQL / MySQL connection string |
ACTIVE_DB |
kv |
Storage backend: kv / d1 / hyperdrive |
COOKIE_SECURE |
false |
Set to true behind HTTPS reverse proxy |
KV_PERSIST |
true |
KV persistence (recommended for Docker) |
ADMIN_USERNAME |
admin |
Initial admin username (Docker/VPS, effective on first boot) |
ADMIN_PASSWORD |
— | Initial admin password (not printed in logs when set, supports password sync on restart) |
See
.env.examplefor the full list of environment variables.
Edit docker-compose.yml, uncomment the PostgreSQL service:
services:
telegram-chatbot:
environment:
- DATABASE_URL=postgresql://telegram:password@postgres:5432/telegram_bot
- ACTIVE_DB=hyperdrive
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: telegram_bot
POSTGRES_USER: telegram
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-telegram_password}
volumes:
- postgres-data:/var/lib/postgresql/dataDocker has no built-in HTTPS. Use a reverse proxy:
Cloudflare Tunnel (Recommended):
cloudflared tunnel --url http://localhost:3000Nginx Reverse Proxy:
server {
listen 443 ssl;
server_name bot.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
}If the permanent admin username / password / 2FA are all lost, Web recovery is not enough. Use the ops CLI:
# Local
npm run admin -- list
npm run admin -- show <username|id>
npm run admin -- bootstrap
npm run admin -- reset-password <username|id> --yes
npm run admin -- reset-password <username|id> --password 'NewPassw0rd!' --yes
npm run admin -- disable-2fa <username|id> --yes
npm run admin -- create newadmin --password 'NewPassw0rd!' --yes
# Docker
docker exec -it telegram-chatbot node scripts/admin-recovery.js list
docker exec -it telegram-chatbot node scripts/admin-recovery.js reset-password ops --yes
docker exec -it telegram-chatbot node scripts/admin-recovery.js disable-2fa ops --yesNotes:
listshows username, id, 2FA status, whether login is blocked, and whether it is the bootstrap accountreset-passwordresets the password and revokes all sessions for that user- If
--passwordis omitted, a random password is generated and printed once - Plaintext passwords cannot be recovered from hashes
- A retired bootstrap account (
login=blocked) is usually not the permanent login account — reset the real admin, orcreatea new one
🤖 Bot Settings
| Setting | Description | Required |
|---|---|---|
BOT_TOKEN |
Telegram Bot Token (from @BotFather) | ✅ |
FORUM_GROUP_ID |
Forum supergroup ID (starts with -100) |
✅ |
ADMIN_IDS |
Admin Telegram IDs (comma-separated) | ✅ |
BOT_LOCALE |
Bot language: zh-hans / zh-hant / en |
❌ |
Getting Group ID: Use the "Query Chat ID" helper in WebUI settings — enter group username or link to resolve.
🔗 Webhook Settings
| Setting | Description |
|---|---|
WEBHOOK_URL |
Webhook URL, format: https://domain/webhook |
After setting the webhook, WEBHOOK_SECRET is auto-generated for request verification.
On Cloudflare Pages, CAPTCHA_SITE_URL is auto-filled from the Webhook URL origin.
🔐 Verification Settings
| Setting | Default | Description |
|---|---|---|
VERIFICATION_ENABLED |
true |
Verification toggle |
CAPTCHA_TYPE |
math |
Verification type (see table below) |
VERIFICATION_TIMEOUT |
300 |
Timeout in seconds (60-3600) |
MAX_VERIFICATION_ATTEMPTS |
3 |
Max retry attempts (1-10) |
CAPTCHA_SITE_URL |
— | Verification page site URL |
Per-type settings:
| Setting | Used by |
|---|---|
TURNSTILE_SITE_KEY / TURNSTILE_SECRET_KEY |
Cloudflare Turnstile |
RECAPTCHA_SITE_KEY / RECAPTCHA_SECRET_KEY |
Google reCAPTCHA v2 |
RECAPTCHA_V3_SITE_KEY / RECAPTCHA_V3_SECRET_KEY |
Google reCAPTCHA v3 |
RECAPTCHA_V3_SCORE_THRESHOLD |
reCAPTCHA v3 min score (default 0.5) |
HCAPTCHA_SITE_KEY / HCAPTCHA_SECRET_KEY |
hCaptcha |
🛡️ Moderation Settings
| Setting | Default | Description |
|---|---|---|
AUTO_UNBLOCK_ENABLED |
true |
Allow banned users to appeal |
WHITELIST_ENABLED |
false |
Whitelist feature (skip verification) |
BOT_COMMAND_FILTER |
true |
Filter /xxx commands from forwarding |
ADMIN_NOTIFY_ENABLED |
false |
Admin DM notifications |
ZALGO_FILTER_ENABLED |
true |
Filter Zalgo abnormal text |
MESSAGE_FILTER_RULES |
[] |
Keyword filter rules (JSON array) |
⏰ Message Management Settings
| Setting | Default | Description |
|---|---|---|
USER_MSG_DELETE_SECONDS |
30 |
Auto-delete user messages (0=disabled) |
INLINE_KB_MSG_DELETE_ENABLED |
true |
Auto-delete button messages toggle |
INLINE_KB_MSG_DELETE_SECONDS |
30 |
Auto-delete button messages timeout |
WELCOME_ENABLED |
true |
Welcome message toggle |
WELCOME_MESSAGE |
— | Welcome message content (HTML supported) |
🔒 Security Settings
| Setting | Default | Description |
|---|---|---|
LOGIN_SESSION_TTL |
86400 |
WebUI login expiration (seconds) |
Passwords stored with PBKDF2 (100,000 iterations SHA-256). TOTP two-factor authentication supported. If username/password/2FA are all lost, use the ops CLI or Cloudflare manual recovery (see Deployment). Plaintext passwords cannot be recovered from hashes.
User sends first message
↓
Bot sends verification message (buttons/image/link, depending on type)
↓
User completes verification
↓
Bot marks user as verified → forwards pending message to admin
↓
Subsequent messages forwarded directly, no re-verification needed
- After timeout, bot auto-edits verification message to "⏳ Verification timed out"
- Cleans up all verification data (verify record, pending message, webverify KV)
- User can re-trigger verification by sending a new message
Whitelisted users skip verification. Add via WebUI or bot command /wl <userID>.
Two filter rule types supported:
| Type | Description | Example |
|---|---|---|
text |
Plain text match (case-insensitive) | spam, advertisement |
regex |
Regular expression match | /https?:\/\/t\.me\/\w+/i |
WebUI: Settings → Message Filter → Select type → Enter content → Add
Bot commands:
/addfilter text keyword
/addfilter regex /https?:\/\/t\.me\/\w+/i
/delfilter 1
/filters
Regex Basic Syntax
| Syntax | Description | Example |
|---|---|---|
. |
Any single character | a.c matches abc, a1c |
* |
Previous char 0+ times | ab*c matches ac, abc, abbc |
+ |
Previous char 1+ times | ab+c matches abc, abbc |
? |
Previous char optional | ab?c matches ac, abc |
\d |
Digit [0-9] |
\d+ matches one or more digits |
\w |
Word char [a-zA-Z0-9_] |
\w+ matches a word |
\s |
Whitespace | \s+ matches one or more spaces |
[abc] |
Character set | [aeiou] matches any vowel |
[^abc] |
Negated set | [^0-9] matches non-digit |
(abc) |
Group | (ab)+ matches ab, abab |
a|b |
Or | cat|dog matches cat or dog |
^ |
Start of line | ^Hello matches Hello at start |
$ |
End of line | end$ matches end at end |
Common Filter Regex Examples
# Match Telegram invite links
/addfilter regex /t\.me\/(joinchat\/|\+)\w+/i
# Match all external links
/addfilter regex /https?:\/\/[^\s]+/i
# Match phone numbers
/addfilter regex /\b1[3-9]\d{9}\b/
# Match specific keywords (Chinese)
/addfilter regex /加群|进群|入群/
# Match pure emoji messages (5+ consecutive)
/addfilter regex /^[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]{5,}$/u
Regex Testing & Debugging
Before adding complex regex rules, test them with online tools:
- regex101.com — Most popular regex tester with syntax highlighting and explanation
- regexr.com — Visual regex matching
- debuggex.com — Regex visualization (state machine diagrams)
- Open regex101.com
- Paste your test message text in "Test String"
- Enter your regex in "Regular Expression" (without leading/trailing
/and flags) - Select flags on the right:
g(global),i(case-insensitive),m(multiline) - Confirm the match results are correct, then add in bot:
/addfilter regex /your-regex/flags
- Regex matching targets extracted plain text from message content (not raw JSON), including text, usernames, titles, etc.
- Avoid nested quantifiers like
(a+)+— these cause ReDoS (Regex Denial of Service) - The system auto-detects and rejects regex patterns with ReDoS risk at creation time
- Match target string is limited to 4096 characters to prevent performance issues with long messages
- Regex match failures (syntax errors etc.) silently return no-match without affecting message processing
ReDoS Protection
Built-in ReDoS (Regular Expression Denial of Service) protection:
- Creation-time detection: Nested quantifier patterns (e.g.,
(a+)+,(a|a)+) are automatically detected and rejected - Match-time protection: Regex matching is wrapped in try-catch, target string limited to 4096 characters
- Silent errors: Match failures don't affect normal message processing
If your regex is rejected, it contains patterns that may cause catastrophic backtracking. Simplify the regex or use more specific character classes.
| Backend | Use Case | Features |
|---|---|---|
| KV | Small-medium | Zero config, key-value storage |
| D1 | Medium-large | SQLite, SQL queries, transactions |
| Hyperdrive | Production | PostgreSQL / MySQL, external database |
One-click switch in WebUI Settings → Storage Management. System auto-syncs data.
⚠️ Stop bot message processing before switching to avoid data inconsistency during sync.
Three modes supported:
- Plain — Direct SQL text export
- Base64 — Base64 encoded
- AES-256-GCM — Encrypted export, requires password
| Mechanism | Description |
|---|---|
| Password Hashing | PBKDF2 (100,000 iterations SHA-256), backward-compatible with legacy salt:sha256 |
| Two-Factor Auth | TOTP (RFC 6238), login page recovery supported |
| Webhook Verification | Secret Token header validation |
| SQL Injection Prevention | All queries use parameterized bindings |
| Default Admin | Random password on first boot (printed in logs); auto-disabled after real registration |
| ReDoS Protection | Regex creation-time detection, match-time protection |
| Regex Error Handling | Match exceptions handled silently, no message flow disruption |
# Install dependencies (frontend + backend)
npm install
cd server && npm install && cd ..
# Build frontend
npm run build
# Start full stack (static UI + API + Webhook)
npm run preview
# equivalent to: node server/index.js
# open http://localhost:3000
# Stop local server
npm run stop
# or press Ctrl+C in the start terminal
# Static-only preview (no API — not for local integration)
npm run preview:staticHow to stop the local server:
| Method | Command / Action | Notes |
|---|---|---|
| Recommended | npm run stop |
Stops by PID file / listening port |
| Foreground | Ctrl+C in the start terminal |
Graceful shutdown |
| Alias | npm run server:stop |
Same as npm run stop |
npm run previewstartsserver/index.js, serving the UI together with/apiand/webhook.
Running plainvite preview(preview:static) alone will make/api/*requests fail with empty/non-JSON responses.
Ifbetter-sqlite3cannot be installed (missing Python / build tools), the server falls back to in-memory KV (data is lost on restart), which is fine for local integration.
WebUI 500 / Blank Page
Check that KV is bound with variable name KV (uppercase). Redeploy after binding.
In Docker mode, check KV_FILE path has write permissions.
Webhook Setup Fails
- Bot Token is correct (format:
123456:ABC-DEF...) - URL must be public HTTPS:
https://domain/webhook - Docker users need a reverse proxy for HTTPS
Messages Not Forwarding
- Group has topics enabled (supergroup → Settings → Topics)
- Bot is a group admin (needs manage topics permission)
FORUM_GROUP_IDis correct (starts with-100)- User has passed verification
Forgot Password
- With 2FA enabled: Use the login page "Recover" flow with TOTP code
- Without 2FA: Manually edit storage (delete web_users records in KV/D1/database, restart to rebuild default admin)
Docker Has No HTTPS
Use one of:
- Cloudflare Tunnel (recommended):
cloudflared tunnel --url http://localhost:3000 - Nginx reverse proxy (see deployment guide)
- Caddy: Auto-HTTPS, simplest configuration
Verification Timeout No Response
- Check
VERIFICATION_TIMEOUTsetting (default 300 seconds) - Turnstile/reCAPTCHA requires correct
CAPTCHA_SITE_URLconfiguration - Unconfigured secret keys auto-fallback to math captcha
- Check container logs:
docker compose logs -f telegram-chatbot
Viewing Container Logs
# Real-time logs
docker compose logs -f telegram-chatbot
# Last 100 lines
docker compose logs --tail 100 telegram-chatbot
# Key log keywords
# [web_app_verify] — Web verification callbacks
# [verify_timeout] — Verification timeout cleanup
# [api/verify] — Verification API operationsSee the LICENSE file in the repository root.