This project has been archived by its maintainers, and is no longer receiving any updates.
Bot Framework
Переиспользуемая Python-библиотека для создания ботов на любых платформах с Clean Architecture.
Документация: botframework.smartist.dev
Платформы
| Платформа | Транспорт | Optional dependency |
|---|---|---|
| Telegram | pyTelegramBotAPI (polling / webhooks) | bot-framework[telegram] |
| Facebook Messenger | Webhooks (FastAPI + uvicorn) | bot-framework[facebook] |
| Max (dev.max.ru) | Long polling (httpx) | bot-framework[max] |
Бизнес-логика (flows, roles, phrases) не зависит от платформы — переключение между мессенджерами требует только замены MessageCore.
Установка
pip install bot-framework[all] # все платформы + postgres + redis
pip install bot-framework[telegram] # только Telegram
pip install bot-framework[max] # только Max
pip install bot-framework[facebook] # только Facebook
Быстрый старт
Telegram
from pathlib import Path
from bot_framework.app import BotApplication
app = BotApplication(
bot_token="YOUR_BOT_TOKEN",
database_url="postgres://user:pass@localhost/dbname",
redis_url="redis://localhost:6379/0",
roles_json_path=Path("data/roles.json"),
phrases_json_path=Path("data/phrases.json"),
)
app.run()
Max
from bot_framework.platform.max import MaxMessageCore
core = MaxMessageCore(token="YOUR_MAX_BOT_TOKEN")
# Регистрация handlers — так же, как для Telegram
core.callback_handler_registry.register(my_callback_handler)
core.message_handler_registry.register(my_message_handler, commands=["start"])
core.run() # запускает long polling
Facebook Messenger
from bot_framework.platform.facebook import FacebookMessageCore
core = FacebookMessageCore(
page_access_token="YOUR_PAGE_TOKEN",
verify_token="YOUR_VERIFY_TOKEN",
)
core.callback_handler_registry.register(my_callback_handler)
core.message_handler_registry.register(my_message_handler, commands=["start"])
core.run(host="0.0.0.0", port=8000) # запускает webhook-сервер
Полный пример: flow с шагами, check_roles и factory
Типичная структура flow:
src/flows/registration_flow/
├── factory.py # Сборка зависимостей, создание Flow
├── handlers/
│ ├── start_registration_handler.py # Запуск flow
│ └── name_input_handler.py # Приём имени → state → flow.route()
├── steps/
│ ├── ask_name_step.py # Проверяет state.name → вызывает presenter
│ └── ask_email_step.py # Проверяет state.email → вызывает presenter
├── presenters/
│ ├── ask_name_presenter.py # Отправка вопроса "Как вас зовут?"
│ ├── ask_email_presenter.py # Отправка вопроса "Ваш email?"
│ └── confirm_presenter.py # Финальное подтверждение
├── entities/
│ └── registration_state.py # Состояние flow
└── repos/
└── redis_registration_state_storage.py
Принцип работы
Handler → записывает данные в State → вызывает flow.route(user)
Flow → итерирует Steps по порядку
Step → проверяет State → если не заполнено, вызывает Presenter → stop
→ если заполнено, return True → next step
- Handler не знает о presenters — только пишет в state и вызывает
flow.route() - Step проверяет своё поле в state и вызывает presenter при необходимости
- Flow задаёт порядок шагов и вызывает
on_completeкогда все шаги пройдены
1. State — состояние flow
# entities/registration_state.py
from pydantic import BaseModel
class RegistrationState(BaseModel):
user_id: int
name: str | None = None
email: str | None = None
2. Steps — шаги flow
Каждый шаг наследует BaseStep. Метод execute() возвращает:
True— шаг завершён, перейти к следующемуFalse— шаг отправил сообщение пользователю (через presenter), ждём ответа
# steps/ask_name_step.py
from bot_framework.domain.flow_management.step_flow import BaseStep
from bot_framework import User
from ..presenters import AskNamePresenter
from ..entities import RegistrationState
class AskNameStep(BaseStep[RegistrationState]):
name = "ask_name"
def __init__(self, presenter: AskNamePresenter) -> None:
self._presenter = presenter
def execute(self, user: User, state: RegistrationState) -> bool:
if state.name is not None:
return True # поле заполнено — следующий шаг
self._presenter.send(chat_id=user.id, language_code=user.language_code)
return False # ждём ввода от пользователя
3. Presenters — отображение
Presenter формирует и отправляет сообщение. Не знает о шагах и handlers:
# presenters/ask_name_presenter.py
from bot_framework import IMessageSender
from bot_framework.domain.language_management.repos.protocols import IPhraseRepo
class AskNamePresenter:
def __init__(
self,
message_sender: IMessageSender,
phrase_repo: IPhraseRepo,
) -> None:
self._message_sender = message_sender
self._phrase_repo = phrase_repo
def send(self, chat_id: int, language_code: str) -> None:
text = self._phrase_repo.get_phrase(
key="registration.ask_name",
language_code=language_code,
)
self._message_sender.send(chat_id=chat_id, text=text)
4. Handlers — обработка ввода пользователя
Handler получает данные от пользователя, записывает в state и вызывает flow.route(user). Handler не вызывает presenters и не знает о шагах — только пишет данные и передаёт управление flow.
Каждый handler использует декоратор @check_roles (callback) или @check_message_roles (message).
Message handler (текстовый ввод):
# handlers/name_input_handler.py
from bot_framework import BotMessage, check_message_roles
from bot_framework.domain.flow_management.step_flow import Flow
from bot_framework.domain.role_management.repos.protocols import IRoleRepo, IUserRepo
from ..entities import RegistrationState
class NameInputHandler:
def __init__(
self,
role_repo: IRoleRepo,
user_repo: IUserRepo,
state_storage: "IStepStateStorage[RegistrationState]",
) -> None:
self.role_repo = role_repo
self.allowed_roles: set[str] | None = None # None = доступно всем
self._user_repo = user_repo
self._state_storage = state_storage
self.flow: Flow[RegistrationState] | None = None
@check_message_roles
def handle(self, message: BotMessage) -> None:
if not message.from_user:
return
state = self._state_storage.get(message.from_user.id)
if state is None:
return
# Только записываем данные в state
state.name = message.text
self._state_storage.save(state)
# Передаём управление flow — он сам вызовет нужный step/presenter
if self.flow:
user = self._user_repo.get_by_id(message.from_user.id)
self.flow.route(user)
Callback handler (запуск flow по кнопке):
# handlers/start_registration_handler.py
from uuid import uuid4
from bot_framework import BotCallback, ICallbackAnswerer, check_roles
from bot_framework.domain.flow_management.step_flow import Flow
from bot_framework.domain.role_management.repos.protocols import IRoleRepo, IUserRepo
from ..entities import RegistrationState
class StartRegistrationHandler:
def __init__(
self,
callback_answerer: ICallbackAnswerer,
role_repo: IRoleRepo,
user_repo: IUserRepo,
) -> None:
self.callback_answerer = callback_answerer
self.role_repo = role_repo
self.allowed_roles: set[str] | None = None
self._user_repo = user_repo
self.flow: Flow[RegistrationState] | None = None
self.prefix = uuid4().hex
@check_roles
def handle(self, callback: BotCallback) -> None:
self.callback_answerer.answer(callback_query_id=callback.id)
user = self._user_repo.get_by_id(callback.user_id)
if self.flow:
self.flow.start(user, source_message=callback.message)
5. Flow — сборка шагов
Flow задаёт порядок шагов и действие по завершении. Внутри flow.route(user) итерирует шаги по порядку — каждый шаг проверяет своё поле в state:
from bot_framework.domain.flow_management.step_flow import Flow
flow = Flow[RegistrationState](
name="registration",
state_factory=lambda user_id: RegistrationState(user_id=user_id),
state_storage=state_storage,
)
flow.add_step(AskNameStep(presenter=ask_name_presenter)) # 1. Имя
flow.add_step(AskEmailStep(presenter=ask_email_presenter)) # 2. Email
flow.on_complete(lambda user, state: confirm_presenter.send(user, state))
6. Factory — сборка всех компонентов
Factory создаёт presenters, steps, flow и handlers. Связывает handlers с flow:
# factory.py
from bot_framework import (
ICallbackAnswerer,
ICallbackHandlerRegistry,
IMessageHandlerRegistry,
IMessageSender,
)
from bot_framework.domain.language_management.repos.protocols import IPhraseRepo
from bot_framework.domain.role_management.repos.protocols import IRoleRepo, IUserRepo
from bot_framework.domain.flow_management.step_flow import Flow, IStepStateStorage
from .steps import AskNameStep, AskEmailStep
from .handlers import StartRegistrationHandler, NameInputHandler
from .presenters import AskNamePresenter, AskEmailPresenter, ConfirmPresenter
from .entities import RegistrationState
class RegistrationFlowFactory:
def __init__(
self,
callback_answerer: ICallbackAnswerer,
message_sender: IMessageSender,
phrase_repo: IPhraseRepo,
role_repo: IRoleRepo,
user_repo: IUserRepo,
state_storage: IStepStateStorage[RegistrationState],
) -> None:
self._callback_answerer = callback_answerer
self._message_sender = message_sender
self._phrase_repo = phrase_repo
self._role_repo = role_repo
self._user_repo = user_repo
self._state_storage = state_storage
self._flow: Flow[RegistrationState] | None = None
self._start_handler: StartRegistrationHandler | None = None
self._name_handler: NameInputHandler | None = None
def _get_flow(self) -> Flow[RegistrationState]:
if self._flow is not None:
return self._flow
ask_name_presenter = AskNamePresenter(
message_sender=self._message_sender,
phrase_repo=self._phrase_repo,
)
ask_email_presenter = AskEmailPresenter(
message_sender=self._message_sender,
phrase_repo=self._phrase_repo,
)
confirm_presenter = ConfirmPresenter(
message_sender=self._message_sender,
phrase_repo=self._phrase_repo,
)
self._flow = Flow[RegistrationState](
name="registration",
state_factory=lambda uid: RegistrationState(user_id=uid),
state_storage=self._state_storage,
)
self._flow.add_step(AskNameStep(presenter=ask_name_presenter))
self._flow.add_step(AskEmailStep(presenter=ask_email_presenter))
self._flow.on_complete(
lambda user, state: confirm_presenter.send(user, state)
)
return self._flow
def _get_start_handler(self) -> StartRegistrationHandler:
if self._start_handler is None:
self._start_handler = StartRegistrationHandler(
callback_answerer=self._callback_answerer,
role_repo=self._role_repo,
user_repo=self._user_repo,
)
self._start_handler.flow = self._get_flow()
return self._start_handler
def _get_name_handler(self) -> NameInputHandler:
if self._name_handler is None:
self._name_handler = NameInputHandler(
role_repo=self._role_repo,
user_repo=self._user_repo,
state_storage=self._state_storage,
)
self._name_handler.flow = self._get_flow()
return self._name_handler
def register_handlers(
self,
callback_registry: ICallbackHandlerRegistry,
message_registry: IMessageHandlerRegistry,
) -> None:
callback_registry.register(self._get_start_handler())
# Регистрация message handlers для текстового ввода
# message_registry.register(self._get_name_handler(), ...)
7. Подключение к BotApplication
from pathlib import Path
from bot_framework.app import BotApplication
app = BotApplication(
bot_token="YOUR_BOT_TOKEN",
database_url="postgres://user:pass@localhost/dbname",
redis_url="redis://localhost:6379/0",
phrases_json_path=Path("data/phrases.json"),
)
factory = RegistrationFlowFactory(
callback_answerer=app.callback_answerer,
message_sender=app.message_sender,
phrase_repo=app.phrase_repo,
role_repo=app.role_repo,
user_repo=app.user_repo,
state_storage=RedisRegistrationStateStorage(redis_url="redis://localhost:6379/0"),
)
factory.register_handlers(
callback_registry=app.callback_handler_registry,
message_registry=app.core.message_handler_registry,
)
app.run()
Flow Stack
Когда нужен Flow Stack
Один flow — это линейная цепочка шагов: шаг 1 → шаг 2 → шаг 3 → завершение. Каждый шаг проверяет одно поле в state и вызывает один presenter.
Но если на каком-то шаге возникает ответвление — например, на шаге «выбор адреса» пользователь нажимает «Добавить новый адрес», и это требует отдельной цепочки шагов (город → улица → дом → квартира) — линейный flow это не покрывает.
В этом случае ответвление оформляется как отдельный flow, и flow соединяются через Flow Stack:
Registration Flow (шаг 1 → шаг 2 → шаг 3)
↓ push("add_address")
Add Address Flow (город → улица → дом)
↓ pop_and_return()
← возврат в Registration Flow на шаг 3
Flow Stack работает как стек вызовов функций: push — входим в дочерний flow, pop_and_return — завершаем его и возвращаемся в родительский.
Правило
- Один flow = одна линейная цепочка шагов (без ветвлений)
- Как только появляется ответвление — выносим его в отдельный flow
- Flow Stack соединяет flow между собой с возможностью возврата
API
from bot_framework.domain.flow_management.services import FlowStackNavigator
from bot_framework.domain.flow_management import FlowRegistry
# Регистрация flow в реестре
registry = FlowRegistry()
registry.register("registration", registration_flow_router)
registry.register("add_address", add_address_flow_router)
# Навигация
navigator = FlowStackNavigator(
storage=redis_flow_stack_storage,
registry=registry,
validator=flow_stack_validator,
)
# Войти в дочерний flow (добавить в стек)
navigator.push(user, "add_address")
# Завершить текущий flow и вернуться к родительскому
navigator.pop_and_return(user)
# Завершить текущий flow без возврата
navigator.terminate(user)
# Очистить весь стек (например, при /start)
navigator.clear_all(user)
Декораторы check_roles
Ограничение доступа к handler по ролям пользователя. Декоратор обязателен для каждого handler.
@check_roles — для callback-обработчиков
from bot_framework import BotCallback, check_roles
class MyHandler:
def __init__(self, role_repo: IRoleRepo, callback_answerer: ICallbackAnswerer):
self.role_repo = role_repo # обязательно
self.callback_answerer = callback_answerer # опционально — показывает alert
self.allowed_roles: set[str] = {"admin"} # None = доступно всем
@check_roles
def handle(self, callback: BotCallback) -> None:
...
@check_message_roles — для message-обработчиков
from bot_framework import BotMessage, check_message_roles
class MyHandler:
def __init__(self, role_repo: IRoleRepo, message_sender: IMessageSender):
self.role_repo = role_repo # обязательно
self.message_sender = message_sender # опционально — отправляет ошибку
self.allowed_roles: set[str] = {"manager"} # None = доступно всем
@check_message_roles
def handle(self, message: BotMessage) -> None:
...
Конфигурация
Роли (data/roles.json)
{
"roles": [
{"name": "admin", "description": "Администратор"},
{"name": "manager", "description": "Менеджер"}
]
}
Фразы (data/phrases.json)
{
"mybot.greeting": {
"ru": "Привет!",
"en": "Hello!"
}
}
Кнопки главного меню
app.add_main_menu_button("mybot.orders", orders_handler)
Ограничение /start по ролям
app.set_start_allowed_roles({"admin", "manager"})
Протоколы сообщений
| Протокол | Метод | Описание |
|---|---|---|
IMessageSender |
send() |
Отправка сообщения |
IMessageReplacer |
replace() |
Редактирование сообщения |
IMessageDeleter |
delete() |
Удаление сообщения |
IDocumentSender |
send_document() |
Отправка файла |
ICallbackAnswerer |
answer() |
Ответ на callback query |
Support Chat (Telegram)
Зеркалирование переписки с пользователем в Telegram-супергруппу с топиками.
app = BotApplication(
bot_token="YOUR_BOT_TOKEN",
database_url="postgres://user:pass@localhost/dbname",
redis_url="redis://localhost:6379/0",
support_chat_id=-1001234567890,
)
Требования: супергруппа с включёнными Topics, бот — админ с правом Manage Topics.
License
MIT
Metadata
Release files for bot-framework 0.9.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| bot_framework-0.9.5.tar.gz | 69.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bot_framework-0.9.5-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 220.0 kB
Release files / bot_framework-0.9.5.tar.gz
| Download URL | bot_framework-0.9.5.tar.gz |
|---|---|
| Size | 69.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ea145a89632e150c892819c6949f37465116df28f184f2af24a1d0c586e0ff88
|
|
BLAKE2b-256 checksum How to use checksums |
6b0c2fcd23757b1d131e915fac3181872ef12ee5f22f4d891be0d8b1762e2303
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / bot_framework-0.9.5-py3-none-any.whl
| Download URL | bot_framework-0.9.5-py3-none-any.whl |
|---|---|
| Size | 150.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b278e0d04abbd4307869bccdac983a86bef4e3a1bc22ae5b0971ae0eba830a6d
|
|
BLAKE2b-256 checksum How to use checksums |
0b5ea99ba2230775e6bc5490fac49b0d6fcd5cde45c8b91a817c3eb2c5ee9b3c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|