docs: поддержка в боте (support.py, топики группы, вики)

hermes committed 2026-10-11 15:12:04 +02:00
1 parent 11c7c41dfb
commit c6d91a1dce
6 files changed
+173 -5

No files matched your search

+8 -3
@@ -10,7 +10,8 @@
| **Coolify приложение** | `social-post-writer-bot-development` (`d32u5zcq2g4odmdpq9hw91yn`) |
| **Сервер приложения** | `spb-easy-dale` (`31.129.103.242`) |
| **Бот** | [@post_master_bot](https://t.me/post_master_bot) |
| **Версия** | `VERSION`, сейчас `0.1.0+48` |
| **Группа поддержки** | супергруппа с темами, `chat.id = -1008000863273` |
| **Версия** | `VERSION`, сейчас `0.1.0+49` |
> Сервер управления Coolify и сервер приложения — **разные хосты**. Проверять контейнер нужно на сервере приложения, а не на хосте Coolify.
@@ -48,7 +49,7 @@ ssh root@31.129.103.242 'docker logs --tail 60 <CONTAINER_NAME>'
Успешный запуск:
```
INFO:__main__:Development deploy notice sent: version=0.1.0+48
INFO:__main__:Development deploy notice sent: version=0.1.0+49
INFO:aiogram.dispatcher:Start polling
INFO:aiogram.dispatcher:Run polling for bot @post_master_bot id=2201652808 - 'Мастер постинга'
```
@@ -76,6 +77,9 @@ INFO:aiogram.dispatcher:Run polling for bot @post_master_bot id=2201652808 - 'М
| `BOT_LOG_PATH` | файл лога (по умолчанию `/app/data/bot.log`) |
| `DEPLOY_ENV` | `development` — включает dev-поведение (уведомления о версии, диагностику, raw update log, dev-оплату) |
| `DEV_CHAT_ID` | чат для уведомлений о новой версии |
| `SUPPORT_GROUP_ID` | id группы операторов (супергруппа с темами); без неё поддержка выключена |
| `SUPPORT_NOTE_PREFIX` | префикс внутренней заметки (default `//`) |
| `SUPPORT_TOPIC_EMOJI_ID` | custom emoji id на топике обращения (optional) |
**Правила:**
- `DEPLOY_ENV` включает dev-поведение только при **точном** значении `development`; production, пустое и неизвестное значение отключают его.
@@ -102,7 +106,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY bot.py apinex_client.py user_store.py help_message.py payments.py keyboards.py raw_updates.py VERSION ./
COPY bot.py apinex_client.py user_store.py help_message.py payments.py keyboards.py raw_updates.py support.py activity_phrases.py VERSION ./
CMD ["python", "bot.py"]
```
@@ -118,6 +122,7 @@ Persistent volume `d32u5zcq2g4odmdpq9hw91yn-social-post-writer-dev-data` → м
|---|---|
| `social_writer.sqlite3` | рабочая БД (пользователи, ToV, посты, идеи, API-логи, подписки, платежи) |
| `bot_updates.sqlite3` | сырые входящие Telegram Update (только development, retention 30 дней) |
| `social_writer.sqlite3` → `support_topics` | привязка пользователя к топику поддержки |
| `bot.log` | ротационный лог приложения (5 MiB × 3 архива) |
WAL-режим, `busy_timeout = 10000`.
+39 -1
@@ -8,6 +8,7 @@ payments.py — оплата Stars: sendInvoice, pre_checkout_query, su
apinex_client.py — клиент APInex (LLM, STT, vision), промпты, логирование вызовов
user_store.py — SQLite: пользователи, ToV, посты, идеи, API-логи, подписки
raw_updates.py — отдельная SQLite-БД сырых входящих Telegram Update (только development)
support.py — поддержка: личка пользователя ↔ персональный топик группы операторов
keyboards.py — клавиатуры главного меню и режимов; маппинг кнопок на команды
help_message.py — Rich Message: WELCOME_RICH_HTML + HELP_SECTIONS/help_view(), plain fallback
VERSION — версия релиза
@@ -100,6 +101,7 @@ update_tone_from_image → _handle_image_message
| `report_error` | Понятная ошибка пользователю; секреты вырезаны |
| `is_development_environment` | `DEPLOY_ENV == "development"` — гейт всех dev-ветвей |
| `configure_raw_update_logging` | Подключает `RawUpdateMiddleware` только в development |
| `open_support` | Открывает обращение; показывает клавиатуру только с «Главное меню» |
### `user_store.py`
@@ -148,6 +150,7 @@ update_tone_from_image → _handle_image_message
| `generated_posts` | История сгенерированных постов (`origin`: generated/revised) |
| `generated_ideas` | История идей (для исключения повторов) |
| `api_call_logs` | JSON-логи вызовов APInex + ошибки (30 дней) |
| `support_topics` | `telegram_user_id` → `thread_id` топика поддержки, `is_active` |
| `subscriptions` | Подписки и триалы |
| `payments` | `telegram_payment_charge_id` — защита от двойного начисления |
@@ -155,6 +158,41 @@ update_tone_from_image → _handle_image_message
Отдельная dev-БД: `/app/data/bot_updates.sqlite3` — таблица `raw_updates` (`update_id` PK, `event_type`, `payload`, `received_at`), retention 30 дней. См. ниже.
### Поддержка
```
пользователь (личка) ──SupportRelayMiddleware──▶ support.relay_user_message
├─ ensure_topic (create_forum_topic, один на пользователя)
├─ send_profile_card (аватарка + имя/username/id)
└─ copy_message в топик
оператор (топик) ──handle_support_group_message──▶ copy_message пользователю
├─ пропуск: sender.is_bot, нет thread_id, топик без известного пользователя
└─ пропуск: сообщение с префиксом SUPPORT_NOTE_PREFIX (внутренняя заметка)
```
- `SupportRelayMiddleware` — `outer_middleware` на `dp.message`: если обращение активно, сообщение
копируется в топик и **не** доходит до обычных обработчиков (возвращает `None`).
- Пока обращение активно, `enter_mode` отклоняет переключение режимов, а `show_main_menu`
снимает `is_active`.
- Функция fail-closed: без `SUPPORT_GROUP_ID` `is_enabled()` → `False`, и поддержка молча выключена.
### Ключевые функции (`support.py`)
| Функция | Что делает |
|---|---|
| `support_group_id()` | Читает `SUPPORT_GROUP_ID`; невалидное значение → поддержка выключена |
| `init_support_table` | Создаёт таблицу `support_topics` |
| `get_topic` / `set_topic` / `forget_topic` | Привязка пользователя к топику |
| `set_active` / `is_support_active` | Флаг открытого обращения |
| `get_user_by_thread` | Обратный поиск пользователя по `thread_id` |
| `topic_title` | «Имя · @username» (без username — «Имя · id»), лимит 128 |
| `send_profile_card` | Аватарка (`get_user_profile_photos`) как первое сообщение топика |
| `ensure_topic` | Создаёт топик при первом обращении, переиспользует существующий |
| `relay_user_message` | `copy_message` в топик; при «thread not found» пересоздаёт топик |
| `handle_support_group_message` | Доставляет ответ оператора пользователю, пропуская заметки |
| `SupportRelayMiddleware` | Перехватывает входящие при активном обращении |
---
## Промпты и модели
@@ -214,7 +252,7 @@ SELECT payload FROM raw_updates WHERE update_id = ?;
.venv/bin/python -m unittest discover -s tests -v
```
155 тестов (на версии `0.1.0+48`). Покрытие: STT, LLM, vision, ToV, reply-поля, изоляция пользователей, API-логи, **raw update log**, режимные клавиатуры и роутинг, платежи (Stars, защита от повторов, dev-подтверждение), ошибки, справка, онбординг, подписки, самостоятельная генерация поста, восстановление обрезанного JSON, индикатор «печатает».
Тесты (на версии `0.1.0+49`). Покрытие: STT, LLM, vision, ToV, reply-поля, изоляция пользователей, API-логи, **raw update log**, режимные клавиатуры и роутинг, платежи (Stars, защита от повторов, dev-подтверждение), ошибки, справка, онбординг, подписки, самостоятельная генерация поста, восстановление обрезанного JSON, индикатор «печатает».
> Запускай через `.venv` проекта: вне виртуального окружения `aiogram` может быть не установлен.
+3
@@ -21,6 +21,7 @@
| **Голосовой ввод** | Голосовые расшифровываются (APInex STT) и работают как текст |
| **Идеи** | «предложи тему» или `/idea` — одна идея (заголовок, угол, зачем) |
| **Режимы** | «Обучение», «Генерация», «Подписка», «Помощь» — нижнее меню с кнопкой возврата |
| **Поддержка** | «🆘 Поддержка» — переписка с оператором в личке, у него в персональном топике группы |
| **Подписка** | Триал 3 дня, затем 30 дней за 100 звёзд (реальная оплата Telegram Stars) |
---
@@ -40,6 +41,7 @@
- **[Пользователю](User-Guide.md)** — сценарии, команды, подписка
- **[Разработчику](Developer-Guide.md)** — архитектура, потоки, функции, БД, логирование
- **[Деплой](Deployment.md)** — Coolify, переменные, деплой-скрипт, проверка после деплоя
- **[Поддержка](Support.md)** — как работает обращение, топики, заметки оператора
- **[Подписка](Subscription.md)** — модель, таблица БД, жизненный цикл, Stars
---
@@ -52,6 +54,7 @@ payments.py — оплата Telegram Stars
apinex_client.py — клиент APInex (LLM, STT, vision)
user_store.py — SQLite: пользователи, ToV, посты, идеи, API-логи, подписки
raw_updates.py — dev-only БД сырых входящих Telegram Update
support.py — поддержка: личка ↔ персональный топик в группе операторов
keyboards.py — клавиатуры режимов
help_message.py — Rich Message справка
VERSION — версия релиза
+110
@@ -0,0 +1,110 @@
# Поддержка
Поддержка работает **внутри бота**: пользователь пишет в личку, сообщения уходят в персональный
топик группы операторов, ответ оператора приходит обратно в личку.
| Компонент | Значение |
|---|---|
| **Группа операторов** | супергруппа «Мастер постинга: Поддержка», `chat.id = -1008000863273` (форум с темами) |
| **Модуль** | `support.py` |
| **Таблица БД** | `support_topics` в `/app/data/social_writer.sqlite3` |
| **Переменная** | `SUPPORT_GROUP_ID` — без неё функция выключена |
---
## Как это выглядит для пользователя
1. Режим «📖 Помощь» → кнопка **«🆘 Поддержка»** (или команда `/support`).
2. Бот пишет: что случилось, опиши проблему прямо здесь, оператор ответит.
3. Пользователь пишет текстом, присылает фото, документ или голосовое — всё уходит оператору.
4. Оператор отвечает в топике — ответ приходит пользователю в личку.
5. **«🏠 Главное меню»** завершает обращение: бот снова работает как обычно.
Отдельного режима (FSM) нет. Обращение — расширение режима «Помощь», поэтому пользователь может
уйти в «Генерацию», потом вернуться через «Помощь → Поддержка» и продолжить разговор:
история в топике сохраняется.
---
## Схема
```
личка пользователя ──copy_message──▶ топик в SUPPORT_GROUP_ID
▲ │
└────────────copy_message─────────────┘
(любое сообщение оператора)
```
- **Один пользователь — один топик.** `thread_id` сохраняется в `support_topics` и переиспользуется.
Повторное обращение продолжает ту же переписку с сохранённой историей.
- **Название топика** — «Имя · @username». Если username нет — «Имя · id».
- **Аватарка**: Telegram Bot API не умеет ставить на топик произвольную картинку, только custom emoji.
Поэтому аватарка профиля уходит **первым сообщением-карточкой** в топик: фото + имя, `@username`,
`user_id` и подсказка про префикс заметки. Custom emoji на самом топике — опционально,
через `SUPPORT_TOPIC_EMOJI_ID`.
- **Все типы контента**: текст, фото, документы, голосовые — пересылаются как есть (`copy_message`),
без пометки «forwarded from».
- **Завершение** снимает флаг `is_active`; топик **не удаляется и не закрывается** — оператор
сохраняет историю и может вернуться к ней позже.
---
## Заметки оператора
Сообщение, начинающееся с `//` (префикс задаётся `SUPPORT_NOTE_PREFIX`), **не отправляется
пользователю** — оно остаётся внутренней заметкой в топике.
```
// клиент просил вернуть деньги, проверить платёж
Ответ пиши без префикса — он уйдёт пользователю.
```
---
## Что нужно от группы
- Супергруппа с **включёнными темами** (`is_forum = true`).
- Бот — участник группы с правами: **управление темами** (`create_forum_topic`) и отправка сообщений.
- Privacy mode бота должен быть **выключен** (или бот добавлен админом), иначе он не увидит
сообщения участников. На практике: если бот — администратор, апдейты приходят всегда.
Проверить, что бот видит группу, можно по dev-логу сырых апдейтов
(`/app/data/bot_updates.sqlite3`): в payload должен быть `chat.id` группы и `chat.type = supergroup`.
---
## Переменные окружения (Coolify)
| Переменная | Обязательна | Назначение |
|---|---|---|
| `SUPPORT_GROUP_ID` | да | id группы операторов. Без неё поддержка выключена (fail-closed) |
| `SUPPORT_NOTE_PREFIX` | нет | префикс внутренней заметки, default `//` |
| `SUPPORT_TOPIC_EMOJI_ID` | нет | custom emoji id на иконке топика |
---
## Хранилище
Таблица `support_topics` в рабочей БД:
| Колонка | Смысл |
|---|---|
| `telegram_user_id` | PK, id пользователя |
| `thread_id` | id топика в группе операторов |
| `is_active` | 1 — обращение открыто, 0 — завершено |
| `created_at` / `updated_at` | время создания и последнего изменения |
Если топик удалили вручную в Telegram, `copy_message` вернёт «thread not found»; бот удалит
запись и создаст топик заново при следующем сообщении.
---
## Типичные проблемы
| Симптом | Причина | Решение |
|---|---|---|
| Кнопка «Поддержка» пишет «временно недоступна» | не задан `SUPPORT_GROUP_ID` | добавить переменную в Coolify и передеплоить |
| Сообщения пользователя не доходят в группу | privacy mode бота или нет прав на темы | сделать бота админом группы |
| Ответ оператора не приходит в личку | топик не связан с пользователем (удалён/создан вручную) | проверить `support_topics`; попросить пользователя написать заново |
| Заметка ушла пользователю | нет префикса `//` | писать заметку с префикса, проверять `SUPPORT_NOTE_PREFIX` |
| Создалось два топика на одного пользователя | запись удалили при активном обращении | удалить лишний топик вручную, оставить тот, что в `support_topics` |
+12 -1
@@ -11,7 +11,7 @@
Одновременно `/start` создаёт пробный период на **3 дня**. Справка автоматически не открывается — она доступна кнопкой «📖 Помощь» или командой `/help`.
Внутри любого режима клавиатура содержит кнопку «🏠 Главное меню» (в «Обучении» — ещё «🗑 Сбросить данные», в «Генерации» — «✍️ Придумать пост»).
Внутри любого режима клавиатура содержит кнопку «🏠 Главное меню» (в «Обучении» — ещё «🗑 Сбросить данные», в «Генерации» — «✍️ Придумать пост», в «Помощи» — «🆘 Поддержка»).
---
@@ -68,6 +68,16 @@
### Голосом
В режимах «Обучение» и «Генерация» отправь голосовое — бот расшифрует его (STT) и обработает как обычный текст.
### Написать в поддержку
В режиме «Помощь» нажми **«🆘 Поддержка»** (или введи `/support`) и опиши проблему — можно приложить
фото, документ или голосовое. Сообщение уйдёт оператору, он ответит прямо в этом чате.
Пока обращение открыто, внизу остаётся только кнопка «Главное меню». Можно отвлечься на другие
режимы и вернуться: обращение продолжится в том же топике с сохранённой историей.
«🏠 Главное меню» завершает обращение — бот снова работает как обычно.
Подробнее — в разделе [Поддержка](Support.md).
---
## Команды
@@ -85,6 +95,7 @@
| `/idea` | Одна идея для поста | Генерация |
| `/balance` | Сколько осталось дней | Подписка |
| `/subscribe` | Оплатить подписку (30 дней) | Подписка |
| `/support` | Написать в поддержку | Любой |
Переключатели режимов работают из любого режима; профильные действия — только в своём. В режиме «Генерация» русские фразы («придумай идею», «о чём написать?») работают как `/idea`.
+1
@@ -3,6 +3,7 @@
- **[Главная](Home.md)**
- **[Пользователю](User-Guide.md)**
- **[Разработчику](Developer-Guide.md)**
- **[Поддержка](Support.md)**
- **[Подписка](Subscription.md)**
- **[Деплой](Deployment.md)**