Создание топика падало с «not enough rights to create a topic» (бот был
обычным участником группы, а не администратором), но пользователь видел
только «Поддержка временно недоступна. Попробуй позже» — по такому сообщению
невозможно понять, что чинить.
- ensure_topic возвращает (thread_id, причина) вместо одного thread_id.
- Текст Telegram логируется на ERROR: по типу исключения причина нечитаема.
- В DEPLOY_ENV=development причина добавляется в сообщение пользователю;
в production остаётся общая заглушка. HTML экранируется.
Проверено: 214 тестов OK (7 новых на причину и её выдачу).
Живая проверка на dev: после выдачи боту прав администратора с «Управление
темами» create_forum_topic создал топик (thread_id 18, пробный удалён).
VERSION: 0.1.0+52 -> 0.1.0+53
Обращение открывается кнопкой «🆘 Поддержка» или /support в режиме «Помощь».
Отдельного FSM-режима нет: пока обращение активно, внизу остаётся только
«Главное меню», а переключение режимов отклоняется.
- support.py: личка пользователя ↔ персональный топик группы SUPPORT_GROUP_ID.
Топик создаётся один раз на пользователя и переиспользуется, поэтому
повторные обращения продолжают ту же переписку. Пересылаются все типы
контента через copy_message; ответ оператора из топика уходит в личку;
сообщение с префиксом // остаётся внутренней заметкой. Функция выключена
без SUPPORT_GROUP_ID.
- Карточка профиля уходит первым сообщением топика: аватарка, имя, username,
id и сводка — сколько дней пользуется ботом, сколько продлений подписки,
сколько дней осталось, обучен ли бот (user_store.support_profile_summary).
- Бот не объясняет пользователю, что «передаёт оператору»: в ответ приходит
случайная фраза из пула SUPPORT_GREETINGS (activity_phrases.support_greeting).
Формулировки безличные, без «я/бот/оператор»; пул переопределяется через
SUPPORT_GREETINGS без деплоя.
Проверено: 207 тестов OK (30 новых в tests/test_support.py).
VERSION: 0.1.0+51 -> 0.1.0+52
Драфт с последней фразой висел в чате ещё несколько секунд после того, как
уже ушло сообщение «✅ Обучение завершено»: содержимое драфта не совпадало
с финальным сообщением, и клиент не мог их слить.
Финальный текст теперь сначала пушится в драфт тем же draft_id, и только
потом уходит реальным сообщением с тем же текстом — драфт заменяется без
задержки. finish() без текста гасит драфт пустым пушем.
Проверено: 198 тестов OK (2 новых на совпадение текста драфта и сообщения).
VERSION: 0.1.0+50 -> 0.1.0+51
Драфт исчезает только когда бот отправляет реальное сообщение. Ряд путей
завершения операции не закрывал статус, из-за чего последняя показанная
фраза висела в чате до конца TTL драфта и выглядела неактуальной.
- OperationStatus: флаг _closed, finish() принимает None (только снять
статус), finish_if_open() — идемпотентное закрытие, _finished_draft_id
сохраняется до обнуления draft_id
- finish_status(status, text) — хелпер, терпит None: закрывает статус на
любом пути выхода без повторения `if status:`
- закрыты пути без finish: _rebuild_account_profile при переданном статусе,
_process_voice -> _classify_and_respond, успешные выходы _handle_image_message
и transcribe_voice через else-ветку, ранние возвраты /help и проверки подписки
Проверено: 176 тестов OK (4 новых на закрытие статуса).
VERSION: 0.1.0+49 -> 0.1.0+50
Статус долгой операции больше не оставляет в чате отдельное сообщение:
в приватном чате он стримится живым драфтом (sendMessageDraft), пока
идет классификация, генерация, чтение скриншотов или распознавание речи.
- activity_phrases.py: пулы фраз по типу операции (generation, training,
images, voice, ideas), случайный интервал 3-8 сек (ACTIVITY_MIN_SECONDS /
ACTIVITY_MAX_SECONDS), фраза не повторяет предыдущую
- OperationStatus: драфт со стабильным draft_id, фоновая ротация фраз,
finish() отправляет финальный статус реальным сообщением, retarget()
меняет пул на лету (generate_ideas), предохранитель ACTIVITY_MAX_ROTATIONS
- в группах и топиках драфты недоступны: прежний answer + edit_text,
ротация фраз отключена; при ошибке драфта автоматический откат
- Dockerfile копирует activity_phrases.py и support.py, иначе контейнер
падает на импорте
Проверено: 172 теста OK,其中包括 14 новых в tests/test_activity_phrases.py.
Тесты нашли и закрыли баг: ротация запускалась и в группах, где каждый тик
был бы реальным редактированием сообщения.
VERSION: 0.1.0+48 -> 0.1.0+49
- keyboards.py: MAIN_MENU (✍️ Написать пост / 💡 Идея / 🎨 Мой стиль / 📖 Помощь / 💳 Подписка / 👤 Баланс)
и LOCKED_MENU — при блокировке первой кнопкой идёт «💳 Оплатить подписку»
- кнопки не обходят роутинг: resolve_button_text превращает их в slash-команду
(/help, /balance, /subscribe, /style) или готовый запрос («Напиши пост», «предложи идею…»)
- новая команда /style показывает сохранённый Tone of Voice и подсказывает, как его настроить
- set_my_commands публикует slash-команды при старте, ошибка не роняет бот
- fix: _send_idea вызывалась на bot.py, но была потеряна в коммите про подписку —
/idea падал с NameError; функция восстановлена
- 17 новых тестов клавиатуры; 92 теста OK; VERSION 0.1.0+26
- payments.py: sendInvoice (XTR, 100 звёзд) -> pre_checkout_query -> successful_payment
- /subscribe больше не активирует подписку сам — только отправляет счёт
- handle_pre_checkout подтверждает только свой payload в XTR на 100 звёзд
- таблица payments по telegram_payment_charge_id защищает от двойного начисления
- 16 новых тестов платежей, старый тест заглушки переписан; 75 тестов OK
- Dockerfile копирует payments.py, VERSION 0.1.0+25
- каждая кнопка в отдельном <tg-button-row align="left"> — во всю ширину сообщения
- из любого раздела доступны кнопки на все остальные (было: «предыдущий»/«следующий»)
- текущий раздел рендерится как <tg-button type="disabled">, чтобы не было активной кнопки на себя
- «⌂ В меню» остаётся отдельной полноширинной кнопкой
- 59 тестов, VERSION 0.1.0+24
- requirements.txt: aiogram 3.26.0 -> 3.31.0 (нативный send_rich_message + edit_message_text(rich_message=...))
- help_message.py: HELP_RICH_HTML -> HELP_SECTIONS (6 разделов) + help_menu_html/help_section_html/help_view/help_plain_view
- навигация на HTML-тегах <tg-button-row>/<tg-button type="callback_data"> (в меню 2 кнопки в ряд, в разделе «В меню» + «Предыдущий»/«Следующий»)
- bot.py: самописный raw-запрос sendRichMessage заменён на bot.send_rich_message; edit_rich_message + callback help_navigation
- онбординг по-прежнему вызывает help_command — кнопки появляются и там
- тест поймал реальный баг: в help_navigation пропущен await у edit_rich_message
- 58 тестов, VERSION 0.1.0+23