admin c85f0a34fe feat: /help разбита на разделы с навигацией инлайн-кнопками (aiogram 3.31.0)
- 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
2026-10-06 23:15:47 +02:00
2026-10-04 19:00:16 +02:00
2026-10-05 01:35:41 +02:00

Social Post Writer Bot — development

Telegram-бот для генерации постов для соцсетей. Готовит текст по явной просьбе пользователя, адаптируя под его Tone of Voice, и работает по модели подписки (триал 3 дня, затем 100 звёзд в месяц).


Содержание


Как работает бот (UX)

Онбординг (/start)

Первое сообщение пользователя запускает онбординг — два сообщения с паузой 0.8 сек между ними:

  1. Welcome (Rich Message) — WELCOME_RICH_HTML: приветствие, примеры запросов, упоминание скриншотов для Tone of Voice.
  2. Вызов help_command — пауза 0.8 сек, затем меню справки с инлайн-кнопками (тот же хендлер, что и по команде /help).

Одновременно /start создаёт запись триала на 3 дня.

Логика: welcome содержит только golden path, всё остальное (тон, голоса, правки, /clear, /idea) — в /help. Онбординг не дублирует текст справки, а вызывает help_command, поэтому правки HELP_RICH_HTML автоматически меняют и онбординг.

Основные сценарии

Что хочет пользователь Что делает Что видит
Написать пост Пишет «напиши пост про запуск продукта» Индикатор «печатает», затем 3 отдельных сообщения — по одному варианту в каждом
Изменить пост Отвечает реплаем на вариант бота: «сделай короче» Снова 3 новых варианта, с учётом исходного текста
Придумать идею «предложи тему» или /idea Одна идея одним сообщением (заголовок, угол, зачем)
Настроить стиль Присылает скриншот(ы) своих постов Бот распознаёт тексты, накапливает их и обновляет ToV; показывает итоговый профиль стиля
Голосом Отправляет голосовое STT расшифровывает → как обычный текстовый запрос
Узнать статус /balance Триал активен (сколько дней) / подписка активна / период истёк
Оплатить /subscribe Просит 100 звёзд, подтверждает активацию на 30 дней
Начать заново /clear Полный сброс данных + обнуление подписки

Индикаторы и обратная связь

  • Пока идёт обработка (классификация, расшифровка, чтение скриншота, генерация) — бот показывает «печатает» (typing_status), обновляя каждые 4 секунды.
  • При ошибке пользователю уходит понятное сообщение: «❌ Не получилось: {этап}. Причина: …» — секреты (ключи, токены) и URL вырезаются.
  • Служебные тексты — всегда на русском. Язык самого поста определяется языком запроса.

Блокировка по подписке

Если подписка не активна (триал/подписка истекли или is_active = 0):

  • Генерация поста и идеи блокируется, бот пишет, что период истёк, и предлагает /subscribe.
  • /help, /start, /balance, /subscribe доступны всегда — пользователь не остаётся в тупике.

Архитектура кода

bot.py               — роутинг, команды, обработка сообщений (aiogram 3.x)
apinex_client.py     — клиент APInex (LLM, STT, vision), промпты, логирование вызовов
user_store.py        — SQLite: пользователи, ToV, посты, идеи, API-логи, подписки
help_message.py      — Rich Message: `WELCOME_RICH_HTML` (онбординг) + `HELP_SECTIONS`/`help_view()` (справка с кнопками), plain fallback
VERSION              — версия релиза (сейчас 0.1.0+23)

Основные потоки

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

handle_text → _classify_and_respond
  ├─ /help? → help_command (без проверки подписки)
  ├─ _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 (пока всё идёт)

Голосовое:

transcribe_voice → _process_voice → transcribe_audio (APInex STT) → _classify_and_respond

Скриншоты (ToV):

update_tone_from_image → _handle_image_message
  ├─ _collect_media_group (собирает галерею, ждёт 1 сек)
  ├─ extract_posts_from_images (APInex vision, inline base64)
  ├─ add_post_samples (накапливает по пользователю)
  └─ create_tone_profile → save_tone_of_voice → показывает стиль

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

Функция Что делает
start(message) Онбординг (welcome Rich Message + вызов help_command) + создание триала
handle_command_text Роутинг команд: /help, /start, /clear, /idea, /balance, /subscribe
_classify_and_respond Ядро: проверка подписки → классификация → генерация
_check_subscription Проверяет активную подписку; если нет — показывает статус и блокирует
_show_subscription_info Формирует сообщение о статусе (триал / подписка / истёк)
balance_command /balance — статус подписки
subscribe_command /subscribe — активация подписки (оплата 100 звёзд)
clear_command /clear — сброс данных + обнуление подписки
help_command /help — меню справки (Rich Message с кнопками) + fallback на plain text
help_navigation Callback help:<section> → edit_message_text(rich_message=...) — переключение разделов
edit_rich_message Редактирование текущего rich-сообщения на новый раздел
typing_status Индикатор «печатает» во время обработки
report_error Понятная ошибка пользователю, секреты вырезаны

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

Функция Что делает
init_db Создаёт таблицы: users, user_post_samples, generated_posts, generated_ideas, api_call_logs, subscriptions
ensure_subscription Создаёт запись подписки с триалом, если нет (INSERT OR IGNORE)
activate_subscription Активирует/продлевает подписку на 30 дней от момента оплаты
deactivate_subscription is_active = 0
is_subscribed Проверяет: запись есть, is_active = 1, срок не истёк
subscription_row Возвращает строку подписки
_days_until Сколько дней осталось (триал — trial_started_at + 3 дня; подписка — last_paid_at); -1 если истёкло/отключено

Подписка и монетизация

Модель: триал 3 дня → подписка 30 дней за 100 звёзд (Telegram Stars).

Константы (user_store.py):

TRIAL_DAYS = 3
SUBSCRIPTION_MONTH_DAYS = 30
SUBSCRIPTION_PRICE_STARS = 100

Таблица subscriptions:

  • telegram_user_id (PK)
  • trial_started_at — момент создания (SQLite CURRENT_TIMESTAMP, UTC-нейтральный)
  • last_paid_at — момент/конец оплаченного периода; NULL = ещё триал
  • is_active — 1/0
  • monthly_days — длительность периода (30)
  • updated_at

Жизненный цикл:

  1. /start → ensure_subscription → триал 3 дня, is_active = 1.
  2. Генерация доступна, пока _days_until > 0.
  3. Триал кончился → _check_subscription блокирует, предлагает /subscribe.
  4. /subscribe → activate_subscription(months=1) → +30 дней от момента оплаты, is_active = 1.
  5. /clear → deactivate_subscription → is_active = 0; нужен /subscribe.

Важно про оплату: приём звёзд — имитация. Бот пишет «пришли 100 звёзд» и сразу активирует подписку, не проводя реальный платёж. Для настоящих платежей нужен отдельный invoicing-флоу (Telegram Payments / sendInvoice для Stars), что в этой версии не реализовано.

Часовые пояса: SQLite CURRENT_TIMESTAMP возвращает UTC без tz. Код аккуратно приводит все значения к naive UTC перед сравнением, чтобы не было сдвигов.


Данные и хранилище

  • SQLite: /app/data/social_writer.sqlite3 (persistent Coolify volume).
  • Per-user данные: ToV, образцы постов со скриншотов, история сгенерированных постов, история идей. История диалогов и исходные изображения не сохраняются.
  • API-логи: JSON запросов/ответов APInex + ошибки разбора, 30 дней. Системные промпты не дублируются — только имя и SHA-256. base64 изображений и бинарное аудио не пишутся. Содержимое логов — приватные данные пользователя.
  • /clear удаляет пользовательские данные (ToV, скриншоты, посты, идеи, настройки) и обнуляет подписку; API-логи сохраняются.

Команды

Команда Что делает Доступна без подписки
/start Онбординг (welcome Rich Message + справка) + создание триала ✅
/help Меню справки: разделы с инлайн-кнопками + plain fallback ✅
/balance Статус подписки / триала ✅
/subscribe Активация подписки (100 звёзд, 30 дней) ✅
/idea Одна идея для поста ❌ (нужна подписка)
/clear Полный сброс данных + обнуление подписки ✅

Русские фразы («придумай идею», «о чём написать?») работают как /idea.


Деплой и конфигурация

  • Gitea: admin/social-post-writer-bot, ветка dev. Push → автодеплой.
  • Coolify: проект Social Post Writer Bot, окружение development, приложение social-post-writer-bot-development (d32u5zcq2g4odmdpq9hw91yn) на spb-easy-dale.
  • Деплой: .hermes/scripts/deploy.sh (POST /api/v1/deploy?uuid=...).
  • VERSION: файл VERSION (0.1.0+19). При DEPLOY_ENV=development и изменении версии бот шлёт уведомление «🚀 Бот обновлён / Версия: X» в DEV_CHAT_ID.
  • Переменные Coolify: BOT_TOKEN, APINEX_API_KEY, APINEX_STT_MODEL, APINEX_INTENT_CLASSIFIER_MODEL, APINEX_POST_WRITER_MODEL, APINEX_IMAGE_TONE_MODEL, BOT_DB_PATH, DEPLOY_ENV, DEV_CHAT_ID.
  • Docker: python:3.12-slim, копирует bot.py, apinex_client.py, user_store.py, help_message.py, VERSION; CMD ["python", "bot.py"].

Тесты

python -m unittest discover -s tests -v

Покрытие: STT, LLM, vision, ToV, reply-поля, изоляция пользователей, API-логи, справка с кнопками (меню, разделы, callback-навигация, plain fallback), онбординг (welcome → вызов help_command), подписки (6 тестов: таблица, триал после /start, оплата, баланс, блокировка, обнуление через /clear).

S
Description
Telegram-бот для генерации постов для соцсетей: три варианта на запрос, Tone of Voice из скриншотов, голосовой ввод (STT), идеи и подписка (триал 3 дня, затем 100 звёзд/месяц). Python 3.12 + aiogram 3.x + APInex LLM/STT/vision + SQLite.
https://t.me/post_master_bot Readme
416 KiB
0 Stars 1 Watchers 0 Forks
Languages
Python 99.5%
Shell 0.4%