правки для деплоя через Coolify

This commit is contained in:
a.grigorev
2026-09-04 15:14:29 +03:00
parent 161fd6576b
commit 1098ff4991
11 changed files with 276 additions and 384 deletions
+13 -11
View File
@@ -1,25 +1,27 @@
# Виртуальное окружение и кэш # Секреты и локальные данные никогда не попадают в контекст сборки
.env
.env.*
google-auth/
*.sqlite3
*.sqlite3-wal
*.sqlite3-shm
*.db
# Окружения и кэш
.venv .venv
venv venv
__pycache__ __pycache__
*.py[cod] *.py[cod]
*.pyo *.egg-info
.pytest_cache .pytest_cache
.mypy_cache .mypy_cache
.ruff_cache .ruff_cache
# Git и IDE # Git, IDE, документация
.git .git
.gitignore .gitignore
.idea .idea
.vscode .vscode
*.swp *.swp
*.swo
# Локальная БД и логи (в проде используем volume)
db.sqlite3
*.log
# Документация и прочее (не нужны в образе)
*.md *.md
WINDOWS_SETUP.md *.log
+11
View File
@@ -24,7 +24,18 @@ HTTPS_PROXY=
HTTP_PROXY= HTTP_PROXY=
NO_PROXY=localhost,127.0.0.1 NO_PROXY=localhost,127.0.0.1
# --- Бренд ---
# Название бренда для промптов GPT (анализ и ответы на отзывы)
BRAND_NAME=
# --- Google Sheets --- # --- 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_WB_SHEET_LINK=
PRODUCTS_OZON_SHEET_LINK= PRODUCTS_OZON_SHEET_LINK=
-1
View File
@@ -38,4 +38,3 @@ env/
*.log *.log
.DS_Store .DS_Store
Thumbs.db Thumbs.db
-55
View File
@@ -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), менять ключ можно без пересборки образа.
+16 -12
View File
@@ -1,21 +1,25 @@
# Production Dockerfile for PrimeKraft bot (Ubuntu 22 / Linux)
FROM python:3.12-slim-bookworm FROM python:3.12-slim-bookworm
# uv для установки зависимостей строго по uv.lock
COPY --from=ghcr.io/astral-sh/uv:0.6 /uv /bin/uv
WORKDIR /app WORKDIR /app
# Копируем зависимости и код (нужен для pip install .) ENV UV_COMPILE_BYTECODE=1 \
COPY pyproject.toml ./ 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 COPY app ./app
# Устанавливаем проект в режиме production (без dev-зависимостей) # Каталог для SQLite. В Coolify/compose сюда монтируется постоянный том.
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir .
# Каталог для БД (volume монтируется в runtime)
RUN mkdir -p /data RUN mkdir -p /data
VOLUME ["/data"]
# Переменная для пути к БД по умолчанию (переопределяется в compose)
ENV DB_PATH=/data/db.sqlite3
ENV PYTHONUNBUFFERED=1
CMD ["python", "-m", "app"] CMD ["python", "-m", "app"]
+163
View File
@@ -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/<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`. Если ключ когда-либо был закоммичен, его нужно отозвать и выпустить заново, удаления из истории недостаточно.
-275
View File
@@ -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\` (шаги 45).
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**.
+4 -4
View File
@@ -119,16 +119,16 @@ async def main():
if "duplicate column" not in str(e).lower(): if "duplicate column" not in str(e).lower():
logger.warning(f"Migration review.bables: {e}") logger.warning(f"Migration review.bables: {e}")
# Создаём настройки для хороших и плохих отзывов при первом запуске.
# Существующие настройки (включая автоответы) не трогаем, чтобы они
# переживали перезапуск и редеплой.
for is_good in [True, False]: for is_good in [True, False]:
settings, _ = await Settings.get_or_create( await Settings.get_or_create(
is_good=is_good, is_good=is_good,
defaults={ defaults={
"analysis_enabled": not is_good, "analysis_enabled": not is_good,
}, },
) )
settings.auto_response_enabled = False
settings.auto_response_empty_enabled = False
await settings.save()
workers = asyncio.ensure_future( workers = asyncio.ensure_future(
asyncio.gather( asyncio.gather(
+17 -7
View File
@@ -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_OZON_SHEET_LINK = env.str("PRODUCTS_OZON_SHEET_LINK", default="")
PRODUCTS_CACHE_TTL = env.int("PRODUCTS_CACHE_TTL", default=60 * 5) 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_API_KEY = env.str("OZON_API_KEY", default="")
OZON_CLIENT_ID = env.str("OZON_CLIENT_ID", default="") OZON_CLIENT_ID = env.str("OZON_CLIENT_ID", default="")
WB_API_KEY = env.str("WB_API_KEY", default="") WB_API_KEY = env.str("WB_API_KEY", default="")
GPTUNNEL_API_KEY = env.str("GPTUNNEL_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" OZON_BASE_URL = "https://api-seller.ozon.ru"
WB_BASE_URL = "https://feedbacks-api.wildberries.ru" WB_BASE_URL = "https://feedbacks-api.wildberries.ru"
GPT_API_URL = "https://gptunnel.ru/v1/chat/completions" 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) OZON_CHECK_INTERVAL = env.int("OZON_CHECK_INTERVAL", default=60)
WB_CHECK_INTERVAL = env.int("WB_CHECK_INTERVAL", default=300) WB_CHECK_INTERVAL = env.int("WB_CHECK_INTERVAL", default=300)
ANALYSIS_PROMPT = """ ANALYSIS_PROMPT = f"""
Вы - представитель бренда Magic Stories и анализируете отзывы клиентов. Вы - представитель бренда {BRAND_NAME} и анализируете отзывы клиентов.
Классифицируйте отзыв по следующим критериям: Классифицируйте отзыв по следующим критериям:
1. Тональность отзыва (позитивная, нейтральная, негативная). 1. Тональность отзыва (позитивная, нейтральная, негативная).
@@ -49,26 +59,26 @@ ANALYSIS_PROMPT = """
Ответ должен быть СТРОГО в формате JSON: Ответ должен быть СТРОГО в формате JSON:
```json ```json
{ {{
"tone": "positive|negative|neutral", "tone": "positive|negative|neutral",
"reason": "причина тональности из списка или пустая строка для нейтральных отзывов", "reason": "причина тональности из списка или пустая строка для нейтральных отзывов",
"is_critical": false // true если отзыв критичный "is_critical": false // true если отзыв критичный
} }}
``` ```
ОБЯЗАТЕЛЬНО используйте ТОЛЬКО указанные категории из списка. Никаких других категорий или вариантов. ОБЯЗАТЕЛЬНО используйте ТОЛЬКО указанные категории из списка. Никаких других категорий или вариантов.
""".strip() """.strip()
ANSWER_PROMPT = """ ANSWER_PROMPT = f"""
Вы — профессиональный работник службы поддержки с многолетним опытом и чутким пониманием психологии человека, Вы — профессиональный работник службы поддержки с многолетним опытом и чутким пониманием психологии человека,
официальный представитель бренда Magic Stories. Ваша задача — вежливо и профессионально отвечать на отзывы клиентов. официальный представитель бренда {BRAND_NAME}. Ваша задача — вежливо и профессионально отвечать на отзывы клиентов.
Ответ должен быть коротким, информативным и соответствовать тональности отзыва: Ответ должен быть коротким, информативным и соответствовать тональности отзыва:
- Негативный отзыв: выразите сожаление за произошедшее, предложите решение или помощь, объясните, как можно исправить ситуацию. - Негативный отзыв: выразите сожаление за произошедшее, предложите решение или помощь, объясните, как можно исправить ситуацию.
- Позитивный отзыв: искренне поблагодарите за выбор бренда и положительный опыт, подтвердив, что вам важна обратная связь. - Позитивный отзыв: искренне поблагодарите за выбор бренда и положительный опыт, подтвердив, что вам важна обратная связь.
- Нейтральный отзыв: подтвердите получение обратной связи и предложите дополнительную помощь, если это нужно. - Нейтральный отзыв: подтвердите получение обратной связи и предложите дополнительную помощь, если это нужно.
Не забывайте, что лесть перед клиентом люди хорошо считывают, поэтому избегайте чрезмерных похвал и канцеляризмов. Не забывайте, что лесть перед клиентом люди хорошо считывают, поэтому избегайте чрезмерных похвал и канцеляризмов.
Опишите решение проблемы или благодарность за положительный отзыв простым и понятным языком, который будет одинаково понятен любому клиенту. Также не отправляй почту Опишите решение проблемы или благодарность за положительный отзыв простым и понятным языком, который будет одинаково понятен любому клиенту. Также не отправляй почту
В конец отзыва всегда добавляйте "С уважением, команда Magic Stories." с новой строки. В конец отзыва всегда добавляйте "С уважением, команда {BRAND_NAME}." с новой строки.
""".strip() """.strip()
# Шаблоны автоответа на отзывы без текста (только оценка) # Шаблоны автоответа на отзывы без текста (только оценка)
+44 -3
View File
@@ -1,15 +1,56 @@
import base64
import binascii
import json
import logging import logging
import re import re
from functools import lru_cache from functools import lru_cache
from pathlib import Path
from typing import Any from typing import Any
import gspread import gspread
from gspread.utils import InsertDataOption, ValueInputOption from gspread.utils import InsertDataOption, ValueInputOption
from app import config
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
sheets_api = gspread.service_account(filename="google-auth/key.json")
# Таймаут HTTP-запросов (сек): защита от зависания при проблемах с сетью/Google API # Таймаут 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 коды, при которых стоит сбросить кэш листа (устаревший объект) # HTTP коды, при которых стоит сбросить кэш листа (устаревший объект)
_CACHE_INVALIDATE_CODES = frozenset({401, 403, 404, 429}) _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: def get_sheet(sheet_link: str) -> gspread.Worksheet:
spreadsheet_id, worksheet_id = parse_google_sheets_url(sheet_link) 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: if worksheet_id is not None:
return spreadsheet.get_worksheet_by_id(worksheet_id) return spreadsheet.get_worksheet_by_id(worksheet_id)
return spreadsheet.sheet1 return spreadsheet.sheet1
+8 -16
View File
@@ -1,28 +1,20 @@
# Docker Compose для продакшена PrimeKraft-бота на Ubuntu 22 # Ручной запуск на сервере: docker compose up -d --build
# Запуск: docker compose up -d # Переменные берутся из .env (если есть) и из окружения. См. .env.example.
# Остановка: docker compose down # В Coolify рекомендуется build pack "Dockerfile", этот файл тогда не нужен.
services: services:
bot: bot:
build: . build: .
restart: unless-stopped restart: unless-stopped
env_file: .env env_file:
- path: .env
required: false
environment: environment:
DB_PATH: /data/db.sqlite3 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: volumes:
# Постоянное хранилище БД
- bot-data:/data - bot-data:/data
# Ключ Google Sheets (сервисный аккаунт) # Вариант с ключом Google в файле вместо GOOGLE_SERVICE_ACCOUNT_JSON:
- ./google-auth:/app/google-auth:ro # - ./google-auth:/app/google-auth:ro
# Для отладки можно раскомментировать и примонтировать .env с хоста:
# - ./.env:/app/.env:ro
volumes: volumes:
bot-data: bot-data: