правки для деплоя через Coolify
This commit is contained in:
+13
-11
@@ -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
|
||||||
|
|||||||
@@ -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=
|
||||||
|
|||||||
@@ -38,4 +38,3 @@ env/
|
|||||||
*.log
|
*.log
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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"]
|
||||||
|
|||||||
@@ -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`. Если ключ когда-либо был закоммичен, его нужно отозвать и выпустить заново, удаления из истории недостаточно.
|
||||||
@@ -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**.
|
|
||||||
+4
-4
@@ -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
@@ -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
@@ -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
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user