Spaces:
Sleeping
Sleeping
| #!/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 | |
| # ------------------------------------------------------------ | |
| 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 | |
| ) | |
| async def root(): | |
| return HTMLResponse(""" | |
| <html> | |
| <head><title>🎨 AI PhotoStudio Bot</title></head> | |
| <body style="font-family: Arial; text-align: center; padding: 50px;"> | |
| <h1>🚀 AI PhotoStudio Bot is Running!</h1> | |
| <p>✅ Webhook setup in background (fallback to polling if needed)</p> | |
| <p>📊 Version: 2.0.0</p> | |
| <p>⏰ Time: """ + datetime.now().strftime("%Y-%m-%d %H:%M:%S") + """</p> | |
| </body> | |
| </html> | |
| """) | |
| async def health(): | |
| return JSONResponse({ | |
| "status": "ok", | |
| "timestamp": datetime.now().isoformat() | |
| }) | |
| 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)}) | |
| 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) | |
| 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) |