1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
//! Procedural macros for [Composure](https://docs.rs/composure), the Discord Interactions
//! framework. Use them through `composure` with its `macros` feature, which re-exports them:
//! `composure::{app, command, group, component, modal, autocomplete, view, rsx, Choice, Modal}`.
//!
//! | Macro | |
//! |---|---|
//! | [`#[app]`](macro@app) | Collects Commands, Groups, handlers, Views and other Apps into the handler an Adapter serves |
//! | [`#[command]`](macro@command) | A Command from a function: Options from its parameters, the description from its doc comment |
//! | [`#[group]`](macro@group) | A Command whose subcommands are other Commands |
//! | [`#[autocomplete]`](macro@autocomplete) | Suggestions for an Option while the user types |
//! | [`#[component]`](macro@component) | Answers buttons and selects by `custom_id` prefix, with state in the `custom_id` |
//! | [`#[modal]`](macro@modal) | Answers modal submits by `custom_id` prefix |
//! | [`#[derive(Modal)]`](derive@Modal) | A form shown as a modal, read back typed |
//! | [`#[derive(Choice)]`](derive@Choice) | An enum of fixed Choices |
//! | [`rsx!`](macro@rsx) | Markup for message and modal layouts |
//! | [`#[view]`](macro@view) | A layout rendered from Props, with inline handlers |
//!
//! The [Composure guide](https://bluefrog130.github.io/composure-rs/) explains
//! each in depth; [Views](https://bluefrog130.github.io/composure-rs/views.html#tag-reference)
//! lists every `rsx!` tag and attribute.
use TokenStream;
/// Makes a unit struct an App: the Interaction Handler routing to the Commands, Groups, Component
/// and Modal Handlers, Views and other Apps listed in `handlers(..)`, in any mix and order.
///
/// ```ignore
/// /// Everything the bot serves
/// #[app(handlers(ping, echo, Admin, counter_click, on_feedback, Card, Moderation),
/// theme = THEME, events = MyEvents)]
/// pub struct Bot;
/// ```
///
/// - Implements `InteractionHandler` (for every State all listed items accept), `EventHandler`
/// and `CommandSetDefinition` (for Sync); `Bot.with_fallback(handler)` sends what nothing
/// listed handles (components, modals, autocomplete, commands) to `handler`
/// - Apps nest: a module can declare its own `#[app]` next to its handlers and be listed in the
/// root one
/// - `theme = THEME` (a `Theme` constant): fills in unset colors and branding on this App's
/// responses; a nested App's own Theme wins for its Routes, the outer one fills in the rest
/// - `events = MyEvents` (a unit struct implementing `EventHandler`): handles Webhook Events;
/// at most one App in a tree sets it
///
/// Two Commands with the same name and type, two handlers with the same `custom_id` prefix, more
/// Commands than Discord allows in one Scope, or a placed sub-View with handlers that is not
/// listed fail to compile.
/// Defines a Command from a function. Parameters after an optional leading `ctx: Ctx` become
/// Options; the doc comment becomes the description.
///
/// ```ignore
/// /// Ban a member
/// #[command(scope = "dev", permissions = Permissions::BanMembers)]
/// async fn ban(
/// ctx: Ctx<Env>,
/// #[option("Who to ban")] user: &User,
/// #[option("Days of messages to delete", min = 0, max = 7)] days: Option<u8>,
/// ) -> Result<Ephemeral<String>, HandlerError> { ... }
/// ```
///
/// - `ctx: Ctx` works with any State, `ctx: Ctx<T>` only with State `T`, and `ctx: Ctx<impl
/// Bound>` (or `fn f<S: Bound>(ctx: Ctx<S>)`) with any State meeting the bound
/// - `Option<T>` parameters are not required; `&str`, `&User`, `&Role`, `&PartialChannel`,
/// `&Attachment`, and `&Message` borrow from the Interaction instead of copying
/// - Settings: `name`, `desc`, `scope`, `permissions`, `contexts(...)`,
/// `integration_types(...)`, `nsfw`, and `user` / `message` for context-menu commands, which
/// take one target parameter (`User`, `ResolvedMember`, or `Message`, owned or borrowed)
/// - `#[option("Description", ...)]` then `name`, `min`, `max`, `min_len`, `max_len`,
/// `channel_types(...)`, `autocomplete = handler` (an `#[autocomplete]` function)
///
/// The function stays callable as written; a same-named empty struct is added for
/// `#[app]` and Groups to refer to.
/// Makes a unit struct a Group that routes to its subcommands. Groups can contain Groups one
/// level deep.
///
/// ```ignore
/// /// Moderation tools
/// #[group(subcommands(ban, kick, Roles))]
/// struct Admin;
/// ```
///
/// The name defaults to the struct name in `snake_case`. Settings are the same as `#[command]`
/// minus `user` / `message`.
/// Defines a Component Handler from a function: it answers components whose `custom_id` starts
/// with the prefix. Parameters after an optional leading `ctx: ComponentCtx` are state carried in
/// the `custom_id` (owned, serde-serializable), built with the generated `name::custom_id(..)`.
///
/// ```ignore
/// #[component("vote")]
/// fn vote(ctx: ComponentCtx, poll: u32, up: bool) -> Update<String> { ... }
///
/// // In a Command's response:
/// Component::new_button(ButtonStyle::Success, Some("Up".into()), None,
/// Some(vote::custom_id(poll, true)?), None, None)
/// ```
///
/// The return value answers like a Command's: a new message by default, `Update(..)` to edit the
/// message the component is on, `ctx.defer_update(..)` to do that later. List the handler in an
/// `#[app]`.
/// Defines a Modal Handler from a function: it answers submits of modals whose `custom_id`
/// starts with the prefix. After an optional leading `ctx: ModalCtx` comes the form (a
/// `#[derive(Modal)]` type), then state carried in the `custom_id`. `name::show(..)` is the
/// modal response that opens the form.
///
/// ```ignore
/// #[modal("rename")]
/// fn rename(ctx: ModalCtx, form: Rename, item: u32) -> Ephemeral<String> { ... }
///
/// #[command]
/// fn edit(#[option("Item")] item: u32) -> Result<InteractionResponse, CustomIdError> {
/// rename::show(item)
/// }
/// ```
///
/// List the handler in an `#[app]`.
/// A form shown as a modal: each named field is one input inside a Label.
///
/// ```ignore
/// #[derive(Modal)]
/// #[modal(title = "Report a bug", text = "Thanks for helping!")]
/// struct BugReport {
/// #[input(max_len = 100)]
/// summary: String,
/// #[input(paragraph, label = "What happened?")]
/// details: Option<String>,
/// #[radio]
/// severity: Severity, // a #[derive(Choice)] enum supplies the options
/// #[checkboxes(option("Windows", "win"), option("macOS", "mac"), option("Linux", "linux"))]
/// platforms: Vec<String>,
/// #[file_upload(max = 3, file_types("image"))]
/// screenshots: Vec<Attachment>,
/// #[checkbox(label = "Contact me")]
/// contact: bool,
/// }
/// ```
///
/// The attribute picks the input (default `#[input]`): `input`, `select`, `user_select`,
/// `role_select`, `mentionable_select`, `channel_select`, `radio`, `checkboxes`, `checkbox`,
/// `file_upload`. All take `label` (defaults to the field name), `description`, and `id`
/// (defaults to the field name); then
/// - `input`: `paragraph`, `min_len`, `max_len`, `placeholder`, `value`
/// - `select`: `placeholder`, `min`, `max`, `option("Label", "value")`...
/// - `user_select`, `role_select`, `mentionable_select`: `placeholder`, `min`, `max`
/// - `channel_select`: those and `channel_types(...)`
/// - `radio`: `option(...)`...; `checkboxes`: `min`, `max`, `option(...)`...
/// - `checkbox`: `default`; `file_upload`: `min`, `max`, `file_types("image", ".pdf")`
///
/// Without `option(...)`, a `#[derive(Choice)]` field type supplies the options. The type
/// decides required-ness (`Option` and `Vec` are optional, `Vec` allows several selections) and
/// how the value is read: `String`, `bool`, `Snowflake`, `User`, `Role`, `PartialChannel`,
/// `Attachment`, or a Choice enum, each also in `Option` or `Vec`; a mismatch with the input (a
/// `bool` text input, a `Vec` radio group) fails to compile. On the struct:
/// `#[modal(title = "...", text = "markdown")]`, `text` repeatable; at most 5 fields and texts.
/// Defines an Autocomplete Handler from a function taking an optional leading `ctx: Ctx` and
/// what the user has typed so far as `input: &str`. Attach it to a string, integer, or number
/// Option with `#[option("...", autocomplete = name)]`.
///
/// ```ignore
/// #[autocomplete]
/// fn fruits(input: &str) -> Vec<&'static str> {
/// FRUITS.iter().copied().filter(|f| f.starts_with(input)).collect()
/// }
///
/// /// Pick a fruit
/// #[command]
/// fn fruit(#[option("Fruit", autocomplete = fruits)] name: &str) -> String { ... }
/// ```
///
/// Return a `Vec` of strings, numbers, `(name, value)` pairs, or
/// `ApplicationCommandOptionChoice`s (at most 25 are sent), or a `Result` of one.
/// Makes a fieldless enum (1-25 variants) usable as an Option restricted to its variants, and as
/// the options of selects, radio groups and checkbox groups.
///
/// ```ignore
/// #[derive(Choice)]
/// enum Flavor {
/// Vanilla,
/// #[choice(name = "Mint chip", value = "mint")]
/// MintChip,
/// }
/// ```
///
/// - `#[choice(name = "...")]`: the name shown to users (1-100 characters); the variant's
/// identifier by default
/// - `#[choice(value = "...")]`: what Discord sends; the identifier by default. If every variant
/// sets an integer `#[choice(value = N)]`, the Option is an integer Option.
///
/// Adds `name()` and `value()` for the chosen variant.
/// Markup for a message or modal layout, as JSX: built-in tags are Discord's components
/// (`<Container>`, `<Section>`, `<Text>`, `<Thumbnail>`, `<Gallery>`/`<Item>`, `<File>`,
/// `<Separator>`, `<Row>`, `<Button>`, `<StringSelect>`/`<Option>`, `<UserSelect>`, ...; in a
/// `<Modal>`: `<Label>`, `<TextInput>`, `<RadioGroup>`, `<CheckboxGroup>`, `<Checkbox>`,
/// `<FileUpload>`), PascalCase tags are `#[view]`s. Evaluates to a `View`, or a `ModalView` for a
/// `<Modal>` root.
///
/// ```ignore
/// rsx! {
/// <Container accent_color=0x5865F2>
/// "## " {title.as_str()}
/// <Section>
/// "Adjacent text and `{..}` join into one text display"
/// <Thumbnail url=avatar/>
/// </Section>
/// <Row>
/// <Button primary on:click=vote(poll, true)>"Up"</Button>
/// <Button url="https://discord.com">"Docs"</Button>
/// </Row>
/// </Container>
/// }
/// ```
///
/// Attributes set the component's fields (`accent_color`, `spoiler`, `disabled`, `id`, ...);
/// a bare attribute is `true`. `{..}` children are text, components, Views, Props, or `Option`s,
/// `Vec`s and iterators of them. `on:click=handler(state..)` / `on:select=..` / `on:submit=..`
/// point at a `#[component]` or `#[modal]` handler; inline handlers (`on:click:name=|ctx| ..`)
/// need the markup to be in a `#[view]`. A `<Section>`'s `<Button>` or `<Thumbnail>` child is
/// its accessory. Wrap attribute values that use `<..>` in braces.
///
/// | Tag | Builds | Attributes (every tag but `<Modal>`, `<Item>` and `<Option>` also takes `id`) |
/// |---|---|---|
/// | `<Container>` | `Container` | `accent_color`, `spoiler` |
/// | `<Section>` | `Section` | `accessory` (or a `<Button>` / `<Thumbnail>` child) |
/// | `<Text>` (`<TextDisplay>`) | `TextDisplay` | `content` (or text children) |
/// | `<Separator>` | `Separator` | `small` / `large`, `spacing`, `divider` |
/// | `<Row>` (`<ActionRow>`) | `ActionRow` | |
/// | `<Thumbnail>` | `Thumbnail` | `url` (required), `description`, `spoiler` |
/// | `<Gallery>` (`<MediaGallery>`) | `MediaGallery` | `<Item url=..>` children: `description`, `spoiler` |
/// | `<File>` | `FileComponent` | `url` (required, `attachment://..`), `spoiler` |
/// | `<Button>` | `ButtonComponent` | `primary` / `secondary` / `success` / `danger`, `style`, `on:click`, `custom_id`, `url`, `sku_id`, `label`, `emoji`, `disabled` |
/// | `<StringSelect>` (`<Select>`) | `StringSelect` | `on:select`, `custom_id`, `name` (modals), `options` (a Choice enum), `placeholder`, `min`, `max`, `disabled`, `required` |
/// | `<UserSelect>`, `<RoleSelect>`, `<MentionableSelect>`, `<ChannelSelect>` | the entity selects | as `<StringSelect>` without `options`, plus `default_values`, `channel_types` |
/// | `<Option>` | `SelectOption` / `GroupOption` | `value` (required), `label` (or text children), `description`, `emoji`, `default` |
/// | `<Modal>` | `ModalView` | `title` (required), `on:submit` or `custom_id` |
/// | `<Label>` | `Label` | `text` (required), `description` |
/// | `<TextInput>` | `TextInput` | `name`, `paragraph`, `style`, `min_len`, `max_len`, `placeholder`, `value`, `required` |
/// | `<RadioGroup>` | `RadioGroup` | `name`, `options`, `required` |
/// | `<CheckboxGroup>` | `CheckboxGroup` | `name`, `options`, `min`, `max`, `required` |
/// | `<Checkbox>` | `Checkbox` | `name`, `default` |
/// | `<FileUpload>` | `FileUpload` | `name`, `min`, `max`, `file_types`, `required` |
/// Defines a View: a layout rendered from its Props (the parameters, owned values), whose
/// `rsx!`s may hold inline handlers.
///
/// ```ignore
/// #[view]
/// pub fn Counter(count: u32, #[prop(default = 1)] step: u32, note: Option<String>) -> View {
/// rsx! {
/// <Text>"Clicked " {count} " times"</Text>
/// <Row>
/// <Button primary on:click:add=move |_| Counter { count: count + step, step, note }>
/// "+" {step}
/// </Button>
/// </Row>
/// }
/// }
/// ```
///
/// Adds a same-named Props struct: return it (or the View) from a Command to send the View, from
/// an inline handler to re-render the message, or place it in markup as `<Counter count=0/>`
/// (`Option` and `#[prop(default = ..)]` Props may be left out; a `children: View` Prop takes the
/// tag's children). An inline handler `on:click:name=move |ctx| ..` (`on:select:name=|ctx,
/// values: Vec<Choice>| ..`, `on:submit:name=|ctx, field: Type, ..| ..` on a `<Modal>`) becomes a
/// handler routed by `custom_id` `prefix:name:state`; the Props it names are its state. The
/// prefix defaults to the name in `snake_case` (`#[view(prefix = "c")]`). List Views with
/// handlers in an `#[app]`.