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- `help_text_fn`: Path to a string-returning function which is used for command help text instead of documentation comments
62    - Useful if you have many commands with very similar help messages: you can abstract the common parts into a function
63
64## Edit tracking (prefix only)
65
66- `track_edits`: Shorthand for `invoke_on_edit`, `track_deletion`, and `reuse_response` (prefix only)
67- `invoke_on_edit`: Reruns the command if an existing invocation message is edited (prefix only)
68- `track_deletion`: Deletes the bot response to a command if the command message is deleted (prefix only)
69- `reuse_response`: After the first response, post subsequent responses as edits to the initial message (prefix only)
70
71## Cooldown
72- `manual_cooldowns`: Allows overriding the framework's built-in cooldowns tracking without affecting other commands.
73- `global_cooldown`: Minimum duration in seconds between invocations, globally
74- `user_cooldown`: Minimum duration in seconds between invocations, per user
75- `guild_cooldown`: Minimum duration in seconds between invocations, per guild
76- `channel_cooldown`: Minimum duration in seconds between invocations, per channel
77- `member_cooldown`: Minimum duration in seconds between invocations, per guild member
78
79## Other
80
81- `on_error`: Error handling function
82- `broadcast_typing`: Trigger a typing indicator while command runs (prefix only)
83- `discard_spare_arguments`: Don't throw an error if the user supplies too many arguments (prefix only)
84- `ephemeral`: Make bot responses ephemeral if possible (slash only)
85    - Only poise's functions, like `poise::send_reply`, respect this preference
86
87# Function parameters
88
89`Context` is the first parameter of all command functions. It's an enum over either PrefixContext or
90SlashContext, which contain a variety of context data each. Context provides some utility methods to
91access data present in both PrefixContext and SlashContext, like `author()` or `created_at()`.
92
93All following parameters are inputs to the command. You can use all types that implement `poise::PopArgument`, `serenity::ArgumentConvert` or `std::str::FromStr`.
94You can also wrap types in `Option` or `Vec` to make them optional or variadic. In addition, there
95are multiple attributes you can use on parameters:
96
97## Meta properties
98
99- `#[description = ""]`: Sets description of the parameter (slash-only)
100- `#[description_localized("locale", "Description")]`: Adds localized description of the parameter (slash-only)
101- `#[name_localized("locale", "new_name")]`: Adds localized name of the parameter (slash-only)
102- `#[autocomplete = "callback()"]`: Sets the autocomplete callback (slash-only)
103- `#[rename = "new_name"]`: Changes the user-facing name of the parameter (slash-only)
104
105## Input filter (slash only)
106
107- `#[channel_types("", "")]`: For channel parameters, restricts allowed channel types (slash-only)
108- `#[min = 0]`: Minimum value for this number parameter (slash-only)
109- `#[max = 0]`: Maximum value for this number parameter (slash-only)
110- `#[min_length = 0]`: Minimum length for this string parameter (slash-only)
111- `#[max_length = 1]`: Maximum length for this string parameter (slash-only)
112
113## Parser settings (prefix only)
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 set the bool to true if the user typed the parameter name literally (prefix-only)
117    - For example with `async fn my_command(ctx: Context<'_>, #[flag] my_flag: bool)`, `~my_command` would set my_flag to false, and `~my_command my_flag` would set my_flag to true
118
119# Help text
120
121Documentation comments are used as command help text. The first paragraph is the command
122description (`Command::description`) and all following paragraphs are the multiline help text
123(`Command::help_text`).
124
125In the multiline help text, put `\` at the end of a line to escape the newline.
126
127Example:
128
129```rust
130/// This is the description of my cool command, it can span multiple
131/// lines if you need to
132///
133/// Here in the following paragraphs, you can give information on how \
134/// to use the command that will be shown in your command's help.
135///
136/// You could also put example invocations here:
137/// `~coolcommand test`
138#[poise::command(slash_command)]
139pub async fn coolcommand(ctx: Context<'_>, s: String) -> Result<(), Error> { ... }
140```
141results in
142```rust
143poise::Command {
144    description: Some("This is the description of my cool command, it can span multiple lines if you need to".into()),
145    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()),
146    ...
147}
148```
149
150# Internals
151
152Internally, this attribute macro generates a function with a single `poise::Command`
153return type, which contains all data about this command. For example, it transforms a function of
154this form:
155```rust
156/// This is a command
157#[poise::command(slash_command, prefix_command)]
158async fn my_command(ctx: Context<'_>) -> Result<(), Error> {
159    // code
160}
161```
162into something like
163```rust
164fn my_command() -> poise::Command<Data, Error> {
165    async fn inner(ctx: Context<'_>) -> Result<(), Error> {
166        // code
167    }
168
169    poise::Command {
170        name: "my_command",
171        description: "This is a command",
172        prefix_action: Some(|ctx| Box::pin(async move {
173            inner(ctx.into()).await
174        })),
175        slash_action: Some(|ctx| Box::pin(async move {
176            inner(ctx.into()).await
177        })),
178        context_menu_action: None,
179        // ...
180    }
181}
182```
183
184If you're curious, you can use [`cargo expand`](https://github.com/dtolnay/cargo-expand) to see the
185exact desugaring
186*/
187#[proc_macro_attribute]
188pub fn command(args: TokenStream, function: TokenStream) -> TokenStream {
189    let args = match darling::ast::NestedMeta::parse_meta_list(args.into()) {
190        Ok(x) => x,
191        Err(e) => return e.into_compile_error().into(),
192    };
193
194    let args = match <command::CommandArgs as darling::FromMeta>::from_list(&args) {
195        Ok(x) => x,
196        Err(e) => return e.write_errors().into(),
197    };
198
199    let function = syn::parse_macro_input!(function as syn::ItemFn);
200
201    match command::command(args, function) {
202        Ok(x) => x,
203        Err(e) => e.write_errors().into(),
204    }
205}
206/**
207Use this derive macro on an enum to easily generate a choice parameter type. A choice parameter
208is mainly useful in slash commands. It allows you to constrain input to a fixed set of choices.
209
210```rust
211#[derive(poise::ChoiceParameter)]
212pub enum MyChoice {
213    #[name = "The first choice"]
214    ChoiceA,
215    // A choice can have multiple names
216    #[name = "The second choice"]
217    #[name = "ChoiceB"]
218    ChoiceB,
219    // Or no name, in which case it falls back to the variant name "ChoiceC"
220    ChoiceC,
221}
222```
223
224Example invocations:
225- `~yourcommand "The first choice"` - without the quotes, each word would count as a separate argument
226- `~yourcommand ChoiceB`
227- `~yourcommand cHoIcEb` - names are case-insensitive
228
229# Localization
230
231In slash commands, you can take advantage of Discord's localization.
232
233```rust
234#[derive(poise::ChoiceParameter)]
235pub enum Food {
236    #[name_localized("de", "Eier")]
237    #[name_localized("es-ES", "Huevos")]
238    Eggs,
239    #[name_localized("de", "Pizza")]
240    #[name_localized("es-ES", "Pizza")]
241    Pizza,
242    #[name_localized("de", "Müsli")]
243    #[name_localized("es-ES", "Muesli")]
244    Cereals,
245}
246```
247
248When invoking your slash command, users will be shown the name matching their locale.
249
250You can also set localized choice names programmatically; see `CommandParameter::choices`
251*/
252#[proc_macro_derive(ChoiceParameter, attributes(name, name_localized))]
253pub fn choice_parameter(input: TokenStream) -> TokenStream {
254    let enum_ = syn::parse_macro_input!(input as syn::DeriveInput);
255
256    match choice_parameter::choice_parameter(enum_) {
257        Ok(x) => x,
258        Err(e) => e.write_errors().into(),
259    }
260}
261
262/// See [`ChoiceParameter`]
263#[deprecated = "renamed to ChoiceParameter"]
264#[proc_macro_derive(SlashChoiceParameter, attributes(name))]
265pub fn slash_choice_parameter(input: TokenStream) -> TokenStream {
266    choice_parameter(input)
267}
268
269/// See `Modal` trait documentation
270#[proc_macro_derive(
271    Modal,
272    attributes(name, placeholder, min_length, max_length, paragraph)
273)]
274pub fn modal(input: TokenStream) -> TokenStream {
275    let struct_ = syn::parse_macro_input!(input as syn::DeriveInput);
276
277    match modal::modal(struct_) {
278        Ok(x) => x,
279        Err(e) => e.write_errors().into(),
280    }
281}