docs: роли, подписи, кнопка «Ответить», версия 0.1.0+59
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`.
|
||||
|
||||
@@ -75,6 +75,8 @@
|
||||
|
||||
Пока обращение открыто, внизу остаётся только кнопка «Главное меню». Можно отвлечься на другие
|
||||
режимы и вернуться: обращение продолжится в том же топике с сохранённой историей.
|
||||
Если оператор ответил после того, как вы вышли, под его сообщением появится кнопка
|
||||
**«↩️ Ответить»** — она вернёт вас в общение с поддержкой.
|
||||
«🏠 Главное меню» завершает обращение — бот снова работает как обычно.
|
||||
|
||||
Подробнее — в разделе [Поддержка](Support.md).
|
||||
|
||||
Reference in new issue
Block a user