Files
mp-answer-bot/app/handlers/commands.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

160 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
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 logging
from aiogram import Router
from aiogram.filters import Command
from aiogram.types import Message
from app import config
from app.handlers.callbacks import _settings_keyboard
from app.models import Settings
from app.services import StatsService
router = Router()
logger = logging.getLogger(__name__)
stats_service = StatsService()
@router.message(Command("allstat", "stat"))
async def cmd_stats(message: Message) -> None:
is_all_time = message.text.startswith("/allstat")
ozon_stats = (
await stats_service.get_all_time_stats("ozon")
if is_all_time
else await stats_service.get_daily_stats("ozon")
)
wb_stats = (
await stats_service.get_all_time_stats("wb")
if is_all_time
else await stats_service.get_daily_stats("wb")
)
ym_stats = (
await stats_service.get_all_time_stats("ym")
if is_all_time
else await stats_service.get_daily_stats("ym")
)
period_text = "за все время" if is_all_time else "за сегодня"
stats_text = f"📊 <b>Статистика {period_text}</b>\n\n"
for platform_name, stats in [
("Ozon", ozon_stats),
("Wildberries", wb_stats),
("Яндекс Маркет", ym_stats),
]:
stats_text += (
f"<b>{platform_name}:</b>\n"
f"Всего отзывов: {stats['total_reviews']}\n"
f"Хороших: {stats['good_reviews']}\n"
f"Плохих: {stats['bad_reviews']}\n"
f"Доля плохих: {stats['bad_ratio']:.1%}\n"
f"Средний рейтинг: {stats['average_rating']:.1f}\n"
f"Критичных: {stats['critical_reviews']} ({stats['critical_ratio']:.1%} от всех, {stats['critical_to_bad_ratio']:.1%} от плохих)\n\n"
f"<b>Оценки:</b>\n"
f"⭐️ 1: {stats['ratings']['1']}\n"
f"⭐️ 2: {stats['ratings']['2']}\n"
f"⭐️ 3: {stats['ratings']['3']}\n"
f"⭐️ 4: {stats['ratings']['4']}\n"
f"⭐️ 5: {stats['ratings']['5']}\n\n"
f"<b>Тональность:</b>\n"
f"😊 Позитивная: {stats['tone']['positive']}\n"
f"😐 Нейтральная: {stats['tone']['neutral']}\n"
f"😠 Негативная: {stats['tone']['negative']}\n\n"
)
await message.answer(stats_text, parse_mode="HTML")
@router.message(Command("settings"))
async def cmd_settings(message: Message) -> None:
settings = await Settings.get_from_context(
message_thread_id=message.message_thread_id
)
await message.answer(
(
f"⚙️ <b>Настройки {'положительных' if settings.is_good else 'отрицательных'} отзывов</b>\n\n"
"/show_prompt - просмотр промптов\n"
"/change_prompt - изменить промпт для аналитики\n"
"/reset_prompt - сбросить промпт для аналитики\n"
"/change_answer_prompt - изменить промпт для ответов на отзывы\n"
"/reset_answer_prompt - сбросить промпт для ответов на отзывы\n\n"
"<b>Шаблоны для отзывов без текста:</b>\n"
"/template_low текст — шаблон для 1–3 ★\n"
"/template_high текст — шаблон для 4–5 ★\n"
"/show_templates — показать текущие шаблоны\n"
),
reply_markup=_settings_keyboard(settings),
parse_mode="HTML",
)
@router.message(Command("show_templates"))
async def cmd_show_templates(message: Message) -> None:
"""Показать текущие шаблоны ответа на отзывы без текста."""
settings = await Settings.get_from_context(
message_thread_id=message.message_thread_id
)
low = settings.template_empty_low_rating or config.TEMPLATE_EMPTY_LOW_RATING
high = settings.template_empty_high_rating or config.TEMPLATE_EMPTY_HIGH_RATING
await message.answer(
"📝 <b>Шаблоны для отзывов без текста</b>\n\n"
f"<b>13 ★:</b> <code>{low}</code>\n\n"
f"<b>45 ★:</b> <code>{high}</code>\n\n"
"Изменить: /template_low текст или /template_high текст",
parse_mode="HTML",
)
@router.message(Command("template_low"))
async def cmd_template_low(message: Message) -> None:
"""Установить шаблон ответа для отзывов без текста с оценкой 1–3 звезды."""
text = (message.text or "").replace("/template_low", "").strip()
if not text:
await message.answer(
"Введите текст шаблона после команды.\n"
"Пример: /template_low Спасибо за отзыв, извините за неудобства."
)
return
for s in await Settings.all():
s.template_empty_low_rating = text
await s.save()
await message.answer(f"✅ Шаблон для 1–3 ★ сохранён:\n<code>{text}</code>", parse_mode="HTML")
@router.message(Command("template_high"))
async def cmd_template_high(message: Message) -> None:
"""Установить шаблон ответа для отзывов без текста с оценкой 4–5 звёзд."""
text = (message.text or "").replace("/template_high", "").strip()
if not text:
await message.answer(
"Введите текст шаблона после команды.\n"
"Пример: /template_high Спасибо за отзыв, рады что вам понравилось!"
)
return
for s in await Settings.all():
s.template_empty_high_rating = text
await s.save()
await message.answer(f"✅ Шаблон для 4–5 ★ сохранён:\n<code>{text}</code>", parse_mode="HTML")
@router.message(Command("max_tokens"))
async def cmd_max_tokens(message: Message) -> None:
settings = await Settings.get_from_context(
message_thread_id=message.message_thread_id
)
args = message.text.split()
if len(args) > 1 and args[1].isdigit():
value = int(args[1])
settings.max_tokens = value
await settings.save()
await message.answer(f"Лимит токенов установлен на {value}")
return
await message.answer(
f"Текущий лимит токенов: {settings.max_tokens}\n"
"Введите /max_tokens число, чтобы обновить его"
)