Регистрация команд¶
Discord показывает команду только после того, как приложение её зарегистрировало. bostie делает это за вас: при запуске бот сравнивает определённые вами команды с теми, что хранит Discord, и отправляет их, только если что-то отличается.
Синхронизация при запуске¶
bot.start() и bot.run() выполняют синхронизацию до подключения к шлюзу. Для каждой области — глобальной или одного сервера — bostie получает зарегистрированные команды вместе с их локализациями, сравнивает их поле за полем и, если они отличаются, заменяет команды области одним запросом. В логе появляется commands up to date (global) или commands synced (global): added [...], removed [...], changed [...].
Команда, которая есть в Discord, но которую вы больше не определяете, удаляется, если только её тип не из тех, что вы вообще не определяете: бот без @bot.entry_point сохраняет команду точки входа, которую Discord создал для его Активности.
Bot(auto_sync=False) пропускает синхронизацию. await bot.sync_commands() прямо сейчас заменяет команды каждой области, изменились они или нет, и возвращает по SyncResult на область с именами, которые были добавлены (added), удалены (removed) и изменены (changed), и с ids, которые Discord присвоил слэш-командам.
Области¶
Команда без guilds глобальная: она работает на каждом сервере, где установлено приложение, и, если это позволяют её contexts, в личных сообщениях. guilds=[...] регистрирует её только на этих серверах.
Пока вы работаете над ботом, регистрируйте всё на тестовом сервере:
import os
import bostie
from bostie import Context, Permissions
dev_guild = int(os.environ.get("DEV_GUILD", "0"))
bot = bostie.Bot(dev_guilds=[dev_guild] if dev_guild else [])
@bot.command
async def about(ctx: Context) -> None:
"""Explain how to use the bot."""
sync = await bot.command_mention("sync")
await ctx.respond(f"Admins can run {sync} after an update.")
@bot.command(default_permissions=Permissions.ADMINISTRATOR, ephemeral=True)
async def sync(ctx: Context) -> None:
"""Send the commands to Discord again."""
results = await bot.sync_commands()
changed = sum(len(r.added) + len(r.removed) + len(r.changed) for r in results)
await ctx.respond(f"{changed} commands changed.")
Bot(dev_guilds=[...]) регистрирует каждую команду, у которой нет собственных guilds, на этих серверах вместо глобальной регистрации. Команда точки входа всегда глобальная, поэтому синхронизация пропускает её и выдаёт предупреждение.
Одно имя в нескольких областях¶
import bostie
from bostie import Context
bot = bostie.Bot()
EVENTS_GUILD = 1335172792320131073
@bot.command(name="rules")
async def rules(ctx: Context) -> None:
"""Show the rules of the bot."""
await ctx.respond("Be kind and stay on topic.")
@bot.command(name="rules", guilds=[EVENTS_GUILD])
async def event_rules(ctx: Context) -> None:
"""Show the rules of this server's events."""
await ctx.respond("Sign up before the event starts.")
Команды могут иметь одно имя, если ни на одном сервере нет двух таких команд: глобальная /rules и /rules для одного сервера или по /rules на каждый сервер. Каждая регистрируется в своей области, а взаимодействие запускает команду, зарегистрированную на его сервере, а если такой нет — глобальную. На этом сервере Discord показывает и глобальную, и серверную команду. При dev_guilds собственная команда сервера занимает место глобальной на этом сервере. Две команды с одним именем, чьи серверы пересекаются, или две глобальные вызывают CommandDefinitionError при регистрации второй. Команды пользователя и сообщения подчиняются тем же правилам.
Синхронизация затрагивает только области, в которых есть команды. Если вы уберёте dev_guilds, команды останутся на тестовом сервере рядом с новыми глобальными; их удаляет await bot.rest.commands.bulk_overwrite_guild(bot.application_id, guild_id, []).
Где можно использовать команды¶
contexts и integration_types действуют на глобальные команды. Задайте их на команде, роутере или боте:
contexts:InteractionContextType.GUILD,BOT_DM(личные сообщения с ботом) иPRIVATE_CHANNEL(другие личные сообщения и групповые ЛС — для приложения, установленного пользователем).integration_types:ApplicationIntegrationType.GUILD_INSTALLиUSER_INSTALL.
Bot(contexts=[...], integration_types=[...]) задаёт значение по умолчанию для каждой глобальной команды, у которой нет собственного. Если они нигде не заданы, bostie не отправляет ни одно из этих полей, и Discord применяет свои значения по умолчанию; для integration_types это контексты установки, настроенные для приложения. Удаление настройки, которую вы задавали, считается изменением, поэтому следующая синхронизация снова даст команде значения Discord по умолчанию.
Упоминания команд¶
await bot.command_mention("roles color set") возвращает </roles color set:ID>, что Discord показывает как кликабельную команду. Метод использует ID из последней синхронизации или один раз на область спрашивает Discord. С guild_id= команда, зарегистрированная на этом сервере, важнее глобальной с тем же именем. Неизвестное имя вызывает LookupError, а вызов до запуска бота — StateError. fmt.command(name, command_id) строит тот же текст из ID, который у вас уже есть.
Переводы¶
name_localizations и description_localizations на команде, группе, подкоманде, Option или Choice переводят один текст. Функция перевода переводит их все:
import bostie
from bostie import Context, Locale
TRANSLATIONS = {
Locale.FRENCH: {"greet": "saluer", "Greet someone": "Saluer quelqu'un"},
Locale.GERMAN: {"greet": "grüßen", "Greet someone": "Jemanden grüßen"},
}
def translate(text: str, locale: Locale) -> str | None:
return TRANSLATIONS.get(locale, {}).get(text)
bot = bostie.Bot(translate=translate)
@bot.command
async def greet(ctx: Context) -> None:
"""Greet someone."""
await ctx.respond(f"Hello! Your client is set to {ctx.locale.value}.")
Bot(translate=function) во время синхронизации вызывает function(text, locale) для каждого имени и описания каждой команды, группы, подкоманды и параметра, а также для каждого имени варианта — по разу на каждую Locale. Если функция вернула None или текст без изменений, для этой локали ничего не добавляется, а локализация, которую вы написали сами, важнее функции. Обработчик видит язык пользователя в ctx.locale, а язык сервера — в ctx.interaction.guild_locale.
Когда Discord отклоняет команды¶
Синхронизация, которую Discord отклоняет с кодом 400, вызывает CommandSyncError. В его сообщении по строке на каждую проблему с именем команды, путём к параметру и сообщением Discord, а error.problems хранит их как объекты SyncProblem. Большинство проблем ловится раньше: декораторы проверяют имена, описания и лимиты при выполнении.