vkbottle-dialog
Декларативные диалоги для VK-ботов поверх vkbottle — порт aiogram-dialog на VK.
Что это
В обычном vkbottle-боте многошаговые сценарии (анкеты, меню, мастера) собираются вручную: свой FSM,
свои клавиатуры, свои проверки "а что если пользователь написал не то". vkbottle-dialog берёт эту
рутину на себя — так же, как aiogram-dialog делает это для aiogram/Telegram:
- Window — экран (текст + клавиатура + приём ввода), Dialog — набор окон на одном FSM-стейте.
- Навигация между окнами (
SwitchTo,Back,Next), между диалогами (Start,Cancel,done()с результатом родителю). - Виджеты клавиатур:
Select/Radio/Multiselect/Toggle/Checkbox, группировкаGroup/Row/Column, пагинацияScrollingGroup. - Приём текстового ввода:
TextInput(с валидацией черезtype_factory),MessageInput. - Хранение состояния диалога:
MemoryStorage(для разработки) илиRedisStorage(для прод).
Quickstart
import os
from vkbottle import Bot
from vkbottle_dialog import Dialog, StartMode, Window, setup_dialogs
from vkbottle_dialog.fsm import State, StatesGroup
from vkbottle_dialog.integration import NotInDialog
from vkbottle_dialog.storage import MemoryStorage
from vkbottle_dialog.widgets.kbd import Cancel, SwitchTo
from vkbottle_dialog.widgets.text import Const
class MenuSG(StatesGroup):
main = State()
info = State()
dialog = Dialog(
Window(
Const("Главное меню. Выберите пункт:"),
SwitchTo(Const("О боте"), id="to_info", state=MenuSG.info),
Cancel(Const("Закрыть")),
state=MenuSG.main,
),
Window(
Const("Это демонстрационный бот на vkbottle-dialog."),
SwitchTo(Const("Назад"), id="to_main", state=MenuSG.main),
state=MenuSG.info,
),
)
bot = Bot(os.environ["VK_TOKEN"])
setup_dialogs(bot, dialog, storage=MemoryStorage())
@bot.on.message(NotInDialog(), text="/start")
async def start(message, dialog_manager):
await dialog_manager.start(MenuSG.main, mode=StartMode.RESET_STACK)
if __name__ == "__main__":
bot.run_forever()
Больше примеров — в examples/: menu.py, survey.py (анкета с валидацией ввода),
pagination.py (список на 30 позиций), nested.py (вложенные диалоги с возвратом результата),
chat.py (работа в беседах).
Обязательно
(а) Правило NotInDialog() / InDialog() для ВСЕХ соседних @bot.on.message-хендлеров.
vkbottle не блокирует события между views: DialogView, который подключает setup_dialogs, —
это всего лишь один из views в labeler.views(), он выполняется наравне с остальными хендлерами, а
не "перехватывает" их. Если рядом с диалогом зарегистрирован обычный @bot.on.message(text="/start")
без NotInDialog(), он сработает поверх активного диалога так, будто диалога нет — сообщение
пользователя внутри диалога может случайно попасть не туда, а повторный /start пересоздаст диалог
поверх уже открытого окна. Каждый хендлер, который должен видеть/не видеть активный диалог,
обязан использовать NotInDialog() (сработает, только если у пользователя нет активного
диалога) или InDialog() (сработает, только если диалог активен) — см. dialog_manager в
сигнатуре хендлера, который эти правила инжектят.
(б) В настройках сообщества обязательно включить событие message_event и Long Poll API.
Инлайн-клавиатуры диалогов кликаются через message_event (callback-кнопки) — без этого события
нажатия на кнопки будут молча теряться. Long Poll API должен быть включён, версия Long Poll API
5.199 (событие message_event доступно с 5.103).
Соответствие aiogram-dialog → vkbottle-dialog
| aiogram-dialog | vkbottle-dialog | Статус |
|---|---|---|
Dialog, Window, DialogManager |
Dialog, Window, DialogManager |
✅ |
Const, Format, Case, Multi, Or |
Const, Format, Case, Multi, Or |
✅ |
Button, Url, SwitchTo, Back, Next, Cancel, Start |
то же самое | ✅ |
Group, Row, Column |
Group, Row, Column |
✅ |
Select, Radio, Multiselect, Toggle, Checkbox |
то же самое | ✅ |
ScrollingGroup + пейджеры (NumberedPager, FirstPage/PrevPage/CurrentPage/NextPage/LastPage, SwitchPage) |
то же самое | ✅ |
MessageInput, TextInput |
MessageInput, TextInput |
✅ |
Calendar |
Calendar (COMPACT, WIDE) + TimeSelect |
✅ |
Counter |
Counter |
✅ |
ListGroup |
List (постраничный вывод page_size) |
✅ |
Text + ScrollingGroup |
ScrollingText |
✅ |
Медиа-виджеты (StaticMedia) |
StaticMedia + MediaResolver (кэш загрузок) |
✅ |
ListGroup, SubManager (v0.3) |
ListGroup + SubManager — per-row состояние, ManagedListGroup.find_for_item |
✅ |
DynamicMedia (v0.3) |
DynamicMedia — селектор str/MagicFilter/callable |
✅ |
| Карусель (v0.3) | Carousel — VK-эксклюзив, за пределами оригинала (template, не media group) |
✅ |
StartMode.NEW_STACK |
— | 🗺️ роадмап (пока NotImplementedError; без него AccessSettings.user_ids в основном не наблюдаем — см. ниже) |
AccessSettings (v0.3) |
Хук access_validator= в setup_dialogs (тихий отказ) — без общих (shared) стеков user_ids почти не проявляется, см. «Демо-бот» |
✅ (частично, см. оговорку) |
| Jinja-шаблоны для текста (v0.3) | Jinja — extra [jinja], autoescape=False (у VK нет HTML); свой jinja2.Environment (кастомные фильтры/глобалы/лоадер) — через setup_dialogs(jinja_env=...) |
✅ |
| Мульти-инстанс (v0.3) | RedisLockRegistry — распределённый lock с TTL/heartbeat, setup_dialogs(locks=...) |
✅ |
Календарь и лимиты VK
Calendar (v0.2): два режима верстки —
CalendarLayout.COMPACT(по умолчанию): 6 дней на страницу (по 3 в строке), пагинация; подходит для беседы (≤10 кнопок).CalendarLayout.WIDE: весь месяц за раз (до 35 кнопок), ≤ 5 дней в строке; работает только в личных сообщениях (в беседе упадёт сDialogConfigError).
TimeSelect (v0.2): выбор часа/минуты по слотам, постранично, укладывается в лимит 10 кнопок.
Медиа в диалогах (v0.2): StaticMedia + MediaResolver (кэширование attachment-строк за сессию). При отсутствии доступа к медиа вложение деградирует без ошибки; перед продом проверьте доступ кросс-peer (группа ↔ личные сообщения) ручным смоуком.
Ограничения
- Один
setup_dialogs()на процесс —InDialog()/NotInDialog()резолвятся late-binding к последнему активному сетапу. Это не блокирует несколько ИНСТАНСОВ процесса за одним storage: для них передайтеlocks=RedisLockRegistry(redis)вsetup_dialogs(распределённый lock с TTL и heartbeat, см. «VK-расширения» — RedisLockRegistry делает конкурентный доступ нескольких инстансов к одному стеку безопасным). StartMode.NEW_STACKне реализован —manager.start(..., mode=StartMode.NEW_STACK)кидаетNotImplementedError. ИспользуйтеStartMode.NORMALилиStartMode.RESET_STACK.TextKeyboardFactoryработает только в личных сообщениях. В беседах нижняя (не-инлайн) клавиатура общая на весь чат, поэтому рендер диалога с текстовой клавиатурой в беседе — ошибка конфигурации (DialogConfigError). То же верно дляCalendarLayout.WIDE— в беседе используйте толькоCOMPACT. Тап поbutton_type="text"-кнопке всегда пересылает окно (это обычное сообщение пользователя,message_new, а нижняя клавиатура смены не требует); тап поbutton_type="callback"-кнопке при неизменной нижней клавиатуре — редактирует окно на месте, без пересылки.- 24-часовое окно редактирования сообщений VK. Диалог обновляет своё окно, редактируя одно и то
же сообщение; когда VK перестаёт разрешать правку (окно устарело), пользователь при клике по
устаревшей клавиатуре увидит снекбар «Окно устарело, начните заново» вместо тихого зависания
(настраивается через
stale_snackbar=вsetup_dialogs). - Лимит инлайн-клавиатуры VK — 10 кнопок (и до 6 строк, не больше 5 кнопок в строке) — учитывайте
при проектировании окон с большим числом виджетов; для длинных списков используйте
ScrollingGroup. - Payload-бюджет
ListGroup(спека §1.2). callback_data строки кодируется как"{lg_id}:{item_id}:{child_cb}"и упаковывается в общий payload вместе с intent_id и подписью — держите вложенность неглубокой (≤3 уровня: диалог → ListGroup → дочерний виджет) и id короткими (≤~20 ASCII символов), иначе на рендере —InvalidPayload(payload превысил лимит).
VK-расширения
Помимо API aiogram-dialog, доступны VK-специфичные возможности:
Button(..., color=ButtonColor.POSITIVE)— цвет инлайн-кнопки (PRIMARY/SECONDARY/NEGATIVE/POSITIVE).Button(..., snackbar="Текст")— показать всплывающее уведомление сразу при клике, без ручного вызоваmanager.answer().manager.answer(snackbar=..., open_link=...)— ответ наmessage_event(снекбар и/или открытие ссылки на клиенте); доступно только для событий кнопок, не для обычных сообщений.TextKeyboardFactory(button_type="callback")— кнопки нижней (не-инлайн) клавиатуры шлютmessage_event, как и инлайн-кнопки, вместо обычного текстового сообщения пользователя: клик не засоряет переписку эхом,manager.answer(snackbar=...)работает, а неизменную нижнюю клавиатуру диалог редактирует на месте вместо пересылки окна (см. «Ограничения» ниже). По умолчанию —button_type="text"(кнопка постит текст-заглушку как обычное сообщение, обратная совместимость).button_type="callback"требует клиентской поддержки callback-кнопок (как у инлайн-клавиатуры) — на старых клиентах оставьте дефолтный"text"; явно заданныйmarkup_factoryв окне не проходит через авто-деградацию поinline_supported(см.manager.py show()).
Демо-бот
examples/demo/ — бот-витрина всех виджетов библиотеки: 12 разделов (Лейауты, Скроллы, Селекты,
Календарь, Счётчик, Мультивиджет, Мастер, Нижняя клавиатура (ЛС), VK-фишки, ListGroup, Карусель,
Доступ) из одного главного меню. >10 разделов не влезают в лимит инлайн-клавиатуры (см.
«Ограничения» ниже), поэтому меню — ScrollingGroup с пейджером, не плоский список кнопок.
Исходники — examples/demo/bot_dialogs/, обход всех окон, подсекций и страниц меню автотестом —
tests/test_demo_flow.py.
Новые в v0.3 разделы:
- «📋 ListGroup» (
bot_dialogs/list_demo.py) — 3 строки, каждая:Checkbox+Button«удалить» (снекбар), состояние изолировано per-row черезSubManager. Текст над списком —Jinjaс{% for %}по отмеченным строкам, читает их состояние черезdialog_manager.find("lg").find_for_item("chk", item_id). - «🎠 Карусель» (
bot_dialogs/carousel_demo.py) — 3-элементная VK-карусель товаров (title/description/photo изexamples/demo/media/1..3.png), на каждом элементе — кнопки «Выбрать» (снекбар) и «☰ Меню» (Startв главное меню). Карусель —template, а не inline-клавиатура, и VK не принимаетkeyboardвместе сtemplate(ошибка 100), поэтому вся навигация живёт кнопками ВНУТРИ элементов карусели. Нижней клавиатуры нет — секция работает и в ЛС, и в беседах. - «🔒 Доступ» (
bot_dialogs/access_demo.py) — честная демонстрация кастомногоStackAccessValidator(AdminOnlyInChatValidator, подключён вbot.pyчерезsetup_dialogs(access_validator=...)): в беседах диалог доступен только администраторам изADMIN_IDS. БезNEW_STACK(см. таблицу выше)AccessSettings.user_idsдля дефолтных per-owner стеков почти не наблюдаем в демо — раздел и его docstring объясняют, почему выбран именно этот сценарий, а не показuser_ids. - DynamicMedia — не отдельный раздел, а подсекция «🖼 Динамическое медиа» внутри «✨ VK-фишки»:
геттер сам выбирает
MediaAttachmentпо состоянию диалога (в отличие отStaticMedia, где путь/url фиксирован вText-виджете).
Настройка сообщества:
- Создайте сообщество VK (или используйте тестовое) и получите ключ доступа сообщества (Управление → Работа с API → Ключи доступа).
- Управление → Работа с API → Long Poll API — включите его, версия Long Poll API 5.199
(событие
message_eventдоступно с 5.103, но 5.199 — актуальная). - Там же, в Типы событий, включите
message_event(иmessage_new) — без него клики по инлайн-кнопкам будут молча теряться (см. «Обязательно» выше).
Получение исходников (examples/ не входит в wheel-пакет на PyPI, нужен клон репозитория):
git clone https://github.com/akkrn/vkbottle-dialog && cd vkbottle-dialog && uv sync
(или pip install -e . вместо uv sync, если вы не используете uv).
Запуск:
VK_TOKEN=<ключ_доступа_сообщества> python -m examples.demo.bot
Опциональные env:
REDIS_URL=redis://127.0.0.1:6379/0— состояние диалогов в Redis (extra[redis]), переживает рестарт бота. Без него —MemoryStorage(всё теряется при перезапуске).DEMO_ADMIN_IDS=12345,67890— включает демо-валидатор доступа (секция «Доступ»): в беседах бот отвечает только этим VK user_id. Без переменной валидатор не подключается — бот работает везде.
systemd-юнит (пример, /etc/systemd/system/vkd-demo.service; /opt/vkbottle-dialog — клон
репозитория из шага выше, с виртуальным окружением .venv, созданным uv sync):
[Unit]
Description=vkbottle-dialog demo bot
[Service]
User=vkbot
WorkingDirectory=/opt/vkbottle-dialog
Environment=VK_TOKEN=<ключ_доступа_сообщества>
ExecStart=/opt/vkbottle-dialog/.venv/bin/python -m examples.demo.bot
Restart=on-failure
[Install]
WantedBy=multi-user.target
Чеклист ручного смоука (после автотестов — вручную, в реальном клиенте VK):
- Все 12 секций открываются и рендерятся в личных сообщениях (ЛС), включая пейджер главного меню (2-я и 3-я страницы — «ListGroup»/«Карусель»/«Доступ»).
- Перед тем как тестировать что-либо в беседе: добавьте бота в тестовую беседу и впишите
свой VK user_id в
ADMIN_IDS(bot_dialogs/access_demo.py).AdminOnlyInChatValidatorдействует на весь бот в беседах, не только на секцию «Доступ» (см. её docstring) — без вашего id в списке каждый клик в беседе будет молча проигнорирован (без снекбара, без ошибки в клиенте), и следующий пункт чеклиста будет выглядеть как необъяснимый повальный сбой. - С вашим id в
ADMIN_IDSв беседе открываются все секции, кроме «Нижней клавиатуры» — она ЛС-only (ожидаемо падение сDialogConfigError:TextKeyboardFactoryобщая на весь чат, см. «Ограничения»). «🎠 Карусель» работает и в беседе — навигация в кнопках элементов, нижней клавиатуры нет. - В «Скроллы → Заглушка» (
StubScroll) картинки реально листаются кнопками‹/›. - Медиа, показанное в одном peer (ЛС), корректно переиспользуется (без повторной загрузки и без
ошибок) при показе того же окна в другом peer (беседе) — кросс-peer кэш
MediaResolver. - С id, которого НЕТ в
ADMIN_IDS, клики в беседе тихо игнорируются — это проверяет сам валидатор (не только его настройку из предыдущих пунктов).
Живой VK-смоук карусели (спека §7 — блокирует финальную приёмку Carousel; FakeApi-тесты
проверяют только структуру JSON и диспатч, не поведение самого VK):
-
photo_idбезaccess_keyна message-фото реально показывает картинку в карусели (иначе — нужна публичная загрузка через wall/album вместо message-аплоада). -
messages.editсtemplate— переход карусель → обычное окно и обратно: библиотека на этой границе делаетDELETE_AND_SEND(omit-семантикаtemplateвmessages.editне подтверждена) — проверить, что старое сообщение удаляется, а не остаётся мёртвым дублем. - Навигация внутри карусели (кнопка «☰ Меню» на элементе) уводит в главное меню, а «Выбрать» показывает снекбар, не меняя окна.
- Редактирование карусели с изменением числа элементов (подгрузка другого набора товаров) корректно перестраивает структуру, а не оставляет старые элементы.
Установка
pip install vkbottle-dialog
Для хранения состояния диалогов в Redis (прод-режим, вместо MemoryStorage):
pip install vkbottle-dialog[redis]
from redis.asyncio import Redis
from vkbottle_dialog.storage import RedisStorage
storage = RedisStorage(Redis.from_url("redis://localhost"))
setup_dialogs(bot, dialog, storage=storage)
English
vkbottle-dialog is a declarative dialog/window framework for VK bots built on top of
vkbottle — a VK port of
aiogram-dialog. It provides Dialog/Window
abstractions, navigation widgets, keyboard widgets (Select, Radio, Multiselect, Toggle,
Checkbox, ScrollingGroup), text input handling (TextInput, MessageInput) and pluggable
storage (MemoryStorage, RedisStorage).
Must read before using: because vkbottle does not block events between views, every neighboring
@bot.on.message handler must use the NotInDialog() / InDialog() rules so it doesn't fire on top
of an active dialog. Your community must also have the message_event event and Long Poll API
enabled, or button clicks will be silently dropped.
See the RU section above for the full feature-parity table with aiogram-dialog, v0.1 limitations
(single-instance, no StartMode.NEW_STACK, text-keyboard degradation only in DMs, VK's 24h message
edit window, 10-button inline limit) and installation instructions
(pip install vkbottle-dialog[redis] for Redis-backed storage). Runnable examples live in
examples/.
License: Apache-2.0.
Release files for vkbottle-dialog 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| vkbottle_dialog-0.3.0.tar.gz | 251.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vkbottle_dialog-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 338.1 kB
Release files / vkbottle_dialog-0.3.0.tar.gz
| Download URL | vkbottle_dialog-0.3.0.tar.gz |
|---|---|
| Size | 251.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6883f1737e4b3d27028db3cdae3e9e0b5708aa2d0d45bcc6250c74595ac8cb3d
|
|
BLAKE2b-256 checksum How to use checksums |
5235d24f4b738fca3a976f9d6e4cd09cc91e95a9e51f86a4388baef5ac076229
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.8.15
|
Release files / vkbottle_dialog-0.3.0-py3-none-any.whl
| Download URL | vkbottle_dialog-0.3.0-py3-none-any.whl |
|---|---|
| Size | 87.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8b7e7764a9f7b66882da14b8c5dca7f6c628e658415308a3f04668689e1579bd
|
|
BLAKE2b-256 checksum How to use checksums |
410572dc8e6d72121dc8ab7d929f45db848ff5e03437827ccebe1b674b2f8635
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.8.15
|