docs: роли, подписи, кнопка «Ответить», версия 0.1.0+59

hermes committed 2026-10-11 21:10:36 +02:00
1 parent 6139319180
commit cad5e15422
5 files changed
+77 -9

No files matched your search

+3 -2
@@ -11,7 +11,7 @@
| **Сервер приложения** | `spb-easy-dale` (`31.129.103.242`) |
| **Бот** | [@post_master_bot](https://t.me/post_master_bot) |
| **Группа поддержки** | супергруппа с темами, `chat.id = -1008000863273` |
| **Версия** | `VERSION`, сейчас `0.1.0+49` |
| **Версия** | `VERSION`, сейчас `0.1.0+59` |
> Сервер управления Coolify и сервер приложения — **разные хосты**. Проверять контейнер нужно на сервере приложения, а не на хосте Coolify.
@@ -49,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+49
INFO:__main__:Development deploy notice sent: version=0.1.0+59
INFO:aiogram.dispatcher:Start polling
INFO:aiogram.dispatcher:Run polling for bot @post_master_bot id=2201652808 - 'Мастер постинга'
```
@@ -80,6 +80,7 @@ INFO:aiogram.dispatcher:Run polling for bot @post_master_bot id=2201652808 - 'М
| `SUPPORT_GROUP_ID` | id группы операторов (супергруппа с темами); без неё поддержка выключена |
| `SUPPORT_NOTE_PREFIX` | префикс внутренней заметки (default `//`) |
| `SUPPORT_GREETINGS` | пул приветствий оператора, разделитель `|` |
| `SUPPORT_OPERATOR_SIGNATURE` | подпись ответа оператора (default `Оператор поддержки`) |
| `SUPPORT_TOPIC_EMOJI_ID` | custom emoji id на топике обращения (optional) |
**Правила:**
+5 -1
@@ -102,6 +102,7 @@ update_tone_from_image → _handle_image_message
| `is_development_environment` | `DEPLOY_ENV == "development"` — гейт всех dev-ветвей |
| `configure_raw_update_logging` | Подключает `RawUpdateMiddleware` только в development |
| `open_support` | Открывает обращение; показывает клавиатуру только с «Главное меню» |
| `_close_support_case` | Снимает `is_active`; вызывается из `enter_mode`, `show_main_menu`, `start` |
### `user_store.py`
@@ -190,6 +191,9 @@ update_tone_from_image → _handle_image_message
| `get_user_by_thread` | Обратный поиск пользователя по `thread_id` |
| `topic_title` | «Имя · @username» (без username — «Имя · id»), лимит 128 |
| `support_greeting` (activity_phrases) | Случайная фраза начала диалога; пул переопределяется `SUPPORT_GREETINGS` |
| `user_signature` | Подпись сообщения пользователя в топике: имя или id |
| `operator_signature` | Подпись ответа оператора; `SUPPORT_OPERATOR_SIGNATURE` |
| `support_reply_keyboard` / `handle_support_reply` | Кнопка «Ответить» под ответом оператора, если обращение закрыто |
| `send_profile_card` | Аватарка (`get_user_profile_photos`) + сводка профиля как первое сообщение топика (один раз) |
| `ensure_topic` | Создаёт топик при первом обращении, переиспользует существующий |
| `relay_user_message` | `copy_message` в топик; при «thread not found» пересоздаёт топик |
@@ -255,7 +259,7 @@ SELECT payload FROM raw_updates WHERE update_id = ?;
.venv/bin/python -m unittest discover -s tests -v
```
Тесты (на версии `0.1.0+49`). Покрытие: STT, LLM, vision, ToV, reply-поля, изоляция пользователей, API-логи, **raw update log**, режимные клавиатуры и роутинг, платежи (Stars, защита от повторов, dev-подтверждение), ошибки, справка, онбординг, подписки, самостоятельная генерация поста, восстановление обрезанного JSON, индикатор «печатает».
Тесты: 251 (на версии `0.1.0+59`). Покрытие: STT, LLM, vision, ToV, reply-поля, изоляция пользователей, API-логи, **raw update log**, режимные клавиатуры и роутинг, платежи (Stars, защита от повторов, dev-подтверждение), ошибки, справка, онбординг, подписки, самостоятельная генерация поста, восстановление обрезанного JSON, индикатор «печатает», **поддержка** (топики, подписи, кнопка «Ответить», закрытие обращения).
> Запускай через `.venv` проекта: вне виртуального окружения `aiogram` может быть не установлен.
+1 -1
@@ -6,7 +6,7 @@
- **Репозиторий:** `admin/social-post-writer-bot`
- **Стек:** Python 3.12, aiogram 3.x, aiohttp, SQLite, APInex (LLM / STT / vision)
- **Деплой:** Coolify, окружение `development`, приложение `social-post-writer-bot-development`
- **Версия разработки:** `0.1.0+48`
- **Версия разработки:** `0.1.0+59`
---
+66 -5
@@ -19,13 +19,22 @@
3. В ответ приходит **приветствие оператора** — случайная фраза из пула, например
«Здравствуйте! На связи поддержка. Расскажите, что случилось — разберёмся».
4. Пользователь пишет текстом, присылает фото, документ или голосовое — всё уходит оператору.
5. Оператор отвечает в топике — ответ приходит пользователю в личку.
6. **«🏠 Главное меню»** завершает обращение: бот снова работает как обычно.
В топике сообщение подписывается: «<Имя>:\n<текст>» (нет имени — «<id>:\n<текст>»).
5. Оператор отвечает в топике — ответ приходит пользователю в личку с подписью
«Оператор поддержки:\n<текст>». Если обращение закрыто, добавляется кнопка
**«↩️ Ответить»** — она снова открывает обращение в том же топике.
6. **Уход из контекста поддержки** завершает обращение: вход в любой режим,
«🏠 Главное меню» или `/start`.
Отдельного режима (FSM) нет. Обращение — расширение режима «Помощь», поэтому пользователь может
уйти в «Генерацию», потом вернуться через «Помощь → Поддержка» и продолжить разговор:
история в топике сохраняется.
**Правило `is_active`:** ушёл из контекста поддержки — обращение неактивно. Флаг снимается
при входе в **любой** режим (`/training`, `/generate`, `/subscription`, `/help`), по
«🏠 Главное меню» и `/start` — всё через `_close_support_case()`. Иначе пользователь
переключился в «Обучение», а сообщение уезжает оператору.
---
## Схема
@@ -78,6 +87,57 @@
---
## Подписи
| Направление | Формат |
|---|---|
| пользователь → топик | «<Имя>:\n<текст>», нет имени — «<id>:\n<текст>» (`user_signature()`) |
| оператор → личка | «Оператор поддержки:\n<текст>» (`SUPPORT_OPERATOR_SIGNATURE`) |
- Caption медиа уходит текстом с той же подписью, само медиа копируется.
- Всё остальное (фото, документы, голосовые без подписи) копируется **как есть**, без подписи.
- У медиа `copy_message` не позволяет подменить caption — поэтому подпись отдельным сообщением.
---
## Кнопка «Ответить»
Оператор может ответить, когда пользователь уже вышел из режима поддержки. Сообщение
приходит с инлайн-кнопкой **«↩️ Ответить»**:
- появляется **только если обращение закрыто** — при активном пользователь и так пишет оператору;
- при нажатии ставит `is_active = 1`, убирает себя с сообщения и показывает клавиатуру
поддержки (только «Главное меню»);
- callback `support:reply:<user_id>` проверяет владельца — чужой нажать не может;
- обращение открывается **в том же топике**, история сохраняется.
---
## Роли и порядок роутеров
Роль бота определяется **контекстом апдейта** (тип и id чата), а не пользователем:
| Роль | Где работает | Модуль |
|---|---|---|
| **работник** | личка пользователя | `bot.py`, фильтр `F.chat.type == "private"` |
| **помощник поддержки** | группа поддержки | `support.py` |
| **аналитик** | сбор аналитики | позже |
| **почтальон** | рассылки | позже |
**Порядок `include_router` критичен** — у aiogram побеждает первый подходящий роутер.
Групповые сценарии регистрируются **до** основного:
```python
dp.include_router(payments.router)
dp.include_router(support.router) # группы — ДО основного
dp.include_router(router) # основной, только private
```
Иначе catch-all `@router.message()` основного роутера перехватывает апдейты из групп и
отвечает в группу «Выбери режим работы кнопкой ниже» вместо пересылки оператору.
Фильтр `F.chat.type == "private"` стоит на роутере `bot.py` и **не** распространяется
на `support.router` — поэтому callback «Ответить» работает.
## Данные профиля в карточке
Собирает `user_store.support_profile_summary()`:
@@ -115,9 +175,10 @@
## Что нужно от группы
- Супергруппа с **включёнными темами** (`is_forum = true`).
- Бот — участник группы с правами: **управление темами** (`create_forum_topic`) и отправка сообщений.
- Privacy mode бота должен быть **выключен** (или бот добавлен админом), иначе он не увидит
сообщения участников. На практике: если бот — администратор, апдейты приходят всегда.
- Бот — **администратор** с правом **«Управление темами»** (`create_forum_topic`).
Обычного участника недостаточно: Telegram вернёт
`Bad Request: not enough rights to create a topic`, и топик не создастся.
В `DEPLOY_ENV=development` бот покажет эту причину прямо в сообщении.
Проверить, что бот видит группу, можно по dev-логу сырых апдейтов
(`/app/data/bot_updates.sqlite3`): в payload должен быть `chat.id` группы и `chat.type = supergroup`.
+2
@@ -75,6 +75,8 @@
Пока обращение открыто, внизу остаётся только кнопка «Главное меню». Можно отвлечься на другие
режимы и вернуться: обращение продолжится в том же топике с сохранённой историей.
Если оператор ответил после того, как вы вышли, под его сообщением появится кнопка
**«↩️ Ответить»** — она вернёт вас в общение с поддержкой.
«🏠 Главное меню» завершает обращение — бот снова работает как обычно.
Подробнее — в разделе [Поддержка](Support.md).