Telegram bot (aiogram 3.13.1) that hosts users' PHP Telegram bots on a pool of Cloudflare Workers. Users upload a PHP file + bot token; the bot uploads the file to a Worker, registers a Telegram webhook via Cloudflare KV routing, and the Worker runs the PHP on incoming updates. Points economy: 1 point = 48 h of hosting, earned via referrals.
pip install -r requirements.txt
cp .env.example .env # fill in BOT_TOKEN, WORKER_URL, INTERNAL_SECRET
python main.pyNo test suite, no linter, no formatter, no CI. Sanity-check Python syntax after edits:
python -m compileall core features main.py config.pyDocker / Railway (railway.toml, Dockerfile) just runs python main.py. The Dockerfile is python:3.11-slim with pip install --no-cache-dir.
Required to start: BOT_TOKEN (program exits if missing). The rest are read at import time.
BOT_TOKEN— Telegram bot tokenWORKER_URL,INTERNAL_SECRET— default Worker registered on first boot ifDATABASE_URLnot set to postgresDATABASE_URL— if starts withpostgresql, uses asyncpg; otherwise SQLite atdata/hosting.db(created on demand, dir auto-created)ADMIN_IDS— comma-separated Telegram user IDs. If empty,is_admin()returns True for everyone (seefeatures/admin/handlers.py:33)CF_ACCOUNT_ID,CF_KV_ID,CF_API_TOKEN,CF_WEBHOOK_BASE— Cloudflare KV routing for webhooks. If any missing,register_routinglogs a warning and no-ops.
main.py entry: init_db → register default worker → start polling + 3 background loops
core/bot.py builds Bot/Dispatcher, wires routers + middlewares (order matters)
core/db.py DB-agnostic layer; branches on global `db_type` ("sqlite"|"postgres")
core/middleware.py AuthMiddleware (subscription gate) + LoggingMiddleware
core/worker.py httpx client to Workers; load balancer + health-check loop
core/scheduler.py expiry + DB-backup background loops
core/keyboards.py all InlineKeyboardMarkup builders (single source of truth)
core/referrals.py user creation + referral processing
core/validators.py PHP file + bot token validation
features/<name>/ one folder per feature: handlers.py + messages.py + __init__.py (exports `router`)
data/ SQLite DB + uploaded PHP files (`data/bots/<user_id>_bot.php`)
- start, 2. invite, 3. forced_channels, 4. deploy, 5. manage, 6. admin
Order matters because handlers in earlier routers can swallow updates. Add new feature routers deliberately and check F.data.startswith(...) conflicts.
LoggingMiddleware registered before AuthMiddleware on both dp.message and dp.callback_query. AuthMiddleware injects data["user_id"] (so handlers can take user_id: int as a parameter) and short-circuits banned users / non-subscribed users. Admins bypass the subscription gate.
DB-agnostic pattern: every function in core/db.py branches on the module-global db_type. When adding a query, write both dialects (asyncpg uses $1, $2; aiosqlite uses ?). Booleans: SQLite stores INTEGER 0/1, Postgres stores BOOLEAN — code uses bool(user.get("is_banned")) to normalize. Booleans across rows are returned via _row_to_dict (SQLite) / dict(row) (postgres).
pool is a module-global: an asyncpg.Pool for postgres, a single aiosqlite.Connection for sqlite. Note aiosqlite.Connection is not a pool but a connection — it's the global handle for the SQLite path.
Schema is created in core/db.py:init() for both backends. Schema changes: edit both branches; for existing SQLite DBs, the _ensure_column helper adds columns idempotently.
Tables: workers, bots, users, forced_channels.
Pattern: feature:action[:param] (string-based, no aiogram CallbackData factories):
main:deploy,main:bots,main:invite,main:helpdeploy:new,deploy:cancelmanage:list,manage:view:<bot_id>,manage:back,manage:start:<id>,manage:stop:<id>,manage:restart:<id>,manage:delete:<id>,manage:cancel:<id>,manage:confirm:stop:<id>,manage:confirm:delete:<id>channels:list,channels:view:<id>,channels:add,channels:del:<id>,channels:confirm_del:<id>,channels:check_sub
deploy:cancel is reused across deploy and admin add-worker flows — both have handlers, each checks its own FSM state before clearing.
All keyboards are in core/keyboards.py. The main menu (main_keyboard()) is an InlineKeyboardMarkup — no reply keyboards. Do not introduce ReplyKeyboardMarkup; that pattern was deliberately removed.
Deploy (features/deploy/handlers.py):
- FSM:
waiting_file→ upload.phptodata/bots/<user_id>_bot.php→waiting_token - Pick worker via
select_best_worker()(lowestbots_countamongstatus='active') - POST file to
{worker}/deploywithX-Internal-Secretheader - Register Cloudflare KV routing
user_id → worker_url setWebhookto{CF_WEBHOOK_BASE}/webhook/{user_id}with thewebhook_secretreturned by the worker- Insert into
bots, increment workerbots_count,consume_point - On error, notify all admins with the error detail
Worker contract: POST /deploy (multipart: file + user_id + bot_token), POST /stop (JSON: user_id), GET /status/{user_id}, GET /health. Responses are JSON; success means {"status": "ok", ...} with optional webhook_secret.
Restart / start use data/bots/<user_id>_bot.php as the file path — restarting a user requires the file to still be on disk. Deleting the bot row does not delete the file.
consume_pointis atomic viaWHERE points >= 1; returns bool.bots.expires_atis set tonow + 48 hourson deploy/start/restart.core/scheduler.py:expiry_check_loopruns every 600 s, marks expired bots stopped, notifies the user.
health_check_loop(60)— pings every worker; flipsstatusbetweenactive/dead.expiry_check_loop(bot, 600)— see above.backup_loop(bot, ADMIN_IDS, 86400)— sendsdata/hosting.dbto all admins once a day. SQLite-only; no-op on postgres (file won't exist).
All bot messages are in Arabic (HTML parse mode). Inline Arabic comments in source. Keep messages Arabic when editing features/*/messages.py.
This project is developed through an agent-orchestration workflow. The primary agent acts as an intelligent coordinator rather than directly implementing changes.
Do not perform implementation work, modify source code, or make changes to the project unless the user explicitly instructs you to do so.
The primary agent should act as an orchestrator:
- Understand the user's request and determine what kind of agent work is needed.
- Delegate implementation and exploration work to the appropriate specialized agent.
- Provide agents with enough context and precise instructions to produce reliable results.
- Do not make assumptions about implementation details when delegation is more appropriate.
- The agent may read relevant parts of the codebase when necessary to improve its understanding or to provide better instructions to another agent. Reading code for context is allowed; making changes without explicit authorization is not.
The workflow will normally use the following agents:
Use @General when the user asks for an agent to perform an actual implementation task.
The coordinator should provide @General with a precise, comprehensive, and self-contained task description. The description should include:
- The exact objective and expected behavior.
- Relevant context about the existing architecture.
- Important constraints and project conventions.
- Files or areas that are likely relevant.
- Any required edge cases.
- What should and should not be changed.
- How the work should be verified.
Do not give vague instructions such as "implement this feature." Give the agent enough information to reason about the task and produce a complete implementation.
Use @Explore when the user asks for investigation, codebase exploration, research, or planning before implementation.
The expected workflow is:
- Launch
@Explorewith a precise description of what needs to be investigated. - Have it inspect the relevant parts of the codebase.
- Have it produce a Markdown document containing the proposed implementation plan.
- Review/use that plan as the basis for the implementation phase.
- When the user requests implementation, launch
@Generalwith the plan and all necessary context to build it.
Do not implement the planned changes yourself merely because @Explore has produced a plan. Wait for explicit user instruction to proceed with implementation.
Any agent that modifies or implements code must run the project's analyzer after completing its work.
The exact command is:
cd ~/Workspaces/bot-creator && pnpm analyze ~/Workspaces/php-hostingThis analyzer is an important part of the project's verification workflow. It performs an in-depth analysis of the codebase and reports discovered problems back to the agent.
The implementation agent must:
- Complete the requested implementation.
- Run the analyzer using the exact command above.
- Carefully inspect the analyzer output.
- Fix any issues reported by the analyzer that are caused by the agent's changes.
- Run the analyzer again when necessary to confirm the fixes.
- Only consider the implementation complete after the analyzer reports that the relevant issues have been resolved.
The analyzer is mandatory and must not be skipped merely because the changes appear simple or because the agent believes the code is already correct.
An agent may create a Git commit only after completing the required verification workflow, including running the project analyzer and addressing the relevant issues it reports.
Do not commit unverified implementation work.
The quality of the result depends heavily on the quality of the instructions given to delegated agents. Whenever launching @General or @Explore, provide a detailed prompt rather than a short command.
For implementation tasks, the coordinator should make the delegated task as self-contained as possible so the implementation agent can work independently without repeatedly asking for clarification about information that could have been provided in the initial prompt.
features/invite/handlers.pyusesF.dataandCallbackQuerybut doesn't import them — adds will fail at module load if those decorators are touched. Fix the imports if you edit that file.MemoryStoragefor FSM (in-process). Multi-instance deploys will break user state.data/is gitignored butdata/bots/accumulates uploaded PHP files — no cleanup on bot deletion.- No tests, no CI, no README. Do not invent them unless asked.