feat: уплотнить кнопки справки
This commit is contained in:
1 parent
a73d8f403d
commit
da0da91479
5 files changed
+51
-24
No files matched your search
@@ -24,7 +24,7 @@ Telegram-бот, который по запросу пользователя г
|
||||
- **Подписка** — при входе показывает активный период и остаток дней; inline-кнопка продления сразу открывает Stars-инвойс (`/balance`, `/subscribe`).
|
||||
- **Помощь** — справка и навигация по её разделам (`/help`).
|
||||
|
||||
`/start` показывает ровно одно welcome-сообщение без автоматического вызова помощи. При входе в любой режим показывается короткий onboarding. Главное меню содержит только четыре пользовательских режима; внутри каждого режима клавиатура состоит из кнопки «Главное меню». Команды `/training`, `/generate`, `/subscription`, `/help` доступны из любого режима; профильные действия — только в своём режиме.
|
||||
`/start` показывает ровно одно welcome-сообщение без автоматического вызова помощи. При входе в любой режим показывается короткий onboarding. Главное меню содержит только четыре пользовательских режима; внутри каждого режима клавиатура состоит из кнопки «Главное меню». В справке `/help` кнопки разделов расположены последовательно, переносятся целиком по мере заполнения строки; отдельной кнопки «В меню» нет, из раздела доступны остальные разделы. Команды `/training`, `/generate`, `/subscription`, `/help` доступны из любого режима; профильные действия — только в своём режиме.
|
||||
|
||||
## Первая версия
|
||||
- Принимать запрос обычным сообщением.
|
||||
@@ -104,9 +104,9 @@ Telegram-бот, который по запросу пользователя г
|
||||
- [x] Исправлен баг: `_send_idea` была потеряна в коммите про подписку — `/idea` падал с `NameError`
|
||||
- [x] Реальная оплата Stars: `sendInvoice` (XTR) → `pre_checkout_query` → `successful_payment`; подписка активируется только после оплаты, `telegram_payment_charge_id` защищает от двойного начисления
|
||||
- [x] В `DEPLOY_ENV=development` после обычного сообщения об инвойсе отдельным сообщением доступна кнопка «🧪 Оплатил (dev)»; callback ограничен пользователем инвойса, а все dev-ветви fail-closed выключены вне development
|
||||
- Версия разработки: `0.1.0+59`
|
||||
- Версия разработки: `0.1.0+60`
|
||||
- [x] Статус долгой операции стримится живым драфтом `sendMessageDraft` в приватном чате и каждые 3–8 сек меняет случайную фразу из пула операции (`activity_phrases.py`); в группах и топиках работает прежний `edit_text`
|
||||
- [x] Справка бота (`/help`) описывает `/balance` и `/subscribe`
|
||||
- [x] Справка `/help`: кнопки разделов идут последовательно и переносятся на новую строку целиком; отдельной кнопки «В меню» нет, из раздела доступны остальные разделы
|
||||
- [x] Проект Gitea «Social Post Writer Bot Roadmap» с задачами (выполненные — в «Готово», будущие — в Backlog)
|
||||
- [x] Ротационный файл лога `/app/data/bot.log` (5 MiB × 3 архива) на persistent volume
|
||||
- [x] Кнопка «✍️ Придумать пост» и `/post`: самостоятельный пост по обученному профилю, гейт по `users.is_trained`
|
||||
|
||||
@@ -14,7 +14,7 @@ Telegram-бот для генерации постов для соцсетей.
|
||||
- [Деплой и конфигурация](#деплой-и-конфигурация)
|
||||
- [Тесты](#тесты)
|
||||
|
||||
Версия разработки: `0.1.0+59`.
|
||||
Версия разработки: `0.1.0+60`.
|
||||
|
||||
---
|
||||
|
||||
@@ -60,6 +60,12 @@ onboarding. В «Подписке» сразу отображаются тип
|
||||
1. Отправляет ровно одно `WELCOME_RICH_HTML` с примерами и описанием режимов.
|
||||
2. Показывает клавиатуру главного меню; справка открывается отдельно кнопкой или командой `/help`.
|
||||
|
||||
### Справка (`/help`)
|
||||
|
||||
Меню разделов и страницы справки используют последовательную раскладку inline-кнопок: кнопки
|
||||
идут друг за другом, а на следующую строку переносятся целиком, когда не помещаются. Из раздела
|
||||
можно перейти в любой другой; отдельной кнопки «В меню» нет, текущий раздел отключён.
|
||||
|
||||
### Поддержка (`support.py`)
|
||||
|
||||
Отдельного FSM-режима нет: обращение — расширение режима «Помощь». Пользователь нажимает
|
||||
@@ -347,7 +353,7 @@ SUBSCRIPTION_PRICE_STARS = 100
|
||||
- **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+59`). При `DEPLOY_ENV=development` и изменении версии бот шлёт уведомление о версии в `DEV_CHAT_ID`.
|
||||
- **VERSION:** файл `VERSION` (`0.1.0+60`). При `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 `//` — префикс заметки, которая не уходит пользователю),
|
||||
|
||||
+20
-12
@@ -64,11 +64,11 @@ HELP_SECTIONS: dict[str, tuple[str, str]] = {
|
||||
}
|
||||
|
||||
HELP_SECTION_ORDER: tuple[str, ...] = tuple(HELP_SECTIONS)
|
||||
HELP_MENU_BUTTON = "⌂ В меню"
|
||||
HELP_BUTTON_ROW_WIDTH = 24
|
||||
|
||||
|
||||
def _menu_buttons(current: str | None = None) -> str:
|
||||
rows = []
|
||||
buttons: list[tuple[str, str]] = []
|
||||
for section in HELP_SECTION_ORDER:
|
||||
title = HELP_SECTIONS[section][0]
|
||||
if section == current:
|
||||
@@ -78,18 +78,26 @@ def _menu_buttons(current: str | None = None) -> str:
|
||||
f'<tg-button type="callback_data" data="help:{section}" style="primary">'
|
||||
f"{title}</tg-button>"
|
||||
)
|
||||
rows.append(f'<tg-button-row align="left">{button}</tg-button-row>')
|
||||
buttons.append((title, button))
|
||||
|
||||
rows = []
|
||||
row: list[str] = []
|
||||
row_width = 0
|
||||
for title, button in buttons:
|
||||
button_width = len(title)
|
||||
separator_width = 1 if row else 0
|
||||
if row and row_width + separator_width + button_width > HELP_BUTTON_ROW_WIDTH:
|
||||
rows.append(f'<tg-button-row align="left">{"".join(row)}</tg-button-row>')
|
||||
row = []
|
||||
row_width = 0
|
||||
separator_width = 0
|
||||
row.append(button)
|
||||
row_width += separator_width + button_width
|
||||
if row:
|
||||
rows.append(f'<tg-button-row align="left">{"".join(row)}</tg-button-row>')
|
||||
return "".join(rows)
|
||||
|
||||
|
||||
def _menu_button_row() -> str:
|
||||
return (
|
||||
f'<tg-button-row align="left">'
|
||||
f'<tg-button type="callback_data" data="help:menu" style="primary">'
|
||||
f"{HELP_MENU_BUTTON}</tg-button></tg-button-row>"
|
||||
)
|
||||
|
||||
|
||||
def help_menu_html(current: str | None = None) -> str:
|
||||
return (
|
||||
f"<h1>{HELP_MENU_TITLE}</h1>"
|
||||
@@ -102,7 +110,7 @@ def help_menu_html(current: str | None = None) -> str:
|
||||
|
||||
def help_section_html(section: str) -> str:
|
||||
title, body = HELP_SECTIONS[section]
|
||||
return f"<h1>{title}</h1>{body}{_menu_button_row()}{_menu_buttons(section)}"
|
||||
return f"<h1>{title}</h1>{body}{_menu_buttons(section)}"
|
||||
|
||||
|
||||
def help_view(section: str | None = None) -> str:
|
||||
|
||||
@@ -147,25 +147,38 @@ class HelpRoutingTests(unittest.TestCase):
|
||||
|
||||
|
||||
class HelpNavigationTests(unittest.TestCase):
|
||||
def test_menu_lists_every_section_as_full_width_button(self):
|
||||
def test_menu_packs_buttons_without_truncating_labels(self):
|
||||
from help_message import HELP_SECTIONS, HELP_SECTION_ORDER, help_menu_html
|
||||
|
||||
menu = help_menu_html()
|
||||
rows = menu.split("<tg-button-row")[1:]
|
||||
self.assertEqual(len(rows), len(HELP_SECTION_ORDER))
|
||||
self.assertLess(len(rows), len(HELP_SECTION_ORDER))
|
||||
for row in rows:
|
||||
self.assertTrue(row.startswith(' align="left">'), row)
|
||||
self.assertEqual(row.count("<tg-button "), 1, row)
|
||||
for section in HELP_SECTIONS:
|
||||
self.assertLessEqual(row.count("<tg-button "), 2, row)
|
||||
labels = [
|
||||
title
|
||||
for title, _body in HELP_SECTIONS.values()
|
||||
if f">{title}</tg-button>" in row
|
||||
]
|
||||
self.assertLessEqual(
|
||||
sum(len(title) for title in labels) + max(0, len(labels) - 1),
|
||||
24,
|
||||
row,
|
||||
)
|
||||
for section, (title, _body) in HELP_SECTIONS.items():
|
||||
self.assertIn(f'data="help:{section}"', menu)
|
||||
self.assertIn(f">{title}</tg-button>", menu)
|
||||
self.assertNotIn('data="help:menu"', menu)
|
||||
self.assertNotIn("В меню", menu)
|
||||
|
||||
def test_section_navigates_to_every_other_section(self):
|
||||
def test_section_navigates_to_other_sections_without_menu_button(self):
|
||||
from help_message import HELP_SECTIONS, help_section_html
|
||||
|
||||
for section in HELP_SECTIONS:
|
||||
html = help_section_html(section)
|
||||
self.assertIn('data="help:menu"', html)
|
||||
self.assertNotIn('data="help:menu"', html)
|
||||
self.assertNotIn("В меню", html)
|
||||
for other in HELP_SECTIONS:
|
||||
if other == section:
|
||||
continue
|
||||
|
||||
Reference in new issue
Block a user