164 lines
14 KiB
Markdown
164 lines
14 KiB
Markdown
# Бот для отзывов Ozon и Wildberries
|
||
|
||
Telegram-бот, который собирает новые отзывы с Ozon и Wildberries, публикует их в группу с топиками, анализирует через GPT и помогает на них отвечать: автоматически или по кнопке.
|
||
|
||
## Что умеет
|
||
|
||
- **Опрос маркетплейсов.** Фоновые воркеры по таймеру запрашивают новые отзывы через Ozon Seller API и WB Feedbacks API. Каждый отзыв сохраняется в SQLite, повторно не обрабатывается.
|
||
- **Раскладка по топикам.** Отзывы с оценкой выше порога (`NEGATIVE_RATING`, по умолчанию 3) уходят в топик хороших, остальные в топик плохих. Отзывы без текста (только оценка) можно направлять в отдельные топики.
|
||
- **Анализ через GPT.** Определяет тональность, причину и критичность отзыва по промпту. Критичные отзывы помечаются `#требует_обработки`.
|
||
- **Ответы.** Три варианта: автоответ через GPT, автоответ по шаблону для отзывов без текста, либо ручной режим с кнопками «Сгенерировать ответ» / «Написать свой ответ» / «Отправить» / «Удалить ответ».
|
||
- **Google Sheets.** Каждый отзыв дописывается строкой в таблицу отзывов. Названия и категории товаров подтягиваются из справочников товаров WB и Ozon.
|
||
- **Статистика.** Команды `/stat` (за сегодня) и `/allstat` (за всё время): количество, доля плохих, средний рейтинг, критичные, тональность.
|
||
|
||
Настройки (промпты, автоответы, модель, шаблоны) хранятся в базе отдельно для хороших и плохих отзывов и меняются прямо из бота. Они сохраняются между перезапусками и редеплоями, поэтому база должна лежать на постоянном томе.
|
||
|
||
## Как это работает
|
||
|
||
```
|
||
Ozon API ─┐ ┌─> Telegram (топики хороших / плохих / без текста)
|
||
├─> воркер ─> SQLite ─> очереди ─┤
|
||
WB API ──┘ │ └─> Google Sheets (таблица отзывов)
|
||
└─> GPT: анализ + генерация ответа
|
||
```
|
||
|
||
Точка входа `app/__main__.py` поднимает четыре бесконечные задачи: опрос Ozon, опрос WB, отправка сообщений в 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` | Клиенты 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](https://docs.astral.sh/uv/) (рекомендуется) или pip
|
||
- Telegram-бот, добавленный в супергруппу с включёнными топиками
|
||
- Сервисный аккаунт Google с доступом к Google Sheets API
|
||
- Ключи API Ozon Seller, Wildberries (категория «Отзывы и вопросы») и gptunnel.ru
|
||
|
||
## Установка и запуск
|
||
|
||
1. Клонируйте репозиторий и установите зависимости:
|
||
|
||
```bash
|
||
uv sync
|
||
```
|
||
|
||
Без uv: `python -m venv .venv`, активировать окружение, затем `pip install -e .`
|
||
|
||
2. Создайте `.env` из шаблона и заполните значения:
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
```
|
||
|
||
Описание всех переменных есть внутри `.env.example`. Минимум для запуска: `BOT_TOKEN`, `TG_CHAT_ID`, `GOOD_REVIEWS_THREAD_ID`, `BAD_REVIEWS_THREAD_ID`.
|
||
|
||
3. Подключите ключ сервисного аккаунта Google одним из способов:
|
||
- положите файл в `google-auth/key.json` (путь можно поменять через `GOOGLE_SERVICE_ACCOUNT_FILE`);
|
||
- или вставьте содержимое JSON-ключа в переменную `GOOGLE_SERVICE_ACCOUNT_JSON`. Переменная имеет приоритет над файлом.
|
||
|
||
Без ключа бот запустится, но не сможет ни читать справочники товаров, ни писать отзывы в таблицу, поэтому обработка отзывов работать не будет.
|
||
|
||
Как получить ключ: в [Google Cloud Console](https://console.cloud.google.com/) создайте проект, включите Google Sheets API, создайте сервисный аккаунт и скачайте JSON-ключ. Затем откройте доступ на редактирование ко всем нужным таблицам для email из поля `client_email` этого ключа.
|
||
|
||
4. Запустите бота:
|
||
|
||
```bash
|
||
uv run python -m app
|
||
```
|
||
|
||
Запускать нужно из корня проекта, потому что пути к `.env` и `google-auth/key.json` относительные.
|
||
|
||
## Деплой в Coolify
|
||
|
||
Бот рассчитан на запуск из Coolify без каких-либо файлов на сервере: все настройки передаются через переменные окружения.
|
||
|
||
1. **Создайте ресурс.** В проекте Coolify нажмите «New Resource», выберите репозиторий (публичный по ссылке или через GitHub App), ветку и build pack **Dockerfile**. Порт и домен оставьте пустыми: бот не поднимает HTTP-сервер. Если Coolify предлагает health check, отключите его.
|
||
|
||
2. **Заполните переменные окружения.** На вкладке «Environment Variables» добавьте переменные из [.env.example](.env.example). Минимальный набор для рабочего инстанса:
|
||
|
||
| Переменная | Значение |
|
||
|------------|----------|
|
||
| `BOT_TOKEN` | Токен бота от @BotFather |
|
||
| `TG_CHAT_ID`, `GOOD_REVIEWS_THREAD_ID`, `BAD_REVIEWS_THREAD_ID` | Группа и топики |
|
||
| `GOOGLE_SERVICE_ACCOUNT_JSON` | Содержимое `key.json` одной строкой или в base64 |
|
||
| `REVIEWS_SHEET_LINK`, `PRODUCTS_WB_SHEET_LINK`, `PRODUCTS_OZON_SHEET_LINK` | Ссылки на таблицы |
|
||
| `OZON_API_KEY`, `OZON_CLIENT_ID`, `WB_API_KEY`, `GPTUNNEL_API_KEY` | Ключи API |
|
||
| `BRAND_NAME` | Название бренда для промптов |
|
||
|
||
Ключ Google удобнее передавать в base64, тогда в значении нет кавычек и переносов строк:
|
||
|
||
```bash
|
||
base64 -w0 key.json
|
||
```
|
||
|
||
Бот сам определит формат: если значение начинается с `{`, оно читается как JSON, иначе декодируется из base64.
|
||
|
||
3. **Подключите постоянное хранилище.** На вкладке «Storages» добавьте volume с точкой монтирования `/data`. Там лежит `db.sqlite3` с историей отзывов и настройками. Без тома база будет теряться при каждом редеплое, и бот заново отправит в Telegram последние отзывы с маркетплейсов.
|
||
|
||
4. **Нажмите Deploy.** Образ соберётся по `Dockerfile` с зависимостями из `uv.lock`. В логах контейнера должны появиться строки `Start polling` и `Checking for new ... reviews`.
|
||
|
||
**Несколько инстансов.** Для каждого бренда или магазина создайте отдельный ресурс из того же репозитория со своим набором переменных и своим volume. Инстансы не зависят друг от друга. Можно указать одну и ту же ветку, тогда все они обновятся при пуше, если включён автодеплой.
|
||
|
||
## Запуск в Docker вручную
|
||
|
||
```bash
|
||
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](https://t.me/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-го уровня`.
|
||
|
||
Если в ссылке на таблицу есть `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`. Если ключ когда-либо был закоммичен, его нужно отозвать и выпустить заново, удаления из истории недостаточно.
|