diff --git a/.dockerignore b/.dockerignore index 889225f..f02c7c1 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,25 +1,27 @@ -# Виртуальное окружение и кэш +# Секреты и локальные данные никогда не попадают в контекст сборки +.env +.env.* +google-auth/ +*.sqlite3 +*.sqlite3-wal +*.sqlite3-shm +*.db + +# Окружения и кэш .venv venv __pycache__ *.py[cod] -*.pyo +*.egg-info .pytest_cache .mypy_cache .ruff_cache -# Git и IDE +# Git, IDE, документация .git .gitignore .idea .vscode *.swp -*.swo - -# Локальная БД и логи (в проде используем volume) -db.sqlite3 -*.log - -# Документация и прочее (не нужны в образе) *.md -WINDOWS_SETUP.md +*.log diff --git a/.env.example b/.env.example index d34a79f..6a3e762 100644 --- a/.env.example +++ b/.env.example @@ -24,7 +24,18 @@ HTTPS_PROXY= HTTP_PROXY= NO_PROXY=localhost,127.0.0.1 +# --- Бренд --- +# Название бренда для промптов GPT (анализ и ответы на отзывы) +BRAND_NAME= + # --- Google Sheets --- +# Ключ сервисного аккаунта. Один из двух вариантов: +# 1) содержимое JSON-ключа одной строкой (или в base64) в переменной ниже — для Coolify +GOOGLE_SERVICE_ACCOUNT_JSON= +# 2) путь к файлу ключа (по умолчанию google-auth/key.json) — для локального запуска +# GOOGLE_SERVICE_ACCOUNT_FILE=google-auth/key.json +# Таблица, куда бот дописывает отзывы +REVIEWS_SHEET_LINK= # Справочники товаров (название, категория) по артикулам PRODUCTS_WB_SHEET_LINK= PRODUCTS_OZON_SHEET_LINK= diff --git a/.gitignore b/.gitignore index f0d656d..4176058 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,3 @@ env/ *.log .DS_Store Thumbs.db - diff --git a/DOCKER_DEPLOY.md b/DOCKER_DEPLOY.md deleted file mode 100644 index c09dd32..0000000 --- a/DOCKER_DEPLOY.md +++ /dev/null @@ -1,55 +0,0 @@ -# Деплой бота PrimeKraft в Docker на Ubuntu 22 - -## Требования на сервере - -- Ubuntu 22.04 LTS -- Docker и Docker Compose: - `sudo apt update && sudo apt install -y docker.io docker-compose-v2` - (или [официальная установка Docker](https://docs.docker.com/engine/install/ubuntu/)) - -## Подготовка на сервере - -1. Склонируйте или скопируйте проект на сервер в каталог, например `/opt/primekraft-bot`. - -2. Создайте файл **`.env`** в корне проекта (рядом с `docker-compose.yml`) со всеми переменными, как в локальной разработке (см. WINDOWS_SETUP.md). Пример: - ```env - BOT_TOKEN=... - TG_CHAT_ID=... - GOOD_REVIEWS_THREAD_ID=... - BAD_REVIEWS_THREAD_ID=... - REVIEWS_SHEET_LINK=... - OZON_API_KEY=... - OZON_CLIENT_ID=... - WB_API_KEY=... - GPTUNNEL_API_KEY=... - ``` - -3. Положите ключ Google Sheets в каталог **`google-auth/key.json`** (сервисный аккаунт). - -## Запуск - -```bash -cd /opt/primekraft-bot -docker compose build -docker compose up -d -``` - -Проверка логов: -```bash -docker compose logs -f bot -``` - -## Остановка и обновление - -- Остановка: `docker compose down` -- Остановка с удалением данных (БД): `docker compose down -v` -- Пересборка и перезапуск после изменений кода: - ```bash - docker compose build --no-cache - docker compose up -d - ``` - -## Данные - -- База SQLite хранится в Docker-томе **`bot-data`** (путь в контейнере: `/data/db.sqlite3`). При `docker compose down` без `-v` данные сохраняются. -- Файл **`google-auth/key.json`** монтируется с хоста (read-only), менять ключ можно без пересборки образа. diff --git a/Dockerfile b/Dockerfile index ef54399..75604df 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,21 +1,25 @@ -# Production Dockerfile for PrimeKraft bot (Ubuntu 22 / Linux) FROM python:3.12-slim-bookworm +# uv для установки зависимостей строго по uv.lock +COPY --from=ghcr.io/astral-sh/uv:0.6 /uv /bin/uv + WORKDIR /app -# Копируем зависимости и код (нужен для pip install .) -COPY pyproject.toml ./ +ENV UV_COMPILE_BYTECODE=1 \ + UV_LINK_MODE=copy \ + UV_PROJECT_ENVIRONMENT=/app/.venv \ + PATH="/app/.venv/bin:$PATH" \ + PYTHONUNBUFFERED=1 \ + DB_PATH=/data/db.sqlite3 + +# Сначала только зависимости, чтобы слой кэшировался между сборками +COPY pyproject.toml uv.lock ./ +RUN uv sync --frozen --no-dev + COPY app ./app -# Устанавливаем проект в режиме production (без dev-зависимостей) -RUN pip install --no-cache-dir --upgrade pip && \ - pip install --no-cache-dir . - -# Каталог для БД (volume монтируется в runtime) +# Каталог для SQLite. В Coolify/compose сюда монтируется постоянный том. RUN mkdir -p /data - -# Переменная для пути к БД по умолчанию (переопределяется в compose) -ENV DB_PATH=/data/db.sqlite3 -ENV PYTHONUNBUFFERED=1 +VOLUME ["/data"] CMD ["python", "-m", "app"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..8ae8f8c --- /dev/null +++ b/README.md @@ -0,0 +1,163 @@ +# Бот для отзывов 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//`. +- Все команды бота работают только внутри разрешённых топиков. Настройки применяются к тому типу отзывов, в чьём топике вызвана команда (хорошие или плохие). + +## 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`. Если ключ когда-либо был закоммичен, его нужно отозвать и выпустить заново, удаления из истории недостаточно. diff --git a/WINDOWS_SETUP.md b/WINDOWS_SETUP.md deleted file mode 100644 index 69cb2b7..0000000 --- a/WINDOWS_SETUP.md +++ /dev/null @@ -1,275 +0,0 @@ -# Запуск проекта PrimeKraft на Windows (с нуля) - -Проект — Telegram-бот для работы с отзывами Ozon/Wildberries, GPT-анализом и Google Таблицами. Изначально настроен под Linux, но на Windows запускается без изменений кода. - -Ниже шаги для компьютера, где **ничего не установлено** (ни Python, ни что-то ещё). - ---- - -## Какие данные куда записать - -### 1. Файл `.env` (в корне проекта) - -Создайте файл **`.env`** в папке с проектом (рядом с `pyproject.toml`) и заполните переменные **без кавычек**, по одной в строке: - -| Переменная | Что подставить | Обязательно | -|------------|----------------|-------------| -| `BOT_TOKEN` | Токен бота от [@BotFather](https://t.me/BotFather) | Да | -| `TG_CHAT_ID` | ID чата (число, например `-1003569236790`) — куда слать отзывы | Да | -| `GOOD_REVIEWS_THREAD_ID` | ID топика для положительных отзывов (число) | Да | -| `BAD_REVIEWS_THREAD_ID` | ID топика для отрицательных отзывов (число) | Да | -| `EMPTY_LOW_RATING_THREAD_ID` | ID топика для отзывов без текста 1–3 ★ (или не указывать) | Нет | -| `EMPTY_HIGH_RATING_THREAD_ID` | ID топика для отзывов без текста 4–5 ★ (или не указывать) | Нет | -| `NEGATIVE_RATING` | Порог «плохого» отзыва: оценка ≤ этого числа = плохой (по умолчанию 3) | Нет | -| `REVIEWS_SHEET_LINK` | Полная ссылка на Google-таблицу, куда пишутся отзывы | Да (если нужна таблица) | -| `PRODUCTS_WB_SHEET_LINK` | Ссылка на таблицу с товарами WB (артикулы и т.д.) | Для WB | -| `PRODUCTS_OZON_SHEET_LINK` | Ссылка на таблицу с товарами Ozon (Ozon ID и т.д.) | Для Ozon | -| `OZON_API_KEY` | API-ключ личного кабинета Ozon | Для Ozon | -| `OZON_CLIENT_ID` | Client-Id личного кабинета Ozon | Для Ozon | -| `WB_API_KEY` | Токен API Wildberries (кабинет продавца) | Для WB | -| `GPTUNNEL_API_KEY` | Ключ для GPTunnel (генерация ответов и анализ) | Для GPT-функций | -| `OZON_CHECK_INTERVAL` | Интервал проверки Ozon в секундах (по умолчанию 60) | Нет | -| `WB_CHECK_INTERVAL` | Интервал проверки WB в секундах (по умолчанию 300) | Нет | - -**Как узнать ID чата и топика:** добавьте бота [@userinfobot](https://t.me/userinfobot) в чат — он покажет ID. ID топика в ссылке на обсуждение: `https://t.me/c/1234567890/5` → топик = **5**. - -Сохраните `.env` в кодировке **UTF-8**. - ---- - -### 2. Google-таблица отзывов (REVIEWS_SHEET_LINK) - -Таблица, в которую бот дописывает строки с отзывами. В **первой строке** задайте заголовки ровно в таком порядке (12 столбцов): - -| № | Заголовок столбца | -|---|--------------------| -| 1 | Дата | -| 2 | Площадка | -| 3 | Название товара | -| 4 | Категория | -| 5 | Подкатегория | -| 6 | ID товара | -| 7 | Оценка | -| 8 | Текст отзыва | -| 9 | Тональность | -| 10 | Причина | -| 11 | Критичность | -| 12 | Тэги | - -Бот сам заполняет данные; новые строки добавляются вниз. Доступ к таблице должен быть у сервисного аккаунта (см. ниже). - ---- - -### 3. Google Sheets — ключ `key.json` - -Чтобы бот мог читать и писать таблицы: - -1. Создайте проект в [Google Cloud Console](https://console.cloud.google.com/). -2. Включите **Google Sheets API**. -3. Создайте **сервисный аккаунт**, скачайте JSON-ключ. -4. Положите файл в проект: **`google-auth/key.json`** (папка `google-auth` в корне проекта). -5. В каждой нужной Google-таблице нажмите «Настройки доступа» и дайте доступ на **редактирование** email’у сервисного аккаунта (из `key.json`, поле `client_email`). - ---- - -## Шаг 1. Установить Python 3.12 - -1. Откройте в браузере: **https://www.python.org/downloads/** -2. Скачайте **Python 3.12.x** (кнопка "Download Python 3.12.x"). -3. Запустите установщик. -4. **Важно:** в первом окне включите галочку **"Add python.exe to PATH"**. -5. Нажмите **"Install Now"** и дождитесь окончания установки. -6. Закройте и заново откройте **PowerShell** или **CMD**, чтобы подхватился PATH. - -Проверка в терминале: - -```powershell -python --version -``` - -Должно быть что-то вроде: `Python 3.12.x`. - ---- - -## Шаг 2. Установить менеджер зависимостей uv (рекомендуется) - -В проекте используется **uv** (как на Linux). Его можно поставить одной командой. - -В **PowerShell** выполните: - -```powershell -powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" -``` - -После установки закройте и снова откройте терминал, затем проверьте: - -```powershell -uv --version -``` - -Если по какой-то причине uv ставить не хотите, можно обойтись только pip (см. альтернативу в конце). - ---- - -## Шаг 3. Перейти в папку проекта и установить зависимости - -Откройте PowerShell (или CMD) и перейдите в каталог проекта, например: - -```powershell -cd C:\Users\a.shestakov\Desktop\primekraftProd -``` - -Установите зависимости и создайте виртуальное окружение одной командой: - -```powershell -uv sync -``` - -Эта команда по файлам `pyproject.toml` и `uv.lock` создаст виртуальное окружение (например, `.venv`) и установит все пакеты. На Windows всё то же самое, что и на Linux. - ---- - -## Шаг 4. Файл с переменными окружения (.env) - -Приложение читает настройки из файла **`.env`** в **корне проекта** (рядом с `pyproject.toml`). - -1. В папке `primekraftProd` создайте файл с именем **`.env`** (точка в начале). -2. Откройте его любым редактором и заполните переменные. Минимум для запуска бота: - -```env -BOT_TOKEN=ваш_токен_от_BotFather -TG_CHAT_ID=ид_вашего_чата_в_Telegram -GOOD_REVIEWS_THREAD_ID=ид_топика_хороших_отзывов -BAD_REVIEWS_THREAD_ID=ид_топика_плохих_отзывов -``` - -Остальные переменные из `app/config.py` (Ozon, WB, GPT, таблицы) можно добавить позже или оставить пустыми по умолчанию. Пример полного набора: - -```env -BOT_TOKEN=... -TG_CHAT_ID=... -GOOD_REVIEWS_THREAD_ID=... -BAD_REVIEWS_THREAD_ID=... -# Топики для отзывов без текста (только оценка): 1–3 и 4–5 звёзд (необязательно; если не заданы, используются топики плохих/хороших) -EMPTY_LOW_RATING_THREAD_ID=... -EMPTY_HIGH_RATING_THREAD_ID=... -NEGATIVE_RATING=3 - -REVIEWS_SHEET_LINK=... -PRODUCTS_WB_SHEET_LINK=... -PRODUCTS_OZON_SHEET_LINK=... - -OZON_API_KEY=... -OZON_CLIENT_ID=... -WB_API_KEY=... -GPTUNNEL_API_KEY=... - -OZON_CHECK_INTERVAL=60 -WB_CHECK_INTERVAL=300 -``` - -Сохраните файл в кодировке **UTF-8**. - ---- - -## Шаг 5. Google Таблицы (key.json) - -Для работы с Google Таблицами нужен сервисный аккаунт. - -1. В корне проекта должна быть папка **`google-auth`** и в ней файл **`key.json`** (ключ сервисного аккаунта из Google Cloud). -2. Если у вас уже есть `key.json` с Linux-сервера — просто скопируйте его в `primekraftProd\google-auth\key.json`. -3. Если ключа нет — создайте проект в Google Cloud, включите Google Sheets API и создайте сервисный аккаунт, затем скачайте JSON-ключ и положите его в `google-auth\key.json`. - -Путь в коде задан как `google-auth/key.json` — на Windows такой относительный путь тоже работает, если запуск идёт из корня проекта. - ---- - -## Шаг 6. Запуск бота - -Обязательно запускайте из **корня проекта** (там, где лежат `pyproject.toml` и `.env`): - -```powershell -cd C:\Users\a.shestakov\Desktop\primekraftProd -uv run python -m app -``` - -Либо, если сначала активируете виртуальное окружение: - -```powershell -.\.venv\Scripts\Activate.ps1 -python -m app -``` - -В консоли должны появиться логи; бот начнёт опрос Telegram и фоновые проверки Ozon/WB по таймерам. - -Остановка: **Ctrl+C** в том же окне терминала. - ---- - -## Краткая шпаргалка (если уже всё установлено) - -```powershell -cd C:\Users\a.shestakov\Desktop\primekraftProd -uv sync -# Проверить .env и google-auth/key.json -uv run python -m app -``` - ---- - -## Если не хотите использовать uv - -1. Установите Python 3.12 (шаг 1). -2. В папке проекта создайте виртуальное окружение и установите зависимости: - -```powershell -cd C:\Users\a.shestakov\Desktop\primekraftProd -python -m venv .venv -.\.venv\Scripts\Activate.ps1 -pip install -e . -``` - -3. Создайте и заполните `.env`, положите `key.json` в `google-auth\` (шаги 4–5). -4. Запуск: - -```powershell -python -m app -``` - ---- - -## Возможные проблемы на Windows - -- **"python не найден"** — при установке Python не была отмечена опция "Add to PATH". Переустановите Python с этой галочкой или добавьте путь к `python.exe` в PATH вручную. -- **Ошибки при `uv sync`** — убедитесь, что открыт именно PowerShell/CMD и вы в папке `primekraftProd` (проверьте: `dir pyproject.toml` или `ls pyproject.toml`). -- **Ошибка про .env или BOT_TOKEN** — файл `.env` должен быть в корне проекта и переменные записаны без кавычек: `BOT_TOKEN=123:ABC...`. -- **Google Sheets / key.json** — если не используете таблицы, часть функций бота будет падать при обращении к ним; для минимального запуска достаточно BOT_TOKEN и TG_CHAT_ID (и при необходимости топиков). - -После выполнения этих шагов проект на Windows запускается так же, как на Linux: те же зависимости, та же точка входа `python -m app`. - ---- - -## Как запустить на Windows (кратко) - -1. **Проверьте, что заполнено:** - - Файл **`.env`** в корне проекта (минимум: `BOT_TOKEN`, `TG_CHAT_ID`, `GOOD_REVIEWS_THREAD_ID`, `BAD_REVIEWS_THREAD_ID`; для таблицы — `REVIEWS_SHEET_LINK`; для Ozon/WB — ключи и ссылки на таблицы товаров). - - Файл **`google-auth/key.json`** — ключ сервисного аккаунта Google (если используете таблицы). - - В таблице отзывов первая строка — заголовки (Дата, Площадка, … Тэги). - -2. **Откройте PowerShell** (или CMD) и перейдите в папку проекта: - ```powershell - cd "C:\Users\a.shestakov\Downloads\бот телеграм прайм\бот телеграм прайм\primekraftProd_02_02_2026_23_30\primekraftProd" - ``` - (или ваш путь к папке `primekraftProd`; если в пути есть пробелы — возьмите путь в кавычки.) - -3. **Установите зависимости** (если ещё не ставили): - ```powershell - uv sync - ``` - -4. **Запустите бота:** - ```powershell - uv run python -m app - ``` - -5. В консоли появятся логи; бот начнёт опрос Telegram и проверки Ozon/WB. Остановка — **Ctrl+C**. diff --git a/app/__main__.py b/app/__main__.py index 4e3e495..b72c411 100644 --- a/app/__main__.py +++ b/app/__main__.py @@ -119,16 +119,16 @@ async def main(): if "duplicate column" not in str(e).lower(): logger.warning(f"Migration review.bables: {e}") + # Создаём настройки для хороших и плохих отзывов при первом запуске. + # Существующие настройки (включая автоответы) не трогаем, чтобы они + # переживали перезапуск и редеплой. for is_good in [True, False]: - settings, _ = await Settings.get_or_create( + await Settings.get_or_create( is_good=is_good, defaults={ "analysis_enabled": not is_good, }, ) - settings.auto_response_enabled = False - settings.auto_response_empty_enabled = False - await settings.save() workers = asyncio.ensure_future( asyncio.gather( diff --git a/app/config.py b/app/config.py index bec08ed..0b34242 100644 --- a/app/config.py +++ b/app/config.py @@ -20,11 +20,21 @@ PRODUCTS_WB_SHEET_LINK = env.str("PRODUCTS_WB_SHEET_LINK", default="") PRODUCTS_OZON_SHEET_LINK = env.str("PRODUCTS_OZON_SHEET_LINK", default="") PRODUCTS_CACHE_TTL = env.int("PRODUCTS_CACHE_TTL", default=60 * 5) +# Ключ сервисного аккаунта Google: либо содержимое JSON (или его base64) +# в переменной окружения, либо путь к файлу. +GOOGLE_SERVICE_ACCOUNT_JSON = env.str("GOOGLE_SERVICE_ACCOUNT_JSON", default="") +GOOGLE_SERVICE_ACCOUNT_FILE = env.str( + "GOOGLE_SERVICE_ACCOUNT_FILE", default="google-auth/key.json" +) + OZON_API_KEY = env.str("OZON_API_KEY", default="") OZON_CLIENT_ID = env.str("OZON_CLIENT_ID", default="") WB_API_KEY = env.str("WB_API_KEY", default="") GPTUNNEL_API_KEY = env.str("GPTUNNEL_API_KEY", default="") +# Название бренда, от имени которого бот анализирует отзывы и отвечает на них +BRAND_NAME = env.str("BRAND_NAME", default="Magic Stories") + OZON_BASE_URL = "https://api-seller.ozon.ru" WB_BASE_URL = "https://feedbacks-api.wildberries.ru" GPT_API_URL = "https://gptunnel.ru/v1/chat/completions" @@ -32,8 +42,8 @@ GPT_API_URL = "https://gptunnel.ru/v1/chat/completions" OZON_CHECK_INTERVAL = env.int("OZON_CHECK_INTERVAL", default=60) WB_CHECK_INTERVAL = env.int("WB_CHECK_INTERVAL", default=300) -ANALYSIS_PROMPT = """ -Вы - представитель бренда Magic Stories и анализируете отзывы клиентов. +ANALYSIS_PROMPT = f""" +Вы - представитель бренда {BRAND_NAME} и анализируете отзывы клиентов. Классифицируйте отзыв по следующим критериям: 1. Тональность отзыва (позитивная, нейтральная, негативная). @@ -49,26 +59,26 @@ ANALYSIS_PROMPT = """ Ответ должен быть СТРОГО в формате JSON: ```json -{ +{{ "tone": "positive|negative|neutral", "reason": "причина тональности из списка или пустая строка для нейтральных отзывов", "is_critical": false // true если отзыв критичный -} +}} ``` ОБЯЗАТЕЛЬНО используйте ТОЛЬКО указанные категории из списка. Никаких других категорий или вариантов. """.strip() -ANSWER_PROMPT = """ +ANSWER_PROMPT = f""" Вы — профессиональный работник службы поддержки с многолетним опытом и чутким пониманием психологии человека, -официальный представитель бренда Magic Stories. Ваша задача — вежливо и профессионально отвечать на отзывы клиентов. +официальный представитель бренда {BRAND_NAME}. Ваша задача — вежливо и профессионально отвечать на отзывы клиентов. Ответ должен быть коротким, информативным и соответствовать тональности отзыва: - Негативный отзыв: выразите сожаление за произошедшее, предложите решение или помощь, объясните, как можно исправить ситуацию. - Позитивный отзыв: искренне поблагодарите за выбор бренда и положительный опыт, подтвердив, что вам важна обратная связь. - Нейтральный отзыв: подтвердите получение обратной связи и предложите дополнительную помощь, если это нужно. Не забывайте, что лесть перед клиентом люди хорошо считывают, поэтому избегайте чрезмерных похвал и канцеляризмов. Опишите решение проблемы или благодарность за положительный отзыв простым и понятным языком, который будет одинаково понятен любому клиенту. Также не отправляй почту -В конец отзыва всегда добавляйте "С уважением, команда Magic Stories." с новой строки. +В конец отзыва всегда добавляйте "С уважением, команда {BRAND_NAME}." с новой строки. """.strip() # Шаблоны автоответа на отзывы без текста (только оценка) diff --git a/app/services/sheets.py b/app/services/sheets.py index a3fb2de..1281473 100644 --- a/app/services/sheets.py +++ b/app/services/sheets.py @@ -1,15 +1,56 @@ +import base64 +import binascii +import json import logging import re from functools import lru_cache +from pathlib import Path from typing import Any import gspread from gspread.utils import InsertDataOption, ValueInputOption +from app import config + logger = logging.getLogger(__name__) -sheets_api = gspread.service_account(filename="google-auth/key.json") + # Таймаут HTTP-запросов (сек): защита от зависания при проблемах с сетью/Google API -sheets_api.set_timeout(30) +_SHEETS_HTTP_TIMEOUT = 30 + + +def _load_service_account_info(raw: str) -> dict[str, Any]: + """Разобрать ключ из переменной окружения: чистый JSON или его base64.""" + raw = raw.strip() + if not raw.startswith("{"): + try: + raw = base64.b64decode(raw, validate=True).decode("utf-8") + except (binascii.Error, UnicodeDecodeError) as e: + raise ValueError( + "GOOGLE_SERVICE_ACCOUNT_JSON: ожидается JSON ключа сервисного " + "аккаунта или его base64" + ) from e + return json.loads(raw) + + +@lru_cache(maxsize=1) +def get_client() -> gspread.Client: + """Клиент Google Sheets. Создаётся при первом обращении, не при импорте.""" + if config.GOOGLE_SERVICE_ACCOUNT_JSON: + info = _load_service_account_info(config.GOOGLE_SERVICE_ACCOUNT_JSON) + client = gspread.service_account_from_dict(info) + logger.info("Google Sheets: ключ загружен из GOOGLE_SERVICE_ACCOUNT_JSON") + else: + key_path = Path(config.GOOGLE_SERVICE_ACCOUNT_FILE) + if not key_path.is_file(): + raise FileNotFoundError( + f"Ключ сервисного аккаунта Google не найден: {key_path}. " + "Задайте GOOGLE_SERVICE_ACCOUNT_JSON или положите файл по пути " + "GOOGLE_SERVICE_ACCOUNT_FILE" + ) + client = gspread.service_account(filename=str(key_path)) + logger.info("Google Sheets: ключ загружен из файла %s", key_path) + client.set_timeout(_SHEETS_HTTP_TIMEOUT) + return client # HTTP коды, при которых стоит сбросить кэш листа (устаревший объект) _CACHE_INVALIDATE_CODES = frozenset({401, 403, 404, 429}) @@ -31,7 +72,7 @@ def parse_google_sheets_url(url: str) -> tuple[str, int | None]: def get_sheet(sheet_link: str) -> gspread.Worksheet: spreadsheet_id, worksheet_id = parse_google_sheets_url(sheet_link) - spreadsheet = sheets_api.open_by_key(spreadsheet_id) + spreadsheet = get_client().open_by_key(spreadsheet_id) if worksheet_id is not None: return spreadsheet.get_worksheet_by_id(worksheet_id) return spreadsheet.sheet1 diff --git a/docker-compose.yml b/docker-compose.yml index 7c9f260..11b1880 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,28 +1,20 @@ -# Docker Compose для продакшена PrimeKraft-бота на Ubuntu 22 -# Запуск: docker compose up -d -# Остановка: docker compose down +# Ручной запуск на сервере: docker compose up -d --build +# Переменные берутся из .env (если есть) и из окружения. См. .env.example. +# В Coolify рекомендуется build pack "Dockerfile", этот файл тогда не нужен. services: bot: build: . restart: unless-stopped - env_file: .env + env_file: + - path: .env + required: false environment: DB_PATH: /data/db.sqlite3 - ALL_PROXY: ${ALL_PROXY} - HTTPS_PROXY: ${HTTPS_PROXY} - HTTP_PROXY: ${HTTP_PROXY} - NO_PROXY: ${NO_PROXY} - dns: - - 1.1.1.1 - - 8.8.8.8 volumes: - # Постоянное хранилище БД - bot-data:/data - # Ключ Google Sheets (сервисный аккаунт) - - ./google-auth:/app/google-auth:ro - # Для отладки можно раскомментировать и примонтировать .env с хоста: - # - ./.env:/app/.env:ro + # Вариант с ключом Google в файле вместо GOOGLE_SERVICE_ACCOUNT_JSON: + # - ./google-auth:/app/google-auth:ro volumes: bot-data: