Skip to content

Commit

Permalink
docs: update and improve documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
baptiste0928 committed May 16, 2024
1 parent 9c2b560 commit 0cebe3b
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 23 deletions.
1 change: 1 addition & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
11 changes: 11 additions & 0 deletions examples/xkcd-bot/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<XkcdComic> {
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)
Expand Down
73 changes: 65 additions & 8 deletions examples/xkcd-bot/interactions/command.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 {
Expand All @@ -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<()> {
Expand All @@ -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 <num>` command.
pub async fn run(&self, interaction: Interaction, client: &Client) -> anyhow::Result<()> {
Expand All @@ -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<Embed> {
let image = ImageSource::url(&comic.image_url)?;
Expand Down
13 changes: 13 additions & 0 deletions twilight-interactions/src/command/command_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
/// <div class="warning">
///
/// Autocomplete models are not meant to be used alone: you should use a regular
/// model to handle interactions submit, and another for autocomplete interactions.
///
/// </div>
///
/// ```
/// use twilight_interactions::command::{AutocompleteValue, CommandModel, ResolvedUser};
///
Expand All @@ -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`].
///
/// <div class="warning">
///
/// 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
///
/// </div>
///
/// ```
/// use twilight_interactions::command::CommandModel;
/// #
Expand Down
31 changes: 30 additions & 1 deletion twilight-interactions/src/command/create_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Item = (ToString, ToString)>`. See the module documentation to
Expand Down Expand Up @@ -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<String, String>,
}

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<Item = (impl Into<String>, impl Into<String>)>,
) -> Self {
Expand All @@ -162,13 +177,27 @@ 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,
pub(crate) localizations: HashMap<String, String>,
}

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<String>,
localizations: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
Expand Down
56 changes: 42 additions & 14 deletions twilight-interactions/src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
//! ```
Expand All @@ -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<Item = (ToString, ToString)>`, 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:
Expand All @@ -69,12 +92,17 @@
//! | `MENTIONABLE` | [`ResolvedMentionable`], [`Id<GenericMarker>`] |
//! | `ATTACHMENT` | [`Attachment`], [`Id<AttachmentMarker>`] |
//!
//! 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<UserMarker>`]: twilight_model::id::Id
//! [`InteractionChannel`]: twilight_model::application::interaction::InteractionChannel
//! [`InteractionChannel`]:
//! twilight_model::application::interaction::InteractionChannel
//! [`Id<ChannelMarker>`]: twilight_model::id::Id
//! [`Role`]: twilight_model::guild::Role
//! [`Id<RoleMarker>`]: twilight_model::id::Id
Expand Down

0 comments on commit 0cebe3b

Please sign in to comment.