import asyncio import logging import sys from datetime import datetime from aiohttp import web from aiogram import Bot, Dispatcher from aiogram.client.default import DefaultBotProperties from aiogram.enums import ParseMode from aiogram.fsm.storage.memory import MemoryStorage from aiogram.webhook.aiohttp_server import ( SimpleRequestHandler, setup_application, ) from config import config from database.db import init_db from handlers import admin, owner, user # ─────────────────────────── LOGGING ───────────────────────────────────── logging.basicConfig( level=logging.INFO, format="%(asctime)s | %(levelname)-8s | %(name)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S", stream=sys.stdout, ) logger = logging.getLogger(__name__) BOT_STATUS = {"running": False, "started_at": None, "error": None} # Webhook path — Telegram will POST updates here WEBHOOK_PATH = "/webhook" # ─────────────────────────── HEALTH ENDPOINTS ──────────────────────────── async def handle_root(request: web.Request) -> web.Response: status = "✅ running" if BOT_STATUS["running"] else "⏳ starting" error = f"\n❌ Last error: {BOT_STATUS['error']}" if BOT_STATUS["error"] else "" # ملاحظة للمستخدم في الصفحة الرئيسية manual_notice = "\n\n💡 Note: Webhook is set manually to bypass HF outbound block." body = ( f"🤖 Knowledge Base Bot — {status}\n" f"🕒 Server time : {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')} UTC\n" f"🚀 Bot started : {BOT_STATUS['started_at'] or 'not yet'}" f"{error}{manual_notice}" ) return web.Response(text=body, content_type="text/plain", status=200) async def handle_health(request: web.Request) -> web.Response: import json payload = { "status" : "ok" if BOT_STATUS["running"] else "starting", "bot_ok" : BOT_STATUS["running"], "started": BOT_STATUS["started_at"], "error" : BOT_STATUS["error"], } return web.Response( text=json.dumps(payload), content_type="application/json", status=200, ) # ─────────────────────────── DISPATCHER ────────────────────────────────── def build_dispatcher() -> Dispatcher: dp = Dispatcher(storage=MemoryStorage()) dp.include_router(owner.router) dp.include_router(admin.router) dp.include_router(user.router) return dp # ─────────────────────────── MAIN ──────────────────────────────────────── async def main() -> None: print(f"\n{'='*50}") print(f" Startup at {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')} UTC") print(f"{'='*50}\n") # ── 1. Validate config ──────────────────────────────────── try: config.validate() except EnvironmentError as e: logger.critical(str(e)) BOT_STATUS["error"] = str(e) app = web.Application() app.router.add_get("/", handle_root) runner = web.AppRunner(app) await runner.setup() await web.TCPSite(runner, config.WEB_HOST, config.WEB_PORT).start() while True: await asyncio.sleep(3600) # ── 2. Database with retry ──────────────────────────────── for attempt in range(1, 6): try: logger.info(f"🔌 DB connect attempt {attempt}/5…") await init_db() logger.info("✅ Database ready.") break except Exception as exc: BOT_STATUS["error"] = str(exc) logger.error(f"❌ DB failed: {exc}") if attempt < 5: await asyncio.sleep(5) else: logger.critical("💥 DB unavailable.") break # ── 3. Bot Setup ────────────────────────────────────────── bot = Bot( token=config.BOT_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML), ) dp = build_dispatcher() # ⚠️ تم تعطيل الحذف والتسجيل التلقائي للويب هوك هنا لتجنب حجب HF # التسجيل يتم يدوياً من المتصفح كما فعلت سابقاً logger.info("ℹ️ Skipping automatic webhook registration (HF compatibility mode).") BOT_STATUS["running"] = True BOT_STATUS["started_at"] = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC") BOT_STATUS["error"] = None # ── 4. aiohttp app with webhook handler ─────────────────── app = web.Application() # Health endpoints app.router.add_get("/", handle_root) app.router.add_get("/health", handle_health) # تسجيل معالج الويب هوك (هذا يجعل البوت "يسمع" للرسائل القادمة) SimpleRequestHandler(dispatcher=dp, bot=bot).register(app, path=WEBHOOK_PATH) setup_application(app, dp, bot=bot) # ── 5. Start server ─────────────────────────────────────── runner = web.AppRunner(app) await runner.setup() site = web.TCPSite(runner, host=config.WEB_HOST, port=config.WEB_PORT) await site.start() logger.info(f"🚀 Server is UP on http://{config.WEB_HOST}:{config.WEB_PORT}/") logger.info(f"📡 Waiting for Telegram updates via: {config.WEBHOOK_URL}{WEBHOOK_PATH}") # Keep running forever while True: await asyncio.sleep(3600) if __name__ == "__main__": try: asyncio.run(main()) except KeyboardInterrupt: logger.info("👋 Shutdown by user.")