Files
ai-char-bot/aiws.py
2026-02-11 11:15:21 +03:00

232 lines
8.5 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import asyncio
import logging
import os
from contextlib import asynccontextmanager
from aiogram import Bot, Dispatcher, Router, F
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from aiogram.filters import CommandStart, Command
from aiogram.types import Message
from aiogram.fsm.storage.mongo import MongoStorage
from dotenv import load_dotenv
from fastapi import FastAPI
from motor.motor_asyncio import AsyncIOMotorClient
from starlette.middleware.cors import CORSMiddleware
# --- ИМПОРТЫ ПРОЕКТА ---
from adapters.google_adapter import GoogleAdapter
from adapters.s3_adapter import S3Adapter
from api.service.generation_service import GenerationService
from api.service.album_service import AlbumService
from middlewares.album import AlbumMiddleware
from middlewares.auth import AuthMiddleware
from middlewares.dao import DaoMiddleware
# Репозитории и DAO
from repos.char_repo import CharacterRepo
from repos.user_repo import UsersRepo
from repos.dao import DAO
# Роутеры
from routers.auth_router import router as auth_router
from routers.gen_router import router as gen_router
from routers.char_router import router as char_router
from routers.assets_router import router as assets_router # Роутер бота для ассетов
from api.endpoints.assets_router import router as api_assets_router # Роутер FastAPI
from api.endpoints.character_router import router as api_char_router # Роутер FastAPI
from api.endpoints.generation_router import router as api_gen_router
from api.endpoints.auth import router as api_auth_router
from api.endpoints.admin import router as api_admin_router
from api.endpoints.album_router import router as api_album_router
load_dotenv()
logger = logging.getLogger(__name__)
# --- КОНФИГУРАЦИЯ ---
BOT_TOKEN = os.getenv("BOT_TOKEN")
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
MONGO_HOST = os.getenv("MONGO_HOST") # Например: mongodb://localhost:27017
DB_NAME = os.getenv("DB_NAME", "my_bot_db") # Имя базы данных
ADMIN_ID = int(os.getenv("ADMIN_ID", 0))
def setup_logging():
logging.basicConfig(level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s (%(filename)s:%(lineno)d): %(message)s",
force=True)
# --- ИНИЦИАЛИЗАЦИЯ ЗАВИСИМОСТЕЙ ---
bot = Bot(token=BOT_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
# Клиент БД создаем глобально, чтобы он был доступен и боту (Storage), и API
mongo_client = AsyncIOMotorClient(MONGO_HOST)
# Репозитории
# Репозитории
users_repo = UsersRepo(mongo_client)
char_repo = CharacterRepo(mongo_client)
# S3 Adapter
s3_adapter = S3Adapter(
endpoint_url=os.getenv("MINIO_ENDPOINT", "http://31.59.58.220:9000"),
aws_access_key_id=os.getenv("MINIO_ACCESS_KEY", "minioadmin"),
aws_secret_access_key=os.getenv("MINIO_SECRET_KEY", "minioadmin"),
bucket_name=os.getenv("MINIO_BUCKET", "ai-char")
)
dao = DAO(mongo_client, s3_adapter) # Главный DAO для бота
gemini = GoogleAdapter(api_key=GEMINI_API_KEY)
generation_service = GenerationService(dao, gemini, bot)
album_service = AlbumService(dao)
# Dispatcher
dp = Dispatcher(storage=MongoStorage(mongo_client, db_name=DB_NAME))
# Внедрение зависимостей (глобально для бота)
dp["repo"] = users_repo
dp["admin_id"] = ADMIN_ID
dp["gemini"] = gemini
# --- НАСТРОЙКА РОУТЕРОВ БОТА ---
# 1. Роутеры без мидлварей (например, auth)
dp.include_router(auth_router)
# 2. Основные роутеры
main_router = Router()
dp.include_router(main_router)
dp.include_router(assets_router)
dp.include_router(char_router)
dp.include_router(gen_router)
# --- НАСТРОЙКА MIDDLEWARES БОТА ---
# DaoMiddleware прокидывает объект 'dao' во все хендлеры
dp.update.middleware(DaoMiddleware(dao=dao))
# AuthMiddleware проверяет права доступа
main_router.message.middleware(AuthMiddleware(repo=users_repo, admin_id=ADMIN_ID))
gen_router.message.middleware(AuthMiddleware(repo=users_repo, admin_id=ADMIN_ID))
assets_router.message.middleware(AuthMiddleware(repo=users_repo, admin_id=ADMIN_ID))
# AlbumMiddleware для обработки групп фото
gen_router.message.middleware(AlbumMiddleware(latency=0.8))
# --- LIFESPAN (Запуск FastAPI + Bot) ---
@asynccontextmanager
async def lifespan(app: FastAPI):
# --- STARTUP ---
setup_logging()
print("🚀 Starting up...")
# 1. Настройка DAO для FastAPI
# Используем уже созданный mongo_client
db = mongo_client[DB_NAME]
# Инициализируем DAO для ассетов и кладем в state приложения
# Теперь в эндпоинтах можно делать request.app.state.assets_dao
app.state.mongo_client = mongo_client
app.state.gemini_client = gemini
app.state.bot = bot
app.state.s3_adapter = s3_adapter
app.state.album_service = album_service
app.state.users_repo = users_repo # Добавляем репозиторий в state
print("✅ DB & DAO initialized")
# 2. ЗАПУСК БОТА (в фоне)
# Важно: handle_signals=False, чтобы бот не перехватывал сигналы остановки у uvicorn
# Мы НЕ передаем сюда dao=..., так как он уже подключен через Middleware выше
# polling_task = asyncio.create_task(
# dp.start_polling(bot, handle_signals=False)
# )
# print("🤖 Bot polling started")
yield
# --- SHUTDOWN ---
print("🛑 Shutting down...")
# 3. Остановка бота
polling_task.cancel()
try:
await polling_task
except asyncio.CancelledError:
print("🤖 Bot polling stopped")
# 4. Отключение БД
# Обычно Motor закрывать не обязательно при выходе, но хорошим тоном считается
# mongo_client.close()
print("🛑 DB Connection closed")
# --- НАСТРОЙКА FASTAPI ---
app = FastAPI(title="Assets API", lifespan=lifespan)
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# Подключаем роутер API
from api.endpoints.auth import router as auth_api_router
from api.endpoints.admin import router as admin_api_router
from api.endpoints.project_router import router as project_api_router
app.include_router(auth_api_router)
app.include_router(admin_api_router)
app.include_router(api_assets_router)
app.include_router(api_char_router)
app.include_router(api_gen_router)
app.include_router(api_album_router)
app.include_router(api_admin_router)
app.include_router(api_auth_router)
app.include_router(project_api_router)
# --- ХЕНДЛЕРЫ БОТА (Main Router) ---
@main_router.message(Command("help"))
async def show_help(message: Message) -> None:
await message.answer(" <b>Справка:</b>\n\n"
"📝 <b>Текст:</b> Просто отправь промпт.\n"
"🎨 <b>Фото:</b> /image {промпт} (или прикрепи фото с подписью).\n\n"
"⚠️ Диалоги не сохраняются (каждое сообщение — новый запрос).")
@main_router.message(CommandStart())
async def cmd_start(message: Message):
await message.answer("👋 Привет! Я готов к работе.\n\n"
"Напиши мне, что нужно сгенерировать, или используй /help.")
# --- ЗАПУСК ---
if __name__ == "__main__":
import uvicorn
setup_logging()
async def main():
# Создаем конфигурацию uvicorn вручную
# loop="asyncio" заставляет использовать стандартный цикл
config = uvicorn.Config(app, host="0.0.0.0", port=8090, loop="asyncio", timeout_keep_alive=120, env_file=".env.development")
server = uvicorn.Server(config)
# Запускаем сервер (lifespan запустится внутри)
await server.serve()
try:
# Сами запускаем цикл, контролируя аргументы
asyncio.run(main())
except KeyboardInterrupt:
# Корректно обрабатываем выход
pass