Добавить поддержку Яндекс Маркета (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>
This commit is contained in:
@@ -10,7 +10,7 @@ from tortoise.exceptions import DoesNotExist
|
||||
|
||||
from app import config
|
||||
from app.models import Review, Settings
|
||||
from app.services import GPTClient, OzonClient, WildberriesClient
|
||||
from app.services import GPTClient, OzonClient, WildberriesClient, YandexMarketClient
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = Router()
|
||||
@@ -147,6 +147,33 @@ async def callback_wb_delete(call: CallbackQuery) -> None:
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("ym_delete:"))
|
||||
async def callback_ym_delete(call: CallbackQuery) -> None:
|
||||
comment_id = call.data.split(":", 1)[1]
|
||||
|
||||
ym_client = YandexMarketClient()
|
||||
result = await ym_client.delete_comment(comment_id)
|
||||
|
||||
if "error" not in result:
|
||||
try:
|
||||
review = await Review.filter(response_id=comment_id, platform="ym").first()
|
||||
if review:
|
||||
review.response_text = None
|
||||
review.response_id = None
|
||||
review.responded_at = None
|
||||
await review.save()
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating review after comment deletion: {e}")
|
||||
|
||||
await call.answer("Комментарий успешно удалён на Яндекс Маркете.")
|
||||
await call.message.reply("🗑 Ответ успешно удален с Яндекс Маркета")
|
||||
else:
|
||||
await call.answer("Не удалось удалить комментарий.")
|
||||
await call.message.reply(
|
||||
f"❌ Ошибка удаления комментария: {result.get('error')}"
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("generate_answer:"))
|
||||
async def callback_generate_answer(call: CallbackQuery) -> None:
|
||||
review_id = call.data.split(":", 1)[1]
|
||||
@@ -344,6 +371,34 @@ async def callback_send_answer(call: CallbackQuery) -> None:
|
||||
f"❌ Ошибка отправки ответа на Wildberries: {result.get('error')}"
|
||||
)
|
||||
|
||||
elif review.platform == "ym":
|
||||
ym_client = YandexMarketClient()
|
||||
result = await ym_client.post_comment(
|
||||
review.external_id, review.response_text
|
||||
)
|
||||
|
||||
if "comment_id" in result:
|
||||
review.response_id = result["comment_id"]
|
||||
await review.save()
|
||||
|
||||
markup = InlineKeyboardBuilder()
|
||||
markup.add(
|
||||
InlineKeyboardButton(
|
||||
text="Удалить",
|
||||
callback_data=f"ym_delete:{result['comment_id']}",
|
||||
)
|
||||
)
|
||||
|
||||
await call.message.answer(
|
||||
f"✅ Ответ на #review{review_id} успешно отправлен на Яндекс Маркет",
|
||||
reply_to_message_id=review.message_id,
|
||||
reply_markup=markup.as_markup(),
|
||||
)
|
||||
else:
|
||||
await call.message.reply(
|
||||
f"❌ Ошибка отправки ответа на Яндекс Маркет: {result.get('error')}"
|
||||
)
|
||||
|
||||
await call.answer("Ответ отправлен")
|
||||
review.responded_at = datetime.now()
|
||||
await review.save()
|
||||
|
||||
@@ -28,12 +28,21 @@ async def cmd_stats(message: Message) -> None:
|
||||
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)]:
|
||||
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"
|
||||
|
||||
@@ -2,6 +2,7 @@ import asyncio
|
||||
import html
|
||||
import logging
|
||||
from asyncio import Queue
|
||||
from urllib.parse import quote
|
||||
from datetime import datetime
|
||||
|
||||
from aiogram import Router, types
|
||||
@@ -15,6 +16,9 @@ from app.services.sheets import add_rows_to_sheet
|
||||
logger = logging.getLogger(__name__)
|
||||
router = Router()
|
||||
|
||||
PLATFORM_NAMES = {"ozon": "Ozon", "wb": "Wildberries", "ym": "Яндекс Маркет"}
|
||||
PLATFORM_TAGS = {"ozon": "🟦 #Ozon", "wb": "🟪 #Wildberries", "ym": "🟨 #ЯндексМаркет"}
|
||||
|
||||
send_queue = Queue()
|
||||
sheet_queue = Queue()
|
||||
|
||||
@@ -52,7 +56,7 @@ def render_review_to_sheet(review: Review) -> list[str]:
|
||||
# Порядок столбцов таблицы (12 колонок): 1–8 данные, 9 тональность, 10 причина, 11 критичность, 12 Тэги
|
||||
return [
|
||||
formatted_date,
|
||||
"Ozon" if review.platform == "ozon" else "Wildberries",
|
||||
PLATFORM_NAMES.get(review.platform, review.platform),
|
||||
review.product_name or "-",
|
||||
review.product_category or "-",
|
||||
review.product_subcategory or "-",
|
||||
@@ -192,6 +196,9 @@ async def send_review(review: Review) -> None:
|
||||
)
|
||||
if review.platform == "ozon":
|
||||
product_url = f"https://www.ozon.ru/product/{review.product_id}/"
|
||||
elif review.platform == "ym":
|
||||
# У Маркета нет публичной ссылки по SKU продавца — ведём в поиск по нему
|
||||
product_url = f"https://market.yandex.ru/search?text={quote(review.product_id)}"
|
||||
else:
|
||||
product_url = (
|
||||
f"https://www.wildberries.ru/catalog/{review.product_id}/detail.aspx"
|
||||
@@ -219,7 +226,7 @@ async def send_review(review: Review) -> None:
|
||||
+ f"{'✅ Положительный' if review.is_good else '⚠️ Отрицательный'} отзыв №{count + 1}\n"
|
||||
f"📅 Дата: {formatted_date}\n"
|
||||
f"📦 Товар: <code>{escaped_product_id}</code> #{category_hashtag}\n"
|
||||
f"{'🟪 #Wildberries' if review.platform == 'wb' else '🟦 #Ozon'}\n"
|
||||
f"{PLATFORM_TAGS.get(review.platform, '#' + review.platform)}\n"
|
||||
f"🔗 <a href='{product_url}'>Ссылка</a>\n"
|
||||
f"⭐ Оценка: <code>{review.rating}/5</code>\n"
|
||||
f"💬 Отзыв: {escaped_text}\n\n"
|
||||
|
||||
Reference in New Issue
Block a user