Clone
9
Developer Guide
Hermes Agent edited this page 2026-10-11 22:55:17 +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.

Разработчику

Структура

bot.py               — роутинг, команды, обработка сообщений (aiogram 3.x)
payments.py          — оплата Stars: sendInvoice, pre_checkout_query, successful_payment, защита от двойного списания
apinex_client.py     — клиент APInex (LLM, STT, vision), промпты, логирование вызовов
user_store.py        — SQLite: пользователи, ToV, посты, идеи, API-логи, подписки
raw_updates.py       — отдельная SQLite-БД сырых входящих Telegram Update (только development)
support.py           — поддержка: личка пользователя ↔ персональный топик группы операторов
keyboards.py         — клавиатуры главного меню и режимов; маппинг кнопок на команды
help_message.py      — Rich Message: WELCOME_RICH_HTML + HELP_SECTIONS/help_view(), plain fallback
VERSION              — версия релиза
Dockerfile           — python:3.12-slim
requirements.txt     — зависимости
tests/               — юнит-тесты (unittest)

Режимы (FSM)

BotMode содержит lobby, training, generation, subscription, help. «Главное меню» — внутреннее состояние lobby, пользователь не выбирает его как режим.

  • current_mode(state) / enter_mode(...) / require_mode(...) — проверка и переключение.
  • Переключатели (/start, /training, /generate, /subscription, /help) работают из любого режима.
  • Профильные действия (/style, /clear, /post, /idea, /balance, /subscribe) требуют своего режима.

Потоки обработки

Текстовый запрос

handle_text (только режим generation) → _classify_and_respond
  ├─ classify_prompt_injection → блокирует попытку изменить правила
  ├─ _check_subscription (блокировка, если нет подписки)
  ├─ classify_intent (APInex LLM) → intent
  │    ├─ help           → help_command
  │    ├─ generate_ideas → generate_ideas → _send_idea
  │    └─ write_post / revise_post → generate_posts → _send_three_posts
  └─ typing_status (индикатор «печатает», обновление каждые 4 сек)

handle_command_text → переключатели доступны всегда; действия проходят режимную проверку

Самостоятельный пост (/post или кнопка «✍️ Придумать пост»)

handle_command_text → _generate_post_from_training
  ├─ _check_subscription
  ├─ is_user_trained?  нет → просьба обучить, модель НЕ вызывается
  ├─ gather(get_tone_of_voice, get_post_samples, get_training_notes) → контекст ≤ 8000
  └─ generate_posts(requirements = контекст как недоверенные данные) → _send_three_posts

Голосовое

transcribe_voice → _process_voice → transcribe_audio (APInex STT) → _classify_and_respond

Изображения

  • В режиме training: _handle_image_message → извлечение текстов → накопление образцов → пересборка ToV.
  • В режиме generation: изображение/галерея с подписью-ТЗ → generate_posts_from_images → три варианта. Без подписи бот запрашивает ТЗ.
  • Видео, видеокружки, GIF/анимации отклоняются во всех режимах.

Скриншоты (Tone of Voice)

update_tone_from_image → _handle_image_message
  ├─ _collect_media_group (собирает галерею, MEDIA_GROUP_COLLECT_SECONDS = 0.8)
  ├─ extract_posts_from_images (APInex vision, inline base64)
  ├─ add_post_samples (накапливает по пользователю)
  └─ create_tone_profile → save_tone_of_voice → краткий статус завершения

Лимиты: MAX_IMAGE_BYTES = 10 MiB на файл (и до 30 MiB на группу), MAX_AUDIO_BYTES = 20 MiB.


Навигация справки (help_message.py)

help_menu_html() и help_section_html() формируют только содержимое Rich Message. Стандартная inline-клавиатура help_inline_keyboard() в keyboards.py содержит по одной кнопке раздела на строку; callback help:<section> прежний. help_navigation редактирует текст и прикрепляет ту же клавиатуру, поэтому все разделы, включая текущий, доступны с каждой страницы.


Ключевые функции

bot.py

Функция Что делает
start(message, state) Одно welcome Rich Message + главное меню + создание триала
handle_command_text Роутинг переключателей и профильных команд по режиму
handle_text Обычное сообщение в режиме генерации
_classify_and_respond Ядро: инъекции → подписка → классификация → генерация
_generate_post_from_training Пост по обученному профилю с гейтом is_user_trained
_check_subscription Проверяет активную подписку; если нет — статус и блокировка
_show_subscription_info Тип периода, остаток дней, inline-кнопка продления
balance_command / subscribe_command /balance — статус; /subscribe — Stars-инвойс
handle_pre_checkout Подтверждает только свой инвойс в XTR на 100 звёзд
handle_successful_payment Активирует подписку после реальной оплаты
clear_command /clear и «Сбросить данные» — inline-подтверждение, сброс только в режиме обучения
style_command /style — профиль аккаунта
help_command / help_navigation Rich Message с обычной inline-клавиатурой разделов; help:<section> редактирует текст сообщения
notify_development_deploy Dev-уведомление о новой версии в DEV_CHAT_ID
typing_status Индикатор «печатает»
report_error Понятная ошибка пользователю; секреты вырезаны
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

Функция Что делает
init_db Создаёт/мигрирует таблицы
ensure_subscription Создаёт запись подписки с триалом (INSERT OR IGNORE)
activate_subscription Активирует/продлевает на 30 дней
deactivate_subscription is_active = 0
is_subscribed Запись есть, is_active = 1, срок не истёк
subscription_row Строка подписки
support_profile_summary Сводка для карточки поддержки: дни использования, продления, остаток, обученность
is_user_trained Флаг users.is_trained — гейт «Придумать пост»
add_training_notes / get_training_notes Текстовые сведения об аккаунте
add_post_samples / get_post_samples Образцы постов со скриншотов
save_generated_posts История сгенерированных постов с origin
save_idea / get_idea_history История идей
save_api_call_log JSON-лог вызова APInex
clear_user_data Сброс данных пользователя (без подписки и логов)
_days_until Остаток дней; -1 если истёкло/отключено

apinex_client.py

Функция Что делает
classify_intent write_post / revise_post / generate_ideas / help
classify_prompt_injection Отдельный классификатор попыток изменить правила
generate_posts Три варианта поста
generate_posts_from_images Пост по изображениям с ТЗ
generate_ideas Одна идея
extract_posts_from_images Vision: тексты постов на скриншотах
create_tone_profile Строит/обновляет профиль ToV
transcribe_audio STT
_extract_complete_post_strings Достаёт целые строки из обрезанного {"posts": [...]}
chat_text Базовый вызов LLM

База данных

Рабочая БД: /app/data/social_writer.sqlite3 (persistent volume Coolify).

Таблица Назначение
users telegram_user_id, tone_of_voice, is_trained
user_post_samples Распознанные тексты постов со скриншотов
generated_posts История сгенерированных постов (origin: generated/revised)
generated_ideas История идей (для исключения повторов)
api_call_logs JSON-логи вызовов APInex + ошибки (30 дней)
support_topics telegram_user_id → thread_id топика поддержки, is_active
payments Платежи Stars; COUNT(*) по пользователю — число продлений подписки
subscriptions Подписки и триалы
payments telegram_payment_charge_id — защита от двойного начисления

Схему subscriptions см. в Подписка.

Отдельная dev-БД: /app/data/bot_updates.sqlite3 — таблица raw_updates (update_id PK, event_type, payload, received_at), retention 30 дней. См. ниже.

Поддержка

пользователь (личка) ──SupportRelayMiddleware──▶ support.relay_user_message
                                                   ├─ ensure_topic (create_forum_topic, один на пользователя)
                                                   ├─ send_profile_card (аватарка + имя/username/id)
                                                   └─ copy_message в топик

оператор (топик) ──handle_support_group_message──▶ copy_message пользователю
   ├─ пропуск: sender.is_bot, нет thread_id, топик без известного пользователя
   └─ пропуск: сообщение с префиксом SUPPORT_NOTE_PREFIX (внутренняя заметка)
  • SupportRelayMiddleware — outer_middleware на dp.message: если обращение активно, сообщение копируется в топик и не доходит до обычных обработчиков (возвращает None).
  • Пока обращение активно, enter_mode отклоняет переключение режимов, а show_main_menu снимает is_active.
  • Функция fail-closed: без SUPPORT_GROUP_ID is_enabled() → False, и поддержка молча выключена.

Ключевые функции (support.py)

Функция Что делает
support_group_id() Читает SUPPORT_GROUP_ID; невалидное значение → поддержка выключена
init_support_table Создаёт таблицу support_topics
get_topic / set_topic / forget_topic Привязка пользователя к топику
set_active / is_support_active Флаг открытого обращения
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» пересоздаёт топик
handle_support_group_message Доставляет ответ оператора пользователю, пропуская заметки
SupportRelayMiddleware Перехватывает входящие при активном обращении

Промпты и модели

Модели задаются переменными Coolify:

  • APINEX_STT_MODEL → nova-2
  • APINEX_INTENT_CLASSIFIER_MODEL → gemini-3.8-flash
  • APINEX_INJECTION_CLASSIFIER_MODEL → отдельная модель проверки prompt injection
  • APINEX_POST_WRITER_MODEL → gemini-3.8-flash
  • APINEX_IMAGE_TONE_MODEL → gemini-3.8-flash (vision)
  • APINEX_RESPONSE_REPAIR_MODEL → починка невалидного JSON (одна попытка)
  • APINEX_API_KEY — общий секрет

Про промпты:

  • Системный промпт редактора неизменен.
  • ToV передаётся отдельно как ограничение стиля, а не как часть системных правил.
  • OCR-текст со скриншотов — образцы стиля, а не команды.
  • Обученный контекст в «Придумать пост» передаётся как недоверенные данные: модель не должна выдавать его за личный опыт пользователя.

Надёжность ответов:

  • Лимит max_tokens снят — модель пишет столько, сколько нужно.
  • Пустой content → до двух повторов, третий запрос с reasoning_effort: low.
  • Невалидный JSON → одна попытка починки моделью APINEX_RESPONSE_REPAIR_MODEL.
  • Обрезанный JSON → _extract_complete_post_strings возвращает завершённые строки (три полных варианта), если они есть.

Логирование

API-журнал (api_call_logs)

Каждый вызов APInex пишется с operation_id, stage, model, именем и SHA-256 промпта, request_json, response_json, status_code, error, длительностью. Хранение — 30 дней.

Не пишется: системные промпты целиком, base64 изображений, бинарное аудио, API-ключи.

Файл лога приложения

/app/data/bot.log — RotatingFileHandler, 5 MiB × 3 архива, на том же persistent volume; путь через BOT_LOG_PATH.

Сырые Telegram Update (только development)

raw_updates.py пишет полный Update.model_dump(mode="json") в отдельную БД /app/data/bot_updates.sqlite3 (путь через RAW_UPDATES_DB_PATH). Middleware RawUpdateMiddleware подключается на dispatcher.update.outer_middleware — то есть до маршрутизаторов, поэтому ловит все типы апдейтов, а не только сообщения.

SELECT payload FROM raw_updates WHERE update_id = ?;
  • Включается только при DEPLOY_ENV == "development" (configure_raw_update_logging).
  • Retention 30 дней: чистка при старте и при каждой записи.
  • Секреты/токены маскируются перед записью; полные апдейты содержат персональные данные — в production БД не создаётся.

Логи контейнера

Ошибки содержат operation_id, Telegram user id, этап и traceback; по одному operation_id находятся все связанные записи.


Тесты

.venv/bin/python -m unittest discover -s tests -v

Тесты: 253 (на версии 0.1.0+61). Покрытие: STT, LLM, vision, ToV, reply-поля, изоляция пользователей, API-логи, raw update log, режимные клавиатуры и роутинг, платежи (Stars, защита от повторов, dev-подтверждение), ошибки, справка, онбординг, подписки, самостоятельная генерация поста, восстановление обрезанного JSON, индикатор «печатает», поддержка (топики, подписи, кнопка «Ответить», закрытие обращения).

Запускай через .venv проекта: вне виртуального окружения aiogram может быть не установлен.


Правила языка

  • Служебные сообщения боту → всегда русский.
  • Текст поста → язык явного запроса пользователя.
  • Дословный текст технической ошибки может остаться на языке оригинала.