- help_message.py: добавлен WELCOME_RICH_HTML (приветствие, примеры, скриншоты) - bot.py: send_rich_help -> send_rich_html(html, stage) + send_rich_message с plain-fallback - bot.py: start теперь шлёт welcome Rich Message, пауза 0.8с, затем help_command - help_command вызывает общий send_rich_message — правки HELP_RICH_HTML меняют и онбординг - тесты: OnboardingTests (3), README/IDEA/VERSION 0.1.0+22
Social Post Writer Bot — development
Telegram-бот для генерации постов для соцсетей. Готовит текст по явной просьбе пользователя, адаптируя под его Tone of Voice, и работает по модели подписки (триал 3 дня, затем 100 звёзд в месяц).
Содержание
- Как работает бот (UX)
- Архитектура кода
- Подписка и монетизация
- Данные и хранилище
- Команды
- Деплой и конфигурация
- Тесты
Как работает бот (UX)
Онбординг (/start)
Первое сообщение пользователя запускает онбординг — два сообщения с паузой 0.8 сек между ними:
- Welcome (Rich Message) —
WELCOME_RICH_HTML: приветствие, примеры запросов, упоминание скриншотов для Tone of Voice. - Вызов
help_command— пауза 0.8 сек, затем тот же Rich Message справки, что и по команде/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_RICH_HTML` (справка), plain fallback
VERSION — версия релиза (сейчас 0.1.0+22)
Основные потоки
Текстовый запрос:
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 |
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— момент создания (SQLiteCURRENT_TIMESTAMP, UTC-нейтральный)last_paid_at— момент/конец оплаченного периода;NULL= ещё триалis_active— 1/0monthly_days— длительность периода (30)updated_at
Жизненный цикл:
/start→ensure_subscription→ триал 3 дня,is_active = 1.- Генерация доступна, пока
_days_until > 0. - Триал кончился →
_check_subscriptionблокирует, предлагает/subscribe. /subscribe→activate_subscription(months=1)→ +30 дней от момента оплаты,is_active = 1./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 |
Подробная справка (Rich Message + 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-логи, Rich help, онбординг (welcome → вызов help_command), подписки (6 тестов: таблица, триал после /start, оплата, баланс, блокровка, обнуление через /clear).