poise_macros/lib.rs
1/*!
2Procedural macros used in poise, like [`macro@command`]
3*/
4
5mod choice_parameter;
6mod command;
7mod modal;
8mod util;
9
10use proc_macro::TokenStream;
11
12/**
13This macro transforms plain functions into poise bot commands.
14
15Documentation comments are used as help text. The first line is a single-line description,
16displayed in listings of your bot's commands (i.e. `~help`). Following paragraphs are detailed explanations,
17for example for command-specific help (i.e. `~help command_name`). Escape newlines with `\`
18
19# Macro arguments
20
21`#[poise::command]` accepts a number of arguments to configure the command:
22
23## Command types
24
25- `prefix_command`: Generate a prefix command
26- `slash_command`: Generate a slash command
27- `context_menu_command`: Generate a context menu command
28
29## Meta properties
30
31- `subcommands`: List of subcommands `subcommands("foo", "bar", "baz")`
32- `name_localized`: Adds localized name of the parameter `name_localized("locale", "new_name")` (slash-only)
33- `description_localized`: Adds localized description of the parameter `description_localized("locale", "Description")` (slash-only)
34- `rename`: Choose an alternative command name instead of the function name
35 - Useful if your command name is a Rust keyword, like `move`
36- `aliases`: Command name aliases (only applies to prefix commands)
37- `category`: Category of this command which affects placement in the help command
38- `custom_data`: Arbitrary expression that will be boxed and stored in `Command::custom_data`
39- `identifying_name`: Optionally, a unique identifier for this command for your personal usage
40- `install_context`: Installation contexts where this command is available (slash-only)
41- `interaction_context`: Interaction contexts where this command is available (slash-only)
42
43## Checks
44
45- `required_permissions`: Permissions which the command caller needs to have
46- `required_bot_permissions`: Permissions which the bot is known to need
47- `default_member_permissions`: Like `required_permissions`, but checked server-side (slash only)
48 - Due to being checked server-side, users without the required permissions are prevented from executing the command in the first place, which is a better experience
49 - However, `default_member_permissions` has no effect on subcommands, which always inherit their permissions from the top-level command
50 - Also, guild owners can freely change the required permissions for any bot command for their guild
51- `owners_only`: Restricts command callers to a configurable list of owners (see FrameworkOptions)
52- `guild_only`: Restricts command callers to only run on a guild
53- `dm_only`: Restricts command callers to only run on a DM
54- `nsfw_only`: Restricts command callers to only run on a NSFW channel
55- `subcommand_required`: Requires a subcommand to be specified (prefix only)
56- `check`: Path to a function which is invoked for every invocation. If the function returns false, the command is not executed (can be used multiple times)
57
58## Help-related arguments
59
60- `hide_in_help`: Hide this command in help menus
61
62## Edit tracking (prefix only)
63
64- `track_edits`: Shorthand for `invoke_on_edit`, `track_deletion`, and `reuse_response` (prefix only)
65- `invoke_on_edit`: Reruns the command if an existing invocation message is edited (prefix only)
66- `track_deletion`: Deletes the bot response to a command if the command message is deleted (prefix only)
67- `reuse_response`: After the first response, post subsequent responses as edits to the initial message (prefix only)
68
69## Cooldown
70- `manual_cooldowns`: Allows overriding the framework's built-in cooldowns tracking without affecting other commands.
71- `global_cooldown`: Minimum duration in seconds between invocations, globally
72- `user_cooldown`: Minimum duration in seconds between invocations, per user
73- `guild_cooldown`: Minimum duration in seconds between invocations, per guild
74- `channel_cooldown`: Minimum duration in seconds between invocations, per channel
75- `member_cooldown`: Minimum duration in seconds between invocations, per guild member
76
77## Other
78
79- `on_error`: Error handling function
80- `broadcast_typing`: Trigger a typing indicator while command runs (prefix only)
81- `discard_spare_arguments`: Don't throw an error if the user supplies too many arguments (prefix only)
82- `ephemeral`: Make bot responses ephemeral if possible (slash only)
83 - Only poise's functions, like `poise::send_reply`, respect this preference
84
85# Function parameters
86
87`Context` is the first parameter of all command functions. It's an enum over either PrefixContext or
88SlashContext, which contain a variety of context data each. Context provides some utility methods to
89access data present in both PrefixContext and SlashContext, like `author()` or `created_at()`.
90
91All following parameters are inputs to the command. You can use all types that implement
92`PopArgument` (for prefix commands) or `SlashArgument` (for slash commands). You can also wrap
93types in `Option` or `Vec` to make them optional or variadic. In addition, there are multiple
94attributes you can use on parameters:
95
96## Meta properties
97
98- `#[description = ""]`: Sets description of the parameter (slash-only)
99- `#[description_localized("locale", "Description")]`: Adds localized description of the parameter (slash-only)
100- `#[name_localized("locale", "new_name")]`: Adds localized name of the parameter (slash-only)
101- `#[autocomplete = "callback()"]`: Sets the autocomplete callback (slash-only)
102- `#[rename = "new_name"]`: Changes the user-facing name of the parameter (slash-only)
103
104## Input filter (slash only)
105
106- `#[channel_types("", "")]`: For channel parameters, restricts allowed channel types (slash-only)
107- `#[min = 0]`: Minimum value for this number parameter (slash-only)
108- `#[max = 0]`: Maximum value for this number parameter (slash-only)
109- `#[min_length = 0]`: Minimum length for this string parameter (slash-only)
110- `#[max_length = 1]`: Maximum length for this string parameter (slash-only)
111
112## Parser settings
113- `#[string]`: Indicates that a type implements `FromStr` and should be parsed from a string argument.
114- `#[rest]`: Use the entire rest of the message for this parameter (prefix-only)
115- `#[lazy]`: Can be used on Option and Vec parameters and is equivalent to regular expressions' laziness (prefix-only)
116- `#[flag]`: Can be used on a bool parameter to make it optional and default to `false`; additionally,
117 in prefix commands only, the user can pass in the parameter name literally to set it to `true`.
118 - For example with `async fn my_command(ctx: Context<'_>, #[flag] my_flag: bool)`, `~my_command` or `/my_command` would set `my_flag` to false, while `~my_command my_flag` would set `my_flag` to true
119
120# Help text
121
122Documentation comments are used as command help text. The first paragraph is the command
123description (`Command::description`) and all following paragraphs are the multiline help text
124(`Command::help_text`).
125
126In the multiline help text, put `\` at the end of a line to escape the newline.
127
128Example:
129
130```rust
131/// This is the description of my cool command, it can span multiple
132/// lines if you need to
133///
134/// Here in the following paragraphs, you can give information on how \
135/// to use the command that will be shown in your command's help.
136///
137/// You could also put example invocations here:
138/// `~coolcommand test`
139#[poise::command(slash_command)]
140pub async fn coolcommand(ctx: Context<'_>, s: String) -> Result<(), Error> { ... }
141```
142results in
143```rust
144poise::Command {
145 description: Some("This is the description of my cool command, it can span multiple lines if you need to".into()),
146 help_text: Some("Here in the following paragraphs, you can give information on how to use the command that will be shown in your command's help.\n\nYou could also put example invocations here:\n`~coolcommand test`".into()),
147 ...
148}
149```
150
151# Internals
152
153Internally, this attribute macro generates a function with a single `poise::Command`
154return type, which contains all data about this command. For example, it transforms a function of
155this form:
156```rust
157/// This is a command
158#[poise::command(slash_command, prefix_command)]
159async fn my_command(ctx: Context<'_>) -> Result<(), Error> {
160 // code
161}
162```
163into something like
164```rust
165fn my_command() -> poise::Command<Data, Error> {
166 async fn inner(ctx: Context<'_>) -> Result<(), Error> {
167 // code
168 }
169
170 poise::Command {
171 name: "my_command",
172 description: "This is a command",
173 prefix_action: Some(|ctx| Box::pin(async move {
174 inner(ctx.into()).await
175 })),
176 slash_action: Some(|ctx| Box::pin(async move {
177 inner(ctx.into()).await
178 })),
179 context_menu_action: None,
180 // ...
181 }
182}
183```
184
185If you're curious, you can use [`cargo expand`](https://github.com/dtolnay/cargo-expand) to see the
186exact desugaring
187*/
188#[proc_macro_attribute]
189pub fn command(args: TokenStream, function: TokenStream) -> TokenStream {
190 let args = match darling::ast::NestedMeta::parse_meta_list(args.into()) {
191 Ok(x) => x,
192 Err(e) => return e.into_compile_error().into(),
193 };
194
195 let args = match <command::CommandArgs as darling::FromMeta>::from_list(&args) {
196 Ok(x) => x,
197 Err(e) => return e.write_errors().into(),
198 };
199
200 let function = syn::parse_macro_input!(function as syn::ItemFn);
201
202 match command::command(args, function) {
203 Ok(x) => x,
204 Err(e) => e.write_errors().into(),
205 }
206}
207/**
208Use this derive macro on an enum to easily generate a choice parameter type. A choice parameter
209is mainly useful in slash commands. It allows you to constrain input to a fixed set of choices.
210
211```rust
212#[derive(poise::ChoiceParameter)]
213pub enum MyChoice {
214 #[name = "The first choice"]
215 ChoiceA,
216 // A choice can have multiple names
217 #[name = "The second choice"]
218 #[name = "ChoiceB"]
219 ChoiceB,
220 // Or no name, in which case it falls back to the variant name "ChoiceC"
221 ChoiceC,
222}
223```
224
225Example invocations:
226- `~yourcommand "The first choice"` - without the quotes, each word would count as a separate argument
227- `~yourcommand ChoiceB`
228- `~yourcommand cHoIcEb` - names are case-insensitive
229
230# Localization
231
232In slash commands, you can take advantage of Discord's localization.
233
234```rust
235#[derive(poise::ChoiceParameter)]
236pub enum Food {
237 #[name_localized("de", "Eier")]
238 #[name_localized("es-ES", "Huevos")]
239 Eggs,
240 #[name_localized("de", "Pizza")]
241 #[name_localized("es-ES", "Pizza")]
242 Pizza,
243 #[name_localized("de", "Müsli")]
244 #[name_localized("es-ES", "Muesli")]
245 Cereals,
246}
247```
248
249When invoking your slash command, users will be shown the name matching their locale.
250
251You can also set localized choice names programmatically; see `CommandParameter::choices`
252*/
253#[proc_macro_derive(ChoiceParameter, attributes(name, name_localized))]
254pub fn choice_parameter(input: TokenStream) -> TokenStream {
255 let enum_ = syn::parse_macro_input!(input as syn::DeriveInput);
256
257 match choice_parameter::choice_parameter(enum_) {
258 Ok(x) => x,
259 Err(e) => e.write_errors().into(),
260 }
261}
262
263/// See [`ChoiceParameter`]
264#[deprecated = "renamed to ChoiceParameter"]
265#[proc_macro_derive(SlashChoiceParameter, attributes(name))]
266pub fn slash_choice_parameter(input: TokenStream) -> TokenStream {
267 choice_parameter(input)
268}
269
270/// See `Modal` trait documentation
271#[proc_macro_derive(
272 Modal,
273 attributes(name, placeholder, min_length, max_length, paragraph)
274)]
275pub fn modal(input: TokenStream) -> TokenStream {
276 let struct_ = syn::parse_macro_input!(input as syn::DeriveInput);
277
278 match modal::modal(struct_) {
279 Ok(x) => x,
280 Err(e) => e.write_errors().into(),
281 }
282}