hermes 4076765618 fix: показывать причину сбоя поддержки в development
Создание топика падало с «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
2026-10-11 17:04:33 +02:00
2026-10-04 19:00:16 +02:00

Social Post Writer Bot — development

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


Содержание

Версия разработки: 0.1.0+51.


Синхронизация документации

README.md и IDEA.MD — синхронизируемые документы проекта. Если меняются сведения о функциях, текущем поведении, статусе, версии, решениях или процессе разработки в одном файле, внеси те же смысловые изменения во второй. Перед завершением задачи проверь оба файла и убедись, что общие сведения совпадают; различаться могут только детали, специфичные для самого документа.

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

Dev-подтверждение подписки

Только при точном DEPLOY_ENV=development после обычного сообщения о счёте отдельным сообщением показывается кнопка «🧪 Оплатил (dev)». Она включает доступ на 30 дней без реального платежа и доступна только пользователю, которому выставлен счёт. В production, при пустом или неизвестном значении DEPLOY_ENV dev-кнопка и callback не выдают доступ.

Reply-клавиатура

После /start отображается одно welcome-сообщение с нижним меню; справка автоматически не вызывается. В главном меню доступны только пользовательские режимы:

🎓 Обучение   ✍️ Генерация
💳 Подписка   📖 Помощь

«Главное меню» — внутренняя точка входа после /start и выхода из режима, а не отдельный режим. Внутри любого режима reply-клавиатура содержит только «🏠 Главное меню». В «Генерации» добавляется «✍️ Придумать пост», в «Обучении» — «🗑 Сбросить данные», в «Помощи» — «🆘 Поддержка». При входе показывается короткий onboarding. В «Подписке» сразу отображаются тип активного периода и оставшиеся дни; inline-кнопка «Продлить подписку» сразу открывает Stars-инвойс на оплату.

Команды перехода: /start, /training, /generate, /subscription, /help; /start возвращает в главное меню. Команды /style и /clear доступны только в обучении; /post и /idea — в генерации; /balance и /subscribe — в подписке. Все команды публикуются через set_my_commands при старте.

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

/start создаёт запись триала, очищает старое состояние FSM и возвращает пользователя в главное меню:

  1. Отправляет ровно одно WELCOME_RICH_HTML с примерами и описанием режимов.
  2. Показывает клавиатуру главного меню; справка открывается отдельно кнопкой или командой /help.

Поддержка (support.py)

Отдельного FSM-режима нет: обращение — расширение режима «Помощь». Пользователь нажимает «🆘 Поддержка» (или /support), и до выхода в главное меню все его сообщения уходят оператору.

Схема:

личка пользователя  ──copy_message──▶  топик в SUPPORT_GROUP_ID
        ▲                                      │
        └────────────copy_message─────────────┘
                  (любое сообщение оператора)
  • Топик создаётся один на пользователя и переиспользуется: support_topics хранит telegram_user_id → thread_id и флаг активности. Название — «Имя · @username» (без username — «Имя · id»).
  • Telegram не даёт поставить на топик произвольную картинку, только custom emoji (SUPPORT_TOPIC_EMOJI_ID). Аватарка профиля уходит первым сообщением-карточкой в топик (get_user_profile_photos → send_photo с подписью «Имя / @username / ID»).
  • Топик создаётся сразу при первом нажатии «🆘 Поддержка», а не при первом сообщении. Карточка со сводкой профиля уходит один раз — при создании топика; повторные обращения пишут в тот же топик и ничего не дублируют. Если топик удалили вручную, при следующем сообщении он создаётся заново — и сводка уйдёт снова.
  • Сводка профиля в карточке (user_store.support_profile_summary): ID пользователя; сколько дней пользуется ботом; сколько раз продлевалась подписка; сколько дней осталось; обучен бот или нет.
  • Первое сообщение — приветствие оператора. Бот не объясняет, что «передаёт» сообщение: это разрушает ощущение живого диалога. Пользователь видит случайную фразу из пула SUPPORT_GREETINGS (activity_phrases.support_greeting()), например «Здравствуйте! На связи поддержка. Расскажите, что случилось — разберёмся.» Формулировки безличные и без «я/бот/оператор», чтобы подходить любому оператору. Пул можно переопределить без деплоя: SUPPORT_GREETINGS в Coolify, разделитель |.
  • Ответ оператора — любое сообщение в топике от живого участника. Сообщение с префиксом // (SUPPORT_NOTE_PREFIX) остаётся заметкой внутри топика и пользователю не уходит.
  • Завершение — кнопка «🏠 Главное меню»: обращение деактивируется, топик не удаляется. Повторное «Поддержка» продолжает переписку в том же топике с сохранённой историей.
  • Пока обращение активно, клавиатура содержит только «Главное меню», а переключение в другие режимы отклоняется — чтобы режимы и генерация не смешивались с обращением.

Переменные Coolify: SUPPORT_GROUP_ID (обязательно, без неё функция выключена), SUPPORT_NOTE_PREFIX (default //), SUPPORT_TOPIC_EMOJI_ID (optional custom emoji id), SUPPORT_GREETINGS (optional; пул приветствий оператора, разделитель |).

Группа должна быть супергруппой с включёнными темами (is_forum), бот — её администратором с правом «Управление темами» (create_forum_topic). Обычного участника недостаточно: Telegram вернёт «not enough rights to create a topic», и топик не создастся. В DEPLOY_ENV=development бот покажет эту причину прямо в сообщении об ошибке, в production — только общую заглушку.

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

Что хочет пользователь Что делает Что видит
Написать пост В режиме «Генерация» пишет «напиши пост про запуск продукта» Индикатор «печатает», затем 3 отдельных сообщения
Изменить пост В режиме «Генерация» отвечает реплаем на вариант бота Три новых варианта с учётом исходного текста
Придумать идею В режиме «Генерация» пишет «предложи тему» или /idea Одна идея одним сообщением
Поблагодарить или написать не по теме В режиме «Генерация» отправляет обычное сообщение Одна фраза из заготовленного набора; генератор постов/идей не вызывается
Пост по изображению В режиме «Генерация» отправляет изображение/галерею с подписью-ТЗ Vision изучает изображения и бот возвращает 3 варианта поста
Изображение без ТЗ В режиме «Генерация» отправляет изображение/галерею без подписи Бот спрашивает, о чём написать, затем принимает обычный текст или reply
Настроить стиль В режиме «Обучение» присылает скриншоты, текст, голос или аудио Бот накапливает сведения, обновляет профиль аккаунта и показывает только статус завершения
Пост по обученным данным В режиме «Генерация» нажимает «✍️ Придумать пост» или вводит /post Бот сам выбирает тему по накопленному профилю и возвращает 3 варианта; без обучения просит сначала обучить
Голосом В режиме «Генерация» отправляет голосовое STT расшифровывает → обычный текстовый запрос
Узнать статус / продлить Входит в режим «Подписка» и нажимает «Продлить подписку» Текущий тип периода, остаток дней и Stars-инвойс на 100 звёзд
Очистить данные В режиме «Обучение» нажимает «Сбросить данные» или вводит /clear, затем подтверждает Очищает стиль и историю, не меняя подписку или триал

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

  • Пока идёт обработка (классификация, расшифровка, чтение скриншота, генерация) — бот показывает «печатает» (typing_status, обновление каждые 4 секунды).
  • В приватном чате статус операции стримится живым драфтом (sendMessageDraft): он не тратит лимит отправки в чат, не оставляет после себя сообщения и анимируется при смене текста. Пока операция длится, драфт каждые 3–8 секунд (случайно) получает случайную фразу из пула, привязанного к типу операции, — видно, что работа не встала. Финальный результат уходит обычным сообщением.
  • В группах и топиках драфты недоступны: там работает прежний механизм — отдельное сообщение-статус, которое редактируется по этапам; перед результатом оно меняется на ✅, при ошибке — на ❌. Ротация фраз в группах отключена.
  • Пулы фраз живут в activity_phrases.py (generation, training, images, voice, ideas), интервал задаётся переменными ACTIVITY_MIN_SECONDS / ACTIVITY_MAX_SECONDS.
  • Драфт исчезает только когда бот отправляет реальное сообщение, поэтому каждый путь завершения операции обязан закрывать статус (status.finish(...) либо finish_status(status, ...), который терпит None). Иначе последняя фраза висит в чате до конца TTL драфта. finish_if_open() — страховка от повторного закрытия.
  • В режиме «Генерация» запрос сначала проверяет отдельный LLM-классификатор prompt injection, затем классификатор намерений. При обнаружении попытки изменить правила генератор не вызывается; ошибка проверки блокирует дальнейшую обработку. Модель проверки настраивается отдельно через APINEX_INJECTION_CLASSIFIER_MODEL.
  • Благодарность и сообщения не по теме получают случайную заготовленную фразу и не запускают генерацию/идеи. Режим «Обучение» этим изменением не затронут.
  • В режиме «Генерация» изображения и галереи используются для подготовки поста; подпись считается ТЗ. Без подписи бот сначала запрашивает ТЗ. Видео, видеокружки и GIF/анимации отклоняются во всех режимах.
  • При ошибке пользователь получает общий безопасный ответ. Только в development отдельным сообщением дополнительно отправляется диагностика; production не раскрывает текст исключения.
  • Служебные тексты — всегда на русском. Язык самого поста определяется языком запроса.

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

Если подписка не активна (триал/подписка истекли или is_active = 0), генерация блокируется. Переключатели режимов доступны всегда; для оплаты пользователь переходит в режим «Подписка».

Самостоятельный пост по обученным данным

Кнопка «✍️ Придумать пост» (режим «Генерация») и команда /post идут не через классификатор намерений, а в _generate_post_from_training: сначала проверяется подписка, затем флаг обучения is_user_trained (users.is_trained). Если профиль не обучен, бот просит зайти в режим «Обучение» и прислать сведения об аккаунте или примеры постов, не вызывая модель. Иначе собранный контекст (профиль аккаунта, образцы постов, обучающие заметки, до 8000 символов) передаётся как недоверенные данные, и модель сама выбирает одну тему. Обученный флаг выставляется при сохранении Tone of Voice или обучающих заметок и сбрасывается /clear.


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

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              — версия релиза

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

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

handle_text (только в режиме генерации) → _classify_and_respond
  ├─ _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 (пока всё идёт)

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

Скриншоты (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, state) Одно welcome-сообщение, главное меню и создание триала
handle_command_text Роутинг глобальных переключателей и профильных команд по режиму
_classify_and_respond Ядро: проверка подписки → классификация → генерация
_check_subscription Проверяет активную подписку; если нет — показывает статус и блокирует
_generate_post_from_training /post и кнопка «✍️ Придумать пост»: пост по обученному профилю, с проверкой is_user_trained
_show_subscription_info Показывает тип периода, остаток дней и inline-кнопку продления в режиме подписки
balance_command /balance — статус подписки
subscribe_command /subscribe — отправляет Stars-инвойс на 30 дней (подписка активируется только после оплаты)
handle_pre_checkout Подтверждает только свой инвойс в XTR на 100 звёзд, всё остальное отклоняет
handle_successful_payment Активирует подписку после реальной оплаты; telegram_payment_charge_id пишется в payments для защиты от повторного начисления
clear_command /clear и кнопка «Сбросить данные» показывают inline-подтверждение; сброс только по confirm в режиме обучения
help_command /help — меню справки (Rich Message с кнопками) + fallback на plain text
help_navigation Callback help:<section> → edit_message_text(rich_message=...) — переключение разделов
edit_rich_message Редактирование текущего rich-сообщения на новый раздел
typing_status Индикатор «печатает» во время обработки
OperationStatus Статус операции: в приватном чате — живой драфт с ротацией фраз, иначе — сообщение с edit_text
_draft_supported Драфты доступны только в приватных чатах

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

Функция Что делает
next_phrase(kind, exclude) Случайная фраза из пула операции, не повторяет только что показанную
random_interval() Случайная пауза между сменами фраз, ACTIVITY_MIN_SECONDS..ACTIVITY_MAX_SECONDS
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 Возвращает строку подписки
is_user_trained Флаг users.is_trained: обучен ли профиль (гейт для «Придумать пост»)
_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 → sendInvoice (XTR, 100 звёзд) → pre_checkout_query (проверка суммы/валюты/payload) → successful_payment → activate_subscription(months=1) → +30 дней.
  5. /clear очищает данные обучения и историю; подписка и триал не изменяются.

Оплата реальная: payments.py проводит настоящий платёж Telegram Stars. Подписка активируется только в successful_payment — сам /subscribe её не продлевает. telegram_payment_charge_id каждого платежа пишется в таблицу payments, поэтому один платёж не может активировать подписку дважды; по этому же id делается возврат через refundStarPayment. Платится валюта XTR, provider_token пустой (цифровой товар).

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


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

  • SQLite: /app/data/social_writer.sqlite3 (persistent Coolify volume).
  • Постоянное хранилище dev: volume d32u5zcq2g4odmdpq9hw91yn-social-post-writer-dev-data смонтирован в /app/data контейнера на сервере Coolify spb-easy-dale (31.129.103.242). Сервер приложения и сервер управления Coolify могут быть разными: сначала смотри назначенный приложению Server в записи Coolify, затем проверяй контейнер именно на нём. Не ищи контейнер только на хосте Coolify.
  • Где смотреть ошибки: подключись к spb-easy-dale, найди контейнер по имени d32u5zcq2g4odmdpq9hw91yn-* командой docker ps, затем смотри его stdout/stderr через docker logs --since 24h <container>. После деплоя ротационный файл приложения будет доступен как /app/data/bot.log (до 5 MiB и 3 архива); он сохраняется в указанном persistent volume при пересоздании контейнера. До деплоя изменения логирования ещё не действуют.
  • API-журнал ошибки: открой /app/data/social_writer.sqlite3, таблицу api_call_logs; записи связываются по operation_id и времени. Файл/БД находятся в volume на сервере приложения, не в локальном checkout и не на сервере Coolify, если это другой хост.
  • Сырые Telegram Update (только dev): отдельная БД /app/data/bot_updates.sqlite3, таблица raw_updates; хранится JSON входящего Update, update_id, тип события и время получения. Поиск по update_id: SELECT payload FROM raw_updates WHERE update_id = ?. Записи старше 30 дней удаляются при старте dev-бота и при сохранении новых апдейтов. Чувствительные значения маскируются; Update могут содержать тексты и персональные данные пользователей. В production middleware не подключается и БД не создаётся.
  • Обращения в поддержку: таблица support_topics в той же рабочей БД — telegram_user_id (PK), thread_id топика в группе операторов, is_active, время создания и обновления. Один пользователь — один топик; при завершении запись не удаляется, только снимается is_active, поэтому повторное обращение продолжается с сохранённой историей. Если топик удалили вручную в Telegram, при следующем сообщении он создаётся заново.
  • Per-user данные: профиль аккаунта/ToV, текстовые обучающие заметки, образцы постов со скриншотов, история сгенерированных постов и идеи. В «Обучении» текст/голос/скриншоты накапливают и обновляют профиль, а не запускают генерацию. История диалогов и исходные изображения не сохраняются.
  • Пустые ответы APInex: при пустом content бот делает до двух повторов; третий запрос добавляет reasoning_effort: low. Если ответ всё ещё пустой, операция завершается ошибкой и каждая попытка попадает в API-журнал. Ошибки HTTP автоматически не повторяются. APInex документирует low, но не режим отключения reasoning; поддержка параметра зависит от модели.
  • Починка формата JSON: при синтаксически невалидном JSON выполняется не более одной отдельной попытки модели APINEX_RESPONSE_REPAIR_MODEL; затем исходные проверки структуры/схемы запускаются заново. Ошибки API, пустые ответы и JSON с неверной схемой автоматически не исправляются. В Coolify development задана APINEX_RESPONSE_REPAIR_MODEL.
  • Обрезанный JSON: если модель вернула валидную схему, но текст поста обрывается на середине (незакрытая строка/массив), бот доотправки не требует: _extract_complete_post_strings достаёт из ответа завершённые строки и возвращает три полных варианта. Если целых строк меньше трёх или они не найдены — операция завершается ошибкой. Лимит max_tokens у вызовов LLM снят: модель пишет столько, сколько нужно, без искусственного обрезания.
  • API-логи: JSON запросов/ответов APInex, тело ответа провайдера при ошибке и ошибки разбора хранятся в SQLite 30 дней. Записи связаны operation_id и содержат этап, модель, HTTP-статус и длительность. Системные промпты не дублируются — только имя и SHA-256; секреты и base64 изображений маскируются, бинарное аудио не пишется. Пользовательские тексты в запросах/ответах — приватные данные.
  • Логи контейнера: ошибки операций содержат operation_id, Telegram user id, этап и полный traceback; ошибки APInex дополнительно показывают модель, статус, длительность и диагностическую структуру ответа без вывода текста пользовательского запроса. Ищи все связанные записи по одному operation_id.
  • /clear (только режим «Обучение») удаляет пользовательские данные (ToV, скриншоты, посты, идеи, настройки), не меняя подписку и триал; API-логи сохраняются.

Команды

Команда Что делает Режим
/start Возврат в главное меню Любой
/training, /generate, /subscription, /help Переключение режима Любой
/style, /clear Профиль аккаунта; сброс накопленного обучения Обучение
/post, /idea Самостоятельный пост по обученному профилю; идея Генерация; для генерации нужна подписка
/balance, /subscribe Статус или оплата Stars Подписка
/support Написать в поддержку Любой

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


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

  • Gitea: admin/social-post-writer-bot, ветка dev. Push → автодеплой.
  • Coolify: проект Social Post Writer Bot, окружение development, приложение social-post-writer-bot-development (d32u5zcq2g4odmdpq9hw91yn) на spb-easy-dale.
  • Деплой: bash /root/.hermes/scripts/gitea_deploy.sh dev (POST /api/v1/deploy?uuid=...&force=false).
  • Окружение: DEPLOY_ENV включает dev-функции только при точном значении development; production, пустое и неизвестное значение отключают их. BOT_ENV отдельно выбирает Telegram Bot API и не используется для включения dev-поведения.
  • VERSION: файл VERSION (0.1.0+51). При DEPLOY_ENV=development и изменении версии бот шлёт уведомление о версии в DEV_CHAT_ID.
  • Переменные Coolify: BOT_TOKEN, APINEX_API_KEY, APINEX_STT_MODEL, APINEX_INTENT_CLASSIFIER_MODEL, APINEX_INJECTION_CLASSIFIER_MODEL, APINEX_POST_WRITER_MODEL, APINEX_IMAGE_TONE_MODEL, APINEX_RESPONSE_REPAIR_MODEL (optional; default uses the standard model), BOT_DB_PATH, RAW_UPDATES_DB_PATH (optional; default /app/data/bot_updates.sqlite3), DEPLOY_ENV, DEV_CHAT_ID, BOT_LOG_PATH (optional; default /app/data/bot.log), ACTIVITY_MIN_SECONDS / ACTIVITY_MAX_SECONDS (optional; default 3 / 8 — границы паузы между сменами фраз в драфте), ACTIVITY_MAX_ROTATIONS (optional; default 12 — предохранитель от бесконечной ротации), SUPPORT_GROUP_ID (id супергруппы с темами для обращений; без неё поддержка выключена), SUPPORT_NOTE_PREFIX (optional; default // — префикс заметки, которая не уходит пользователю), SUPPORT_GREETINGS (optional; пул приветствий оператора, разделитель |), SUPPORT_TOPIC_EMOJI_ID (optional; custom emoji id на топике обращения).
  • Docker: python:3.12-slim, копирует исходники бота, включая raw_updates.py, support.py и activity_phrases.py.

Тесты

python -m unittest discover -s tests -v

Покрытие: STT, LLM, vision, ToV, reply-поля, изоляция пользователей, API-логи, raw update log (сохранение/чтение, выключение вне development, очистка по retention), режимные клавиатуры и роутинг (переключатели, возврат в главное меню), платежи (Stars, защита от повторов, dev-подтверждение), ошибки, справка, onboarding, подписки (статус при входе, продление, оплата, баланс, блокировка, сохранение подписки при /clear), самостоятельная генерация поста (гейт по флагу обучения, контекст из профиля), восстановление обрезанного JSON, поддержка (кнопка и клавиатура обращения, support=True оставляет только «Главное меню»).

Запускай через виртуальное окружение проекта: .venv/bin/python -m unittest discover -s tests (вне его aiogram может быть не установлен).

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%