跳转至

注册命令

只有在应用注册了命令之后,Discord 才会显示它。bostie 会替你完成这件事:机器人启动时,它会把你定义的命令与 Discord 上保存的命令进行比较,只有存在差异时才发送。

启动时的同步

bot.start() 和 bot.run() 会在网关连接之前进行同步。对于每个作用域(全局或某一个服务器),bostie 会获取已注册的命令及其本地化内容,逐字段比较,若有差异,就用一次请求替换该作用域中的全部命令。日志会显示 commands up to date (global) 或 commands synced (global): added [...], removed [...], changed [...]。

Discord 上存在、但你已不再定义的命令会被移除,除非它属于你从未定义过的类型:没有 @bot.entry_point 的机器人会保留 Discord 为其 Activity 创建的入口点命令。

Bot(auto_sync=False) 会跳过同步。await bot.sync_commands() 会立即替换所有作用域的命令,不论是否有变化,并为每个作用域返回一个 SyncResult,其中包含 added、removed 和 changed 的名称,以及 Discord 分配给斜杠命令的 ids。

作用域

没有设置 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。如果你已经有命令 ID,fmt.command(name, command_id) 可以构建同样的文本。

翻译

命令、命令组、子命令、Option 或 Choice 上的 name_localizations 和 description_localizations 用于翻译某一段文本。翻译函数则可以一次翻译全部文本:

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) 会在同步期间,对每条命令、命令组、子命令和选项的每个名称和描述,以及每个候选项名称,针对每个 Locale 各调用一次 function(text, locale)。返回 None 或原样返回文本时,该语言区域不会添加任何内容;你自己写的本地化优先于该函数。处理函数可以在 ctx.locale 中看到用户的语言,在 ctx.interaction.guild_locale 中看到服务器的语言。

当 Discord 拒绝命令时

被 Discord 以 400 拒绝的同步会引发 CommandSyncError。它的消息每个问题占一行,指明命令、选项路径和 Discord 的消息,error.problems 以 SyncProblem 对象的形式保存这些问题。大多数问题会更早被发现:装饰器在运行时就会检查名称、描述和各项限制。