Files
mp-answer-bot/app/__main__.py
T
KirillandClaude Fable 5.1 4534ab2683 Добавить поддержку Яндекс Маркета (goods-feedback API)
- app/services/ym.py: клиент Partner API — getGoodsFeedbacks с пагинацией и
  фильтром по дате, updateGoodsFeedbackComment (ответ), deleteGoodsFeedbackComment
  (удаление), обработка новых отзывов по аналогии с WB/Ozon
- воркер ym_worker, включается при заданных YM_API_KEY и YM_BUSINESS_ID
- кнопки отправки/удаления ответа, платформа в сообщении и в таблице,
  статистика /stat и /allstat
- переменные YM_API_KEY, YM_BUSINESS_ID, YM_CHECK_INTERVAL, PRODUCTS_YM_SHEET_LINK
- README и .env.example

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-04 20:13:38 +03:00

175 lines
6.2 KiB
Python

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
from app.services.ym import YandexMarketClient
# 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 ym_worker():
ym_client = YandexMarketClient()
while True:
try:
logger.info("Checking for new Yandex Market reviews...")
ym_result = await ym_client.process_new_reviews()
logger.info(
f"Processed {ym_result['processed']} Yandex Market reviews, {ym_result['new']} new"
)
except Exception as e:
logger.error(
f"Error during Yandex Market review check: {e.__class__.__name__}: {e}"
)
logger.info(f"Next check in {config.YM_CHECK_INTERVAL} seconds")
await asyncio.sleep(config.YM_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,
},
)
worker_tasks = [
ozon_worker(),
wb_worker(),
send_queue_worker(),
sheet_queue_worker(),
]
# Опрос Яндекс Маркета включается только при заданных ключе и businessId
if config.YM_API_KEY and config.YM_BUSINESS_ID:
worker_tasks.append(ym_worker())
else:
logger.info("YM_API_KEY / YM_BUSINESS_ID not set, Yandex Market polling disabled")
workers = asyncio.ensure_future(
asyncio.gather(*worker_tasks)
) # future must be saved to prevent GC
print(f"Workers future: {workers}")
await dp.start_polling(bot)
if __name__ == "__main__":
asyncio.run(main())