- example channel: https://t.me/ILIVIR3
Version 0.7.3
A Telegram channel content-processing bot built on Cloudflare Workers. 100% free, serverless, AI-powered.
Features · Architecture · Setup · Usage · Configuration · Troubleshooting · Changelog
AI Admin is a self-hosted Telegram bot that accepts raw posts in private chat, cleans them, rewrites them with AI (Gemini or OpenRouter), formats them with HTML, and publishes them to your channel. It can also edit posts already published in a channel where the bot is an admin.
The entire stack runs on the Cloudflare Workers free tier — no servers, no databases, no monthly bill. A busy channel processing 50 posts per day will use less than 10% of the free quota.
Most Telegram channel admins manually copy-paste content from other channels, strip spam/promo footers, fix formatting, and re-post. AI Admin automates the entire pipeline while preserving the things that matter: GitHub links, code blocks, AI/Midjourney prompts, and the original author's voice.
- Accepts text, photo, video, document, animation, and album (media group) posts
- Cleans promotional footers (
🆔 @user,@channel | desc,....separator lines) while preserving the bot's own footer - Removes spam links (Telegram invite links, sticker packs) but preserves technical links (GitHub, docs, npm, arxiv, etc.)
- Strips attribution lines (
via @user,source: @user,@DevTwitter | Author) - Preserves code blocks, AI/Midjourney prompts, and long technical instructions verbatim
- Detects language (Persian vs English) automatically and never translates
- Smart long-post handling: balanced split for text posts, AI summarize for media captions
- Persian number → emoji number conversion for English-titled lists (
۱.→1️⃣)
- Gemini as primary provider (gemini-3-flash-preview, gemini-2.5-flash, etc.)
- OpenRouter as fallback (llama-3.3-70b, qwen3-next-80b, gemma-4-31b, gpt-oss-120b, etc.)
- 11 providers race in parallel via
Promise.any+AbortController— the moment any one succeeds, all others are cancelled (saves tokens) - Smart fallback ordering: preferred provider's top 2 models first, then the other provider's models, then the rest
- Compact system prompts (under 800 tokens) — keeps API cost low
max_tokenscapped at 2500 (still 2× Telegram's 4096-char limit)- Explicit warnings in logs when API keys are missing (helps diagnose "format-only fallback" issues)
- Inline-keyboard settings menu (
/menu) — no command spam - 8 toggle buttons: Language, Rewrite mode, Personality, Footer, AI Provider, Schedule, Approve mode, Channel editing
/startshows bot intro to all users (admins and non-admins)/menushows admin panel (admins only)- Non-admin users get format-only response (no AI, no publish)
- Approve mode: bot sends a preview with ✅ Publish / ❌ Reject buttons before publishing
- Admin list: owner can add/remove additional admins (stored in global KV key, so any admin can authorize any user)
- Native Telegram scheduling via
schedule_date(posts appear in Telegram's "Scheduled Messages" view) - Smart interval: respects minimum gap between scheduled posts
- Configurable: delay hours, interval minutes, posts per day
- Permission check before scheduling (verifies bot has
can_post_messages) - Verification after scheduling (compares
result.datewithschedule_dateto detect silent immediate send)
- Settings cache (30s TTL) — pipeline calls
getSettings()2-3× per request, now only 1 KV read - Admin list cache (60s TTL) —
isAuthorized()runs on every request, now 0 KV reads after first - Last-scheduled cache (60s TTL) + write-through on set
- Stats batch threshold increased 10 → 20 — 2× fewer KV writes
- Parallel media group operations —
Promise.allinstead of sequential loops - Net result: ~60% reduction in KV operations vs Prime v0.6.11
- Built-in debug dashboard at
/debug(protected byDEBUG_TOKEN) - Conditional logging: when
DEBUG_MODE=false, no debug logs are written to KV /debug_schedulecommand for testing scheduling with a dummy message/checkpermscommand for verifying bot permissions in channel- Raw request logging, update logging, error logging (all gated by
DEBUG_MODE)
┌──────────────┐ webhook ┌──────────────────────┐
│ Telegram │ ────────────► │ Cloudflare Worker │
│ Channel │ ◄──────────── │ (this code) │
└──────────────┘ publish └──────────┬───────────┘
│
┌──────────────────┼──────────────────┐
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌──────────┐
│ KV │ │ Gemini │ │OpenRouter│
│ (cache) │ │ (primary)│ │(fallback)│
└─────────┘ └──────────┘ └──────────┘
RECEIVE → EXTRACT → CLASSIFY → CLEAN → [REWRITE / SUMMARIZE] → FORMAT → PUBLISH
If any step fails, the bot falls back to format-only (no AI rewrite, just clean + format + publish). No post is ever dropped.
ai-admin/
├── VERSION # 0.7.3
├── package.json # version 0.7.3 + npm scripts
├── wrangler.toml # Cloudflare Worker config
├── README.md # this file
├── LICENSE # MIT
├── ai/
│ ├── profiles/
│ │ ├── index.js
│ │ └── ilivir3/ # ILIVIR3 channel profile
│ │ ├── index.js
│ │ ├── rules.js
│ │ ├── soul.js
│ │ └── style.js
├── scripts/ # test + utility scripts
└── src/
├── index.js # entry point + webhook handler
├── pipeline.js # content processing pipelines
├── admin.js # admin panel + isAuthorized (async)
├── kv.js # KV helpers (v0.7.3 cached)
├── ai.js # AI client (11 providers racing)
├── cleaner.js # spam/promo/footer removal
├── formatter.js # HTML formatting engine
├── telegram.js # Telegram Bot API wrapper
├── debug.js # debug dashboard + logging
├── html-utils.js # HTML tag closer + truncator
├── classifier.js # rule-based + AI classifier
└── prompts.js # system prompts
| Service | Where to get | Cost |
|---|---|---|
| Cloudflare account | dash.cloudflare.com | Free |
| Telegram bot | @BotFather → /newbot |
Free |
| Telegram channel | Create your own, add bot as admin | Free |
| Gemini API key | aistudio.google.com/apikey | Free (1500 req/day) |
| OpenRouter API key | openrouter.ai/keys | Free (fallback) |
| Node.js 18+ | nodejs.org | Free |
| Your Telegram user ID | @userinfobot | Free |
git clone https://github.com/YOUR_USERNAME/ai-admin.git
cd ai-admin
npm installnpx wrangler loginA browser window opens. Sign in to your Cloudflare account.
npm run deployOutput looks like:
Published ai-admin (1.23 sec)
https://ai-admin.<your-subdomain>.workers.dev
Save this URL — you'll need it for the webhook setup.
- Go to dash.cloudflare.com
- Left menu: Storage & Databases → Workers KV
- Click Create a namespace
- Name:
ai_admin_settings - Click Add
- Go to Workers & Pages
- Click the
ai-adminworker - Settings tab
- Bindings section → click Add binding
- Choose KV Namespace
- Configure:
- Variable name:
SETTINGS(exactly, uppercase) - KV namespace:
ai_admin_settings(the one from step 4)
- Variable name:
- Click Save and deploy
Same Settings tab, Variables and Secrets section. Click Add → choose Secret (encrypted).
| Secret name | Value | Source |
|---|---|---|
BOT_TOKEN |
123456:ABC-... |
@BotFather |
GEMINI_API_KEY |
AIza... |
aistudio.google.com |
OPENROUTER_API_KEY |
sk-or-v1-... |
openrouter.ai/keys |
(Optional) Add WEBHOOK_SECRET with a random string like mySecret123abc for webhook security.
Click Save and deploy.
Same Variables and Secrets section, this time choose Plain text:
| Variable | Example value | Notes |
|---|---|---|
ADMIN_ID |
123456789 |
Your numeric Telegram ID |
TARGET_CHANNEL |
@your_channel |
Channel username (with @) or -100xxx numeric ID |
FOOTER_TEXT |
🌀 @ILIVIR3 |
Default footer text |
DEBUG_MODE |
false |
Set to true to enable debug logs in KV |
DEFAULT_AI_PROVIDER |
gemini |
gemini or openrouter |
GEMINI_MODEL |
gemini-3-flash-preview |
Primary Gemini model |
OPENROUTER_MODEL |
meta-llama/llama-3.3-70b-instruct:free |
Primary OpenRouter model |
OPENROUTER_FALLBACK_MODELS |
meta-llama/llama-3.3-70b-instruct:free,qwen/qwen3-next-80b-a3b-instruct:free,google/gemma-4-31b-it:free,openai/gpt-oss-120b:free,nousresearch/hermes-3-llama-3.1-405b:free,nvidia/nemotron-3-ultra-550b-a55b:free |
Comma-separated fallback models |
Click Save and deploy.
Send an HTTP request to Telegram to register your Worker as the webhook:
curl "https://api.telegram.org/bot<YOUR_BOT_TOKEN>/setWebhook" \
-H "Content-Type: application/json" \
-d '{
"url": "https://ai-admin.<your-subdomain>.workers.dev/webhook",
"secret_token": "<YOUR_WEBHOOK_SECRET>",
"allowed_updates": ["message", "callback_query", "channel_post"],
"drop_pending_updates": true
}'Expected response:
{"ok":true,"result":true,"description":"Webhook was set"}Verify:
curl "https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getWebhookInfo"If url is empty or last_error_message is present, something is wrong.
- Open your channel
- Channel Info → Administrators → Add Admin
- Select the bot
- Grant Post Messages permission (required for scheduling and posting)
- (Optional) Grant Edit Messages permission (required for channel-edit feature)
Send /start to the bot in a private chat. You should see the bot intro. Then send /menu to open the admin panel:
⚙️ AI Admin — Settings
Current configuration:
🌐 Language: auto
✍️ Rewrite: normal
🎭 Personality: friendly
🤖 AI Provider: gemini
📢 Footer: 🌀 @ILIVIR3
🔐 Approve: OFF
📅 Schedule: OFF
[⚙️ Settings] [🧠 AI Mode]
[🌐 Language] [✍️ Rewrite]
[🎭 Personality] [📢 Footer]
[🤖 AI Provider] [📊 Stats]
[🔐 Approve] [📅 Schedule]
[👤 Admins]
Now send a test post (text or media) — the bot will process it and publish to your channel.
| Command | Who | Description |
|---|---|---|
/start |
Anyone | Shows bot introduction |
/menu |
Admins only | Opens settings panel |
/footer <text> |
Admins only | Changes footer text |
/checkperms |
Admins only | Verifies bot permissions in channel |
/debug_schedule |
Admins only | Tests scheduling with a dummy message |
/help |
Anyone | Shows help text |
| Button | Action |
|---|---|
| ⚙️ Settings | Re-display current settings |
| 🧠 AI Mode | Combined preset (Provider + Rewrite) in one click |
| 🌐 Language | Auto / Persian / English |
| ✍️ Rewrite | None / Light / Normal / Summary |
| 🎭 Personality | Friendly / Professional / Technical / News |
| 📢 Footer | Change footer text |
| 🤖 AI Provider | Gemini / OpenRouter |
| 📊 Stats | Processing statistics |
| 🔐 Approve | Toggle approve mode (preview before publish) |
| 📅 Schedule | Toggle scheduling + configure interval |
| 👤 Admins | Add/remove additional admins (owner only) |
- Private chat to bot → bot processes, publishes to channel, sends feedback
- New post in channel (if bot is admin with edit permission) → bot edits the post in place
- Every post goes through: classify → clean → rewrite (if needed) → format → publish
- If AI fails → falls back to format-only (never drops the post)
- If post is too long → text posts get split, media posts get AI-summarized
When approve mode is ON:
- Send a post to the bot
- Bot processes it and sends a preview to your private chat
- Click ✅ Publish to publish, or ❌ Reject to discard
- Preview data is stored in KV with 1-hour TTL
When scheduling is ON:
- Send a post to the bot
- Bot calculates the next scheduled time based on:
schedule_delay_hours(minimum delay before first post)schedule_interval_minutes(gap between scheduled posts)schedule_posts_per_day(overrides interval if set)
- Bot calls Telegram's
sendMessagewithschedule_date - Post appears in the channel's Scheduled Messages view
- You can edit or delete scheduled posts from there before they auto-publish
Permission requirements for scheduling:
- ✅ Post Messages (required)
- ✅ Edit Messages (for channel-edit feature)
⚠️ Bot must be an administrator, not just a member
To view scheduled posts: open the channel → tap channel name → Scheduled Messages (clock icon 🕐)
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
BOT_TOKEN |
Secret | Yes | — | Telegram bot token from @BotFather |
GEMINI_API_KEY |
Secret | Yes* | — | Gemini API key |
OPENROUTER_API_KEY |
Secret | Yes* | — | OpenRouter API key |
WEBHOOK_SECRET |
Secret | No | — | Random string for webhook security |
DEBUG_TOKEN |
Secret | No | — | Protects /debug endpoint |
ADMIN_ID |
Var | Yes | — | Owner's Telegram user ID |
TARGET_CHANNEL |
Var | Yes | — | Channel @username or -100xxx ID |
FOOTER_TEXT |
Var | No | 🌀 @ILIVIR3 |
Default footer text |
DEBUG_MODE |
Var | No | false |
Enable debug logs in KV |
DEFAULT_AI_PROVIDER |
Var | No | gemini |
gemini or openrouter |
GEMINI_MODEL |
Var | No | gemini-3-flash-preview |
Primary Gemini model |
OPENROUTER_MODEL |
Var | No | meta-llama/llama-3.3-70b-instruct:free |
Primary OpenRouter model |
OPENROUTER_FALLBACK_MODELS |
Var | No | (see wrangler.toml) | Comma-separated fallback models |
*At least one of GEMINI_API_KEY or OPENROUTER_API_KEY must be set. Both are recommended for fallback.
Gemini (free tier, 1500 req/day):
gemini-3-flash-preview(newest, recommended)gemini-2.5-flash(stable)gemini-3.1-flash-lite-preview(cheapest)gemini-2.5-flash-lite(cheapest stable)gemini-2.0-flash(legacy)
OpenRouter free models:
meta-llama/llama-3.3-70b-instruct:free(best quality)qwen/qwen3-next-80b-a3b-instruct:freegoogle/gemma-4-31b-it:freeopenai/gpt-oss-120b:freenousresearch/hermes-3-llama-3.1-405b:freenvidia/nemotron-3-ultra-550b-a55b:free
| Key | Type | Default | Description |
|---|---|---|---|
language_mode |
string | auto |
auto / fa / en |
rewrite_mode |
string | normal |
none / light / normal / summary |
personality_mode |
string | friendly |
friendly / professional / technical / news |
footer_text |
string | 🌀 @ILIVIR3 |
Footer appended to every post |
ai_provider |
string | openrouter |
gemini / openrouter |
channel_editing_enabled |
boolean | false |
Edit channel posts in place |
edit_intensity |
number | 60 |
0-100, how aggressively to edit |
emoji_level |
number | 20 |
0-100, emoji density |
active_profile |
string|null | null |
Active profile name (e.g. ilivir3) |
scheduling_enabled |
boolean | false |
Enable native Telegram scheduling |
schedule_delay_hours |
number | 24 |
Hours to delay before first scheduled post |
schedule_interval_minutes |
number | 30 |
Minutes between scheduled posts |
schedule_posts_per_day |
number | 0 |
If >0, overrides interval (1440/ppd) |
admin_list |
array | [] |
Additional admin user IDs |
approve_enabled |
boolean | false |
Preview before publishing |
stats |
object | {processed:0, rewritten:0, failed:0} |
Processing counters |
Everything runs on free tiers:
| Service | Free tier | Enough for |
|---|---|---|
| Cloudflare Workers | 100K requests/day | ~70 posts per minute |
| Cloudflare KV | 100K reads + 1K writes/day | ~1000 posts per day (with v0.7.3 caching: ~3000) |
| Google Gemini | 1500 req/day, 15 RPM | Most channels |
| OpenRouter free models | Unlimited (rate-limited) | Excellent fallback |
| Telegram Bot API | Unlimited | Always free |
A busy channel processing 50 posts per day will use:
- ~30 KV writes, ~50 KV reads (with v0.7.3 caching)
- ~50 AI requests (split between Gemini and OpenRouter)
- ~100 Worker requests
Well under 10% of free tier limits.
# All unit tests
npm test
# Specific test suites (in scripts/)
node scripts/test-units.mjs # cleaner, classifier, formatter
node scripts/test-pipeline.mjs # integration pipeline
node scripts/test-admin.mjs # admin panel spec compliance
node scripts/test-media-group.mjs # album handling
node scripts/test-channel-edit.mjs # channel edit feature
node scripts/test-timeout.mjs # timeout handling# Check webhook status
curl "https://api.telegram.org/bot<TOKEN>/getWebhookInfo"If last_error_message is present, the Worker threw an error. Check logs:
npm run tail- Verify the bot is admin in the channel with Post Messages permission
TARGET_CHANNELis set correctly (@usernameor-100xxx)- Check the bot's private chat for error messages
- Run
/checkpermsto verify permissions
- Verify
GEMINI_API_KEYis set as a Secret (not plain text) - Verify
OPENROUTER_API_KEYis set as a Secret (recommended for fallback) - Check
wrangler taillogs for warnings like⚠️ OPENROUTER_API_KEY not set - Try switching AI Provider in the admin panel
- Free models are sometimes rate-limited — wait a few seconds and retry
- Run
/checkperms— bot must have Post Messages permission - Bot must be an administrator (not just a member)
- Check
wrangler tailfor[sched]logs showing the verification step - Telegram silently sends immediately if the bot lacks Post Messages permission
Go to dashboard: Workers & Pages → ai-admin → Settings → Bindings — verify a binding exists with the exact name SETTINGS (uppercase).
Preview data is stored in KV with a 1-hour TTL. If you wait too long, you'll see "Post data expired. Please resend." Just send the post again.
Set DEBUG_TOKEN as a secret, then visit:
https://ai-admin.<your-subdomain>.workers.dev/debug?token=<DEBUG_TOKEN>
The dashboard shows: bot info, KV status, AI provider status, recent logs, scheduled posts, and stats.
- Only
ADMIN_ID(and users inadmin_list) can access the admin panel - Non-admin users only see
/start— all other messages get format-only response WEBHOOK_SECRETprevents webhook spoofing (optional but recommended)- All tokens stored as Cloudflare Secrets (encrypted, not in code)
- Never commit
.dev.varsto git DEBUG_MODE=falseby default — no sensitive data written to KV
In src/ai.js, add a new provider function and include it in the aiComplete providers list.
In src/formatter.js, define a new engine object with name, parseMode, format(), wrapLink(), wrapFooter() methods, then registerEngine() it.
Create a new folder under ai/profiles/<name>/ with index.js, rules.js, soul.js, style.js. Register it in ai/profiles/index.js. Then set active_profile in settings.
Restarted from user-verified Prime v0.6.11. Applied only the safest optimizations.
KV optimizations (no behavior changes):
- Added settings cache (30s TTL) — pipeline calls
getSettings()2-3× per request, now 1 KV read - Added admin_list cache (60s TTL) —
isAuthorized()runs every request, now 0 KV reads after first - Added last-scheduled-time cache (60s TTL) + write-through on set
- Increased
BATCH_FLUSH_THRESHOLDfrom 10 → 20 (2× fewer stats writes) - Changed
listMediaGroupItemsanddeleteMediaGroupto usePromise.allparallel operations
AI optimizations (no behavior changes):
- Reduced
max_tokensfrom 3096 → 2500 (still 2× Telegram's char limit) - Added explicit warnings when API keys are missing (logging only)
Bug fixes from previous v0.7.x releases:
- Removed
truncateInput()that broke long-post summarization (introduced in v0.7.1) - Removed Wave-1 parallelism limit that broke AI fallback (introduced in v0.7.1)
- Removed prompt tightening that risked quality regression (introduced in v0.7.1)
Net result: ~60% reduction in KV operations, ~15% reduction in AI tokens, zero behavior changes.
- Removes
🆔 @username,🎮 @channel | desc,@channel • descpatterns - Removes separator lines (
....,----,–––) - Removes leftover emojis after
@usernameremoval - Bot's own footer
🌀 @ILIVIR3is preserved (never removed)
- URLs and markdown links stripped before ASCII letter counting (link-heavy paragraphs no longer misclassified as English)
- Persian numbers (۱., ۲., ۳.) at start of English-titled lines → emoji numbers (1️⃣, 2️⃣, 3️⃣)
- Persian numbers in Persian paragraphs preserved
admin_liststored in global KV key (global:admin_list) instead of per-user settingsgetAdminList()/isAdminInList()read from global keyisAuthorized()is now async — checksADMIN_IDfirst, then global admin list- Any admin can authorize any user (cross-admin)
approve_enabledsetting: bot sends preview with ✅ Publish / ❌ Reject buttons- Preview data stored in KV with 1-hour TTL
- Owner can add/remove additional admins via the admin panel
- Updated Gemini models:
gemini-3-flash-preview(primary),gemini-2.5-flash, etc. - Updated OpenRouter models:
meta-llama/llama-3.3-70b-instruct:free(primary), 5 fallbacks - Smart fallback ordering: preferred top 2 → other provider → rest
/startshows bot intro to all users (admins and non-admins)/menushows admin panel (admins only)- Non-admin users get format-only response (no AI, no publish)
- Removed
?? "HTML"and?? falsedefaults that conflicted withschedule_date - Cast
schedule_datetoNumber()in all send functions - Added
invalidateChatIdCache()for fresh resolution /debug_schedulenow runs 4 tests: HTML, Plain, Minimal, Raw API
- In-memory stats batching (flush every N increments instead of every request)
AbortControllercancels losing AI providers when one succeeds (saves tokens)- Conditional debug logging (gated by
DEBUG_MODE) - Removed cron-based scheduling queue (native Telegram only)
- Reverted to native Telegram
schedule_date(posts appear in Scheduled Messages view) - Added
checkSchedulingPermissions()to verify bot hascan_post_messages - Added
verifyScheduled()to detect silent immediate send - New command:
/checkperms
- AI rewrite with Gemini + OpenRouter fallback
- Admin panel with 8 inline buttons
- Media group (album) support
- Channel editing feature
- HTML formatting engine
MIT — do whatever you want.
Built for the ILIVIR3 Telegram channel. Powered by Cloudflare Workers, Google Gemini, and OpenRouter.