- 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>
16 KiB
Бот для отзывов Ozon, Wildberries и Яндекс Маркета
Telegram-бот, который собирает новые отзывы с Ozon, Wildberries и Яндекс Маркета, публикует их в группу с топиками, анализирует через GPT и помогает на них отвечать: автоматически или по кнопке.
Что умеет
- Опрос маркетплейсов. Фоновые воркеры по таймеру запрашивают новые отзывы через Ozon Seller API, WB Feedbacks API и Яндекс Маркет Partner API (goods-feedback). Каждый отзыв сохраняется в SQLite, повторно не обрабатывается.
- Раскладка по топикам. Отзывы с оценкой выше порога (
NEGATIVE_RATING, по умолчанию 3) уходят в топик хороших, остальные в топик плохих. Отзывы без текста (только оценка) можно направлять в отдельные топики. - Анализ через GPT. Определяет тональность, причину и критичность отзыва по промпту. Критичные отзывы помечаются
#требует_обработки. - Ответы. Три варианта: автоответ через GPT, автоответ по шаблону для отзывов без текста, либо ручной режим с кнопками «Сгенерировать ответ» / «Написать свой ответ» / «Отправить» / «Удалить ответ».
- Google Sheets. Каждый отзыв дописывается строкой в таблицу отзывов. Названия и категории товаров подтягиваются из справочников товаров WB, Ozon и Яндекс Маркета.
- Статистика. Команды
/stat(за сегодня) и/allstat(за всё время): количество, доля плохих, средний рейтинг, критичные, тональность.
Настройки (промпты, автоответы, модель, шаблоны) хранятся в базе отдельно для хороших и плохих отзывов и меняются прямо из бота. Они сохраняются между перезапусками и редеплоями, поэтому база должна лежать на постоянном томе.
Как это работает
Ozon API ─┐ ┌─> Telegram (топики хороших / плохих / без текста)
WB API ──┼─> воркер ─> SQLite ─> очереди ─┤
YM API ──┘ │ └─> Google Sheets (таблица отзывов)
└─> GPT: анализ + генерация ответа
Точка входа app/__main__.py поднимает бесконечные задачи: опрос Ozon, опрос WB, опрос Яндекс Маркета (если заданы YM_API_KEY и YM_BUSINESS_ID), отправка сообщений в Telegram, запись в Google Sheets. Затем запускает polling Telegram. Бот реагирует только на сообщения из чата TG_CHAT_ID в разрешённых топиках.
Структура кода:
| Путь | Назначение |
|---|---|
app/config.py |
Чтение переменных окружения и .env, промпты по умолчанию, шаблоны, настройка ORM |
app/handlers/reviews.py |
Формирование сообщения об отзыве, очереди в Telegram и Sheets |
app/handlers/callbacks.py |
Кнопки: генерация, отправка, удаление ответа, переключатели настроек |
app/handlers/commands.py |
Команды /stat, /allstat, /settings, шаблоны, /max_tokens |
app/handlers/prompt.py |
Просмотр, изменение и сброс промптов |
app/services/ozon.py, wb.py, ym.py |
Клиенты API маркетплейсов и обработка новых отзывов |
app/services/gpt.py |
Запросы к GPT, разбор JSON анализа |
app/services/sheets.py |
Работа с Google Sheets через gspread |
app/services/stats.py |
Подсчёт статистики |
app/models/ |
Модели Review и Settings (Tortoise ORM) |
Требования
- Python 3.12+
- uv (рекомендуется) или pip
- Telegram-бот, добавленный в супергруппу с включёнными топиками
- Сервисный аккаунт Google с доступом к Google Sheets API
- Ключи API Ozon Seller, Wildberries (категория «Отзывы и вопросы»), Яндекс Маркета (Api-Key с доступом «Общение с покупателями», необязательно) и gptunnel.ru
Установка и запуск
-
Клонируйте репозиторий и установите зависимости:
uv syncБез uv:
python -m venv .venv, активировать окружение, затемpip install -e . -
Создайте
.envиз шаблона и заполните значения:cp .env.example .envОписание всех переменных есть внутри
.env.example. Минимум для запуска:BOT_TOKEN,TG_CHAT_ID,GOOD_REVIEWS_THREAD_ID,BAD_REVIEWS_THREAD_ID. -
Подключите ключ сервисного аккаунта Google одним из способов:
- положите файл в
google-auth/key.json(путь можно поменять черезGOOGLE_SERVICE_ACCOUNT_FILE); - или вставьте содержимое JSON-ключа в переменную
GOOGLE_SERVICE_ACCOUNT_JSON. Переменная имеет приоритет над файлом.
Без ключа бот запустится, но не сможет ни читать справочники товаров, ни писать отзывы в таблицу, поэтому обработка отзывов работать не будет.
Как получить ключ: в Google Cloud Console создайте проект, включите Google Sheets API, создайте сервисный аккаунт и скачайте JSON-ключ. Затем откройте доступ на редактирование ко всем нужным таблицам для email из поля
client_emailэтого ключа. - положите файл в
-
Запустите бота:
uv run python -m appЗапускать нужно из корня проекта, потому что пути к
.envиgoogle-auth/key.jsonотносительные.
Деплой в Coolify
Бот рассчитан на запуск из Coolify без каких-либо файлов на сервере: все настройки передаются через переменные окружения.
-
Создайте ресурс. В проекте Coolify нажмите «New Resource», выберите репозиторий (публичный по ссылке или через GitHub App), ветку и build pack Dockerfile. Порт и домен оставьте пустыми: бот не поднимает HTTP-сервер. Если Coolify предлагает health check, отключите его.
-
Заполните переменные окружения. На вкладке «Environment Variables» добавьте переменные из .env.example. Минимальный набор для рабочего инстанса:
Переменная Значение BOT_TOKENТокен бота от @BotFather TG_CHAT_ID,GOOD_REVIEWS_THREAD_ID,BAD_REVIEWS_THREAD_IDГруппа и топики GOOGLE_SERVICE_ACCOUNT_JSONСодержимое key.jsonодной строкой или в base64REVIEWS_SHEET_LINK,PRODUCTS_WB_SHEET_LINK,PRODUCTS_OZON_SHEET_LINK,PRODUCTS_YM_SHEET_LINKСсылки на таблицы OZON_API_KEY,OZON_CLIENT_ID,WB_API_KEY,YM_API_KEY,YM_BUSINESS_ID,GPTUNNEL_API_KEYКлючи API BRAND_NAMEНазвание бренда для промптов Ключ Google удобнее передавать в base64, тогда в значении нет кавычек и переносов строк:
base64 -w0 key.jsonБот сам определит формат: если значение начинается с
{, оно читается как JSON, иначе декодируется из base64. -
Подключите постоянное хранилище. На вкладке «Storages» добавьте volume с точкой монтирования
/data. Там лежитdb.sqlite3с историей отзывов и настройками. Без тома база будет теряться при каждом редеплое, и бот заново отправит в Telegram последние отзывы с маркетплейсов. -
Нажмите Deploy. Образ соберётся по
Dockerfileс зависимостями изuv.lock. В логах контейнера должны появиться строкиStart pollingиChecking for new ... reviews.
Несколько инстансов. Для каждого бренда или магазина создайте отдельный ресурс из того же репозитория со своим набором переменных и своим volume. Инстансы не зависят друг от друга. Можно указать одну и ту же ветку, тогда все они обновятся при пуше, если включён автодеплой.
Запуск в Docker вручную
docker compose up -d --build
Compose читает .env из корня, если файл есть, и хранит базу в томе bot-data. Ключ Google передаётся через GOOGLE_SERVICE_ACCOUNT_JSON либо монтированием google-auth/ (закомментировано в docker-compose.yml). Логи: docker compose logs -f bot. Остановка: docker compose down (с флагом -v удалится и база).
Настройка Telegram
- Узнать ID чата: добавьте в группу бота @userinfobot или посмотрите в ссылке на сообщение.
- ID топика: число в конце ссылки на топик
https://t.me/c/<chat>/<topic>. - Все команды бота работают только внутри разрешённых топиков. Настройки применяются к тому типу отзывов, в чьём топике вызвана команда (хорошие или плохие).
Google-таблицы
Таблица отзывов (REVIEWS_SHEET_LINK). Первая строка должна содержать 12 заголовков в таком порядке:
Дата | Площадка | Название товара | Категория | Подкатегория | ID товара | Оценка | Текст отзыва | Тональность | Причина | Критичность | Тэги
Справочник товаров WB (PRODUCTS_WB_SHEET_LINK). Ключевой столбец SKU или Артикул WB. Используются столбцы Название/Наименование и Категория/Категория продавца.
Справочник товаров Ozon (PRODUCTS_OZON_SHEET_LINK). Ключевой столбец Ozon ID или ozon id акутальный. Используются Товары, Тип товара, Категория 2-го уровня.
Справочник товаров Яндекс Маркета (PRODUCTS_YM_SHEET_LINK, необязательно). Ключевой столбец SKU, Ваш SKU, offerId или Артикул — это SKU продавца (offerId в API). Используются столбцы Название/Наименование/Название товара и Категория/Категория продавца. Если таблица не задана или товар не найден, название и категория записываются как «неизвестно».
Яндекс Маркет
Используются методы Partner API getGoodsFeedbacks (список отзывов, с пагинацией и фильтром по дате), updateGoodsFeedbackComment (создание комментария к отзыву) и deleteGoodsFeedbackComment (удаление ответа по кнопке). Текст отзыва собирается из комментария, достоинств и недостатков. Фото из отзыва отправляются в Telegram вместе с сообщением. API отдаёт отзывы не старше 6 месяцев, поэтому при первом запуске бот заберёт отзывы за этот период.
Если в ссылке на таблицу есть gid=, берётся этот лист, иначе первый. Справочники кэшируются на 5 минут.
Команды бота
| Команда | Действие |
|---|---|
/settings |
Панель настроек с переключателями: автоответ с текстом, автоответ без текста, анализ, модель GPT |
/stat, /allstat |
Статистика за сегодня / за всё время |
/show_prompt |
Показать текущие промпты анализа и ответа |
/change_prompt, /reset_prompt |
Изменить / сбросить промпт анализа |
/change_answer_prompt, /reset_answer_prompt |
Изменить / сбросить промпт ответа |
/template_low текст, /template_high текст |
Шаблоны ответа на отзывы без текста (1–3 и 4–5 звёзд) |
/show_templates |
Показать текущие шаблоны |
/max_tokens N |
Лимит токенов для ответа GPT |
/cancel |
Отменить ввод промпта или ответа |
Промпты по умолчанию заданы в app/config.py, название бренда в них подставляется из BRAND_NAME. Промпт копируется в базу при первом запуске, поэтому смена BRAND_NAME на уже работающем инстансе не изменит сохранённый промпт: сбросьте его командами /reset_prompt и /reset_answer_prompt.
Данные
База SQLite (db.sqlite3) создаётся автоматически при первом запуске. Таблицы review и settings создаются через generate_schemas, недостающие колонки добавляются простой миграцией при старте.
Что не должно попадать в git
Файлы .env, google-auth/key.json и *.sqlite3 уже исключены через .gitignore. Если ключ когда-либо был закоммичен, его нужно отозвать и выпустить заново, удаления из истории недостаточно.