Slash Commands¶
Slash Commands Primer¶
As Discord has now decided to ban bots from reading messages without the intent enabled, you should now be using slash commands wherever possible.
Read more about this on the discord documentation yourself.
You should at least have a basic idea of:
interactions
the
applications.commands
OAuth scope
For example slash command code see the examples directory
Creating a Basic Slash Command¶
Your first slash command can be written very easily:
# Import the command handler
import lightbulb
# Instantiate a Bot instance
bot = lightbulb.Bot(token="your_token_here", prefix="your_prefix_here")
# Create a custom slash command class and implement
# the abstract methods
class Echo(lightbulb.SlashCommand):
description = "Repeats your input."
# Options
text: str = lightbulb.Option("Text to repeat")
async def callback(self, context):
await context.respond(context.options.text)
# Add the slash command to the bot
bot.add_slash_command(Echo)
# Run the bot
# Note that this is blocking meaning no code after this line will run
# until the bot is shut off
bot.run()
Creating a Slash Command Group¶
Creating a slash command group is very similar to creating a normal slash command in that all it requires is for you to
create your own subclass of the base class SlashCommandGroup
and implement all the
required abstract methods.
To add a subcommand to your group, you must create your own subclass of the base class
SlashSubCommand
and, as you’ve probably guessed, implement all the required
abstract methods. In order to link it to the slash command group, you must decorate the subcommand with the decorator
subcommand
. An example can be seen below of a very simple
slash command group:
# Import the command handler
import lightbulb
# Instantiate a Bot instance
bot = lightbulb.Bot(token="your_token_here", prefix="your_prefix_here")
class Foo(lightbulb.SlashCommandGroup):
description = "Test slash command group."
@Foo.subcommand()
class Bar(lightbulb.SlashSubCommand):
description = "Test subcommand."
# Options
baz: str = lightbulb.Option("Test subcommand option.")
async def callback(self, context):
await context.respond(context.options.baz)
Creating a Slash Command Subgroup¶
To create a slash command subgroup, you must first create a slash command group as seen in the previous
section. The SlashCommandGroup
class provides a subgroup
decorator that
should be used in place of the subcommand
decorator when adding a subgroup to the parent group. The subgroup
should inherit from the SlashSubGroup
base class.
Adding a subcommand to the subgroup is the exact same as adding a subcommand to the parent group as was seen in the stage above. Below is a simple example of a subgroup implementation, with some of the implementation left out as you may refer to the previous sections for more details.
# Import the command handler
import lightbulb
# Instantiate a Bot instance
bot = lightbulb.Bot(token="your_token_here", prefix="your_prefix_here")
class Foo(lightbulb.SlashCommandGroup):
...
@Foo.subgroup()
class Bar(lightbulb.SlashSubGroup):
description = "Test subgroup."
@Bar.subcommand()
class Baz(lightbulb.SlashSubCommand):
...
Slash Command Option Typehints¶
The defining of slash command options uses type-hinting in order to infer the type to send discord. All the
permitted types can be seen below. Note that if you wrap the type in a typing.Optional
then the option
will be set as not-required unless specified otherwise in the associated Option
object.
Example:
text: str = Option("string option")
number: typing.Optional[int] = Option("non-required integer option")
user: hikari.User = Option("user option")
choice: str = Option("option with choices", choices=["foo", "bar", "baz"])
foo: typing.Optional[str] = Option("option with default", default="foo")
Permitted types:
str
(hikari.OptionType.STRING
)int
(hikari.OptionType.INTEGER
)bool
(hikari.OptionType.BOOLEAN
)float
(hikari.OptionType.FLOAT
)hikari.User
(hikari.OptionType.USER
)hikari.TextableChannel
(hikari.OptionType.CHANNEL
)hikari.Role
(hikari.OptionType.ROLE
)hikari.Snowflake
(hikari.OptionType.MENTIONABLE
)
See also
Discord’s documentation on command option types.
Slash Command Checks¶
You can use some of the lightbulb built-in checks with slash commands. Only the SlashCommand
and SlashSubCommand
classes support checks. The checks will be run prior to the command’s callback being invoked and, similar to message command
checks, will raise a CheckFailure`
exception if they do not pass. Checks are defined as
a sequence of Check
objects defined in the slash command class as seen below.
import lightbulb
class OwnerOnlySlashCommand(lightbulb.SlashCommand):
name = "foo"
description = "bar"
# Defining the list of checks
# You can use any built-in checks, as long as it is explicitly
# stated in the docstring that slash commands are supported.
# You can also use custom checks by wrapping the check function
# in a Check object
checks = [
lightbulb.owner_only, # built-in check
lightbulb.Check(some_check_function), # custom check
]
Slash Command Cooldowns¶
It is very easy to implement cooldowns for your slash commands. All you need to do is assign a cooldown manager
to the class variable cooldown_manager
. All of lightbulb’s built-in cooldown buckets work with slash commands.
import lightbulb
class Foo(lightbulb.SlashCommand):
...
# Static cooldown
cooldown_manager = lightbulb.CooldownManager(5, 1, lightbulb.UserBucket)
# Dynamic cooldown
cooldown_manager = lightbulb.CooldownManager(callback=some_function_that_returns_a_Bucket)
API Reference¶
Error
The inclusion of slash commands within a Plugin class is not supported.
Top level classes¶
- class lightbulb.slash_commands.Option(description: str, name: typing.Optional[str] = None, required: typing.Optional[bool] = None, choices: typing.Optional[typing.Sequence[str, int, float, hikari.Snowflakeish, hikari.CommandChoice]] = None, default: typing.Optional[typing.Any] = None)¶
Bases:
object
Dataclass representing a command option.
Examples
Usage in a slash command:
class Echo(SlashCommand): description = "Repeats the input" # Options text: str = Option("Text to repeat") async def callback(self, context): await context.respond(context.options.text)
- choices: typing.Optional[typing.Sequence[str, int, float, hikari.Snowflakeish, hikari.CommandChoice]] = None¶
Sequence of the choices for the option. Defaults to
None
. Ifhikari.CommandChoice
objects are not provided then one will be built from the choice with the name set to the string representation of the value.
- default: typing.Optional[typing.Any] = None¶
The default value for the option. Defaults to
None
.
- class lightbulb.slash_commands.SlashCommand(*args, **kwargs)¶
Bases:
lightbulb.slash_commands.commands.BaseSlashCommand
,lightbulb.slash_commands.commands.WithGetOptions
,lightbulb.slash_commands.commands.WithAsyncCallback
,lightbulb.slash_commands.commands.WithCreationMethods
,lightbulb.slash_commands.commands.WithGetCommand
,lightbulb.slash_commands.commands.WithChecks
,abc.ABC
Abstract base class for top level slash commands. All slash commands that are not groups should inherit from this class.
All abstract methods must be implemented by your custom slash command class. A list of the abstract methods and properties you are required to implement for this class can be seen below:
description
(class variable)callback
(instance method)
- async create(app: Union[hikari.guilds.PartialApplication, hikari.snowflakes.Snowflake, int], guild_id: Optional[Union[hikari.snowflakes.Snowflake, int]] = None) hikari.commands.Command ¶
Creates the command for a specific guild, or globally if no guild ID is given.
- Parameters
app (hikari.SnowflakeishOr[hikari.PartialApplication]) – The application to add the command to.
guild_id (Optional[
int
]) – The ID of the guild to create the command for.
- Returns
The command object created.
- Return type
hikari.Command
- get_options() Sequence[hikari.commands.CommandOption] ¶
Get the options for the command.
- Returns
Options for the command.
- Return type
Sequence[
hikari.CommandOption
]
- class lightbulb.slash_commands.SlashCommandContext(bot: command_handler.Bot, interaction: hikari.CommandInteraction, command: commands.BaseSlashCommand)¶
Bases:
object
The context a slash command was invoked under.
- Parameters
bot (
Bot
) – The bot instance that received the slash command.interaction (
hikari.CommandInteraction
) – The interaction for this slash command invocation.command (
SlashCommand
) – TheSlashCommand
object that was invoked.
- property author: hikari.users.User¶
Alias for
SlashCommandContext.user
.
- bot¶
The bot instance that received the slash command.
- property channel_id: hikari.snowflakes.Snowflake¶
The channel ID that the slash command was invoked in.
- property command: hikari.commands.Command¶
The
hikari.Command
object for this specific context.
- property command_id¶
The ID of the slash command being invoked.
- async delete_response() None ¶
Alias for
hikari.CommandInteraction.delete_initial_response
.- Returns
None
- async edit_response(*args, **kwargs) None ¶
Alias for
hikari.CommandInteraction.edit_initial_response
. See Hikari documentation for args and kwargs you can pass in.- Returns
None
- async fetch_response() hikari.messages.Message ¶
Alias for
hikari.CommandInteraction.fetch_initial_response
.- Returns
The message object for the initial response.
- Return type
hikari.Message
- async followup(content: undefined.UndefinedOr[typing.Any] = UNDEFINED, *, username: undefined.UndefinedOr[str] = UNDEFINED, avatar_url: undefined.UndefinedOr[str] = UNDEFINED, tts: undefined.UndefinedOr[bool] = UNDEFINED, attachment: undefined.UndefinedOr[files_.Resourceish] = UNDEFINED, attachments: undefined.UndefinedOr[typing.Sequence[files_.Resourceish]] = UNDEFINED, component: undefined.UndefinedOr[special_endpoints.ComponentBuilder] = UNDEFINED, components: undefined.UndefinedOr[typing.Sequence[special_endpoints.ComponentBuilder]] = UNDEFINED, embed: undefined.UndefinedOr[embeds_.Embed] = UNDEFINED, embeds: undefined.UndefinedOr[typing.Sequence[embeds_.Embed]] = UNDEFINED, mentions_everyone: undefined.UndefinedOr[bool] = UNDEFINED, user_mentions: undefined.UndefinedOr[typing.Union[snowflakes.SnowflakeishSequence[users_.PartialUser], bool]] = UNDEFINED, role_mentions: undefined.UndefinedOr[typing.Union[snowflakes.SnowflakeishSequence[guilds_.PartialRole], bool]] = UNDEFINED, flags: typing.Union[undefined.UndefinedType, int, messages_.MessageFlag] = UNDEFINED) messages_.Message ¶
Execute the webhook to create a message.
- Parameters
content (hikari.undefined.UndefinedOr[typing.Any]) –
If provided, the message contents. If hikari.undefined.UNDEFINED, then nothing will be sent in the content. Any other value here will be cast to a builtins.str.
If this is a hikari.embeds.Embed and no embed kwarg is provided, then this will instead update the embed. This allows for simpler syntax when sending an embed alone.
Likewise, if this is a hikari.files.Resource, then the content is instead treated as an attachment if no attachment and no attachments kwargs are provided.
username (hikari.undefined.UndefinedOr[builtins.str]) – If provided, the username to override the webhook’s username for this request.
avatar_url (hikari.undefined.UndefinedOr[builtins.str]) – If provided, the url of an image to override the webhook’s avatar with for this request.
tts (hikari.undefined.UndefinedOr[bool]) – If provided, whether the message will be sent as a TTS message.
attachment (hikari.undefined.UndefinedOr[hikari.files.Resourceish]) – If provided, the message attachment. This can be a resource, or string of a path on your computer or a URL.
attachments (hikari.undefined.UndefinedOr[typing.Sequence[hikari.files.Resourceish]]) – If provided, the message attachments. These can be resources, or strings consisting of paths on your computer or URLs.
component (hikari.undefined.UndefinedOr[hikari.api.special_endpoints.ComponentBuilder]) – If provided, builder object of the component to include in this message.
components (hikari.undefined.UndefinedOr[typing.Sequence[hikari.api.special_endpoints.ComponentBuilder]]) – If provided, a sequence of the component builder objects to include in this message.
embed (hikari.undefined.UndefinedOr[hikari.embeds.Embed]) – If provided, the message embed.
embeds (hikari.undefined.UndefinedOr[typing.Sequence[hikari.embeds.Embed]]) – If provided, the message embeds.
mentions_everyone (hikari.undefined.UndefinedOr[builtins.bool]) – If provided, whether the message should parse @everyone/@here mentions.
user_mentions (hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], builtins.bool]]) – If provided, and builtins.True, all mentions will be parsed. If provided, and builtins.False, no mentions will be parsed. Alternatively this may be a collection of hikari.snowflakes.Snowflake, or hikari.users.PartialUser derivatives to enforce mentioning specific users.
role_mentions (hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], builtins.bool]]) – If provided, and builtins.True, all mentions will be parsed. If provided, and builtins.False, no mentions will be parsed. Alternatively this may be a collection of hikari.snowflakes.Snowflake, or hikari.guilds.PartialRole derivatives to enforce mentioning specific roles.
flags (typing.Union[hikari.undefined.UndefinedType, builtins.int, hikari.messages.MessageFlag]) –
The flags to set for this webhook message.
- !!! warning
As of writing this can only be set for interaction webhooks and the only settable flag is EPHEMERAL; this field is just ignored for non-interaction webhooks.
warning (!!!) – As of writing, username and avatar_url are ignored for interaction webhooks.
- Returns
The created message object.
- Return type
- Raises
hikari.errors.NotFoundError – If the current webhook is not found.
hikari.errors.BadRequestError – This can be raised if the file is too large; if the embed exceeds the defined limits; if the message content is specified only and empty or greater than 2000 characters; if neither content, file or embeds are specified. If any invalid snowflake IDs are passed; a snowflake may be invalid due to it being outside of the range of a 64 bit integer.
hikari.errors.UnauthorizedError – If you pass a token that’s invalid for the target webhook.
builtins.ValueError – If either ExecutableWebhook.token is builtins.None or more than 100 unique objects/entities are passed for role_mentions or user_mentions or if `token is not available.
builtins.TypeError – If both attachment and attachments are specified.
- get_channel() Optional[hikari.channels.GuildChannel] ¶
The cached channel that the command was invoked in, or
None
if not found.
- get_guild() Optional[hikari.guilds.GatewayGuild] ¶
The cached guild that the command was invoked in, or
None
if not found.
- property guild_id: Optional[hikari.snowflakes.Snowflake]¶
The guild ID that the slash command was invoked in, or
None
if in DMs.
- initial_response_sent¶
Whether or not an initial response has been sent for the interaction.
- property interaction: hikari.interactions.command_interactions.CommandInteraction¶
The interaction for this slash command invocation.
- property member: Optional[hikari.interactions.base_interactions.InteractionMember]¶
The
hikari.InteractionMember
object for the user that invoked the slash command, orNone
if in DMs.
- property options: lightbulb.slash_commands.context.SlashCommandOptionsWrapper¶
The values for the slash command’s various options.
- Returns
- property raw_options: Mapping[str, hikari.interactions.command_interactions.CommandInteractionOption]¶
A mapping of
str
option name tohikari.CommandInteractionOption
containing the options the command was invoked with.
- property resolved: Optional[hikari.interactions.command_interactions.ResolvedOptionData]¶
Optional[
hikari.ResolvedOptionData
] objects resolved for the provided command options.
- async respond(content: hikari.undefined.UndefinedType = UNDEFINED, **kwargs: hikari.undefined.UndefinedType) hikari.messages.Message ¶
Alias for
hikari.CommandInteraction.create_initial_response
but without having to pass in theresponse_type
(it is set tohikari.ResponseType.MESSAGE_CREATE
) See Hikari documentation for kwargs you can pass in. You can override the response type if you want to by setting the kwargresponse_type
manually.- Parameters
content (
hikari.UndefinedType
) – The message content, generallystr
.- Keyword Arguments
**kwargs – Kwargs passed to
hikari.CommandInteraction.create_initial_response
orhikari.CommandInteraction.execute
if you have already responded to the interaction using this method.- Returns
The message object for this response.
- Return type
hikari.Message
Note
This will be a shortcut to
followup
if you have already responded to the interaction using this method.
- property user: hikari.users.User¶
The user object for the user that invoked the slash command.
- class lightbulb.slash_commands.SlashCommandGroup(bot: command_handler.Bot)¶
Bases:
lightbulb.slash_commands.commands.BaseSlashCommand
,lightbulb.slash_commands.commands.WithGetOptions
,lightbulb.slash_commands.commands.WithCreationMethods
,lightbulb.slash_commands.commands.WithGetCommand
,abc.ABC
Abstract base class for slash command groups. All slash command groups should inherit from this class.
All abstract methods must be implemented by your custom slash command group class. A list of the abstract methods and properties you are required to implement for this class can be seen below:
description
(class variable)
- async create(app: Union[hikari.guilds.PartialApplication, hikari.snowflakes.Snowflake, int], guild_id: Optional[Union[hikari.snowflakes.Snowflake, int]] = None) hikari.commands.Command ¶
Creates the command for a specific guild, or globally if no guild ID is given.
- Parameters
app (hikari.SnowflakeishOr[hikari.PartialApplication]) – The application to add the command to.
guild_id (Optional[
int
]) – The ID of the guild to create the command for.
- Returns
The command object created.
- Return type
hikari.Command
- get_options() Sequence[hikari.commands.CommandOption] ¶
Get the options for the command.
- Returns
Options for the command.
- Return type
Sequence[
hikari.CommandOption
]
- classmethod subcommand() Callable[[Type[lightbulb.slash_commands.commands.SlashSubCommand]], Type[lightbulb.slash_commands.commands.SlashSubCommand]] ¶
Decorator which registers a subcommand to this slash command group.
- classmethod subgroup() Callable[[Type[lightbulb.slash_commands.commands.SlashSubGroup]], Type[lightbulb.slash_commands.commands.SlashSubGroup]] ¶
Decorator which registers a subgroup to this slash command group.
- class lightbulb.slash_commands.SlashCommandOptionsWrapper(options: Mapping[str, hikari.interactions.command_interactions.CommandInteractionOption], defaults: Mapping[str, Any])¶
Bases:
object
A wrapper class for
options
which allows the user to access the option values through a more user_friendly, attribute syntax.option_name
will return either the option’s value, orNone
if the option was not provided in the slash command invocation.This is accessible through
options
- class lightbulb.slash_commands.SlashSubCommand(*args, **kwargs)¶
Bases:
lightbulb.slash_commands.commands.BaseSlashCommand
,lightbulb.slash_commands.commands.WithAsOption
,lightbulb.slash_commands.commands.WithAsyncCallback
,lightbulb.slash_commands.commands.WithChecks
,abc.ABC
Abstract base class for slash subcommands. All slash subcommands should inherit from this class.
All abstract methods must be implemented by your custom slash subcommand class. A list of the abstract methods and properties you are required to implement for this class can be seen below:
description
(class variable)callback
(instance method)
- as_option() hikari.commands.CommandOption ¶
Creates and returns the appropriate
hikari.CommandOption
representation for this subcommand class.- Returns
The
CommandOption
version of the subcommand class.- Return type
hikari.CommandOption
- class lightbulb.slash_commands.SlashSubGroup(bot: command_handler.Bot)¶
Bases:
lightbulb.slash_commands.commands.BaseSlashCommand
,lightbulb.slash_commands.commands.WithAsOption
,abc.ABC
Abstract base class for slash subgroups. All slash subgroups should inherit from this class.
All abstract methods must be implemented by your custom slash subgroup class. A list of the abstract methods and properties you are required to implement for this class can be seen below:
description
(class variable)
- as_option() hikari.commands.CommandOption ¶
Creates and returns the appropriate
hikari.CommandOption
representation for this subcommand class.- Returns
The
CommandOption
version of the subcommand class.- Return type
hikari.CommandOption
- classmethod subcommand() Callable[[Type[lightbulb.slash_commands.commands.SlashSubCommand]], Type[lightbulb.slash_commands.commands.SlashSubCommand]] ¶
Decorator which registers a subcommand to this slash command group.
Template classes¶
- class lightbulb.slash_commands.BaseSlashCommand(bot: command_handler.Bot)¶
Bases:
abc.ABC
Abstract base class for slash command-like classes.
- Parameters
bot (
Bot
) – The bot instance the command will be added to.
- bot¶
The bot instance that the slash command is registered to.
- class lightbulb.slash_commands.WithAsOption¶
Bases:
abc.ABC
- abstract as_option() hikari.commands.CommandOption ¶
Creates and returns the appropriate
hikari.CommandOption
representation for this subcommand class.- Returns
The
CommandOption
version of the subcommand class.- Return type
hikari.CommandOption
- class lightbulb.slash_commands.WithAsyncCallback¶
Bases:
abc.ABC
- abstract async callback(context: context_.SlashCommandContext) None ¶
The slash command callback method. This method will be called whenever the slash command is invoked.
If the slash command being invoked is a subcommand then the
options
attribute will have been replaced by the options that the subcommand was invoked with, instead of those that the command as a whole was invoked with (they can still be accessed throughoptions
if necessary).- Parameters
context (
SlashCommandContext
) – The context that the slash command was invoked under.- Returns
None
- class lightbulb.slash_commands.WithCreationMethods¶
Bases:
abc.ABC
- async auto_create(app: Union[hikari.guilds.PartialApplication, hikari.snowflakes.Snowflake, int]) None ¶
Creates the command in the appropriate scopes (guilds, global) automatically.
- Parameters
app (hikari.SnowflakeishOr[hikari.PartialApplication]) – The application to add the command to.
- Returns
None
- async auto_delete(app: Union[hikari.guilds.PartialApplication, hikari.snowflakes.Snowflake, int]) None ¶
Deletes the command from the appropriate scopes (guilds, global) automatically.
- Parameters
app (hikari.SnowflakeishOr[hikari.PartialApplication]) – The application to remove the command from.
- Returns
None
- abstract async create(app: Union[hikari.guilds.PartialApplication, hikari.snowflakes.Snowflake, int], guild_id: Optional[Union[hikari.snowflakes.Snowflake, int]] = None) hikari.commands.Command ¶
Creates the command for a specific guild, or globally if no guild ID is given.
- Parameters
app (hikari.SnowflakeishOr[hikari.PartialApplication]) – The application to add the command to.
guild_id (Optional[
int
]) – The ID of the guild to create the command for.
- Returns
The command object created.
- Return type
hikari.Command
- async delete(app: Union[hikari.guilds.PartialApplication, hikari.snowflakes.Snowflake, int], guild_id: Optional[Union[hikari.snowflakes.Snowflake, int]] = None) None ¶
Deletes the command for a specific guild, or globally if no guild ID is given.
- Parameters
app (hikari.SnowflakeishOr[hikari.PartialApplication]) – The application to delete the command from.
guild_id (Optional[hikari.Snowflakeish]) – The ID of the guild to delete the command for.
- Returns
None
- class lightbulb.slash_commands.WithGetCommand¶
Bases:
abc.ABC
- get_command(guild_id: Optional[Union[hikari.snowflakes.Snowflake, int]] = None) Optional[hikari.commands.Command] ¶
Gets the
hikari.Command
instance of this command class for a givenguild_id
, or the global instance if noguild_id
is provided. ReturnsNone
if no instance is found for the givenguild_id
.- Parameters
guild_id (Optional[
hikari.Snowflakeish
) – The guild ID to get thehikari.Command
instance for.- Returns
Command instance for that guild, or
None
if no instance exists.- Return type
Optional[
hikari.Command
]
- class lightbulb.slash_commands.WithGetOptions¶
Bases:
abc.ABC
- property enabled_guilds: Optional[Union[hikari.snowflakes.Snowflake, int, Sequence[Union[hikari.snowflakes.T, hikari.snowflakes.Snowflake, int]]]]¶
The guilds that the slash command is enabled in. If
None
or an empty sequence, the command will be added as a global command. Defaults tolightbulb.command_handler.Bot.default_enabled_guilds
, which is an empty list unless otherwise specified.- Returns
- Guilds that the command
is enabled in, or
None
or empty sequence if the command is global.
- Return type
Optional[Union[
hikari.Snowflakeish
,hikari.SnowflakeishSequence
]]
- abstract get_options() Sequence[hikari.commands.CommandOption] ¶
Get the options for the command.
- Returns
Options for the command.
- Return type
Sequence[
hikari.CommandOption
]
Appendix¶
The method hikari-lightbulb uses for defining slash commands is very different to the method used to define message/prefix commands. If you would prefer to use a similar decorator-chain definition style for slash commands then you can use filament.
A short filament example can be seen below:
import filament
import lightbulb
bot = lightbulb.Bot(...)
@filament.with_option(type=str, name="text", description="the text to repeat")
@filament.slash_command(description="repeats your input")
async def echo(context: lightbulb.SlashCommandContext) -> None:
await context.respond(context.options.text)
bot.add_slash_command(echo)
bot.run()
As far as the bot is concerned, filament slash commands are functionally identical to slash commands defined through the normal method that hikari-lightbulb provides, so everything you can do with hikari-lightbulb slash commands can also be done with filament slash commands.
There is however, one notable exception to this rule in that filament slash commands cannot be detected
when using lightbulb.command_handler.Bot.autodiscover_slash_commands
. If using filament slash commands,
you should instead use filament’s autodiscovery function.