import asyncio import logging from aiogram import Bot, Dispatcher, F from tortoise import Tortoise from aiogram.client.session.aiohttp import AiohttpSession from app import config from app.handlers import callbacks, commands, prompt from app.handlers.reviews import send_queue_worker, sheet_queue_worker from app.models import Settings from app.services.ozon import OzonClient from app.services.wb import WildberriesClient # Configure logging logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", ) logger = logging.getLogger(__name__) async def ozon_worker(): ozon_client = OzonClient() while True: try: logger.info("Checking for new Ozon reviews...") ozon_result = await ozon_client.process_new_reviews() logger.info( f"Processed {ozon_result['processed']} Ozon reviews, {ozon_result['new']} new" ) except Exception as e: logger.error(f"Error during Ozon review check: {e.__class__.__name__}: {e}") logger.info(f"Next check in {config.OZON_CHECK_INTERVAL} seconds") await asyncio.sleep(config.OZON_CHECK_INTERVAL) async def wb_worker(): wb_client = WildberriesClient() while True: try: logger.info("Checking for new Wildberries reviews...") wb_result = await wb_client.process_new_reviews() logger.info( f"Processed {wb_result['processed']} Wildberries reviews, {wb_result['new']} new" ) except Exception as e: logger.error( f"Error during Wildberries review check: {e.__class__.__name__}: {e}" ) logger.info(f"Next check in {config.WB_CHECK_INTERVAL} seconds") await asyncio.sleep(config.WB_CHECK_INTERVAL) async def main(): """Set up and run the bot.""" session = ( AiohttpSession(proxy=(config.TG_PROXY,)) if config.TG_PROXY else AiohttpSession() ) bot = Bot(token=config.BOT_TOKEN, session=session) dp = Dispatcher() dp.include_router(commands.router) dp.include_router(callbacks.router) dp.include_router(prompt.prompt_router) allowed_threads = [ config.BAD_REVIEWS_THREAD_ID, config.GOOD_REVIEWS_THREAD_ID, ] if config.EMPTY_LOW_RATING_THREAD_ID: allowed_threads.append(config.EMPTY_LOW_RATING_THREAD_ID) if config.EMPTY_HIGH_RATING_THREAD_ID: allowed_threads.append(config.EMPTY_HIGH_RATING_THREAD_ID) dp.message.filter( (F.chat.id == config.TG_CHAT_ID) & F.message_thread_id.in_(allowed_threads) ) await Tortoise.init( db_url=config.TORTOISE_ORM["connections"]["default"], modules={"models": ["app.models"]}, ) await Tortoise.generate_schemas() # Миграция: добавить колонку автоответа без текста, если её ещё нет try: conn = Tortoise.get_connection("default") await conn.execute_query( "ALTER TABLE settings ADD COLUMN auto_response_empty_enabled INTEGER DEFAULT 0" ) except Exception as e: if "duplicate column" not in str(e).lower(): logger.warning(f"Migration auto_response_empty_enabled: {e}") # Миграция: шаблоны ответа на отзывы без текста (1–3 и 4–5 звёзд) for col in ("template_empty_low_rating", "template_empty_high_rating"): try: conn = Tortoise.get_connection("default") await conn.execute_query( f"ALTER TABLE settings ADD COLUMN {col} TEXT" ) except Exception as e: if "duplicate column" not in str(e).lower(): logger.warning(f"Migration {col}: {e}") # Миграция: WB bables (теги отзыва) в таблицу review try: conn = Tortoise.get_connection("default") await conn.execute_query( "ALTER TABLE review ADD COLUMN bables TEXT" ) except Exception as e: if "duplicate column" not in str(e).lower(): logger.warning(f"Migration review.bables: {e}") # Создаём настройки для хороших и плохих отзывов при первом запуске. # Существующие настройки (включая автоответы) не трогаем, чтобы они # переживали перезапуск и редеплой. for is_good in [True, False]: await Settings.get_or_create( is_good=is_good, defaults={ "analysis_enabled": not is_good, }, ) workers = asyncio.ensure_future( asyncio.gather( ozon_worker(), wb_worker(), send_queue_worker(), sheet_queue_worker(), ) ) # future must be saved to prevent GC print(f"Workers future: {workers}") await dp.start_polling(bot) if __name__ == "__main__": asyncio.run(main())