From 0cebe3b6f8b56e29e9a0a05ab803d3dda88fbd81 Mon Sep 17 00:00:00 2001 From: Baptiste Girardeau Date: Thu, 16 May 2024 02:06:41 +0200 Subject: [PATCH] docs: update and improve documentation --- examples/Cargo.toml | 1 + examples/xkcd-bot/api.rs | 11 +++ examples/xkcd-bot/interactions/command.rs | 73 +++++++++++++++++-- .../src/command/command_model.rs | 13 ++++ .../src/command/create_command.rs | 31 +++++++- twilight-interactions/src/command/mod.rs | 56 ++++++++++---- 6 files changed, 162 insertions(+), 23 deletions(-) diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 88a6ee7..4dcb479 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -8,6 +8,7 @@ license = "ISC" [dev-dependencies] anyhow = "1" serde = { version = "1", features = ["derive"] } +fastrand = "2.1" futures-util = { version = "0.3", default-features = false } reqwest = { version = "0.12", features = ["json"] } diff --git a/examples/xkcd-bot/api.rs b/examples/xkcd-bot/api.rs index 624ae98..22f2870 100644 --- a/examples/xkcd-bot/api.rs +++ b/examples/xkcd-bot/api.rs @@ -39,6 +39,17 @@ impl XkcdComic { Ok(response.error_for_status()?.json().await?) } + /// Get a random xkcd comic. + pub async fn get_random() -> anyhow::Result { + let latest = Self::get_latest().await?; + let number = fastrand::u32(1..=latest.number); + + match Self::get_number(number).await? { + Some(comic) => Ok(comic), + None => Err(anyhow::Error::msg("failed to get random comic")), + } + } + /// Return the comic URL. pub fn url(&self) -> String { format!("https://xkcd.com/{}", self.number) diff --git a/examples/xkcd-bot/interactions/command.rs b/examples/xkcd-bot/interactions/command.rs index 753d5e7..32b596c 100644 --- a/examples/xkcd-bot/interactions/command.rs +++ b/examples/xkcd-bot/interactions/command.rs @@ -1,6 +1,6 @@ use anyhow::Context; use twilight_http::Client; -use twilight_interactions::command::{CommandModel, CreateCommand}; +use twilight_interactions::command::{CommandModel, CreateCommand, DescLocalizations}; use twilight_model::{ application::interaction::{application_command::CommandData, Interaction}, channel::message::Embed, @@ -13,14 +13,19 @@ use twilight_util::builder::{ use crate::api::XkcdComic; -/// Explore xkcd comics #[derive(CommandModel, CreateCommand, Debug)] -#[command(name = "xkcd")] +#[command(name = "xkcd", desc_localizations = "xkcd_desc")] pub enum XkcdCommand { #[command(name = "latest")] Latest(XkcdLatestCommand), #[command(name = "number")] Number(XkcdNumberCommand), + #[command(name = "random")] + Random(XkcdRandomCommand), +} + +fn xkcd_desc() -> DescLocalizations { + DescLocalizations::new("Explore xkcd comics", [("fr", "Explorer les comics xkcd")]) } impl XkcdCommand { @@ -38,15 +43,22 @@ impl XkcdCommand { match command { XkcdCommand::Latest(command) => command.run(interaction, client).await, XkcdCommand::Number(command) => command.run(interaction, client).await, + XkcdCommand::Random(command) => command.run(interaction, client).await, } } } -/// Get the latest xkcd comic #[derive(CommandModel, CreateCommand, Debug)] -#[command(name = "latest")] +#[command(name = "latest", desc_localizations = "xkcd_latest_desc")] pub struct XkcdLatestCommand; +fn xkcd_latest_desc() -> DescLocalizations { + DescLocalizations::new( + "Show the latest xkcd comic", + [("fr", "Afficher le dernier comic xkcd")], + ) +} + impl XkcdLatestCommand { /// Run the `/xkcd latest` command. pub async fn run(&self, interaction: Interaction, client: &Client) -> anyhow::Result<()> { @@ -72,15 +84,25 @@ impl XkcdLatestCommand { } } -/// Get a specific xkcd comic by number #[derive(CommandModel, CreateCommand, Debug)] -#[command(name = "number")] +#[command(name = "number", desc_localizations = "xkcd_number_desc")] pub struct XkcdNumberCommand { /// Comic number - #[command(min_value = 1)] + #[command(min_value = 1, desc_localizations = "xkcd_number_arg_desc")] pub number: i64, } +fn xkcd_number_desc() -> DescLocalizations { + DescLocalizations::new( + "Show a specific xkcd comic", + [("fr", "Afficher un comic xkcd spécifique")], + ) +} + +fn xkcd_number_arg_desc() -> DescLocalizations { + DescLocalizations::new("Comic number", [("fr", "Numéro du comic")]) +} + impl XkcdNumberCommand { /// Run the `/xkcd number ` command. pub async fn run(&self, interaction: Interaction, client: &Client) -> anyhow::Result<()> { @@ -107,6 +129,41 @@ impl XkcdNumberCommand { } } +#[derive(CommandModel, CreateCommand, Debug)] +#[command(name = "random", desc_localizations = "xkcd_random_desc")] +pub struct XkcdRandomCommand; + +fn xkcd_random_desc() -> DescLocalizations { + DescLocalizations::new( + "Show a random xkcd comic", + [("fr", "Afficher un comic xkcd aléatoire")], + ) +} + +impl XkcdRandomCommand { + /// Run the `/xkcd random` command. + pub async fn run(&self, interaction: Interaction, client: &Client) -> anyhow::Result<()> { + let comic = XkcdComic::get_random().await?; + let embed = crate_comic_embed(comic)?; + + let client = client.interaction(interaction.application_id); + let data = InteractionResponseDataBuilder::new() + .embeds([embed]) + .build(); + + let response = InteractionResponse { + kind: InteractionResponseType::ChannelMessageWithSource, + data: Some(data), + }; + + client + .create_response(interaction.id, &interaction.token, &response) + .await?; + + Ok(()) + } +} + /// Create a Discord embed for a comic fn crate_comic_embed(comic: XkcdComic) -> anyhow::Result { let image = ImageSource::url(&comic.image_url)?; diff --git a/twilight-interactions/src/command/command_model.rs b/twilight-interactions/src/command/command_model.rs index 9ff6d05..3377dfe 100644 --- a/twilight-interactions/src/command/command_model.rs +++ b/twilight-interactions/src/command/command_model.rs @@ -73,9 +73,13 @@ use crate::error::{ParseError, ParseOptionError, ParseOptionErrorType}; /// will not make the parsing fail. /// - It is not possible to derive [`CreateCommand`] on autocomplete models. /// +///
+/// /// Autocomplete models are not meant to be used alone: you should use a regular /// model to handle interactions submit, and another for autocomplete interactions. /// +///
+/// /// ``` /// use twilight_interactions::command::{AutocompleteValue, CommandModel, ResolvedUser}; /// @@ -96,6 +100,15 @@ use crate::error::{ParseError, ParseOptionError, ParseOptionErrorType}; /// Subcommand groups work the same way as regular subcommands, except the /// variant type is another enum implementing [`CommandModel`]. /// +///
+/// +/// When using subcommands, you should parse and create the command using the +/// top-level command. See the [`xkcd-bot` example] for example usage. +/// +/// [`xkcd-bot` example]: https://github.com/baptiste0928/twilight-interactions/tree/main/examples/xkcd-bot +/// +///
+/// /// ``` /// use twilight_interactions::command::CommandModel; /// # diff --git a/twilight-interactions/src/command/create_command.rs b/twilight-interactions/src/command/create_command.rs index d1cbbdf..95c505f 100644 --- a/twilight-interactions/src/command/create_command.rs +++ b/twilight-interactions/src/command/create_command.rs @@ -73,7 +73,8 @@ use super::{internal::CreateOptionData, ResolvedMentionable, ResolvedUser}; /// | `max_value`, `min_value` | `i64` or `f64` | Field | Set the maximum and/or minimum value permitted. | /// | `max_length`, `min_length` | `u16` | Field | Maximum and/or minimum string length permitted. | /// -/// [^perms]: Path to a function that returns [`Permissions`]. +/// [^perms]: Path to a function that returns [`Permissions`]. Permissions can +/// only be set on top-level commands /// /// [^localization]: Path to a function that returns a type that implements /// `IntoIterator`. See the module documentation to @@ -144,12 +145,26 @@ pub trait CreateOption: Sized { fn create_option(data: CreateOptionData) -> CommandOption; } +/// Localization data for command names. +/// +/// This type is used in the `name_localizations` attribute of the +/// [`CreateCommand`] and [`CreateOption`] traits. See the [module +/// documentation](crate::command) for more information. #[derive(Debug, Clone, PartialEq)] pub struct NameLocalizations { pub(crate) localizations: HashMap, } impl NameLocalizations { + /// Create a new [`NameLocalizations`]. + /// + /// The localizations must be a tuple where the first element is a valid + /// [Discord locale] and the second element is the localized value. + /// + /// See [Localization] on Discord Developer Docs for more information. + /// + /// [Discord locale]: https://discord.com/developers/docs/reference#locales + /// [Localization]: https://discord.com/developers/docs/interactions/application-commands#localization pub fn new( localizations: impl IntoIterator, impl Into)>, ) -> Self { @@ -162,6 +177,11 @@ impl NameLocalizations { } } +/// Localization data for command descriptions. +/// +/// This type is used in the `desc_localizations` attribute of the +/// [`CreateCommand`] trait. See the [module documentation](crate::command) for +/// more information. #[derive(Debug, Clone, PartialEq)] pub struct DescLocalizations { pub(crate) fallback: String, @@ -169,6 +189,15 @@ pub struct DescLocalizations { } impl DescLocalizations { + /// Create a new [`DescLocalizations`]. + /// + /// The localizations must be a tuple where the first element is a valid + /// [Discord locale] and the second element is the localized value. + /// + /// See [Localization] on Discord Developer Docs for more information. + /// + /// [Discord locale]: https://discord.com/developers/docs/reference#locales + /// [Localization]: https://discord.com/developers/docs/interactions/application-commands#localization pub fn new( fallback: impl Into, localizations: impl IntoIterator, impl Into)>, diff --git a/twilight-interactions/src/command/mod.rs b/twilight-interactions/src/command/mod.rs index c3546b4..d369d75 100644 --- a/twilight-interactions/src/command/mod.rs +++ b/twilight-interactions/src/command/mod.rs @@ -12,7 +12,8 @@ //! - Command option choices with the [`CommandOption`] and [`CreateOption`] //! traits. //! -//! Read the documentation of these traits for usage examples. +//! Read the documentation of the [`CommandModel`] and [`CreateCommand`] traits +//! for more information and the complete list of supported attributes. //! //! ## Example //! ``` @@ -28,31 +29,53 @@ //! } //! ``` //! +//! For a more complete example, see the [`examples/xkcd-bot`] directory in the +//! library repository. +//! +//! [`examples/xkcd-bot`]: +//! https://github.com/baptiste0928/twilight-interactions/tree/main/examples/xkcd-bot +//! //! ## Localization -//! Localization of names and descriptions of slash commands is supported -//! using the `name_localizations` and `desc_localizations` attributes on -//! applicable structs. +//! Command names and descriptions can be localized using the +//! `name_localizations` and `desc_localizations` attributes on the command +//! structs and fields. +//! +//! - For command names, you should provide the `name` attribute with the +//! default command name, and `name_localizations` with the name of a function +//! that returns a [`NameLocalizations`] struct. +//! +//! - For description, you should only provide the `name_localizations` +//! attribute with the name of a function that returns a [`DescLocalizations`] +//! struct. +//! +//! These structs take a list of tuples, where the first tuple element is a +//! valid [Discord locale] and the second tuple element is the localized +//! value. //! -//! The attribute takes a function that returns any type that implements -//! `IntoIterator`, where the first tuple element -//! is a valid [locale](https://discord.com/developers/docs/reference#locales) -//! and the second tuple element is the localized value. +//! [Discord locale]: https://discord.com/developers/docs/reference#locales //! //! ``` -//! use twilight_interactions::command::{CommandModel, CreateCommand, ResolvedUser, DescLocalizations}; +//! use twilight_interactions::command::{ +//! CommandModel, CreateCommand, ResolvedUser, NameLocalizations, DescLocalizations +//! }; //! //! #[derive(CommandModel, CreateCommand)] -//! #[command(name = "hello", desc_localizations = "hello_desc")] +//! #[command( +//! name = "hello", +//! name_localizations = "hello_name", +//! desc_localizations = "hello_desc" +//! )] //! struct HelloCommand; //! +//! pub fn hello_name() -> NameLocalizations { +//! NameLocalizations::new([("fr", "bonjour"), ("de", "hallo")]) +//! } +//! //! pub fn hello_desc() -> DescLocalizations { //! DescLocalizations::new("Say hello", [("fr", "Dis bonjour"), ("de", "Sag Hallo")]) //! } //! ``` //! -//! See the documentation of the traits to see where these attributes can be -//! used. -//! //! ## Supported types //! The [`CommandOption`] and [`CreateOption`] traits are implemented for the //! following types: @@ -69,12 +92,17 @@ //! | `MENTIONABLE` | [`ResolvedMentionable`], [`Id`] | //! | `ATTACHMENT` | [`Attachment`], [`Id`] | //! +//! Option choices are supported for the `STRING`, `INTEGER` and `NUMBER` option +//! types. See the [`CommandOption`] and [`CreateOption`] traits documentation +//! for more information. +//! //! [`from_interaction`]: CommandModel::from_interaction //! //! [`Cow`]: std::borrow::Cow //! [`User`]: twilight_model::user::User //! [`Id`]: twilight_model::id::Id -//! [`InteractionChannel`]: twilight_model::application::interaction::InteractionChannel +//! [`InteractionChannel`]: +//! twilight_model::application::interaction::InteractionChannel //! [`Id`]: twilight_model::id::Id //! [`Role`]: twilight_model::guild::Role //! [`Id`]: twilight_model::id::Id