Photobot / app.py
Dmitry1313's picture
Update app.py
f330fd7 verified
#!/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("""
<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>
""")
@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)