First
This commit is contained in:
@@ -0,0 +1,606 @@
|
||||
from fastapi import FastAPI, HTTPException, UploadFile, File, Form
|
||||
from fastapi.responses import JSONResponse, HTMLResponse, FileResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
import aiohttp
|
||||
from aiohttp_socks import ProxyConnector
|
||||
import asyncio
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
from dotenv import load_dotenv
|
||||
import base64
|
||||
from typing import Optional
|
||||
import json
|
||||
import logging
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# Настройка логирования
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
# Настройка CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Создание папок
|
||||
os.makedirs("photos", exist_ok=True)
|
||||
os.makedirs("static", exist_ok=True)
|
||||
|
||||
# Монтируем статику
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
app.mount("/photos", StaticFiles(directory="photos"), name="photos")
|
||||
|
||||
# Конфигурация из .env
|
||||
PIAPI_API_KEY = os.getenv("PIAPI_API_KEY")
|
||||
PIAPI_API_URL = os.getenv("PIAPI_API_URL", "https://api.piapi.ai/api/v1/task")
|
||||
PROXY_URL = os.getenv("PROXY_URL")
|
||||
USE_PROXY = os.getenv("USE_PROXY", "true").lower() == "true"
|
||||
IMGBB_API_KEY = os.getenv("IMGBB_API_KEY", "")
|
||||
|
||||
MODEL_NAME = os.getenv("MODEL_NAME", "gemini")
|
||||
TASK_TYPE = os.getenv("TASK_TYPE", "nano-banana-pro")
|
||||
ASPECT_RATIO = os.getenv("ASPECT_RATIO", "9:16")
|
||||
STRENGTH = float(os.getenv("STRENGTH", "0.65"))
|
||||
SAFETY_LEVEL = os.getenv("SAFETY_LEVEL", "medium")
|
||||
OUTPUT_FORMAT = os.getenv("OUTPUT_FORMAT", "png")
|
||||
|
||||
HOST = os.getenv("HOST", "0.0.0.0")
|
||||
PORT = int(os.getenv("PORT", 8000))
|
||||
|
||||
# Хранилище опубликованных фото
|
||||
published_photos = []
|
||||
# Временное хранилище для фото перед стилизацией
|
||||
temp_photos = {}
|
||||
|
||||
# Список фотосессий
|
||||
PHOTO_SESSIONS = {
|
||||
"vintage": {
|
||||
"name": "📷 Винтажная фотосессия",
|
||||
"description": "Эффект старой фотографии 1950-х годов",
|
||||
"icon": "📷",
|
||||
"prompt": """CRITICAL: Keep the person's face, facial features, and identity EXACTLY as in the original image. Do not change or replace the face.
|
||||
|
||||
Transform this photo into a vintage 1950s style photograph. Add sepia tone, film grain, soft vignette, and aged paper texture. The photo should look like it was taken with an old film camera. Preserve all facial details and natural expressions."""
|
||||
},
|
||||
"cyberpunk": {
|
||||
"name": "🤖 Cyberpunk 2077",
|
||||
"description": "Неоновый киберпанк стиль",
|
||||
"icon": "🤖",
|
||||
"prompt": """CRITICAL: Keep the person's face, facial features, and identity EXACTLY as in the original image. Do not change or replace the face.
|
||||
|
||||
Transform this photo into a cyberpunk style with neon lights (pink, cyan, purple), dark futuristic city background, holographic elements, and sci-fi atmosphere. Add neon rim lighting on the person's face and body. Style inspired by Cyberpunk 2077."""
|
||||
},
|
||||
"fantasy": {
|
||||
"name": "🧝 Эльфийская фэнтези",
|
||||
"description": "Волшебный фэнтези мир",
|
||||
"icon": "🧝",
|
||||
"prompt": """CRITICAL: Keep the person's face, facial features, and identity EXACTLY as in the original image. Do not change or replace the face.
|
||||
|
||||
Transform this photo into a fantasy/elf style. Add magical glowing forest background with fireflies, mystical atmosphere, subtle elf ears if appropriate, flowing elegant clothes, and soft magical lighting. Create an enchanted forest vibe."""
|
||||
},
|
||||
"royal": {
|
||||
"name": "👑 Королевский портрет",
|
||||
"description": "Портрет в стиле европейской аристократии",
|
||||
"icon": "👑",
|
||||
"prompt": """CRITICAL: Keep the person's face, facial features, and identity EXACTLY as in the original image. Do not change or replace the face.
|
||||
|
||||
Transform this photo into a royal/aristocratic portrait. Add elegant palace background with golden details, luxurious velvet curtains, ornate frames, and dramatic Rembrandt lighting. Style reminiscent of 18th century European nobility portraits."""
|
||||
},
|
||||
"anime": {
|
||||
"name": "🎨 Аниме-арт",
|
||||
"description": "Превращение в аниме персонажа",
|
||||
"icon": "🎨",
|
||||
"prompt": """CRITICAL: Keep the person's face, facial features, and identity EXACTLY as in the original image. Do not change or replace the face.
|
||||
|
||||
Transform this photo into an anime/manga art style. Use cel-shading technique, vibrant colors, anime-style large expressive eyes (while preserving original face shape), and detailed anime background. Style inspired by modern Japanese animation."""
|
||||
},
|
||||
"painting": {
|
||||
"name": "🎭 Художественная картина",
|
||||
"description": "Стиль масляной живописи",
|
||||
"icon": "🎭",
|
||||
"prompt": """CRITICAL: Keep the person's face, facial features, and identity EXACTLY as in the original image. Do not change or replace the face.
|
||||
|
||||
Transform this photo into an oil painting masterpiece. Use visible brushstrokes, rich textures, artistic color palette, and gallery lighting. Style inspired by classical portrait paintings like Rembrandt or Vermeer. Create a timeless artistic quality."""
|
||||
},
|
||||
"beach": {
|
||||
"name": "🏖️ Пляжная фотосессия",
|
||||
"description": "Летнее настроение на пляже",
|
||||
"icon": "🏖️",
|
||||
"prompt": """CRITICAL: Keep the person's face, facial features, and identity EXACTLY as in the original image. Do not change or replace the face.
|
||||
|
||||
Transform this photo into a beautiful beach photoshoot. Add tropical beach background with golden sand, turquoise ocean, palm trees, and golden hour sunset lighting. Create a relaxed summer vacation atmosphere with warm colors."""
|
||||
},
|
||||
"professional": {
|
||||
"name": "💼 Деловой портрет",
|
||||
"description": "Профессиональный корпоративный стиль",
|
||||
"icon": "💼",
|
||||
"prompt": """CRITICAL: Keep the person's face, facial features, and identity EXACTLY as in the original image. Do not change or replace the face.
|
||||
|
||||
Transform this photo into a professional corporate portrait. Add modern office background with city view, professional lighting, business attire enhancement, and confident atmosphere. Create a polished headshot suitable for LinkedIn or company website."""
|
||||
},
|
||||
"space": {
|
||||
"name": "🚀 Космическое путешествие",
|
||||
"description": "Астронавт в космосе",
|
||||
"icon": "🚀",
|
||||
"prompt": """CRITICAL: Keep the person's face, facial features, and identity EXACTLY as in the original image. Do not change or replace the face.
|
||||
|
||||
Transform this photo into a space/astronaut theme. Add spacesuit elements, cosmic starry background with nebulas, Earth or Moon in the distance, sci-fi helmet reflection, and futuristic space station interior. Create an epic space exploration feel."""
|
||||
},
|
||||
"wedding": {
|
||||
"name": "💒 Свадебная фотосессия",
|
||||
"description": "Романтический свадебный стиль",
|
||||
"icon": "💒",
|
||||
"prompt": """CRITICAL: Keep the person's face, facial features, and identity EXACTLY as in the original image. Do not change or replace the face.
|
||||
|
||||
Transform this photo into a romantic wedding/engagement style. Add elegant wedding attire, beautiful floral arch background with roses and peonies, soft golden hour lighting, and romantic dreamy atmosphere. Create timeless love story vibes."""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def get_proxy_connector():
|
||||
"""Создает новый ProxyConnector для SOCKS5 при каждом вызове"""
|
||||
if not USE_PROXY or not PROXY_URL:
|
||||
return None
|
||||
|
||||
try:
|
||||
match = re.search(r'socks5://([^:]+):([^@]+)@([^:]+):(\d+)', PROXY_URL)
|
||||
if match:
|
||||
username = match.group(1)
|
||||
password = match.group(2)
|
||||
host = match.group(3)
|
||||
port = int(match.group(4))
|
||||
connector = ProxyConnector.from_url(f"socks5://{username}:{password}@{host}:{port}")
|
||||
return connector
|
||||
else:
|
||||
from urllib.parse import urlparse
|
||||
parsed = urlparse(PROXY_URL)
|
||||
connector = ProxyConnector.from_url(f"socks5://{parsed.hostname}:{parsed.port}")
|
||||
return connector
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Ошибка создания прокси коннектора: {e}")
|
||||
return None
|
||||
|
||||
|
||||
async def upload_image_to_imgbb(image_bytes: bytes) -> str:
|
||||
"""Загружает изображение на imgbb и возвращает URL"""
|
||||
if not IMGBB_API_KEY:
|
||||
return await upload_image_to_telegraph(image_bytes)
|
||||
|
||||
logger.info("📤 Загрузка изображения на imgbb...")
|
||||
|
||||
image_base64 = base64.b64encode(image_bytes).decode('utf-8')
|
||||
|
||||
data = {
|
||||
'key': IMGBB_API_KEY,
|
||||
'image': image_base64,
|
||||
'expiration': 3600
|
||||
}
|
||||
|
||||
try:
|
||||
connector = get_proxy_connector()
|
||||
async with aiohttp.ClientSession(connector=connector) as session:
|
||||
async with session.post('https://api.imgbb.com/1/upload', data=data, timeout=30) as response:
|
||||
if response.status == 200:
|
||||
result = await response.json()
|
||||
if result.get('success'):
|
||||
url = result['data']['url']
|
||||
logger.info(f"✅ Изображение загружено на imgbb")
|
||||
return url
|
||||
else:
|
||||
raise Exception(f"Ошибка imgbb: {result}")
|
||||
else:
|
||||
raise Exception(f"HTTP {response.status}")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Ошибка загрузки на imgbb: {e}")
|
||||
return await upload_image_to_telegraph(image_bytes)
|
||||
|
||||
|
||||
async def upload_image_to_telegraph(image_bytes: bytes) -> str:
|
||||
"""Загружает изображение на Telegraph"""
|
||||
logger.info("📤 Загрузка изображения на Telegraph...")
|
||||
|
||||
form_data = aiohttp.FormData()
|
||||
form_data.add_field('file', image_bytes, filename='image.jpg', content_type='image/jpeg')
|
||||
|
||||
try:
|
||||
connector = get_proxy_connector()
|
||||
async with aiohttp.ClientSession(connector=connector) as session:
|
||||
async with session.post('https://telegra.ph/upload', data=form_data, timeout=30) as response:
|
||||
if response.status == 200:
|
||||
result = await response.json()
|
||||
if result and len(result) > 0:
|
||||
url = f"https://telegra.ph{result[0]['src']}"
|
||||
logger.info(f"✅ Изображение загружено на Telegraph")
|
||||
return url
|
||||
else:
|
||||
raise Exception(f"Ошибка Telegraph: {result}")
|
||||
else:
|
||||
raise Exception(f"HTTP {response.status}")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Ошибка загрузки на Telegraph: {e}")
|
||||
return None
|
||||
|
||||
|
||||
async def poll_task_result(task_id: str, max_attempts: int = 60) -> str:
|
||||
"""Ожидание завершения задачи"""
|
||||
get_url = f"https://api.piapi.ai/api/v1/task/{task_id}"
|
||||
headers = {"x-api-key": PIAPI_API_KEY}
|
||||
|
||||
for attempt in range(max_attempts):
|
||||
try:
|
||||
connector = get_proxy_connector()
|
||||
async with aiohttp.ClientSession(connector=connector) as session:
|
||||
async with session.get(get_url, headers=headers, timeout=30) as response:
|
||||
if response.status != 200:
|
||||
logger.warning(f"HTTP {response.status}, повтор...")
|
||||
await asyncio.sleep(3)
|
||||
continue
|
||||
|
||||
data = await response.json()
|
||||
data_info = data.get('data', {})
|
||||
status = data_info.get('status', 'unknown')
|
||||
output = data_info.get('output')
|
||||
|
||||
logger.info(f"Задача {task_id}: статус {status} (попытка {attempt + 1})")
|
||||
|
||||
if status == 'completed' and output:
|
||||
result_url = None
|
||||
if isinstance(output, dict):
|
||||
if output.get('image_urls') and len(output['image_urls']) > 0:
|
||||
result_url = output['image_urls'][0]
|
||||
elif output.get('url'):
|
||||
result_url = output['url']
|
||||
elif output.get('image'):
|
||||
result_url = output['image']
|
||||
|
||||
if result_url:
|
||||
logger.info(f"✅ Задача {task_id} выполнена!")
|
||||
return result_url
|
||||
|
||||
elif status == 'failed':
|
||||
error = data_info.get('error', {})
|
||||
error_msg = error.get('message', 'Unknown error')
|
||||
raise Exception(f"Задача не выполнена: {error_msg}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка опроса (попытка {attempt + 1}): {e}")
|
||||
|
||||
await asyncio.sleep(3)
|
||||
|
||||
raise Exception("Превышено время ожидания")
|
||||
|
||||
|
||||
async def process_with_nano_banana(image_bytes: bytes, prompt: str, session_id: str = None) -> str:
|
||||
"""Обработка изображения через Nano Banana Pro"""
|
||||
|
||||
if not PIAPI_API_KEY or PIAPI_API_KEY == "your_api_key_here":
|
||||
raise Exception("API ключ не настроен")
|
||||
|
||||
image_url = await upload_image_to_imgbb(image_bytes)
|
||||
if not image_url:
|
||||
raise Exception("Не удалось загрузить изображение на хостинг")
|
||||
|
||||
logger.info(f"🔄 Отправка запроса в Nano Banana Pro...")
|
||||
|
||||
payload = {
|
||||
"model": MODEL_NAME,
|
||||
"task_type": TASK_TYPE,
|
||||
"input": {
|
||||
"image_urls": [image_url],
|
||||
"prompt": prompt,
|
||||
"output_format": OUTPUT_FORMAT,
|
||||
"aspect_ratio": ASPECT_RATIO,
|
||||
"strength": STRENGTH,
|
||||
"safety_level": SAFETY_LEVEL
|
||||
}
|
||||
}
|
||||
|
||||
headers = {
|
||||
"x-api-key": PIAPI_API_KEY,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
try:
|
||||
connector = get_proxy_connector()
|
||||
async with aiohttp.ClientSession(connector=connector) as session:
|
||||
async with session.post(PIAPI_API_URL, headers=headers, json=payload, timeout=60) as response:
|
||||
if response.status != 200:
|
||||
response_text = await response.text()
|
||||
raise Exception(f"HTTP {response.status}: {response_text[:200]}")
|
||||
|
||||
data = await response.json()
|
||||
|
||||
if data.get('code') != 200:
|
||||
raise Exception(f"API Error: {data.get('message')}")
|
||||
|
||||
task_id = data.get('data', {}).get('task_id')
|
||||
if not task_id:
|
||||
raise Exception(f"Не получен task_id: {data}")
|
||||
|
||||
logger.info(f"✅ Создана задача: {task_id}")
|
||||
|
||||
result_url = await poll_task_result(task_id)
|
||||
return result_url
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Ошибка: {e}")
|
||||
raise
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
with open("static/index.html", "r", encoding="utf-8") as f:
|
||||
return HTMLResponse(content=f.read())
|
||||
|
||||
|
||||
@app.get("/display")
|
||||
async def display():
|
||||
with open("static/display.html", "r", encoding="utf-8") as f:
|
||||
return HTMLResponse(content=f.read())
|
||||
|
||||
|
||||
@app.get("/sessions")
|
||||
async def get_sessions():
|
||||
"""Возвращает список доступных фотосессий"""
|
||||
return JSONResponse({"sessions": PHOTO_SESSIONS})
|
||||
|
||||
|
||||
@app.post("/upload-temp")
|
||||
async def upload_temp(file: UploadFile = File(...)):
|
||||
"""Временная загрузка фото перед выбором стиля"""
|
||||
try:
|
||||
content = await file.read()
|
||||
session_id = hashlib.md5(f"{content}{datetime.now().timestamp()}".encode()).hexdigest()
|
||||
|
||||
# Сохраняем оригинал
|
||||
original_filename = f"temp_{session_id}_{file.filename}"
|
||||
original_path = f"photos/{original_filename}"
|
||||
|
||||
with open(original_path, "wb") as f:
|
||||
f.write(content)
|
||||
|
||||
temp_photos[session_id] = {
|
||||
"path": original_path,
|
||||
"filename": original_filename,
|
||||
"content": content,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
return JSONResponse({
|
||||
"success": True,
|
||||
"session_id": session_id,
|
||||
"preview_url": f"/photos/{original_filename}"
|
||||
})
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/process-with-style")
|
||||
async def process_with_style(
|
||||
session_id: str = Form(...),
|
||||
style_id: str = Form(...)
|
||||
):
|
||||
"""Обработка фото с выбранным стилем"""
|
||||
try:
|
||||
if session_id not in temp_photos:
|
||||
raise HTTPException(status_code=404, detail="Фото не найдено")
|
||||
|
||||
if style_id not in PHOTO_SESSIONS:
|
||||
raise HTTPException(status_code=404, detail="Стиль не найден")
|
||||
|
||||
photo_data = temp_photos[session_id]
|
||||
style = PHOTO_SESSIONS[style_id]
|
||||
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
|
||||
|
||||
logger.info(f"🔄 Обработка фото со стилем: {style['name']}")
|
||||
|
||||
if not PIAPI_API_KEY or PIAPI_API_KEY == "your_api_key_here":
|
||||
return JSONResponse({
|
||||
"success": True,
|
||||
"original": photo_data["path"],
|
||||
"processed": photo_data["path"],
|
||||
"original_url": f"/photos/{photo_data['filename']}",
|
||||
"processed_url": f"/photos/{photo_data['filename']}",
|
||||
"timestamp": timestamp,
|
||||
"warning": "⚠️ API ключ не настроен"
|
||||
})
|
||||
|
||||
try:
|
||||
result_url = await process_with_nano_banana(photo_data["content"], style["prompt"])
|
||||
|
||||
if result_url:
|
||||
connector = get_proxy_connector()
|
||||
|
||||
async with aiohttp.ClientSession(connector=connector) as session:
|
||||
async with session.get(result_url, timeout=60) as resp:
|
||||
if resp.status == 200:
|
||||
processed_filename = f"processed_{timestamp}.{OUTPUT_FORMAT}"
|
||||
processed_path = f"photos/{processed_filename}"
|
||||
|
||||
processed_content = await resp.read()
|
||||
with open(processed_path, "wb") as f:
|
||||
f.write(processed_content)
|
||||
|
||||
logger.info(f"✅ Обработанное фото сохранено: {processed_filename}")
|
||||
|
||||
return JSONResponse({
|
||||
"success": True,
|
||||
"original": photo_data["path"],
|
||||
"processed": processed_path,
|
||||
"original_url": f"/photos/{photo_data['filename']}",
|
||||
"processed_url": f"/photos/{processed_filename}",
|
||||
"timestamp": timestamp,
|
||||
"style_name": style["name"]
|
||||
})
|
||||
else:
|
||||
raise Exception(f"HTTP {resp.status}")
|
||||
else:
|
||||
raise Exception("Не получен URL результата")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Ошибка: {e}")
|
||||
return JSONResponse({
|
||||
"success": True,
|
||||
"original": photo_data["path"],
|
||||
"processed": photo_data["path"],
|
||||
"original_url": f"/photos/{photo_data['filename']}",
|
||||
"processed_url": f"/photos/{photo_data['filename']}",
|
||||
"timestamp": timestamp,
|
||||
"warning": f"⚠️ Ошибка: {str(e)[:100]}"
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Критическая ошибка: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/publish-photo")
|
||||
async def publish_photo(photo_path: str = Form(...)):
|
||||
try:
|
||||
photo_info = {
|
||||
"path": photo_path,
|
||||
"published_at": datetime.now().isoformat(),
|
||||
"id": hashlib.md5(f"{photo_path}{datetime.now().timestamp()}".encode()).hexdigest()
|
||||
}
|
||||
published_photos.append(photo_info)
|
||||
|
||||
logger.info(f"📢 Фото опубликовано: {photo_path}")
|
||||
|
||||
return JSONResponse({
|
||||
"success": True,
|
||||
"photo_id": photo_info["id"],
|
||||
"message": "Фото опубликовано"
|
||||
})
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/get-published-photos")
|
||||
async def get_published_photos():
|
||||
return JSONResponse({"photos": published_photos})
|
||||
|
||||
|
||||
@app.get("/download-photo/{photo_id}")
|
||||
async def download_photo(photo_id: str):
|
||||
for photo in published_photos:
|
||||
if photo["id"] == photo_id:
|
||||
photo_path = photo["path"].lstrip("/")
|
||||
if os.path.exists(photo_path):
|
||||
return FileResponse(
|
||||
photo_path,
|
||||
media_type='image/jpeg',
|
||||
filename=os.path.basename(photo_path)
|
||||
)
|
||||
raise HTTPException(status_code=404, detail="Фото не найдено")
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
proxy_status = "not configured"
|
||||
if USE_PROXY and PROXY_URL:
|
||||
connector = get_proxy_connector()
|
||||
if connector:
|
||||
try:
|
||||
async with aiohttp.ClientSession(connector=connector) as session:
|
||||
async with session.get("https://api.ipify.org?format=json", timeout=10) as resp:
|
||||
if resp.status == 200:
|
||||
ip_data = await resp.json()
|
||||
proxy_status = f"working (IP: {ip_data.get('ip')})"
|
||||
else:
|
||||
proxy_status = "error"
|
||||
except Exception as e:
|
||||
proxy_status = f"error: {str(e)}"
|
||||
else:
|
||||
proxy_status = "failed to create connector"
|
||||
|
||||
return {
|
||||
"status": "ok",
|
||||
"api_configured": bool(PIAPI_API_KEY and PIAPI_API_KEY != "your_api_key_here"),
|
||||
"model": MODEL_NAME,
|
||||
"proxy_enabled": USE_PROXY,
|
||||
"proxy_status": proxy_status,
|
||||
"published_photos_count": len(published_photos),
|
||||
"available_sessions": len(PHOTO_SESSIONS)
|
||||
}
|
||||
|
||||
|
||||
@app.get("/test-proxy")
|
||||
async def test_proxy():
|
||||
results = {
|
||||
"proxy_enabled": USE_PROXY,
|
||||
"tests": {}
|
||||
}
|
||||
|
||||
if not USE_PROXY or not PROXY_URL:
|
||||
results["message"] = "Прокси не настроен"
|
||||
return JSONResponse(results)
|
||||
|
||||
connector = get_proxy_connector()
|
||||
|
||||
if not connector:
|
||||
results["tests"]["proxy_connector"] = {
|
||||
"status": "error",
|
||||
"message": "Не удалось создать прокси коннектор"
|
||||
}
|
||||
return JSONResponse(results)
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession(connector=connector) as session:
|
||||
async with session.get("https://api.ipify.org?format=json", timeout=15) as resp:
|
||||
if resp.status == 200:
|
||||
ip_data = await resp.json()
|
||||
results["tests"]["ip_check"] = {
|
||||
"status": "success",
|
||||
"ip": ip_data.get("ip")
|
||||
}
|
||||
else:
|
||||
results["tests"]["ip_check"] = {
|
||||
"status": "error",
|
||||
"message": f"HTTP {resp.status}"
|
||||
}
|
||||
except Exception as e:
|
||||
results["tests"]["ip_check"] = {
|
||||
"status": "error",
|
||||
"message": str(e)
|
||||
}
|
||||
|
||||
success_tests = sum(1 for test in results["tests"].values() if test.get("status") == "success")
|
||||
results["summary"] = {
|
||||
"tests_passed": success_tests,
|
||||
"tests_total": len(results["tests"]),
|
||||
"proxy_working": success_tests > 0
|
||||
}
|
||||
|
||||
return JSONResponse(results)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
print("=" * 60)
|
||||
print("📸 Нейрофотосессия - Сервер запущен")
|
||||
print("=" * 60)
|
||||
print(f"🌐 Адрес: http://localhost:{PORT}")
|
||||
print(f"📱 Оператор: http://localhost:{PORT}")
|
||||
print(f"🖼️ Галерея: http://localhost:{PORT}/display")
|
||||
print("=" * 60)
|
||||
print(f"🎨 Доступно стилей: {len(PHOTO_SESSIONS)}")
|
||||
for style_id, style in PHOTO_SESSIONS.items():
|
||||
print(f" {style['icon']} {style['name']}")
|
||||
print("=" * 60)
|
||||
|
||||
uvicorn.run(app, host=HOST, port=PORT)
|
||||
Reference in New Issue
Block a user