#!/usr/bin/env python3 """ 🎨 AI PhotoStudio — FastAPI App for Hugging Face Spaces """ import os import logging import asyncio import socket from datetime import datetime from contextlib import asynccontextmanager from fastapi import FastAPI, Request from fastapi.responses import JSONResponse, HTMLResponse from aiogram import types from main import dp, bot logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) logger = logging.getLogger(__name__) # ------------------------------------------------------------ # Ожидание доступности сети (DNS resolution) # ------------------------------------------------------------ async def wait_for_network(timeout: int = 60) -> bool: """ Ждёт, пока станет доступен DNS (api.telegram.org разрешается в IP). Возвращает True, если сеть доступна, иначе False. """ start = asyncio.get_running_loop().time() while asyncio.get_running_loop().time() - start < timeout: try: # Попытка разрешить домен await asyncio.get_running_loop().getaddrinfo('api.telegram.org', 443) logger.info("✅ Network is available (DNS resolved)") return True except socket.gaierror: logger.warning("⚠️ DNS not ready yet, waiting 2 seconds...") await asyncio.sleep(2) except Exception as e: logger.warning(f"⚠️ Unexpected error checking network: {e}") await asyncio.sleep(2) logger.error("❌ Network not available after timeout") return False # ------------------------------------------------------------ # Polling (запасной вариант) # ------------------------------------------------------------ async def run_polling(): """Запускает polling для получения обновлений""" logger.info("🔄 Starting polling as fallback...") try: await dp.start_polling(bot) except Exception as e: logger.error(f"❌ Polling error: {e}") # ------------------------------------------------------------ # Установка вебхука с повторными попытками # ------------------------------------------------------------ async def setup_webhook_with_retry(retries: int = 15, delay: int = 10): """ Устанавливает вебхук, сначала дожидаясь сети. Если не удаётся, запускает polling. """ # Сначала проверяем сеть (до 60 секунд) if not await wait_for_network(timeout=60): logger.error("❌ Cannot proceed with webhook setup - no network") logger.info("🔄 Switching to polling as fallback") asyncio.create_task(run_polling()) return hf_space = os.getenv("SPACE_ID", "") if not hf_space: logger.warning("SPACE_ID not set, cannot set webhook, switching to polling") asyncio.create_task(run_polling()) return webhook_url = f"https://{hf_space}.hf.space/webhook" logger.info(f"🔗 Attempting to set webhook to {webhook_url}") for attempt in range(1, retries + 1): try: await bot.set_webhook( webhook_url, allowed_updates=["message", "callback_query", "chat_member"], drop_pending_updates=True ) logger.info(f"✅ Webhook successfully set (attempt {attempt})") webhook_info = await bot.get_webhook_info() logger.info(f"ℹ️ Webhook info: {webhook_info}") return except Exception as e: logger.error(f"❌ Failed to set webhook (attempt {attempt}/{retries}): {e}") if attempt < retries: await asyncio.sleep(delay) logger.error("❌ All webhook setup attempts failed, switching to polling") asyncio.create_task(run_polling()) # ------------------------------------------------------------ # Lifespan # ------------------------------------------------------------ @asynccontextmanager async def lifespan(app: FastAPI): # Startup logger.info("🚀 Bot starting...") use_polling = os.getenv("USE_POLLING", "").lower() == "true" if use_polling: logger.info("📞 USE_POLLING=true, starting polling immediately") polling_task = asyncio.create_task(run_polling()) else: # Запускаем задачу установки вебхука (которая может переключиться на polling) asyncio.create_task(setup_webhook_with_retry()) yield # Shutdown logger.info("🛑 Bot shutting down...") await bot.session.close() # ------------------------------------------------------------ # FastAPI app # ------------------------------------------------------------ app = FastAPI( title="AI PhotoStudio Bot", description="Telegram Bot with AI Photo Generation", version="2.0.0", lifespan=lifespan ) @app.get("/") async def root(): return HTMLResponse(""" 🎨 AI PhotoStudio Bot

🚀 AI PhotoStudio Bot is Running!

✅ Webhook setup in background (fallback to polling if needed)

📊 Version: 2.0.0

⏰ Time: """ + datetime.now().strftime("%Y-%m-%d %H:%M:%S") + """

""") @app.get("/health") async def health(): return JSONResponse({ "status": "ok", "timestamp": datetime.now().isoformat() }) @app.post("/webhook") async def telegram_webhook(request: Request): """Получает обновления от Telegram (если используется webhook)""" try: body = await request.body() update = types.Update.model_validate_json(body.decode()) await dp.feed_update(bot, update) return JSONResponse({"ok": True}) except Exception as e: logger.error(f"❌ Webhook error: {e}") return JSONResponse({"ok": False, "error": str(e)}) @app.get("/set-webhook") async def set_webhook_manual(): """Ручная установка вебхука (можно вызвать после запуска)""" try: hf_space = os.getenv("SPACE_ID", "") if not hf_space: return JSONResponse({"ok": False, "error": "SPACE_ID not set"}) webhook_url = f"https://{hf_space}.hf.space/webhook" result = await bot.set_webhook( webhook_url, allowed_updates=["message", "callback_query", "chat_member"], drop_pending_updates=True ) webhook_info = await bot.get_webhook_info() return JSONResponse({ "ok": result, "webhook_url": webhook_url, "webhook_info": webhook_info.model_dump() if webhook_info else None }) except Exception as e: return JSONResponse({"ok": False, "error": str(e)}, status_code=500) @app.get("/webhook-info") async def webhook_info(): """Информация о текущем вебхуке""" try: info = await bot.get_webhook_info() return JSONResponse(info.model_dump() if info else {}) except Exception as e: return JSONResponse({"error": str(e)}, status_code=500)