From 8c0aeacadb93d3b56fb98beb882eaef1f79cd652 Mon Sep 17 00:00:00 2001 From: Zeyla Hellyer Date: Tue, 23 May 2017 10:15:26 -0700 Subject: [PATCH] Add more examples and improve some others Add examples to some functions, and update some of the old examples to use the `?` operator instead of unwrapping. --- src/builder/create_invite.rs | 142 ++++++++++++++- src/builder/edit_guild.rs | 35 +++- src/builder/edit_profile.rs | 9 +- src/builder/edit_role.rs | 10 +- src/builder/execute_webhook.rs | 74 +++++++- src/builder/get_messages.rs | 32 +++- src/cache/mod.rs | 69 +++++++- src/client/context.rs | 166 ++++++++++++++--- src/client/mod.rs | 313 ++++++++++++++++++++++++++++++++- src/gateway/shard.rs | 151 ++++++++++++++-- src/model/misc.rs | 2 +- src/utils/colour.rs | 19 +- src/utils/message_builder.rs | 208 ++++++++++++++++++++-- src/utils/mod.rs | 154 ++++++++++++++-- 14 files changed, 1278 insertions(+), 106 deletions(-) diff --git a/src/builder/create_invite.rs b/src/builder/create_invite.rs index 67e33fc3a4b..98fc2b940bb 100644 --- a/src/builder/create_invite.rs +++ b/src/builder/create_invite.rs @@ -11,17 +11,43 @@ use ::internal::prelude::*; /// /// Create an invite with a max age of 3600 seconds and 10 max uses: /// -/// ```rust,ignore -/// use serenity::Client; -/// use std::env; +/// ```rust,no_run +/// # use serenity::Client; +/// # +/// # let mut client = Client::login(""); +/// # +/// use serenity::client::CACHE; /// -/// let mut client = Client::login(&env::var("DISCORD_BOT_TOKEN").unwrap()); +/// client.on_message(|_, msg| { +/// if msg.content == "!createinvite" { +/// let channel = match CACHE.read().unwrap().guild_channel(msg.channel_id) { +/// Some(channel) => channel, +/// None => { +/// let _ = msg.channel_id.say("Error creating invite"); /// -/// client.on_message(|_, message| { -/// if message.content == "!invite" { -/// let invite = message.channel_id.create_invite(|i| i -/// .max_age(3600) -/// .max_uses(10)); +/// return; +/// }, +/// }; +/// +/// let reader = channel.read().unwrap(); +/// +/// let invite = match reader.create_invite(|i| i.max_age(3600).max_uses(10)) { +/// Ok(invite) => invite, +/// Err(why) => { +/// println!("Err creating invite: {:?}", why); +/// +/// if let Err(why) = msg.channel_id.say("Error creating invite") { +/// println!("Err sending err msg: {:?}", why); +/// } +/// +/// return; +/// }, +/// }; +/// +/// drop(reader); +/// +/// let content = format!("Here's your invite: {}", invite.url()); +/// let _ = msg.channel_id.say(&content); /// } /// }); /// ``` @@ -37,6 +63,28 @@ impl CreateInvite { /// Set to `0` for an invite which does not expire after an amount of time. /// /// Defaults to `86400`, or 24 hours. + /// + /// # Examples + /// + /// Create an invite with a max age of `3600` seconds, or 1 hour: + /// + /// ```rust,no_run + /// # use serenity::client::CACHE; + /// # use serenity::model::ChannelId; + /// # use std::error::Error; + /// # + /// # fn try_main() -> Result<(), Box> { + /// # let channel = CACHE.read().unwrap().guild_channel(81384788765712384).unwrap(); + /// # let channel = channel.read().unwrap(); + /// # + /// let invite = channel.create_invite(|i| i.max_age(3600))?; + /// # Ok(()) + /// # } + /// # + /// # fn main() { + /// # try_main().unwrap(); + /// # } + /// ``` pub fn max_age(mut self, max_age: u64) -> Self { self.0.insert("max_age".to_owned(), Value::Number(Number::from(max_age))); @@ -48,6 +96,28 @@ impl CreateInvite { /// Set to `0` for an invite which does not expire after a number of uses. /// /// Defaults to `0`. + /// + /// # Examples + /// + /// Create an invite with a max use limit of `5`: + /// + /// ```rust,no_run + /// # use serenity::client::CACHE; + /// # use serenity::model::ChannelId; + /// # use std::error::Error; + /// # + /// # fn try_main() -> Result<(), Box> { + /// # let channel = CACHE.read().unwrap().guild_channel(81384788765712384).unwrap(); + /// # let channel = channel.read().unwrap(); + /// # + /// let invite = channel.create_invite(|i| i.max_uses(5))?; + /// # Ok(()) + /// # } + /// # + /// # fn main() { + /// # try_main().unwrap(); + /// # } + /// ``` pub fn max_uses(mut self, max_uses: u64) -> Self { self.0.insert("max_uses".to_owned(), Value::Number(Number::from(max_uses))); @@ -57,6 +127,28 @@ impl CreateInvite { /// Whether an invite grants a temporary membership. /// /// Defaults to `false`. + /// + /// # Examples + /// + /// Create an invite which is temporary: + /// + /// ```rust,no_run + /// # use serenity::client::CACHE; + /// # use serenity::model::ChannelId; + /// # use std::error::Error; + /// # + /// # fn try_main() -> Result<(), Box> { + /// # let channel = CACHE.read().unwrap().guild_channel(81384788765712384).unwrap(); + /// # let channel = channel.read().unwrap(); + /// # + /// let invite = channel.create_invite(|i| i.temporary(true))?; + /// # Ok(()) + /// # } + /// # + /// # fn main() { + /// # try_main().unwrap(); + /// # } + /// ``` pub fn temporary(mut self, temporary: bool) -> Self { self.0.insert("temporary".to_owned(), Value::Bool(temporary)); @@ -66,6 +158,28 @@ impl CreateInvite { /// Whether or not to try to reuse a similar invite. /// /// Defaults to `false`. + /// + /// # Examples + /// + /// Create an invite which is unique: + /// + /// ```rust,no_run + /// # use serenity::client::CACHE; + /// # use serenity::model::ChannelId; + /// # use std::error::Error; + /// # + /// # fn try_main() -> Result<(), Box> { + /// # let channel = CACHE.read().unwrap().guild_channel(81384788765712384).unwrap(); + /// # let channel = channel.read().unwrap(); + /// # + /// let invite = channel.create_invite(|i| i.unique(true))?; + /// # Ok(()) + /// # } + /// # + /// # fn main() { + /// # try_main().unwrap(); + /// # } + /// ``` pub fn unique(mut self, unique: bool) -> Self { self.0.insert("unique".to_owned(), Value::Bool(unique)); @@ -75,6 +189,16 @@ impl CreateInvite { impl Default for CreateInvite { /// Creates a builder with default values, setting `validate` to `null`. + /// + /// # Examples + /// + /// Create a default `CreateInvite` builder: + /// + /// ```rust + /// use serenity::utils::builder::CreateInvite; + /// + /// let invite_builder = CreateInvite::default(); + /// ``` fn default() -> CreateInvite { let mut map = Map::new(); map.insert("validate".to_owned(), Value::Null); diff --git a/src/builder/edit_guild.rs b/src/builder/edit_guild.rs index aa08341dcd6..c01b8ea816e 100644 --- a/src/builder/edit_guild.rs +++ b/src/builder/edit_guild.rs @@ -49,15 +49,25 @@ impl EditGuild { /// Using the utility function - [`utils::read_image`] - to read an image /// from the cwd and encode it in base64 to send to Discord. /// - /// ```rust,ignore + /// ```rust,no_run + /// # use serenity::model::GuildId; + /// # use std::error::Error; + /// # + /// # fn try_main() -> Result<(), Box> { + /// # let mut guild = GuildId(0).get()?; /// use serenity::utils; /// /// // assuming a `guild` has already been bound /// - /// let base64_icon = utils::read_image("./guild_icon.png") - /// .expect("Failed to read image"); + /// let base64_icon = utils::read_image("./guild_icon.png")?; /// - /// let _ = guild.edit(|g| g.icon(base64_icon)); + /// guild.edit(|g| g.icon(Some(&base64_icon)))?; + /// # Ok(()) + /// # } + /// # + /// # fn main() { + /// # try_main().unwrap(); + /// # } /// ``` /// /// [`utils::read_image`]: ../utils/fn.read_image.html @@ -91,14 +101,23 @@ impl EditGuild { /// /// Setting the region to [`Region::UsWest`]: /// - /// ```rust,ignore + /// ```rust,no_run + /// # use serenity::model::GuildId; + /// # use std::error::Error; + /// # + /// # fn try_main() -> Result<(), Box> { + /// # let mut guild = GuildId(0).get()?; /// use serenity::model::Region; /// /// // assuming a `guild` has already been bound /// - /// if let Err(why) = guild.edit(|g| g.region(Region::UsWest)) { - /// println!("Error editing guild's region: {:?}", why); - /// } + /// guild.edit(|g| g.region(Region::UsWest))?; + /// # Ok(()) + /// # } + /// # + /// # fn main() { + /// # try_main().unwrap(); + /// # } /// ``` /// /// [`Region::UsWest`]: ../model/enum.Region.html#variant.UsWest diff --git a/src/builder/edit_profile.rs b/src/builder/edit_profile.rs index d63a512f8c6..a4b5c996526 100644 --- a/src/builder/edit_profile.rs +++ b/src/builder/edit_profile.rs @@ -18,7 +18,13 @@ impl EditProfile { /// A utility method - [`utils::read_image`] - is provided to read an /// image from a file and return its contents in base64-encoded form: /// - /// ```rust,ignore + /// ```rust,no_run + /// # use serenity::Client; + /// # + /// # let mut client = Client::login(""); + /// # + /// # client.on_message(|context, _| { + /// # /// use serenity::utils; /// /// // assuming a `context` has been bound @@ -29,6 +35,7 @@ impl EditProfile { /// let _ = context.edit_profile(|profile| { /// profile.avatar(Some(&base64)) /// }); + /// # }); /// ``` /// /// [`utils::read_image`]: ../fn.read_image.html diff --git a/src/builder/edit_role.rs b/src/builder/edit_role.rs index 64cc42b6dc8..f17947a69b4 100644 --- a/src/builder/edit_role.rs +++ b/src/builder/edit_role.rs @@ -16,11 +16,15 @@ use ::model::{Permissions, Role, permissions}; /// /// # Examples /// -/// Create a hoisted, mentionable role named "a test role": +/// Create a hoisted, mentionable role named `"a test role"`: /// -/// ```rust,ignore +/// ```rust,no_run +/// # use serenity::model::{ChannelId, GuildId}; +/// # let (channel_id, guild_id) = (ChannelId(1), GuildId(2)); +/// # /// // assuming a `channel_id` and `guild_id` has been bound -/// let role = channel_id.create_role(guild_id, |r| r +/// +/// let role = guild_id.create_role(|r| r /// .hoist(true) /// .mentionable(true) /// .name("a test role")); diff --git a/src/builder/execute_webhook.rs b/src/builder/execute_webhook.rs index 67e10be0cf7..68a39fc8f1a 100644 --- a/src/builder/execute_webhook.rs +++ b/src/builder/execute_webhook.rs @@ -15,7 +15,7 @@ use ::internal::prelude::*; /// Creating two embeds, and then sending them as part of the delivery /// payload of [`Webhook::execute`]: /// -/// ```rust,ignore +/// ```rust,no_run /// use serenity::http; /// use serenity::model::Embed; /// use serenity::utils::Colour; @@ -57,6 +57,22 @@ pub struct ExecuteWebhook(pub JsonMap); impl ExecuteWebhook { /// Override the default avatar of the webhook with an image URL. + /// + /// # Examples + /// + /// Overriding the default avatar: + /// + /// ```rust,no_run + /// # use serenity::http; + /// # + /// # let webhook = http::get_webhook_with_token(0, "").unwrap(); + /// # + /// let avatar_url = "https://i.imgur.com/KTs6whd.jpg"; + /// + /// let _ = webhook.execute(|w| w + /// .avatar_url(avatar_url) + /// .content("Here's a webhook")); + /// ``` pub fn avatar_url(mut self, avatar_url: &str) -> Self { self.0.insert("avatar_url".to_owned(), Value::String(avatar_url.to_owned())); @@ -68,6 +84,20 @@ impl ExecuteWebhook { /// Note that when setting at least one embed via [`embeds`], this may be /// omitted. /// + /// # Examples + /// + /// Sending a webhook with a content of `"foo"`: + /// + /// ```rust,no_run + /// # use serenity::client::rest; + /// # + /// # let webhook = rest::get_webhook_with_token(0, "").unwrap(); + /// # + /// if let Err(why) = webhook.execute(|w| w.content("foo")) { + /// println!("Err sending webhook: {:?}", why); + /// } + /// ``` + /// /// [`embeds`]: #method.embeds pub fn content(mut self, content: &str) -> Self { self.0.insert("content".to_owned(), Value::String(content.to_owned())); @@ -96,7 +126,19 @@ impl ExecuteWebhook { /// Whether the message is a text-to-speech message. /// - /// Think carefully before setting this to `true`. + /// # Examples + /// + /// Sending a webhook with text-to-speech enabled: + /// + /// ```rust,no_run + /// # use serenity::client::rest; + /// # + /// # let webhook = rest::get_webhook_with_token(0, "").unwrap(); + /// # + /// if let Err(why) = webhook.execute(|w| w.content("hello").tts(true)) { + /// println!("Err sending webhook: {:?}", why); + /// } + /// ``` pub fn tts(mut self, tts: bool) -> Self { self.0.insert("tts".to_owned(), Value::Bool(tts)); @@ -104,6 +146,20 @@ impl ExecuteWebhook { } /// Override the default username of the webhook. + /// + /// # Examples + /// + /// Overriuding the username to `"hakase"`: + /// + /// ```rust,no_run + /// # use serenity::client::rest; + /// # + /// # let webhook = rest::get_webhook_with_token(0, "").unwrap(); + /// # + /// if let Err(why) = webhook.execute(|w| w.content("hello").username("hakase")) { + /// println!("Err sending webhook: {:?}", why); + /// } + /// ``` pub fn username(mut self, username: &str) -> Self { self.0.insert("username".to_owned(), Value::String(username.to_owned())); @@ -114,9 +170,17 @@ impl ExecuteWebhook { impl Default for ExecuteWebhook { /// Returns a default set of values for a [`Webhook`] execution. /// - /// The only default value is [`tts`] being set to `true`. In the event that - /// there is a bug that Discord defaults `tts` to `true`, at least - /// serenity won't be a part of it. + /// The only default value is [`tts`] being set to `false`. + /// + /// # Examples + /// + /// Creating an `ExecuteWebhook` builder: + /// + /// ```rust + /// use serenity::utils::builder::ExecuteWebhook; + /// + /// let executer = ExecuteWebhook::default(); + /// ``` /// /// [`Webhook`]: ../model/struct.Webhook.html /// [`tts`]: #method.tts diff --git a/src/builder/get_messages.rs b/src/builder/get_messages.rs index b9142a1545d..193adca6ef4 100644 --- a/src/builder/get_messages.rs +++ b/src/builder/get_messages.rs @@ -18,9 +18,37 @@ use ::model::MessageId; /// does not _need_ to be called and defaults to a value of 50. /// /// This should be used only for retrieving messages; see -/// [`Client::get_messages`] for examples. +/// [`GuildChannel::messages`] for examples. /// -/// [`Client::get_messages`]: ../client/struct.Client.html#method.get_messages +/// # Examples +/// +/// Creating a `GetMessages` builder to retrieve the first 25 messages after the +/// message with an Id of `158339864557912064`: +/// +/// ```rust,no_run +/// # use std::error::Error; +/// # +/// # fn try_main() -> Result<(), Box> { +/// use serenity::model::{ChannelId, MessageId}; +/// use serenity::builder::GetMessages; +/// +/// let retriever = GetMessages::default() +/// .after(MessageId(158339864557912064)) +/// .limit(25); +/// +/// // you can then pass it into a function which retrieves messages: +/// let channel_id = ChannelId(81384788765712384); +/// +/// let _messages = channel_id.messages(|_| retriever)?; +/// # Ok(()) +/// # } +/// # +/// # fn main() { +/// # try_main().unwrap(); +/// # } +/// ``` +/// +/// [`GuildChannel::messages`]: ../model/struct.GuildChannel.html#method.messages #[derive(Clone, Debug, Default)] pub struct GetMessages(pub BTreeMap); diff --git a/src/cache/mod.rs b/src/cache/mod.rs index 7bea6756731..bbd1a88a5ba 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -186,7 +186,7 @@ pub struct Cache { /// [`GuildMembersChunkEvent`]: ../model/event/struct.GuildMembersChunkEvent.html /// [`GuildSyncEvent`]: ../model/event/struct.GuildSyncEvent.html /// [`PresenceUpdateEvent`]: ../model/event/struct.PresenceUpdateEvent.html - /// [`Ready`]: ../model/event/struct.ReadyEvent.html + /// [`ReadyEvent`]: ../model/event/struct.ReadyEvent.html pub users: HashMap>>, } @@ -194,13 +194,39 @@ impl Cache { /// Fetches the number of [`Member`]s that have not had data received. /// /// The important detail to note here is that this is the number of - /// _member_s that have not had data downloaded. A single [`User`] may have + /// _member_s that have not had data received. A single [`User`] may have /// multiple associated member objects that have not been received. /// /// This can be used in combination with [`Shard::chunk_guilds`], and can be - /// used to determine how many members have not yet been downloaded. + /// used to determine how many members have not yet been received. + /// + /// ```rust,no_run + /// # use serenity::Client; + /// # + /// # let mut client = Client::login(""); + /// # + /// use serenity::client::CACHE; + /// use std::thread; + /// use std::time::Duration; + /// + /// client.on_ready(|ctx, _| { + /// // Wait some time for guilds to be received. + /// // + /// // You should keep track of this in a better fashion by tracking how + /// // many guilds each `ready` has, and incrementing a counter on + /// // GUILD_CREATEs. Once the number is equal, print the number of + /// // unknown members. + /// // + /// // For demonstrative purposes we're just sleeping the thread for 5 + /// // seconds. + /// thread::sleep(Duration::from_secs(5)); + /// + /// println!("{} unknown members", CACHE.read().unwrap().unknown_members()); + /// }); + /// ``` /// /// [`Member`]: ../model/struct.Member.html + /// [`Shard::chunk_guilds`]: ../gateway/struct.Shard.html#method.chunk_guilds /// [`User`]: ../model/struct.User.html pub fn unknown_members(&self) -> u64 { let mut total = 0; @@ -226,6 +252,16 @@ impl Cache { /// If there are 6 private channels and 2 groups in the cache, then `8` Ids /// will be returned. /// + /// Printing the count of all private channels and groups: + /// + /// ```rust,no_run + /// use serenity::client::CACHE; + /// + /// let amount = CACHE.read().unwrap().all_private_channels().len(); + /// + /// println!("There are {} private channels", amount); + /// ``` + /// /// [`Group`]: ../model/struct.Group.html /// [`PrivateChannel`]: ../model/struct.PrivateChannel.html pub fn all_private_channels(&self) -> Vec { @@ -242,6 +278,22 @@ impl Cache { /// retrieved over all shards are included in this count -- not just the /// current [`Context`]'s shard, if accessing from one. /// + /// # Examples + /// + /// Print all of the Ids of guilds in the Cache: + /// + /// ```rust,no_run + /// # use serenity::Client; + /// # + /// # let mut client = Client::login(""); + /// # + /// use serenity::client::CACHE; + /// + /// client.on_ready(|_, _| { + /// println!("Guilds in the Cache: {:?}", CACHE.read().unwrap().all_guilds()); + /// }); + /// ``` + /// /// [`Context`]: ../client/struct.Context.html /// [`Guild`]: ../model/struct.Guild.html /// [`Shard`]: ../gateway/struct.Shard.html @@ -314,8 +366,14 @@ impl Cache { /// Getting a guild's channel via the Id of the message received through a /// [`Client::on_message`] event dispatch: /// - /// ```rust,ignore - /// use serenity::CACHE; + /// ```rust,no_run + /// # use serenity::Client; + /// # + /// # let mut client = Client::login(""); + /// # + /// # client.on_message(|ctx, message| { + /// # + /// use serenity::client::CACHE; /// /// let cache = CACHE.read().unwrap(); /// @@ -329,6 +387,7 @@ impl Cache { /// return; /// }, /// }; + /// # }); /// ``` /// /// [`ChannelId`]: ../model/struct.ChannelId.html diff --git a/src/client/context.rs b/src/client/context.rs index 22216b1fc0a..9cb76b71858 100644 --- a/src/client/context.rs +++ b/src/client/context.rs @@ -74,8 +74,16 @@ impl Context { /// /// Change the current user's username: /// - /// ```rust,ignore - /// context.edit_profile(|p| p.username("Hakase")); + /// ```rust,no_run + /// # use serenity::Client; + /// # + /// # let mut client = Client::login(""); + /// # + /// # client.on_message(|ctx, msg| { + /// # if msg.content == "!changename" { + /// ctx.edit_profile(|p| p.username("Hakase")); + /// # } + /// # }); /// ``` #[cfg(feature="builder")] pub fn edit_profile EditProfile>(&self, f: F) -> Result { @@ -105,7 +113,22 @@ impl Context { } /// Sets the current user as being [`Online`]. This maintains the current - /// game and `afk` setting. + /// game. + /// + /// # Examples + /// + /// Set the current user to being online on the shard: + /// + /// ```rust,no_run + /// # use serenity::Client; + /// # + /// # let mut client = Client::login(""); + /// client.on_message(|ctx, msg| { + /// if msg.content == "!online" { + /// ctx.online(); + /// } + /// }); + /// ``` /// /// [`Online`]: ../model/enum.OnlineStatus.html#variant.Online pub fn online(&self) { @@ -113,7 +136,22 @@ impl Context { } /// Sets the current user as being [`Idle`]. This maintains the current - /// game and `afk` setting. + /// game. + /// + /// # Examples + /// + /// Set the current user to being idle on the shard: + /// + /// ```rust,no_run + /// # use serenity::Client; + /// # + /// # let mut client = Client::login(""); + /// client.on_message(|ctx, msg| { + /// if msg.content == "!idle" { + /// ctx.idle(); + /// } + /// }); + /// ``` /// /// [`Idle`]: ../model/enum.OnlineStatus.html#variant.Idle pub fn idle(&self) { @@ -121,7 +159,22 @@ impl Context { } /// Sets the current user as being [`DoNotDisturb`]. This maintains the - /// current game and `afk` setting. + /// current game. + /// + /// # Examples + /// + /// Set the current user to being Do Not Disturb on the shard: + /// + /// ```rust,no_run + /// # use serenity::Client; + /// # + /// # let mut client = Client::login(""); + /// client.on_message(|ctx, msg| { + /// if msg.content == "!dnd" { + /// ctx.dnd(); + /// } + /// }); + /// ``` /// /// [`DoNotDisturb`]: ../model/enum.OnlineStatus.html#variant.DoNotDisturb pub fn dnd(&self) { @@ -129,18 +182,47 @@ impl Context { } /// Sets the current user as being [`Invisible`]. This maintains the current - /// game and `afk` setting. + /// game. /// + /// # Examples + /// + /// Set the current user to being invisible on the shard when an + /// [`Event::Ready`] is received: + /// + /// ```rust,no_run + /// # use serenity::Client; + /// # + /// # let mut client = Client::login(""); + /// client.on_ready(|ctx, _| { + /// ctx.invisible(); + /// }); + /// ``` + /// + /// [`Event::Ready`]: ../model/event/enum.Event.html#variant.Ready /// [`Invisible`]: ../model/enum.OnlineStatus.html#variant.Invisible pub fn invisible(&self) { self.shard.lock().unwrap().set_status(OnlineStatus::Invisible); } - /// "Resets" the current user's presence, by setting the game to `None`, - /// the online status to [`Online`], and `afk` to `false`. + /// "Resets" the current user's presence, by setting the game to `None` and + /// the online status to [`Online`]. /// /// Use [`set_presence`] for fine-grained control over individual details. /// + /// # Examples + /// + /// Reset the presence when an [`Event::Resumed`] is received: + /// + /// ```rust,no_run + /// # use serenity::Client; + /// # + /// # let mut client = Client::login(""); + /// client.on_resume(|ctx, _| { + /// ctx.reset_presence(); + /// }); + /// ``` + /// + /// [`Event::Resumed`]: ../model/event/enum.Event.html#variant.Resumed /// [`Online`]: ../model/enum.OnlineStatus.html#variant.Online /// [`set_presence`]: #method.set_presence pub fn reset_presence(&self) { @@ -149,19 +231,29 @@ impl Context { .set_presence(None, OnlineStatus::Online, false) } - /// Sets the current game, defaulting to an online status of [`Online`], and - /// setting `afk` to `false`. + /// Sets the current game, defaulting to an online status of [`Online`]. /// /// # Examples /// - /// Set the current user as playing "Heroes of the Storm": + /// Create a command named `~setgame` that accepts a name of a game to be + /// playing: /// - /// ```rust,ignore + /// ```rust,no_run + /// # use serenity::Client; + /// # + /// # let mut client = Client::login(""); + /// # /// use serenity::model::Game; /// - /// // assuming you are in a context + /// client.on_message(|ctx, msg| { + /// let args = msg.content.splitn(2, ' ').collect::>(); + /// + /// if args.len() < 2 || *unsafe { args.get_unchecked(0) } != "~setgame" { + /// return; + /// } /// - /// context.set_game(Game::playing("Heroes of the Storm")); + /// ctx.set_game(Game::playing(*unsafe { args.get_unchecked(1) })); + /// }); /// ``` /// /// [`Online`]: ../model/enum.OnlineStatus.html#variant.Online @@ -180,6 +272,21 @@ impl Context { /// /// **Note**: Maximum length is 128. /// + /// # Examples + /// + /// When an [`Event::Ready`] is received, set the game name to `"test"`: + /// + /// ```rust,no_run + /// # use serenity::Client; + /// # + /// # let mut client = Client::login(""); + /// # + /// client.on_ready(|ctx, _| { + /// ctx.set_game_name("test"); + /// }); + /// ``` + /// + /// [`Event::Ready`]: ../model/event/enum.Event.html#variant.Ready /// [`GameType`]: ../model/enum.GameType.html /// [`Online`]: ../model/enum.OnlineStatus.html#variant.Online /// [`OnlineStatus`]: ../model/enum.OnlineStatus.html @@ -202,29 +309,38 @@ impl Context { /// /// # Examples /// - /// Setting the current user as having no game, being [`Idle`], - /// and setting `afk` to `true`: + /// Setting the current user as having no game and being [`Idle`]: /// - /// ```rust,ignore + /// ```rust,no_run + /// # use serenity::Client; + /// # + /// # let mut client = Client::login(""); + /// # + /// # client.on_ready(|ctx, _| { + /// # /// use serenity::model::OnlineStatus; /// - /// // assuming you are in a context - /// - /// context.set_game(None, OnlineStatus::Idle, true); + /// ctx.set_presence(None, OnlineStatus::Idle, false); + /// # }); /// ``` /// - /// Setting the current user as playing "Heroes of the Storm", being - /// [`DoNotDisturb`], and setting `afk` to `false`: + /// Setting the current user as playing `"Heroes of the Storm"`, while being + /// [`DoNotDisturb`]: /// /// ```rust,ignore + /// # use serenity::Client; + /// # + /// # let mut client = Client::login(""); + /// # + /// # client.on_ready(|ctx, _| { + /// # /// use serenity::model::{Game, OnlineStatus}; /// - /// // assuming you are in a context - /// /// let game = Game::playing("Heroes of the Storm"); /// let status = OnlineStatus::DoNotDisturb; /// - /// context.set_game(Some(game), status, false); + /// context.set_presence(Some(game), status, false); + /// # }); /// ``` /// /// [`DoNotDisturb`]: ../model/enum.OnlineStatus.html#variant.DoNotDisturb diff --git a/src/client/mod.rs b/src/client/mod.rs index 1ba555a6590..bdaa2aa22bb 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -105,9 +105,61 @@ pub struct Client { /// In the meaning of a context, this data can be accessed through /// [`Context::data`]. /// + /// # Examples + /// + /// Create a `MessageEventCounter` to track the following events: + /// + /// - [`Event::MessageCreate`] + /// - [`Event::MessageDelete`] + /// - [`Event::MessageDeleteBulk`] + /// - [`Event::MessageUpdate`] + /// + /// ```rust,ignore + /// extern crate serenity; + /// extern crate typemap; + /// + /// use serenity::Client; + /// use std::collections::HashMap; + /// use std::env; + /// use typemap::Key; + /// + /// struct MessageEventCounter; + /// + /// impl Key for MessageEventCounter { + /// type Value = HashMap; + /// } + /// + /// let mut client = Client::login(&env::var("DISCORD_TOKEN").unwrap()); + /// + /// { + /// let mut data = client.data.lock().unwrap(); + /// data.insert::(HashMap::default()); + /// } + /// + /// macro_rules! reg { + /// ($ctx:ident $name:expr) => { + /// { + /// let mut data = $ctx.data.lock().unwrap(); + /// let counter = data.get_mut::().unwrap(); + /// let entry = counter.entry($name).or_insert(0); + /// *entry += 1; + /// } + /// }; + /// } + /// + /// client.on_message(|ctx, _| reg!(ctx "MessageCreate")); + /// client.on_message_delete(|ctx, _| reg!(ctx "MessageDelete")); + /// client.on_message_delete_bulk(|ctx, _| reg!(ctx "MessageDeleteBulk")); + /// client.on_message_update(|ctx, _| reg!(ctx "MessageUpdate")); + /// ``` + /// /// Refer to [example 05] for an example on using the `data` field. /// /// [`Context::data`]: struct.Context.html#method.data + /// [`Event::MessageCreate`]: ../model/event/enum.Event.html#variant.MessageCreate + /// [`Event::MessageDelete`]: ../model/event/enum.Event.html#variant.MessageDelete + /// [`Event::MessageDeleteBulk`]: ../model/event/enum.Event.html#variant.MessageDeleteBulk + /// [`Event::MessageUpdate`]: ../model/event/enum.Event.html#variant.MessageUpdate /// [example 05]: https://github.com/zeyla/serenity/tree/master/examples/05_command_framework pub data: Arc>, /// A vector of all active shards that have received their [`Event::Ready`] @@ -137,6 +189,27 @@ impl Client { /// /// Discord has a requirement of prefixing bot tokens with `"Bot "`, which /// this function will automatically do for you if not already included. + /// + /// # Examples + /// + /// Create a Client, using a token from an environment variable: + /// + /// ```rust,no_run + /// # use std::error::Error; + /// # + /// # fn try_main() -> Result<(), Box> { + /// use serenity::Client; + /// use std::env; + /// + /// let token = env::var("DISCORD_TOKEN")?; + /// let client = Client::login(&token); + /// # Ok(()) + /// # } + /// # + /// # fn main() { + /// # try_main().unwrap(); + /// # } + /// ``` pub fn login(bot_token: &str) -> Self { let token = if bot_token.starts_with("Bot ") { bot_token.to_owned() @@ -154,6 +227,32 @@ impl Client { /// See the [framework module-level documentation][framework docs] for more /// information on usage. /// + /// # Examples + /// + /// Create a simple framework that responds to a `~ping` command: + /// + /// ```rust,no_run + /// # use std::error::Error; + /// # + /// # fn try_main() -> Result<(), Box> { + /// use serenity::Client; + /// use std::env; + /// + /// let mut client = Client::login(&env::var("DISCORD_TOKEN")?); + /// client.with_framework(|f| f + /// .configure(|c| c.prefix("~")) + /// .command("ping", |c| c.exec_str("Pong!"))); + /// # Ok(()) + /// # } + /// # + /// # fn main() { + /// # try_main().unwrap(); + /// # } + /// ``` + /// + /// Refer to the documentation for the `framework` module for more in-depth + /// information. + /// /// [`on_message`]: #method.on_message /// [framework docs]: ../framework/index.html #[cfg(feature="framework")] @@ -174,6 +273,30 @@ impl Client { /// Refer to the [Gateway documentation][gateway docs] for more information /// on effectively using sharding. /// + /// # Examples + /// + /// Starting a Client with only 1 shard, out of 1 total: + /// + /// ```rust,no_run + /// # use std::error::Error; + /// # + /// # fn try_main() -> Result<(), Box> { + /// use serenity::client::Client; + /// use std::env; + /// + /// let mut client = Client::login(&env::var("DISCORD_TOKEN")?); + /// + /// if let Err(why) = client.start() { + /// println!("Err with client: {:?}", why); + /// } + /// # Ok(()) + /// # } + /// # + /// # fn main() { + /// # try_main().unwrap(); + /// # } + /// ``` + /// /// [gateway docs]: gateway/index.html#sharding pub fn start(&mut self) -> Result<()> { self.start_connection(None, http::get_gateway()?.url) @@ -191,6 +314,30 @@ impl Client { /// Refer to the [Gateway documentation][gateway docs] for more information /// on effectively using sharding. /// + /// # Examples + /// + /// Start as many shards as needed using autosharding: + /// + /// ```rust,no_run + /// # use std::error::Error; + /// # + /// # fn try_main() -> Result<(), Box> { + /// use serenity::Client; + /// use std::env; + /// + /// let mut client = Client::login(&env::var("DISCORD_TOKEN")?); + /// + /// if let Err(why) = client.start_autosharded() { + /// println!("Err with client: {:?}", why); + /// } + /// # Ok(()) + /// # } + /// # + /// # fn main() { + /// # try_main().unwrap(); + /// # } + /// ``` + /// /// [gateway docs]: gateway/index.html#sharding pub fn start_autosharded(&mut self) -> Result<()> { let mut res = http::get_bot_gateway()?; @@ -216,6 +363,55 @@ impl Client { /// Refer to the [Gateway documentation][gateway docs] for more information /// on effectively using sharding. /// + /// # Examples + /// + /// Start shard 3 of 5: + /// + /// ```rust,no_run + /// # use std::error::Error; + /// # + /// # fn try_main() -> Result<(), Box> { + /// use serenity::Client; + /// use std::env; + /// + /// let mut client = Client::login(&env::var("DISCORD_TOKEN")?); + /// + /// if let Err(why) = client.start_shard(3, 5) { + /// println!("Err with client: {:?}", why); + /// } + /// # Ok(()) + /// # } + /// # + /// # fn main() { + /// # try_main().unwrap(); + /// # } + /// ``` + /// + /// Start shard 0 of 1 (you may also be interested in [`start`] or + /// [`start_autosharded`]): + /// + /// ```rust,no_run + /// # use std::error::Error; + /// # + /// # fn try_main() -> Result<(), Box> { + /// use serenity::Client; + /// use std::env; + /// + /// let mut client = Client::login(&env::var("DISCORD_TOKEN")?); + /// + /// if let Err(why) = client.start_shard(0, 1) { + /// println!("Err with client: {:?}", why); + /// } + /// # Ok(()) + /// # } + /// # + /// # fn main() { + /// # try_main().unwrap(); + /// # } + /// ``` + /// + /// [`start`]: #method.start + /// [`start_autosharded`]: #method.start_autosharded /// [gateway docs]: gateway/index.html#sharding pub fn start_shard(&mut self, shard: u64, shards: u64) -> Result<()> { self.start_connection(Some([shard, shard, shards]), http::get_gateway()?.url) @@ -233,8 +429,32 @@ impl Client { /// Refer to the [Gateway documentation][gateway docs] for more information /// on effectively using sharding. /// + /// # Examples + /// + /// Start all of 8 shards: + /// + /// ```rust,no_run + /// # use std::error::Error; + /// # + /// # fn try_main() -> Result<(), Box> { + /// use serenity::Client; + /// use std::env; + /// + /// let mut client = Client::login(&env::var("DISCORD_TOKEN")?); + /// + /// if let Err(why) = client.start_shards(8) { + /// println!("Err with client: {:?}", why); + /// } + /// # Ok(()) + /// # } + /// # + /// # fn main() { + /// # try_main().unwrap(); + /// # } + /// ``` + /// /// [`start_shard`]: #method.start_shard - /// [`start_shard_range`]: #method.start_shards + /// [`start_shard_range`]: #method.start_shard_range /// [Gateway docs]: gateway/index.html#sharding pub fn start_shards(&mut self, total_shards: u64) -> Result<()> { self.start_connection(Some([0, total_shards - 1, total_shards]), http::get_gateway()?.url) @@ -267,6 +487,26 @@ impl Client { /// let _ = client.start_shard_range([4, 7], 10); /// ``` /// + /// ```rust,no_run + /// # use std::error::Error; + /// # + /// # fn try_main() -> Result<(), Box> { + /// use serenity::Client; + /// use std::env; + /// + /// let mut client = Client::login(&env::var("DISCORD_TOKEN")?); + /// + /// if let Err(why) = client.start_shard_range([4, 7], 10) { + /// println!("Err with client: {:?}", why); + /// } + /// # Ok(()) + /// # } + /// # + /// # fn main() { + /// # try_main().unwrap(); + /// # } + /// ``` + /// /// [`start_shard`]: #method.start_shard /// [`start_shards`]: #method.start_shards /// [Gateway docs]: gateway/index.html#sharding @@ -276,6 +516,26 @@ impl Client { /// Attaches a handler for when a [`ChannelCreate`] is received. /// + /// # Examples + /// + /// If the channel is a guild channel, send `"first"` to the channel when + /// one is created: + /// + /// ```rust,no_run + /// # use serenity::Client; + /// # + /// # let mut client = Client::login(""); + /// use serenity::model::Channel; + /// + /// client.on_channel_create(|ctx, channel| { + /// if let Channel::Guild(ch) = channel { + /// if let Err(why) = ch.read().unwrap().say("first") { + /// println!("Err sending first message: {:?}", why); + /// } + /// } + /// }); + /// ``` + /// /// [`ChannelCreate`]: ../model/event/enum.Event.html#variant.ChannelCreate pub fn on_channel_create(&mut self, handler: F) where F: Fn(Context, Channel) + Send + Sync + 'static { @@ -286,6 +546,34 @@ impl Client { /// Attaches a handler for when a [`ChannelDelete`] is received. /// + /// # Examples + /// + /// If the channel is a guild channel, send the name of the channel to the + /// guild's default channel. + /// + /// ```rust,no_run + /// # use serenity::Client; + /// # + /// # let mut client = Client::login(""); + /// use serenity::model::{Channel, ChannelId}; + /// + /// client.on_channel_delete(|ctx, channel| { + /// if let Channel::Guild(channel) = channel { + /// let (content, default_channel_id) = { + /// let reader = channel.read().unwrap(); + /// let content = format!("A channel named '{}' was deleted.", reader.name); + /// let id = ChannelId(reader.guild_id.0); + /// + /// (content, id) + /// }; + /// + /// if let Err(why) = default_channel_id.say(&content) { + /// println!("Err sending message to default channel: {:?}", why); + /// } + /// } + /// }); + /// ``` + /// /// [`ChannelDelete`]: ../model/event/enum.Event.html#variant.ChannelDelete pub fn on_channel_delete(&mut self, handler: F) where F: Fn(Context, Channel) + Send + Sync + 'static { @@ -1127,6 +1415,29 @@ fn login(token: String) -> Client { /// - The second part of the token is at least 6 characters long; /// - The token does not contain any whitespace prior to or after the token. /// +/// # Examples +/// +/// Validate that a token is valid and that a number of invalid tokens are +/// actually invalid: +/// +/// ```rust,no_run +/// use serenity::client::validate_token; +/// +/// // ensure a valid token is in fact valid: +/// assert!(validate_token("Mjg4NzYwMjQxMzYzODc3ODg4.C_ikow.j3VupLBuE1QWZng3TMGH0z_UAwg").is_ok()); +/// +/// // "cat" isn't a valid token: +/// assert!(validate_token("cat").is_err()); +/// +/// // tokens must have three parts, separated by periods (this is still +/// // actually an invalid token): +/// assert!(validate_token("aaa.abcdefgh.bbb").is_ok()); +/// +/// // the second part must be _at least_ 6 characters long: +/// assert!(validate_token("a.abcdef.b").is_ok()); +/// assert!(validate_token("a.abcde.b").is_err()); +/// ``` +/// /// # Errors /// /// Returns a [`ClientError::InvalidToken`] when one of the above checks fail. diff --git a/src/gateway/shard.rs b/src/gateway/shard.rs index c9886c4ad5d..9c635ecac17 100644 --- a/src/gateway/shard.rs +++ b/src/gateway/shard.rs @@ -188,6 +188,18 @@ impl Shard { /// /// For example, if using 3 shards in total, and if this is shard 1, then it /// can be read as "the second of three shards". + /// + /// # Examples + /// + /// Retrieving the shard info for the second shard, out of two shards total: + /// + /// ```rust,no_run + /// # use serenity::client::gateway::Shard; + /// # + /// # let (shard, _, _) = Shard::new("", "", Some([1, 2])).unwrap(); + /// # + /// assert_eq!(shard.shard_info(), Some([1, 2])); + /// ``` pub fn shard_info(&self) -> Option<[u64; 2]> { self.shard_info } @@ -205,6 +217,20 @@ impl Shard { /// Sets the user's current game, if any. /// /// Other presence settings are maintained. + /// + /// # Examples + /// + /// Setting the current game to playing `"Heroes of the Storm"`: + /// + /// ```rust,no_run + /// # use serenity::client::gateway::Shard; + /// # + /// # let (mut shard, _, _) = Shard::new("", "", Some([0, 1])).unwrap(); + /// # + /// use serenity::model::Game; + /// + /// shard.set_game(Some(Game::playing("Heroes of the Storm"))); + /// ``` pub fn set_game(&mut self, game: Option) { self.current_presence.0 = game; @@ -213,11 +239,26 @@ impl Shard { /// Sets the user's current online status. /// - /// Note that [`Offline`] is not a valid presence, so it is automatically - /// converted to [`Invisible`]. + /// Note that [`Offline`] is not a valid online status, so it is + /// automatically converted to [`Invisible`]. /// /// Other presence settings are maintained. /// + /// # Examples + /// + /// Setting the current online status for the shard to [`DoNotDisturb`]. + /// + /// ```rust,no_run + /// # use serenity::client::gateway::Shard; + /// # + /// # let (mut shard, _, _) = Shard::new("", "", Some([0, 1])).unwrap(); + /// # + /// use serenity::model::OnlineStatus; + /// + /// shard.set_status(OnlineStatus::DoNotDisturb); + /// ``` + /// + /// [`DoNotDisturb`]: ../../model/enum.OnlineStatus.html#variant.DoNotDisturb /// [`Invisible`]: ../../model/enum.OnlineStatus.html#variant.Invisible /// [`Offline`]: ../../model/enum.OnlineStatus.html#variant.Offline pub fn set_status(&mut self, online_status: OnlineStatus) { @@ -239,16 +280,14 @@ impl Shard { /// Set the current user as playing `"Heroes of the Storm"`, being online, /// and not being afk: /// - /// ```rust,ignore + /// ```rust,no_run + /// # use serenity::client::gateway::Shard; + /// # + /// # let (mut shard, _, _) = Shard::new("", "", Some([0, 1])).unwrap(); + /// # /// use serenity::model::{Game, OnlineStatus}; /// - /// // assuming you are in a context - /// - /// context.shard.lock() - /// .unwrap() - /// .set_presence(Some(Game::playing("Heroes of the Storm")), - /// OnlineStatus::Online, - /// false); + /// shard.set_presence(Some(Game::playing("Heroes of the Storm")), OnlineStatus::Online, false); /// ``` pub fn set_presence(&mut self, game: Option, @@ -444,8 +483,32 @@ impl Shard { } } - /// Calculates the heartbeat latency (in nanoseconds) between the shard and - /// Discord. + /// Calculates the heartbeat latency between the shard and the gateway. + /// + /// # Examples + /// + /// When using the [`Client`], output the latency in response to a `"~ping"` + /// message handled through [`Client::on_message`]. + /// + /// ```rust,no_run + /// # use serenity::client::Client; + /// # + /// # let mut client = Client::login("hello source code viewer <3"); + /// client.on_message(|ctx, msg| { + /// if msg.content == "~ping" { + /// if let Some(latency) = ctx.shard.lock().unwrap().latency() { + /// let s = format!("{}.{}s", latency.as_secs(), latency.subsec_nanos()); + /// + /// let _ = msg.channel_id.say(&s); + /// } else { + /// let _ = msg.channel_id.say("N/A"); + /// } + /// } + /// }); + /// ``` + /// + /// [`Client`]: ../struct.Client.html + /// [`Client::on_message`]: ../struct.Client.html#method.on_message // Shamelessly stolen from brayzure's commit in eris: // pub fn latency(&self) -> Option { @@ -487,16 +550,52 @@ impl Shard { /// Requests that one or multiple [`Guild`]s be chunked. /// - /// This will ask Discord to start sending member chunks for large guilds - /// (250 members+). If a guild is over 250 members, then a full member list - /// will not be downloaded, and must instead be requested to be sent in - /// "chunks" containing members. + /// This will ask the gateway to start sending member chunks for large + /// guilds (250 members+). If a guild is over 250 members, then a full + /// member list will not be downloaded, and must instead be requested to be + /// sent in "chunks" containing members. /// /// Member chunks are sent as the [`Event::GuildMembersChunk`] event. Each /// chunk only contains a partial amount of the total members. /// /// If the `cache` feature is enabled, the cache will automatically be /// updated with member chunks. + /// + /// # Examples + /// + /// Chunk a single guild by Id, limiting to 2000 [`Member`]s, and not + /// specifying a query parameter: + /// + /// ```rust,no_run + /// # use serenity::client::gateway::Shard; + /// # + /// # let (shard, _, _) = Shard::new("", "", Some([0, 1])).unwrap(); + /// # + /// use serenity::model::GuildId; + /// + /// let guild_ids = vec![GuildId(81384788765712384)]; + /// + /// shard.chunk_guilds(&guild_ids, Some(2000), None); + /// ``` + /// + /// Chunk a single guild by Id, limiting to 20 members, and specifying a + /// query parameter of `"do"`: + /// + /// ```rust,no_run + /// # use serenity::client::gateway::Shard; + /// # + /// # let (shard, _, _) = Shard::new("", "", Some([0, 1])).unwrap(); + /// # + /// use serenity::model::GuildId; + /// + /// let guild_ids = vec![GuildId(81384788765712384)]; + /// + /// shard.chunk_guilds(&guild_ids, Some(20), Some("do")); + /// ``` + /// + /// [`Event::GuildMembersChunk`]: ../../model/event/enum.Event.html#variant.GuildMembersChunk + /// [`Guild`]: ../../model/struct.Guild.html + /// [`Member`]: ../../model/struct.Member.html pub fn chunk_guilds(&self, guild_ids: &[GuildId], limit: Option, query: Option<&str>) { let msg = json!({ "op": OpCode::GetGuildMembers.num(), @@ -513,11 +612,27 @@ impl Shard { /// Calculates the number of guilds that the shard is responsible for. /// /// If sharding is not being used (i.e. 1 shard), then the total number of - /// guilds in the [`Cache`] will be used. + /// [`Guild`] in the [`Cache`] will be used. /// /// **Note**: Requires the `cache` feature be enabled. /// - /// [`Cache`]: ../cache/struct.Cache.html + /// # Examples + /// + /// Retrieve the number of guilds a shard is responsible for: + /// + /// ```rust,no_run + /// # use serenity::client::gateway::Shard; + /// # + /// # let (shard, _, _) = Shard::new("will anyone read this", "", Some([0, 1])).unwrap(); + /// # + /// let info = shard.shard_info(); + /// let guilds = shard.guilds_handled(); + /// + /// println!("Shard {:?} is responsible for {} guilds", info, guilds); + /// ``` + /// + /// [`Cache`]: ../ext/cache/struct.Cache.html + /// [`Guild`]: ../model/struct.Guild.html #[cfg(feature="cache")] pub fn guilds_handled(&self) -> u16 { let cache = CACHE.read().unwrap(); diff --git a/src/model/misc.rs b/src/model/misc.rs index 4da3c014ce3..3ac8f4723a2 100644 --- a/src/model/misc.rs +++ b/src/model/misc.rs @@ -123,7 +123,7 @@ impl FromStr for RoleId { } /// A version of an emoji used only when solely the Id and name are known. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct EmojiIdentifier { /// The Id of the emoji. pub id: EmojiId, diff --git a/src/utils/colour.rs b/src/utils/colour.rs index 1a4e8562500..71fc25a79f0 100644 --- a/src/utils/colour.rs +++ b/src/utils/colour.rs @@ -24,13 +24,24 @@ macro_rules! colour { /// Passing in a role's colour, and then retrieving its green component /// via [`g`]: /// -/// ```rust,ignore +/// ```rust +/// # use serenity::model::{Role, RoleId, permissions}; /// use serenity::utils::Colour; +/// # +/// # let role = Role { +/// # colour: Colour::blurple(), +/// # hoist: false, +/// # id: RoleId(1), +/// # managed: false, +/// # mentionable: false, +/// # name: "test".to_owned(), +/// # permissions: permissions::PRESET_GENERAL, +/// # position: 7, +/// # }; /// /// // assuming a `role` has already been bound /// -/// let colour = Colour::new(role.colour); -/// let green = colour.g(); +/// let green = role.colour.g(); /// /// println!("The green component is: {}", green); /// ``` @@ -45,7 +56,7 @@ macro_rules! colour { /// assert_eq!(colour.tuple(), (17, 128, 106)); /// ``` /// -/// Colours can also be directly compared for equivilance: +/// Colours can also be directly compared for equivalence: /// /// ```rust /// use serenity::utils::Colour; diff --git a/src/utils/message_builder.rs b/src/utils/message_builder.rs index a6e7e0bfe42..3029226d54b 100644 --- a/src/utils/message_builder.rs +++ b/src/utils/message_builder.rs @@ -12,7 +12,18 @@ use ::model::{ChannelId, Emoji, Mentionable, RoleId, UserId}; /// Build a message, mentioning a [`user`] and an [`emoji`], and retrieving the /// value: /// -/// ```rust,ignore +/// ```rust,no_run +/// # use serenity::model::{Emoji, EmojiId, UserId}; +/// # +/// # let user = UserId(1); +/// # let emoji = Emoji { +/// # id: EmojiId(2), +/// # name: "test".to_owned(), +/// # managed: false, +/// # require_colons: true, +/// # roles: vec![], +/// # }; +/// # /// use serenity::utils::MessageBuilder; /// /// // assuming an `emoji` and `user` have already been bound @@ -32,7 +43,20 @@ use ::model::{ChannelId, Emoji, Mentionable, RoleId, UserId}; pub struct MessageBuilder(pub String); impl MessageBuilder { - /// Creates a new, empty-content builder. + /// Creates a new, empty builder. + /// + /// # Examples + /// + /// Create a new `MessageBuilder`: + /// + /// ```rust + /// use serenity::utils::MessageBuilder; + /// + /// let message = MessageBuilder::new(); + /// + /// // alternatively: + /// let message = MessageBuilder::default(); + /// ``` pub fn new() -> MessageBuilder { MessageBuilder::default() } @@ -41,6 +65,23 @@ impl MessageBuilder { /// /// # Examples /// + /// Create a string mentioning a channel by Id, and then suffixing `"!"`, + /// and finally building it to retrieve the inner String: + /// + /// ```rust + /// use serenity::model::ChannelId; + /// use serenity::utils::MessageBuilder; + /// + /// let channel_id = ChannelId(81384788765712384); + /// + /// let content = MessageBuilder::new() + /// .channel(channel_id) + /// .push("!") + /// .build(); + /// + /// assert_eq!(content, "<#81384788765712384>!"); + /// ``` + /// /// This is equivalent to simply retrieving the tuple struct's first value: /// /// ```rust @@ -62,6 +103,25 @@ impl MessageBuilder { /// Refer to `ChannelId`'s [Display implementation] for more information on /// how this is formatted. /// + /// # Examples + /// + /// Mentioning a [`Channel`] by Id: + /// + /// ```rust + /// use serenity::model::ChannelId; + /// use serenity::utils::MessageBuilder; + /// + /// let channel_id = ChannelId(81384788765712384); + /// + /// let content = MessageBuilder::new() + /// .push("The channel is: ") + /// .channel(channel_id) + /// .build(); + /// + /// assert_eq!(content, "The channel is: <#81384788765712384>"); + /// ``` + /// + /// [`Channel`]: ../model/enum.Channel.html /// [`ChannelId`]: ../model/struct.ChannelId.html /// [`GuildChannel`]: ../model/struct.GuildChannel.html /// [Display implementation]: ../model/struct.ChannelId.html#method.fmt-1 @@ -76,6 +136,31 @@ impl MessageBuilder { /// Refer to `Emoji`s [Display implementation] for more information on how /// this is formatted. /// + /// # Examples + /// + /// Mention an emoji in a message's content: + /// + /// ```rust + /// use serenity::model::{Emoji, EmojiId}; + /// use serenity::utils::MessageBuilder; + /// + /// let emoji = Emoji { + /// id: EmojiId(302516740095606785), + /// managed: true, + /// name: "smugAnimeFace".to_owned(), + /// require_colons: true, + /// roles: vec![], + /// }; + /// + /// let message = MessageBuilder::new() + /// .push("foo ") + /// .emoji(emoji) + /// .push(".") + /// .build(); + /// + /// assert_eq!(message, "foo <:smugAnimeFace:302516740095606785>."); + /// ``` + /// /// [Display implementation]: ../model/struct.Emoji.html#method.fmt pub fn emoji(mut self, emoji: Emoji) -> Self { let _ = write!(self.0, "{}", emoji); @@ -113,7 +198,45 @@ impl MessageBuilder { self } - /// Pushes a code-block to your message, with optional syntax highlighting. + /// Pushes a codeblock to the content, with optional syntax highlighting. + /// + /// # Examples + /// + /// Pushing a Rust codeblock: + /// + /// ```rust,ignore + /// use serenity::utils::MessageBuilder; + /// + /// let code = r#" + /// fn main() { + /// println!("Hello, world!"); + /// } + /// "#; + /// + /// let content = MessageBuilder::new() + /// .push_codeblock(code, Some("rust")) + /// .build(); + /// + /// let expected = r#"```rust + /// fn main() { + /// println!("Hello, world!"); + /// } + /// ```"#; + /// + /// assert_eq!(content, expected); + /// ``` + /// + /// Pushing a codeblock without a language: + /// + /// ```rust + /// use serenity::utils::MessageBuilder; + /// + /// let content = MessageBuilder::new() + /// .push_codeblock("hello", None) + /// .build(); + /// + /// assert_eq!(content, "```\nhello\n```"); + /// ``` pub fn push_codeblock(mut self, content: &str, language: Option<&str>) -> Self { self.0.push_str("```"); @@ -128,7 +251,32 @@ impl MessageBuilder { self } - /// Pushes an inline monospaced text to your message. + /// Pushes inlined monospaced text to the content. + /// + /// # Examples + /// + /// Display a server configuration value to the user: + /// + /// ```rust + /// use serenity::utils::MessageBuilder; + /// + /// let key = "prefix"; + /// let value = "&"; + /// + /// let content = MessageBuilder::new() + /// .push("The setting ") + /// .push_mono(key) + /// .push(" for this server is ") + /// .push_mono(value) + /// .push(".") + /// .build(); + /// + /// let expected = format!("The setting `{}` for this server is `{}`.", + /// key, + /// value); + /// + /// assert_eq!(content, expected); + /// ``` pub fn push_mono(mut self, content: &str) -> Self { self.0.push('`'); self.0.push_str(content); @@ -137,7 +285,27 @@ impl MessageBuilder { self } - /// Pushes an inline italicized text to your message. + /// Pushes inlined italicized text to the content. + /// + /// # Examples + /// + /// Emphasize information to the user: + /// + /// ```rust + /// use serenity::utils::MessageBuilder; + /// + /// let content = MessageBuilder::new() + /// .push("You don't ") + /// .push_italic("always need") + /// .push(" to italicize ") + /// .push_italic("everything") + /// .push(".") + /// .build(); + /// + /// let expected = "You don't _always need_ to italicize _everything_."; + /// + /// assert_eq!(content, expected); + /// ``` pub fn push_italic(mut self, content: &str) -> Self { self.0.push('_'); self.0.push_str(content); @@ -146,7 +314,7 @@ impl MessageBuilder { self } - /// Pushes an inline bold text to your message. + /// Pushes an inline bold text to the content. pub fn push_bold(mut self, content: &str) -> Self { self.0.push_str("**"); self.0.push_str(content); @@ -155,7 +323,7 @@ impl MessageBuilder { self } - /// Pushes an underlined inline text to your message. + /// Pushes an underlined inline text to the content. pub fn push_underline(mut self, content: &str) -> Self { self.0.push_str("__"); self.0.push_str(content); @@ -164,7 +332,7 @@ impl MessageBuilder { self } - /// Pushes a strikethrough inline text to your message. + /// Pushes a strikethrough inline text to the content. pub fn push_strike(mut self, content: &str) -> Self { self.0.push_str("~~"); self.0.push_str(content); @@ -205,7 +373,7 @@ impl MessageBuilder { self } - /// Pushes an inline monospaced text to your message normalizing content. + /// Pushes an inline monospaced text to the content normalizing content. pub fn push_mono_safe(mut self, content: &str) -> Self { self.0.push('`'); self.0.push_str(&normalize(content).replace('`', "'")); @@ -214,7 +382,7 @@ impl MessageBuilder { self } - /// Pushes an inline italicized text to your message normalizing content. + /// Pushes an inline italicized text to the content normalizing content. pub fn push_italic_safe(mut self, content: &str) -> Self { self.0.push('_'); self.0.push_str(&normalize(content).replace('_', " ")); @@ -223,7 +391,7 @@ impl MessageBuilder { self } - /// Pushes an inline bold text to your message normalizing content. + /// Pushes an inline bold text to the content normalizing content. pub fn push_bold_safe(mut self, content: &str) -> Self { self.0.push_str("**"); self.0.push_str(&normalize(content).replace("**", " ")); @@ -232,7 +400,7 @@ impl MessageBuilder { self } - /// Pushes an underlined inline text to your message normalizing content. + /// Pushes an underlined inline text to the content normalizing content. pub fn push_underline_safe(mut self, content: &str) -> Self { self.0.push_str("__"); self.0.push_str(&normalize(content).replace("__", " ")); @@ -241,7 +409,7 @@ impl MessageBuilder { self } - /// Pushes a strikethrough inline text to your message normalizing content. + /// Pushes a strikethrough inline text to the content normalizing content. pub fn push_strike_safe(mut self, content: &str) -> Self { self.0.push_str("~~"); self.0.push_str(&normalize(content).replace("~~", " ")); @@ -286,6 +454,20 @@ impl MessageBuilder { } impl fmt::Display for MessageBuilder { + /// Formats the message builder into a string. + /// + /// This is done by simply taking the internal value of the tuple-struct and + /// writing it into the formatter. + /// + /// # Examples + /// + /// Create a message builder, and format it into a string via the `format!` + /// macro: + /// + /// ```rust + /// use serenity::utils::MessageBuilder; + /// + /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 6ae937ef66e..0ca3e6547f7 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -74,11 +74,11 @@ pub fn is_nsfw(name: &str) -> bool { } } -/// Retrieves the "code" part of an [invite][`RichInvite`] out of a URL. +/// Retrieves the "code" part of an invite out of a URL. /// /// # Examples /// -/// Three formats of codes are supported: +/// Three formats of [invite][`RichInvite`] codes are supported: /// /// 1. Retrieving the code from the URL `"https://discord.gg/0cDvIgU2voY8RSYL"`: /// @@ -123,7 +123,34 @@ pub fn parse_invite(code: &str) -> &str { } } -/// Retreives Id from a username mention. +/// Retreives an Id from a user mention. +/// +/// If the mention is invalid, then `None` is returned. +/// +/// # Examples +/// +/// Retrieving an Id from a valid [`User`] mention: +/// +/// ```rust +/// use serenity::utils::parse_username; +/// +/// // regular username mention +/// assert_eq!(parse_username("<@114941315417899012>"), Some(114941315417899012)); +/// +/// // nickname mention +/// assert_eq!(parse_username("<@!114941315417899012>"), Some(114941315417899012)); +/// ``` +/// +/// Asserting that an invalid username or nickname mention returns `None`: +/// +/// ```rust +/// use serenity::utils::parse_username; +/// +/// assert!(parse_username("<@1149413154aa17899012").is_none()); +/// assert!(parse_username("<@!11494131541789a90b1c2").is_none()); +/// ``` +/// +/// [`User`]: ../model/struct.User.html pub fn parse_username(mention: &str) -> Option { if mention.len() < 4 { return None; @@ -140,13 +167,35 @@ pub fn parse_username(mention: &str) -> Option { } } -/// Retreives Id from a role mention. +/// Retreives an Id from a role mention. +/// +/// If the mention is invalid, then `None` is returned. +/// +/// # Examples +/// +/// Retrieving an Id from a valid [`Role`] mention: +/// +/// ```rust +/// use serenity::utils::parse_role; +/// +/// assert_eq!(parse_role("<@&136107769680887808>"), Some(136107769680887808)); +/// ``` +/// +/// Asserting that an invalid role mention returns `None`: +/// +/// ```rust +/// use serenity::utils::parse_role; +/// +/// assert!(parse_role("<@&136107769680887808").is_none()); +/// ``` +/// +/// [`Role`]: ../model/struct.Role.html pub fn parse_role(mention: &str) -> Option { if mention.len() < 4 { return None; } - if mention.starts_with("<@&") { + if mention.starts_with("<@&") && mention.ends_with('>') { let len = mention.len() - 1; mention[3..len].parse::().ok() } else { @@ -154,13 +203,36 @@ pub fn parse_role(mention: &str) -> Option { } } -/// Retreives Id from a channel mention. +/// Retreives an Id from a channel mention. +/// +/// If the channel mention is invalid, then `None` is returned. +/// +/// # Examples +/// +/// Retrieving an Id from a valid [`Channel`] mention: +/// +/// ```rust +/// use serenity::utils::parse_channel; +/// +/// assert_eq!(parse_channel("<#81384788765712384>"), Some(81384788765712384)); +/// ``` +/// +/// Asserting that an invalid channel mention returns `None`: +/// +/// ```rust +/// use serenity::utils::parse_channel; +/// +/// assert!(parse_channel("<#!81384788765712384>").is_none()); +/// assert!(parse_channel("<#81384788765712384").is_none()); +/// ``` +/// +/// [`Channel`]: ../model/enum.Channel.html pub fn parse_channel(mention: &str) -> Option { if mention.len() < 4 { return None; } - if mention.starts_with("<#") { + if mention.starts_with("<#") && mention.ends_with('>') { let len = mention.len() - 1; mention[2..len].parse::().ok() } else { @@ -168,19 +240,51 @@ pub fn parse_channel(mention: &str) -> Option { } } -/// Retreives name and Id from an emoji mention. +/// Retreives the name and Id from an emoji mention, in the form of an +/// `EmojiIdentifier`. +/// +/// If the emoji usage is invalid, then `None` is returned. +/// +/// # Examples +/// +/// Ensure that a valid [`Emoji`] usage is correctly parsed: +/// +/// ```rust +/// use serenity::model::{EmojiId, EmojiIdentifier}; +/// use serenity::utils::parse_emoji; +/// +/// let expected = EmojiIdentifier { +/// id: EmojiId(302516740095606785), +/// name: "smugAnimeFace".to_owned(), +/// }; +/// +/// assert_eq!(parse_emoji("<:smugAnimeFace:302516740095606785>").unwrap(), expected); +/// ``` +/// +/// Asserting that an invalid emoji usage returns `None`: +/// +/// ```rust +/// use serenity::utils::parse_emoji; +/// +/// assert!(parse_emoji("<:smugAnimeFace:302516740095606785").is_none()); +/// ``` +/// +/// [`Emoji`]: ../model/struct.Emoji.html pub fn parse_emoji(mention: &str) -> Option { let len = mention.len(); + if len < 6 || len > 56 { return None; } - if mention.starts_with("<:") { + if mention.starts_with("<:") && mention.ends_with('>') { let mut name = String::default(); let mut id = String::default(); + for (i, x) in mention[2..].chars().enumerate() { if x == ':' { let from = i + 3; + for y in mention[from..].chars() { if y == '>' { break; @@ -188,11 +292,13 @@ pub fn parse_emoji(mention: &str) -> Option { id.push(y); } } + break; } else { name.push(x); } } + match id.parse::() { Ok(x) => Some(EmojiIdentifier { name: name, @@ -216,8 +322,7 @@ pub fn parse_emoji(mention: &str) -> Option { /// ```rust,no_run /// use serenity::utils; /// -/// let image = utils::read_image("./cat.png") -/// .expect("Failed to read image"); +/// let image = utils::read_image("./cat.png").expect("Failed to read image"); /// ``` /// /// [`EditProfile::avatar`]: ../builder/struct.EditProfile.html#method.avatar @@ -240,6 +345,33 @@ pub fn read_image>(path: P) -> Result { /// Turns a string into a vector of string arguments, splitting by spaces, but /// parsing content within quotes as one individual argument. +/// +/// # Examples +/// +/// Parsing two quoted commands: +/// +/// ```rust +/// use serenity::utils::parse_quotes; +/// +/// let command = r#""this is the first" "this is the second""#; +/// let expected = vec![ +/// "this is the first".to_owned(), +/// "this is the second".to_owned() +/// ]; +/// +/// assert_eq!(parse_quotes(command), expected); +/// ``` +/// +/// ```rust +/// use serenity::utils::parse_quotes; +/// +/// let command = r#""this is a quoted command that doesn't have an ending quotation"#; +/// let expected = vec![ +/// "this is a quoted command that doesn't have an ending quotation".to_owned(), +/// ]; +/// +/// assert_eq!(parse_quotes(command), expected); +/// ``` pub fn parse_quotes(s: &str) -> Vec { let mut args = vec![]; let mut in_string = false;