Поддержка
Поддержка работает внутри бота: пользователь пишет в личку, сообщения уходят в персональный топик группы операторов, ответ оператора приходит обратно в личку.
| Компонент | Значение |
|---|---|
| Группа операторов | супергруппа «Мастер постинга: Поддержка», chat.id = -1008000863273 (форум с темами) |
| Модуль | support.py |
| Таблица БД | support_topics в /app/data/social_writer.sqlite3 |
| Переменная | SUPPORT_GROUP_ID — без неё функция выключена |
Как это выглядит для пользователя
- Режим «📖 Помощь» → кнопка «🆘 Поддержка» (или команда
/support). - Бот пишет: что случилось, опиши проблему прямо здесь, оператор ответит.
- В ответ приходит приветствие оператора — случайная фраза из пула, например «Здравствуйте! На связи поддержка. Расскажите, что случилось — разберёмся».
- Пользователь пишет текстом, присылает фото, документ или голосовое — всё уходит оператору. В топике сообщение подписывается: «<Имя>:\n<текст>» (нет имени — «:\n<текст>»).
- Оператор отвечает в топике — ответ приходит пользователю в личку с подписью «Оператор поддержки:\n<текст>». Если обращение закрыто, добавляется кнопка «↩️ Ответить» — она снова открывает обращение в том же топике.
- Уход из контекста поддержки завершает обращение: вход в любой режим,
«🏠 Главное меню» или
/start.
Отдельного режима (FSM) нет. Обращение — расширение режима «Помощь», поэтому пользователь может уйти в «Генерацию», потом вернуться через «Помощь → Поддержка» и продолжить разговор: история в топике сохраняется.
Правило is_active: ушёл из контекста поддержки — обращение неактивно. Флаг снимается
при входе в любой режим (/training, /generate, /subscription, /help), по
«🏠 Главное меню» и /start — всё через _close_support_case(). Иначе пользователь
переключился в «Обучение», а сообщение уезжает оператору.
Схема
личка пользователя ──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. - Топик создаётся сразу при первом нажатии «🆘 Поддержка», а не при первом сообщении. Карточка уходит один раз — при создании топика. Повторные обращения пишут в тот же топик и ничего не дублируют; если топик удалили вручную, при следующем сообщении он создаётся заново и сводка уйдёт снова.
Приветствие оператора
Бот не говорит пользователю, что «передаст сообщение оператору» — это сразу разрушает
ощущение живого диалога. Вместо этого бот показывает одну случайную фразу из пула
activity_phrases.SUPPORT_GREETINGS:
- «Здравствуйте! На связи поддержка. Расскажите, что случилось — разберёмся.»
- «Добрый день! Слушаю вас: опишите проблему, и постараемся помочь.»
- «Приветствую! Чем могу помочь? Опишите ситуацию подробнее.»
- «Добрый день! На связи. Напишите, что не работает, — посмотрим.»
и ещё шесть вариантов в activity_phrases.py.
Требования к формулировкам:
- без «я», «бот», «оператор» — фраза должна звучать от человека и не спорить с голосом бота;
- без лишних обещаний — только приветствие и приглашение описать проблему.
Переопределение без деплоя: переменная SUPPORT_GREETINGS в Coolify, разделитель |:
Здравствуйте! Чем помочь?|Добрый день! Опишите проблему.
Пустые элементы отбрасываются; если пул пуст — используется встроенный.
Подписи
| Направление | Формат |
|---|---|
| пользователь → топик | «<Имя>:\n<текст>», нет имени — «:\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 побеждает первый подходящий роутер.
Групповые сценарии регистрируются до основного:
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():
| Поле | Источник |
|---|---|
| ID пользователя | users.telegram_user_id |
| Пользуется ботом | полных дней с users.created_at; если записи нет — с subscriptions.trial_started_at |
| Продлений подписки | COUNT(*) по payments пользователя |
| Осталось дней | user_store._days_until по строке подписки; при истёкшем или отсутствующем периоде — «нет активного периода» |
| Бот обучен | флаг users.is_trained — «да» / «нет» |
Сводка считается в отдельном потоке и не роняет обращение: при любой ошибке карточка уходит
без блока полей, топик всё равно создаётся. Таблица payments может отсутствовать — тогда
число продлений считается как 0.
- Все типы контента: текст, фото, документы, голосовые — пересылаются как есть (
copy_message), без пометки «forwarded from». - Завершение снимает флаг
is_active; топик не удаляется и не закрывается — оператор сохраняет историю и может вернуться к ней позже.
Заметки оператора
Сообщение, начинающееся с // (префикс задаётся SUPPORT_NOTE_PREFIX), не отправляется
пользователю — оно остаётся внутренней заметкой в топике.
// клиент просил вернуть деньги, проверить платёж
Ответ пиши без префикса — он уйдёт пользователю.
Что нужно от группы
- Супергруппа с включёнными темами (
is_forum = true). - Бот — администратор с правом «Управление темами» (
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.
Переменные окружения (Coolify)
| Переменная | Обязательна | Назначение |
|---|---|---|
SUPPORT_GROUP_ID |
да | id группы операторов. Без неё поддержка выключена (fail-closed) |
SUPPORT_NOTE_PREFIX |
нет | префикс внутренней заметки, default // |
SUPPORT_GREETINGS |
нет | пул приветствий оператора, разделитель ` |
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 |