docs: полная вики — Home, User Guide, Developer Guide, Subscription, Deployment
1 parent
d9348614a1
commit
fe0e40dba1
6 files changed
+551
-30
No files matched your search
+125
@@ -0,0 +1,125 @@
|
||||
# Деплой
|
||||
|
||||
## Инфраструктура
|
||||
|
||||
| Компонент | Значение |
|
||||
|---|---|
|
||||
| **Gitea** | `admin/social-post-writer-bot`, ветка `dev` (push → автодеплой) |
|
||||
| **Coolify проект** | `Social Post Writer Bot` |
|
||||
| **Coolify окружение** | `development` |
|
||||
| **Coolify приложение** | `social-post-writer-bot-development` (`d32u5zcq2g4odmdpq9hw91yn`) |
|
||||
| **Сервер** | `spb-easy-dale` |
|
||||
| **Бот** | [@post_master_bot](https://t.me/post_master_bot) |
|
||||
|
||||
---
|
||||
|
||||
## Деплой-скрипт
|
||||
|
||||
`.hermes/scripts/deploy.sh` ставит деплой в очередь через Coolify API:
|
||||
|
||||
```bash
|
||||
cd /root/.hermes/projects/social-post-writer-bot
|
||||
bash .hermes/scripts/deploy.sh
|
||||
```
|
||||
|
||||
Скрипт делает `POST /api/v1/deploy?uuid=<APP_UUID>&force=false`.
|
||||
|
||||
> Coolify API **не имеет** endpoint `/applications/{uuid}/deploy` — деплой запускается именно через `/deploy?uuid=...`.
|
||||
|
||||
Переменные скрипта:
|
||||
- `APP_UUID` (по умолчанию `d32u5zcq2g4odmdpq9hw91yn`)
|
||||
- `FORCE` (по умолчанию `false`)
|
||||
- `COOLIFY_API` (по умолчанию `http://10.19.0.2:8000/api/v1`)
|
||||
|
||||
Успешный ответ:
|
||||
```json
|
||||
{"deployments":[{"message":"Application ... deployment queued.","deployment_uuid":"..."}]}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Проверка статуса и логов
|
||||
|
||||
```bash
|
||||
COOLIFY_TOKEN=$(grep '^COOLIFY_TOKEN=' /root/.hermes/.env | cut -d= -f2-)
|
||||
|
||||
# статус
|
||||
curl -sS -H "Authorization: Bearer $COOLIFY_TOKEN" \
|
||||
"http://10.19.0.2:8000/api/v1/applications/d32u5zcq2g4odmdpq9hw91yn"
|
||||
|
||||
# логи
|
||||
curl -sS -H "Authorization: Bearer $COOLIFY_TOKEN" \
|
||||
"http://10.19.0.2:8000/api/v1/applications/d32u5zcq2g4odmdpq9hw91yn/logs"
|
||||
```
|
||||
|
||||
Успешный запуск в логах:
|
||||
```
|
||||
INFO:__main__:Development deploy notice sent: version=0.1.0+19
|
||||
INFO:aiogram.dispatcher:Start polling
|
||||
INFO:aiogram.dispatcher:Run polling for bot @post_master_bot id=2201652808 - 'Мастер постинга'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Переменные окружения (Coolify)
|
||||
|
||||
| Переменная | Назначение |
|
||||
|---|---|
|
||||
| `BOT_TOKEN` | токен Telegram-бота |
|
||||
| `APINEX_API_KEY` | ключ APInex |
|
||||
| `APINEX_STT_MODEL` | модель STT (дефолт `nova-2`) |
|
||||
| `APINEX_INTENT_CLASSIFIER_MODEL` | модель классификации намерений |
|
||||
| `APINEX_POST_WRITER_MODEL` | модель генерации постов/идей |
|
||||
| `APINEX_IMAGE_TONE_MODEL` | модель vision (OCR скриншотов, ToV) |
|
||||
| `BOT_DB_PATH` | путь к SQLite (`/app/data/social_writer.sqlite3`) |
|
||||
| `DEPLOY_ENV` | `development` — включает уведомления о версии |
|
||||
| `DEV_CHAT_ID` | чат для уведомлений о новой версии |
|
||||
|
||||
Секреты хранятся **только** в Coolify, в репозиторий не попадают.
|
||||
|
||||
---
|
||||
|
||||
## Версионирование
|
||||
|
||||
- Версия лежит в файле `VERSION` (сейчас `0.1.0+19`).
|
||||
- При каждом изменении кода версию нужно **поднять**.
|
||||
- При `DEPLOY_ENV=development` бот сравнивает сохранённую версию с текущей и, если они различаются, шлёт в `DEV_CHAT_ID`:
|
||||
> 🚀 Бот обновлён
|
||||
> Версия: X
|
||||
|
||||
> ⚠️ Если версию не поднять, уведомление **не отправится** — `notify_development_deploy` делает `return` при совпадении.
|
||||
|
||||
---
|
||||
|
||||
## Docker
|
||||
|
||||
```dockerfile
|
||||
FROM python:3.12-slim
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
|
||||
WORKDIR /app
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
COPY bot.py apinex_client.py user_store.py help_message.py VERSION ./
|
||||
CMD ["python", "bot.py"]
|
||||
```
|
||||
|
||||
> Важно: если в `bot.py` появляется новый импорт (например, `sqlite3`), он должен быть доступен в образе — иначе контейнер упадёт с `NameError` при старте.
|
||||
|
||||
---
|
||||
|
||||
## Данные
|
||||
|
||||
- SQLite: `/app/data/social_writer.sqlite3`
|
||||
- Persistent volume: `d32u5zcq2g4odmdpq9hw91yn-social-post-writer-dev-data` → монтируется в `/app/data`
|
||||
- WAL-режим, `busy_timeout = 10000`
|
||||
|
||||
---
|
||||
|
||||
## Типичные проблемы
|
||||
|
||||
| Симптом | Причина | Решение |
|
||||
|---|---|---|
|
||||
| `exited:unhealthy`, `NameError` в логах | забыт импорт (напр. `sqlite3`) | добавить импорт, поднять `VERSION`, redeploy |
|
||||
| Нет уведомления о новой версии | версия не изменена | поднять `VERSION` |
|
||||
| Деплой не запускается | неверный `COOLIFY_TOKEN` или uuid | проверить токен/уuid |
|
||||
| `no such table: subscriptions` | БД создана до миграции | вызывается `init_db()` при старте — проверьте, что volume смонтирован |
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
# Разработчику
|
||||
|
||||
## Структура
|
||||
|
||||
```
|
||||
bot.py — роутинг, команды, обработка сообщений (aiogram 3.x)
|
||||
apinex_client.py — клиент APInex (LLM, STT, vision), промпты, логирование вызовов
|
||||
user_store.py — SQLite: пользователи, ToV, посты, идеи, API-логи, подписки
|
||||
help_message.py — Rich Message справка (HTML + plain fallback)
|
||||
VERSION — версия релиза
|
||||
Dockerfile — python:3.12-slim
|
||||
requirements.txt — зависимости
|
||||
tests/ — юнит-тесты (unittest)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Потоки обработки
|
||||
|
||||
### Текстовый запрос
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
### Скриншоты (Tone of Voice)
|
||||
```
|
||||
update_tone_from_image → _handle_image_message
|
||||
├─ _collect_media_group (собирает галерею, ждёт MEDIA_GROUP_COLLECT_SECONDS = 1.0)
|
||||
├─ extract_posts_from_images (APInex vision, inline base64)
|
||||
├─ add_post_samples (накапливает по пользователю)
|
||||
└─ create_tone_profile → save_tone_of_voice → показывает стиль
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ключевые функции
|
||||
|
||||
### `bot.py`
|
||||
|
||||
| Функция | Что делает |
|
||||
|---|---|
|
||||
| `start(message)` | Онбординг (3 сообщения) + создание триала |
|
||||
| `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` | Индикатор «печатает», обновляется каждые 4 сек |
|
||||
| `report_error` | Понятная ошибка пользователю; секреты и URL вырезаются |
|
||||
| `split_text` | Разбивает длинный текст по лимиту (4000) по границам слов |
|
||||
|
||||
### `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` | Возвращает строку подписки |
|
||||
| `_days_until` | Сколько дней осталось; `-1` если истёкло/отключено |
|
||||
| `add_post_samples` | Накапливает образцы постов (без дублей) |
|
||||
| `save_generated_posts` | Сохраняет сгенерированные посты с `origin` |
|
||||
| `save_idea` / `get_idea_history` | История идей |
|
||||
| `save_api_call_log` | Пишет JSON лог вызова APInex |
|
||||
| `clear_user_data` | Полный сброс данных пользователя |
|
||||
|
||||
### `apinex_client.py`
|
||||
|
||||
| Функция | Что делает |
|
||||
|---|---|
|
||||
| `classify_intent` | Определяет намерение: `write_post` / `revise_post` / `generate_ideas` / `help` |
|
||||
| `generate_posts` | Генерирует 3 варианта поста |
|
||||
| `generate_ideas` | Генерирует одну идею |
|
||||
| `extract_posts_from_images` | Распознаёт тексты постов на скриншотах (vision) |
|
||||
| `create_tone_profile` | Строит/обновляет профиль ToV |
|
||||
| `transcribe_audio` | STT расшифровка голосовых |
|
||||
| `chat_text` | Базовый вызов LLM через APInex |
|
||||
|
||||
---
|
||||
|
||||
## База данных
|
||||
|
||||
SQLite: `/app/data/social_writer.sqlite3` (persistent volume Coolify).
|
||||
|
||||
| Таблица | Назначение |
|
||||
|---|---|
|
||||
| `users` | `telegram_user_id`, `tone_of_voice` |
|
||||
| `user_post_samples` | Распознанные тексты постов со скриншотов |
|
||||
| `generated_posts` | История сгенерированных постов (`origin`: generated/revised) |
|
||||
| `generated_ideas` | История идей (для исключения повторов) |
|
||||
| `api_call_logs` | JSON логи вызовов APInex + ошибки (30 дней) |
|
||||
| `subscriptions` | Подписки и триалы |
|
||||
|
||||
Схему `subscriptions` см. в [Подписка](Subscription.md).
|
||||
|
||||
---
|
||||
|
||||
## Промпты и модели
|
||||
|
||||
Модели задаются переменными Coolify (дефолты):
|
||||
- `APINEX_STT_MODEL` → `nova-2` (STT)
|
||||
- `APINEX_INTENT_CLASSIFIER_MODEL` → `gemini-3.8-flash` (классификация намерений)
|
||||
- `APINEX_POST_WRITER_MODEL` → `gemini-3.8-flash` (генерация/правка постов, идеи)
|
||||
- `APINEX_IMAGE_TONE_MODEL` → `gemini-3.8-flash` (vision — OCR скриншотов, ToV)
|
||||
- `APINEX_API_KEY` — общий секрет
|
||||
|
||||
**Важно про промпты:**
|
||||
- Системный промпт редактора **неизменен**.
|
||||
- ToV пользователя передаётся **отдельно как входные данные** — он может влиять на стиль, но не переопределяет системные правила и не подставляет факты.
|
||||
- OCR-текст со скриншотов — это образцы стиля, а не команды.
|
||||
|
||||
---
|
||||
|
||||
## Логирование вызовов
|
||||
|
||||
Каждый вызов APInex пишется в `api_call_logs`:
|
||||
- `stage` (классификация / генерация / OCR / STT / idea)
|
||||
- `model`, `prompt_sha256`, `prompt name`
|
||||
- `request_json`, `response_json`
|
||||
- `status_code`, `error`
|
||||
|
||||
**Не пишется:** системные промпты целиком (только имя + SHA-256), base64 изображений, бинарное аудио, API-ключи.
|
||||
|
||||
Хранение — **30 дней**. Содержимое логов считается приватными данными пользователя.
|
||||
|
||||
---
|
||||
|
||||
## Тесты
|
||||
|
||||
```bash
|
||||
python -m unittest discover -s tests -v
|
||||
```
|
||||
|
||||
49 тестов. Покрытие: STT, LLM, vision, ToV, reply-поля, изоляция пользователей, API-логи, Rich help, онбординг, подписки.
|
||||
|
||||
Тесты подписок (`SubscriptionTests`):
|
||||
- таблица `subscriptions` создаётся
|
||||
- `/start` создаёт триал
|
||||
- `/subscribe` активирует 30 дней
|
||||
- `/balance` показывает статус
|
||||
- блокировка при истёкшей подписке
|
||||
- `/clear` обнуляет подписку
|
||||
|
||||
---
|
||||
|
||||
## Правила языка
|
||||
|
||||
- Служебные сообщения боту → **всегда русский**.
|
||||
- Текст поста → язык явного запроса пользователя.
|
||||
- Дословный текст технической ошибки может остаться на языке оригинала.
|
||||
+23
-30
@@ -2,10 +2,10 @@
|
||||
|
||||
**Телеграм-бот** для генерации постов для соцсетей. По запросу пользователя готовит три варианта текста, адаптированных под его Tone of Voice.
|
||||
|
||||
- Бот: [@post_master_bot](https://t.me/post_master_bot)
|
||||
- Репозиторий: `admin/social-post-writer-bot`
|
||||
- Стек: Python 3.12, aiogram 3.x, aiohttp, SQLite, APInex (LLM / STT / vision)
|
||||
- Деплой: Coolify, окружение `development`, приложение `social-post-writer-bot-development`
|
||||
- **Бот:** [@post_master_bot](https://t.me/post_master_bot)
|
||||
- **Репозиторий:** `admin/social-post-writer-bot`
|
||||
- **Стек:** Python 3.12, aiogram 3.x, aiohttp, SQLite, APInex (LLM / STT / vision)
|
||||
- **Деплой:** Coolify, окружение `development`, приложение `social-post-writer-bot-development`
|
||||
|
||||
---
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
| Возможность | Описание |
|
||||
|---|---|
|
||||
| **Генерация постов** | На явную просьбу или реплай-правку — 3 отдельных варианта сообщениями |
|
||||
| **Генерация постов** | На явную просьбу или реплай-правку — **3 отдельных варианта** сообщениями |
|
||||
| **Tone of Voice** | Пришли скриншоты своих постов — бот распознаёт тексты и накапливает твой стиль |
|
||||
| **Голосовой ввод** | Голосовые сообщения расшифровываются (APInex STT) и работают как текст |
|
||||
| **Идеи** | «предложи тему» или `/idea` — одна идея (заголовок, угол, зачем) |
|
||||
@@ -21,20 +21,25 @@
|
||||
|
||||
---
|
||||
|
||||
## Команды
|
||||
## Быстрый старт
|
||||
|
||||
| Команда | Что делает | Без подписки |
|
||||
|---|---|---|
|
||||
| `/start` | Онбординг (3 сообщения) + создание триала | ✅ |
|
||||
| `/help` | Подробная справка | ✅ |
|
||||
| `/balance` | Статус подписки / триала | ✅ |
|
||||
| `/subscribe` | Активация подписки (100 звёзд, 30 дней) | ✅ |
|
||||
| `/idea` | Одна идея для поста | ❌ |
|
||||
| `/clear` | Полный сброс данных + обнуление подписки | ✅ |
|
||||
1. Напиши боту `/start` — получишь онбординг и пробный период на 3 дня.
|
||||
2. Опиши тему: «напиши пост про запуск нового продукта».
|
||||
3. Получишь три варианта. Не нравится — ответь реплаем: «сделай короче».
|
||||
4. Хочешь, чтобы посты были в твоём стиле — пришли скриншоты своих публикаций.
|
||||
|
||||
---
|
||||
|
||||
## Как работает (для разработчиков)
|
||||
## Разделы вики
|
||||
|
||||
- **[Пользователю](User-Guide.md)** — сценарии, команды, подписка, FAQ
|
||||
- **[Разработчику](Developer-Guide.md)** — архитектура, потоки, функции, БД
|
||||
- **[Деплой](Deployment.md)** — Coolify, переменные, деплой-скрипт, уведомления
|
||||
- **[Подписка](Subscription.md)** — модель, таблица БД, жизненный цикл
|
||||
|
||||
---
|
||||
|
||||
## Ключевые файлы репозитория
|
||||
|
||||
```
|
||||
bot.py — роутинг, команды, обработка сообщений
|
||||
@@ -42,20 +47,8 @@ apinex_client.py — клиент APInex (LLM, STT, vision), промпты, л
|
||||
user_store.py — SQLite: пользователи, ToV, посты, идеи, API-логи, подписки
|
||||
help_message.py — Rich Message справка
|
||||
VERSION — версия релиза
|
||||
Dockerfile — python:3.12-slim
|
||||
tests/ — юнит-тесты (unittest)
|
||||
```
|
||||
|
||||
**Потоки:**
|
||||
- Текст → `classify_intent` → генерация поста / идеи
|
||||
- Голос → `transcribe_audio` (STT) → как текст
|
||||
- Скриншоты → `extract_posts_from_images` (vision) → накопление ToV
|
||||
|
||||
Подробнее — в [README.md](../../src/branch/main/README.md) и `IDEA.MD` репозитория.
|
||||
|
||||
---
|
||||
|
||||
## Данные
|
||||
|
||||
- SQLite: `/app/data/social_writer.sqlite3` (persistent volume Coolify).
|
||||
- Per-user: ToV, образцы постов, истории постов и идей. Диалоги и исходные картинки **не** хранятся.
|
||||
- API-логи (JSON запросов/ответов) — 30 дней, без системных промптов и base64.
|
||||
- `/clear` стирает пользовательские данные и обнуляет подписку; API-логи остаются.
|
||||
Полная документация — в [README.md](../../src/branch/main/README.md) и `IDEA.MD` репозитория.
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
# Подписка
|
||||
|
||||
## Модель
|
||||
|
||||
**Триал 3 дня → подписка 30 дней за 100 звёзд (Telegram Stars).**
|
||||
|
||||
Константы в `user_store.py`:
|
||||
```python
|
||||
TRIAL_DAYS = 3
|
||||
SUBSCRIPTION_MONTH_DAYS = 30
|
||||
SUBSCRIPTION_PRICE_STARS = 100
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Таблица `subscriptions`
|
||||
|
||||
| Колонка | Смысл |
|
||||
|---|---|
|
||||
| `telegram_user_id` | PK, id пользователя |
|
||||
| `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`** → `ensure_subscription` (если записи нет) → `activate_subscription(months=1)` → +30 дней от момента оплаты, `is_active = 1`.
|
||||
5. **`/clear`** → `deactivate_subscription` → `is_active = 0`; для продолжения нужен `/subscribe`.
|
||||
|
||||
**Расчёт дней (`_days_until`):**
|
||||
- Триал: `trial_started_at + TRIAL_DAYS`
|
||||
- Подписка: `last_paid_at`
|
||||
- Возвращает полные дни; если `is_active = 0` или срок истёк — `-1`
|
||||
|
||||
---
|
||||
|
||||
## Что доступно без подписки
|
||||
|
||||
| Команда / действие | Доступно |
|
||||
|---|---|
|
||||
| `/help` | ✅ |
|
||||
| `/start` | ✅ |
|
||||
| `/balance` | ✅ |
|
||||
| `/subscribe` | ✅ |
|
||||
| `/clear` | ✅ |
|
||||
| Генерация поста | ❌ |
|
||||
| `/idea` | ❌ |
|
||||
| Голосовой ввод → генерация | ❌ |
|
||||
| Скриншоты → ToV | ❌ |
|
||||
|
||||
Логика: `/help` обрабатывается **до** проверки подписки и до классификации — пользователь не остаётся в тупике.
|
||||
|
||||
---
|
||||
|
||||
## Сообщения
|
||||
|
||||
**`/balance` при активном триале:**
|
||||
> Триал активен — осталось N дн.
|
||||
> Когда триал закончится, нужно оплатить подписку — команда /subscribe.
|
||||
|
||||
**`/balance` при активной подписке:**
|
||||
> Подписка активна — осталось N дн.
|
||||
> Следующее списание через N дн.
|
||||
|
||||
**При истёкшем периоде:**
|
||||
> Твой период (триал или подписка) истёк. Оплати /subscribe, чтобы продолжить. Стоимость — 100 звёзд в месяц.
|
||||
> Чтобы активировать — пришли 100 звёзд командой /subscribe. *(если ещё не было оплат)*
|
||||
|
||||
**`/subscribe`:**
|
||||
> Активирую подписку на 30 дней. Для оплаты пришли 100 звёзд в этом чате.
|
||||
> Жду оплаты...
|
||||
> Подписка активирована на 30 дн. Спасибо за оплату 100 звёзд!
|
||||
> Теперь доступны генерация постов, идей и голосовые.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Про оплату звёздами
|
||||
|
||||
Приём звёзд — **имитация**. Бот пишет «пришли 100 звёзд» и **сразу** активирует подписку, не проводя реальный платёж.
|
||||
|
||||
Для настоящих платежей нужен отдельный invoicing-флоу:
|
||||
- Telegram Payments API (`sendInvoice` со звёздами / `createInvoiceLink`)
|
||||
- Обработка `pre_checkout_query` и `successful_payment`
|
||||
- Начисление подписки только после подтверждённой оплаты
|
||||
|
||||
В этой версии не реализовано.
|
||||
|
||||
---
|
||||
|
||||
## Часовые пояса
|
||||
|
||||
SQLite `CURRENT_TIMESTAMP` возвращает UTC **без** часового пояса. Код приводит все значения к naive UTC перед сравнением:
|
||||
|
||||
```python
|
||||
def utc(value: str) -> datetime.datetime:
|
||||
dt = datetime.datetime.fromisoformat(value.replace("Z", "+00:00"))
|
||||
if dt.tzinfo is not None:
|
||||
return dt.astimezone(datetime.timezone.utc).replace(tzinfo=None)
|
||||
return dt
|
||||
```
|
||||
|
||||
Это убирает сдвиги при сравнении `expires` и `now`.
|
||||
|
||||
---
|
||||
|
||||
## Тесты
|
||||
|
||||
`SubscriptionTests` в `tests/test_writer_features.py` — 6 тестов:
|
||||
1. таблица `subscriptions` создаётся
|
||||
2. `/start` создаёт триал
|
||||
3. `/subscribe` активирует 30 дней
|
||||
4. `/balance` показывает статус
|
||||
5. генерация блокируется при истёкшей подписке
|
||||
6. `/clear` обнуляет подписку
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
# Пользователю
|
||||
|
||||
## Онбординг
|
||||
|
||||
После `/start` бот присылает **три сообщения** с небольшой паузой:
|
||||
|
||||
1. **Что делаю** — «Привет! Я помогаю писать посты для соцсетей. Расскажи, о чём нужен пост — а я верну три варианта: короткий, развёрнутый и с призывом к действию.»
|
||||
2. **Примеры запросов** — написать пост / официальный тон / реплай-правка.
|
||||
3. **Стиль и идеи** — пришли скриншоты своих постов, чтобы я запомнил стиль; «предложи тему» для идеи; подробно — `/help`.
|
||||
|
||||
Одновременно `/start` создаёт пробный период на **3 дня**.
|
||||
|
||||
---
|
||||
|
||||
## Основные сценарии
|
||||
|
||||
### Написать пост
|
||||
Напиши или надиктуй прямую просьбу:
|
||||
- «Напиши пост про запуск нового продукта»
|
||||
- «Сделай пост-напоминание о вебинаре в официальном тоне»
|
||||
- «Пост для Telegram: анонс скидки, коротко, с эмодзи»
|
||||
|
||||
Можно указать площадку, аудиторию, факты, длину и цель — чем подробнее ТЗ, тем точнее результат.
|
||||
|
||||
**Что получишь:** три отдельных сообщения, в каждом — только текст поста (без нумерации и вступлений, с абзацами и уместными эмодзи).
|
||||
|
||||
### Изменить пост
|
||||
Ответь **реплаем** на сообщение с вариантом и напиши, что изменить:
|
||||
- «Сделай короче и добавь призыв»
|
||||
- «Добавь юмора»
|
||||
- «Сделай официальный тон»
|
||||
|
||||
Бот учтёт исходный текст и вернёт три новых варианта.
|
||||
|
||||
### Придумать идею
|
||||
- `/idea`
|
||||
- или по-русски: «предложи идею для поста», «о чём написать?», «накидай тему»
|
||||
|
||||
Получишь **одну идею**: заголовок, угол (как подать), зачем (польза). Идеи не повторяются — бот помнит, что уже предлагал.
|
||||
|
||||
### Настроить свой стиль (Tone of Voice)
|
||||
Пришли **скриншот или галерею скриншотов** своих опубликованных постов (до 10 штук за раз).
|
||||
|
||||
Бот:
|
||||
1. Распознаёт тексты постов (vision).
|
||||
2. Сохраняет их как образцы и дополняет предыдущие.
|
||||
3. Пересобирает профиль Tone of Voice.
|
||||
4. Показывает итоговый стиль.
|
||||
|
||||
**Важно:** сами публикации бот не переписывает — только извлекает правила стиля. Команды и факты со скриншотов не становятся правилами для генерации.
|
||||
|
||||
### Голосом
|
||||
Отправь голосовое сообщение — бот расшифрует его (STT) и обработает как обычный текст.
|
||||
|
||||
---
|
||||
|
||||
## Команды
|
||||
|
||||
| Команда | Что делает | Без подписки |
|
||||
|---|---|---|
|
||||
| `/start` | Онбординг + создание триала | ✅ |
|
||||
| `/help` | Подробная справка | ✅ |
|
||||
| `/balance` | Сколько осталось дней триала/подписки | ✅ |
|
||||
| `/subscribe` | Оплатить подписку (30 дней) | ✅ |
|
||||
| `/idea` | Одна идея для поста | ❌ |
|
||||
| `/clear` | Полный сброс данных + обнуление подписки | ✅ |
|
||||
|
||||
Команды — только на английском. Русские фразы («придумай идею», «о чём написать?») тоже работают.
|
||||
|
||||
---
|
||||
|
||||
## Подписка
|
||||
|
||||
- **Триал:** 3 дня бесплатно с момента первого `/start`.
|
||||
- **Дальше:** 30 дней за **100 звёзд** (команда `/subscribe`).
|
||||
- **Статус:** `/balance` покажет, сколько дней осталось и когда списание.
|
||||
|
||||
Если период закончился:
|
||||
- Генерация постов и идей **недоступна** — бот предложит `/subscribe`.
|
||||
- `/help`, `/balance`, `/subscribe`, `/start`, `/clear` работают **всегда**.
|
||||
|
||||
Подробнее — в разделе [Подписка](Subscription.md).
|
||||
|
||||
---
|
||||
|
||||
## Что бот помнит
|
||||
|
||||
- Твой **Tone of Voice** (из скриншотов).
|
||||
- **Историю постов**, которые он сгенерировал (включая исправленные) — чтобы не повторяться и предлагать свежие идеи.
|
||||
- **Историю идей** — чтобы не предлагать одно и то же.
|
||||
|
||||
Что бот **НЕ** хранит:
|
||||
- Историю диалогов.
|
||||
- Исходные изображения (скриншоты) — только распознанный текст.
|
||||
|
||||
Команда `/clear` удаляет все твои данные и обнуляет подписку. Технические API-логи при этом сохраняются (они нужны для отладки).
|
||||
|
||||
---
|
||||
|
||||
## Ошибки и обратная связь
|
||||
|
||||
- Пока идёт обработка, бот показывает **«печатает»**.
|
||||
- Если что-то пошло не так, придёт сообщение: «❌ Не получилось: {этап}. Причина: …». Секреты и ссылки в тексте скрыты.
|
||||
- Служебные сообщения — **всегда на русском**. Язык поста — тот, который ты запросил.
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
## Навигация
|
||||
|
||||
- **[Главная](Home.md)**
|
||||
- **[Пользователю](User-Guide.md)**
|
||||
- **[Разработчику](Developer-Guide.md)**
|
||||
- **[Подписка](Subscription.md)**
|
||||
- **[Деплой](Deployment.md)**
|
||||
|
||||
---
|
||||
|
||||
**Бот:** [@post_master_bot](https://t.me/post_master_bot)
|
||||
Reference in new issue
Block a user