diff --git a/Deployment.md b/Deployment.md index e76d553..0993c68 100644 --- a/Deployment.md +++ b/Deployment.md @@ -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 ' Успешный запуск: ``` -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) | **Правила:** diff --git a/Developer-Guide.md b/Developer-Guide.md index 68b87cf..521e1f3 100644 --- a/Developer-Guide.md +++ b/Developer-Guide.md @@ -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` может быть не установлен. diff --git a/Home.md b/Home.md index 6726644..8825ae4 100644 --- a/Home.md +++ b/Home.md @@ -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` --- diff --git a/Support.md b/Support.md index 065e267..e9face9 100644 --- a/Support.md +++ b/Support.md @@ -19,13 +19,22 @@ 3. В ответ приходит **приветствие оператора** — случайная фраза из пула, например «Здравствуйте! На связи поддержка. Расскажите, что случилось — разберёмся». 4. Пользователь пишет текстом, присылает фото, документ или голосовое — всё уходит оператору. -5. Оператор отвечает в топике — ответ приходит пользователю в личку. -6. **«🏠 Главное меню»** завершает обращение: бот снова работает как обычно. + В топике сообщение подписывается: «<Имя>:\n<текст>» (нет имени — «:\n<текст>»). +5. Оператор отвечает в топике — ответ приходит пользователю в личку с подписью + «Оператор поддержки:\n<текст>». Если обращение закрыто, добавляется кнопка + **«↩️ Ответить»** — она снова открывает обращение в том же топике. +6. **Уход из контекста поддержки** завершает обращение: вход в любой режим, + «🏠 Главное меню» или `/start`. Отдельного режима (FSM) нет. Обращение — расширение режима «Помощь», поэтому пользователь может уйти в «Генерацию», потом вернуться через «Помощь → Поддержка» и продолжить разговор: история в топике сохраняется. +**Правило `is_active`:** ушёл из контекста поддержки — обращение неактивно. Флаг снимается +при входе в **любой** режим (`/training`, `/generate`, `/subscription`, `/help`), по +«🏠 Главное меню» и `/start` — всё через `_close_support_case()`. Иначе пользователь +переключился в «Обучение», а сообщение уезжает оператору. + --- ## Схема @@ -78,6 +87,57 @@ --- +## Подписи + +| Направление | Формат | +|---|---| +| пользователь → топик | «<Имя>:\n<текст>», нет имени — «:\n<текст>» (`user_signature()`) | +| оператор → личка | «Оператор поддержки:\n<текст>» (`SUPPORT_OPERATOR_SIGNATURE`) | + +- Caption медиа уходит текстом с той же подписью, само медиа копируется. +- Всё остальное (фото, документы, голосовые без подписи) копируется **как есть**, без подписи. +- У медиа `copy_message` не позволяет подменить caption — поэтому подпись отдельным сообщением. + +--- + +## Кнопка «Ответить» + +Оператор может ответить, когда пользователь уже вышел из режима поддержки. Сообщение +приходит с инлайн-кнопкой **«↩️ Ответить»**: + +- появляется **только если обращение закрыто** — при активном пользователь и так пишет оператору; +- при нажатии ставит `is_active = 1`, убирает себя с сообщения и показывает клавиатуру + поддержки (только «Главное меню»); +- callback `support:reply:` проверяет владельца — чужой нажать не может; +- обращение открывается **в том же топике**, история сохраняется. + +--- + +## Роли и порядок роутеров + +Роль бота определяется **контекстом апдейта** (тип и 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`. diff --git a/User-Guide.md b/User-Guide.md index 2177e95..f4164ab 100644 --- a/User-Guide.md +++ b/User-Guide.md @@ -75,6 +75,8 @@ Пока обращение открыто, внизу остаётся только кнопка «Главное меню». Можно отвлечься на другие режимы и вернуться: обращение продолжится в том же топике с сохранённой историей. +Если оператор ответил после того, как вы вышли, под его сообщением появится кнопка +**«↩️ Ответить»** — она вернёт вас в общение с поддержкой. «🏠 Главное меню» завершает обращение — бот снова работает как обычно. Подробнее — в разделе [Поддержка](Support.md).