4
Support
Hermes Agent edited this page 2026-10-11 21:10:36 +02:00
This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Поддержка

Поддержка работает внутри бота: пользователь пишет в личку, сообщения уходят в персональный топик группы операторов, ответ оператора приходит обратно в личку.

Компонент Значение
Группа операторов супергруппа «Мастер постинга: Поддержка», chat.id = -1008000863273 (форум с темами)
Модуль support.py
Таблица БД support_topics в /app/data/social_writer.sqlite3
Переменная SUPPORT_GROUP_ID — без неё функция выключена

Как это выглядит для пользователя

  1. Режим «📖 Помощь» → кнопка «🆘 Поддержка» (или команда /support).
  2. Бот пишет: что случилось, опиши проблему прямо здесь, оператор ответит.
  3. В ответ приходит приветствие оператора — случайная фраза из пула, например «Здравствуйте! На связи поддержка. Расскажите, что случилось — разберёмся».
  4. Пользователь пишет текстом, присылает фото, документ или голосовое — всё уходит оператору. В топике сообщение подписывается: «<Имя>:\n<текст>» (нет имени — «:\n<текст>»).
  5. Оператор отвечает в топике — ответ приходит пользователю в личку с подписью «Оператор поддержки:\n<текст>». Если обращение закрыто, добавляется кнопка «↩️ Ответить» — она снова открывает обращение в том же топике.
  6. Уход из контекста поддержки завершает обращение: вход в любой режим, «🏠 Главное меню» или /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