docs: поддержка в боте (support.py, топики группы, вики)
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)**
|
||||
|
||||
|
||||
Reference in new issue
Block a user