Роутеры и модули¶
Router хранит команды, слушатели, маршруты, задачи, проверки, хуки и хуки ошибок, как бот, но ботом не является. Роутеры разбивают бота на модули, задают группе команд общие настройки и могут заменяться, пока бот работает. Бот сам является роутером, поэтому всё на этой странице работает и для него.
import logging
import bostie
from bostie import (
ComponentContext,
Context,
ErrorContext,
Forbidden,
InteractionContextType,
Permissions,
ResolvedMember,
Router,
ui,
)
from bostie.checks import bot_has_permissions
from bostie.events import BanAdd
log = logging.getLogger("my-bot")
moderation = Router(
default_permissions=Permissions.BAN_MEMBERS,
contexts=[InteractionContextType.GUILD],
ephemeral=True,
)
moderation.check(bot_has_permissions(Permissions.BAN_MEMBERS))
@moderation.command
async def ban(ctx: Context, member: ResolvedMember) -> None:
"""Ban a member after a confirmation."""
confirm = ui.button("Ban", custom_id=confirmed.id(user_id=member.id))
await ctx.respond(f"Ban {member.mention}?", components=[ui.row(confirm)])
@moderation.on_component("ban:{user_id:int}")
async def confirmed(ctx: ComponentContext, user_id: int) -> None:
if ctx.guild_id is not None:
reason = f"Requested by {ctx.user.username}"
await moderation.bot.rest.bans.create(ctx.guild_id, user_id, reason=reason)
await ctx.update(content="Banned.", components=[])
@moderation.listen
async def banned(event: BanAdd) -> None:
log.info("%s was banned from guild %d", event.user.username, event.guild_id)
@moderation.on_error(Forbidden)
async def cannot_ban(ctx: ErrorContext, error: Forbidden) -> None:
if isinstance(ctx, Context | ComponentContext):
await ctx.respond("That member is above the bot.")
bot = bostie.Bot()
bot.include(moderation)
У роутера есть все декораторы бота: command, group, user_command, message_command, entry_point, listen, listen_raw, on_component, on_modal, every, check, before_invoke, after_invoke и on_error. bot.include(router) добавляет всё это в бота, а также всё, что будет зарегистрировано на роутере позже. Внутри его обработчиков router.bot — это бот, в который он включён; до включения он вызывает StateError.
Роутеры вкладываются: router.include(other) работает так же, и включение внешнего роутера в бота приносит оба.
Значения по умолчанию¶
Router(...) принимает именованными аргументами значения по умолчанию для команд и маршрутов, зарегистрированных на нём:
| Значение по умолчанию | Применяется к |
|---|---|
ephemeral |
ответам его команд и маршрутов |
auto_defer |
его командам и маршрутам |
default_permissions |
его командам верхнего уровня |
nsfw |
его командам верхнего уровня |
contexts, integration_types |
его глобальным командам |
Команда или маршрут сначала использует собственную настройку, затем значения по умолчанию своего роутера, затем каждого роутера, который его включает, от ближайшего наружу, и наконец бота. В примере /ban скрыта от участников без права «Банить участников», доступна только на серверах и отвечает эфемерно, так что видит ответ и может нажать кнопку только модератор, который её запустил.
Проверки, хуки «до» и хуки «после» роутера применяются к его обработчикам; выполняются и проверки и хуки роутеров, которые его включают, начиная с самого внешнего. Хуки ошибок ищутся от ближайшего роутера наружу, как объясняет раздел «Проверки, хуки и ошибки».
Модули¶
bot.include("greetings") импортирует модуль greetings и включает Router, названный в нём router. Модуль без него вызывает TypeError.
bot.exclude("greetings") удаляет всё, что добавил роутер, отменяет его задачи и заставляет Python забыть модуль и его подмодули, так что при повторном включении файл импортируется заново. bot.reload("greetings") делает и то и другое одним вызовом: если новый импорт или включение не удаётся, старый роутер остаётся включённым, а ошибка выбрасывается. Перезагрузка сразу меняет обработчики; если изменились определения команд, вызовите после неё await bot.sync_commands(), как делает модульный пример в своей команде /reload.
exclude и reload также принимают объект Router — для роутеров, которые не являются router какого-либо модуля.
Конфликты¶
Две команды с одинаковым именем, две команды пользователя или сообщения с одинаковым именем, два маршрута с одинаковым шаблоном или одна функция, дважды зарегистрированная как задача, вызывают исключение при добавлении второй, в том числе когда её приносит роутер. Роутер можно включить только один раз и никогда — в самого себя.
Что внутри¶
router.commands, router.context_menus, router.component_routes, router.modal_routes, router.tasks и router.listeners хранят зарегистрированное: команды — по имени и серверам, например ("rules", ()) для глобальной, команды пользователя и сообщения — по типу, имени и серверам, маршруты — по шаблону, задачи — по функции, а слушателей — по событию. router.walk_commands() выдаёт каждую слэш-команду и подкоманду, у которой есть обработчик. router.match_component(custom_id) и router.match_modal(custom_id) возвращают маршрут, до которого доходит custom id, и значения его плейсхолдеров или None, а router.find_error_handlers(error) выдаёт хуки ошибок, которые обработали бы исключение, начиная с ближайшего.