diff --git a/IDEA.MD b/IDEA.MD index 0fa0322..6b4780f 100644 --- a/IDEA.MD +++ b/IDEA.MD @@ -44,7 +44,7 @@ Telegram-бот, который по запросу пользователя г - Coolify приложение: `social-post-writer-bot-development` (`d32u5zcq2g4odmdpq9hw91yn`) на `spb-easy-dale`. - SQLite: `/app/data/social_writer.sqlite3`; persistent volume `d32u5zcq2g4odmdpq9hw91yn-social-post-writer-dev-data` монтируется в `/app/data`. - Секреты хранятся в переменных окружения приложения Coolify; не сохраняются в репозитории. -- `DEPLOY_ENV=development`, `DEV_CHAT_ID` — для уведомления о новой версии; версия в файле `VERSION` (сейчас `0.1.0+23`). +- `DEPLOY_ENV=development`, `DEV_CHAT_ID` — для уведомления о новой версии; версия в файле `VERSION` (сейчас `0.1.0+24`). - Вики Gitea: страницы `Home`, `User-Guide`, `Developer-Guide`, `Subscription`, `Deployment`. ## Статус @@ -67,7 +67,7 @@ Telegram-бот, который по запросу пользователя г - [x] `/idea` возвращает одну идею одним сообщением и сохраняет её для исключения повторов - [x] Все отправленные варианты постов сохраняются по пользователю, включая исправленные; история используется для новых идей - [x] Служебные ответы по-русски, язык текстов постов определяется запросом пользователя -- [x] Справка `/help` разбита на разделы с навигацией инлайн-кнопками (aiogram 3.31.0: `send_rich_message` + `edit_message_text(rich_message=…)` + HTML-теги ``/``) +- [x] Справка `/help` разбита на разделы с навигацией инлайн-кнопками во всю ширину сообщения: из любого раздела доступна кнопка на любой другой, текущий раздел — `type="disabled"` (aiogram 3.31.0: `send_rich_message` + `edit_message_text(rich_message=…)`) - [x] Help command uses new Rich Message, with a plain-text fallback. - [x] Development deployment/version notice and user-visible error details. - [x] Онбординг после `/start`: welcome Rich Message (`WELCOME_RICH_HTML`) → пауза → вызов `help_command`; правки справки автоматически меняют онбординг diff --git a/README.md b/README.md index 2d9e154..10acbb2 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ bot.py — роутинг, команды, обработка со 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) +VERSION — версия релиза (сейчас 0.1.0+24) ``` ### Основные потоки @@ -168,7 +168,7 @@ SUBSCRIPTION_PRICE_STARS = 100 | Команда | Что делает | Доступна без подписки | |---|---|---| | `/start` | Онбординг (welcome Rich Message + справка) + создание триала | ✅ | -| `/help` | Меню справки: разделы с инлайн-кнопками + plain fallback | ✅ | +| `/help` | Меню справки: полноширинные кнопки разделов, переход в любой раздел + plain fallback | ✅ | | `/balance` | Статус подписки / триала | ✅ | | `/subscribe` | Активация подписки (100 звёзд, 30 дней) | ✅ | | `/idea` | Одна идея для поста | ❌ (нужна подписка) | diff --git a/VERSION b/VERSION index ac52cf9..769a760 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.0+23 +0.1.0+24 diff --git a/help_message.py b/help_message.py index 7c2a051..3ce06b7 100644 --- a/help_message.py +++ b/help_message.py @@ -15,7 +15,7 @@ WELCOME_RICH_HTML = """

Привет! Я — бот для написания """ HELP_MENU_TITLE = "Помощь по боту" -HELP_MENU_INTRO = "Выбери раздел — откроется только он." +HELP_MENU_INTRO = "Выбери раздел — откроется только он. Из любого раздела можно перейти в любой." HELP_SECTIONS: dict[str, tuple[str, str]] = { "posts": ( "✍️ Написать пост", @@ -56,55 +56,51 @@ HELP_SECTIONS: dict[str, tuple[str, str]] = { } HELP_SECTION_ORDER: tuple[str, ...] = tuple(HELP_SECTIONS) -HELP_BACK = "⬅️ В меню" -HELP_PREV = "⬅️ Предыдущий" -HELP_NEXT = "➡️ Следующий" +HELP_MENU_BUTTON = "⌂ В меню" -def _section_nav(section: str) -> str: - buttons = [f'{HELP_BACK}'] - index = HELP_SECTION_ORDER.index(section) - if index > 0: - previous = HELP_SECTION_ORDER[index - 1] - buttons.append( - f'{HELP_PREV}' - ) - if index + 1 < len(HELP_SECTION_ORDER): - following = HELP_SECTION_ORDER[index + 1] - buttons.append( - f'{HELP_NEXT}' - ) - return '' + "".join(buttons) + "" +def _menu_buttons(current: str | None = None) -> str: + rows = [] + for section in HELP_SECTION_ORDER: + title = HELP_SECTIONS[section][0] + if section == current: + button = f'{title}' + else: + button = ( + f'' + f"{title}" + ) + rows.append(f'{button}') + return "".join(rows) -def help_menu_html() -> str: - buttons = [ - f'' - f"{HELP_SECTIONS[section][0]}" - for section in HELP_SECTION_ORDER - ] - pairs = "".join( - '' + "".join(buttons[i : i + 2]) + "" - for i in range(0, len(buttons), 2) +def _menu_button_row() -> str: + return ( + f'' + f'' + f"{HELP_MENU_BUTTON}" ) + + +def help_menu_html(current: str | None = None) -> str: return ( f"

{HELP_MENU_TITLE}

" f"

{HELP_MENU_INTRO}

" f"

Я помогаю писать и редактировать посты для соцсетей. " f"Генерирую посты только по явной просьбе.

" - f"{pairs}" + f"{_menu_buttons(current)}" ) def help_section_html(section: str) -> str: title, body = HELP_SECTIONS[section] - return f"

{title}

{body}{_section_nav(section)}" + return f"

{title}

{body}{_menu_button_row()}{_menu_buttons(section)}" def help_view(section: str | None = None) -> str: if section and section in HELP_SECTIONS: return help_section_html(section) - return help_menu_html() + return help_menu_html(section) def help_plain_view(section: str | None = None) -> str: diff --git a/tests/test_release_help.py b/tests/test_release_help.py index 7bc9a36..fc498ff 100644 --- a/tests/test_release_help.py +++ b/tests/test_release_help.py @@ -71,26 +71,39 @@ class HelpRoutingTests(unittest.TestCase): class HelpNavigationTests(unittest.TestCase): - def test_menu_lists_every_section_as_callback_button(self): - from help_message import HELP_SECTIONS, help_menu_html + def test_menu_lists_every_section_as_full_width_button(self): + from help_message import HELP_SECTIONS, HELP_SECTION_ORDER, help_menu_html menu = help_menu_html() - self.assertIn("'), row) + self.assertEqual(row.count("🎨 Tone of Voice', html) + self.assertNotIn('data="help:tone"', html) + for section in HELP_SECTIONS: + if section != "tone": + self.assertIn(f'data="help:{section}" style="primary"', html) def test_unknown_section_falls_back_to_menu(self): from help_message import help_view