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}