diff options
| author | Austin Hellyer <[email protected]> | 2016-11-26 11:37:18 -0800 |
|---|---|---|
| committer | Austin Hellyer <[email protected]> | 2016-11-26 11:37:18 -0800 |
| commit | 77354ab321bec1ff66af0e27eb87a7eec3e3db24 (patch) | |
| tree | 693b43ae7be07be11426faf6e6282d838e426a04 /src | |
| parent | Make Cache::get_channel return a reference (diff) | |
| download | serenity-77354ab321bec1ff66af0e27eb87a7eec3e3db24.tar.xz serenity-77354ab321bec1ff66af0e27eb87a7eec3e3db24.zip | |
Add a bit more docs
Diffstat (limited to 'src')
| -rw-r--r-- | src/client/gateway/error.rs | 6 | ||||
| -rw-r--r-- | src/client/gateway/shard.rs | 48 | ||||
| -rw-r--r-- | src/client/mod.rs | 21 | ||||
| -rw-r--r-- | src/ext/cache/mod.rs | 289 | ||||
| -rw-r--r-- | src/ext/framework/command.rs | 1 | ||||
| -rw-r--r-- | src/ext/framework/configuration.rs | 22 | ||||
| -rw-r--r-- | src/ext/framework/mod.rs | 99 | ||||
| -rw-r--r-- | src/ext/mod.rs | 6 | ||||
| -rw-r--r-- | src/lib.rs | 8 | ||||
| -rw-r--r-- | src/model/gateway.rs | 1 | ||||
| -rw-r--r-- | src/utils/builder/edit_guild.rs | 113 | ||||
| -rw-r--r-- | src/utils/builder/edit_profile.rs | 2 | ||||
| -rw-r--r-- | src/utils/message_builder.rs | 1 |
13 files changed, 592 insertions, 25 deletions
diff --git a/src/client/gateway/error.rs b/src/client/gateway/error.rs index fb44d3f..dc94209 100644 --- a/src/client/gateway/error.rs +++ b/src/client/gateway/error.rs @@ -1,8 +1,12 @@ use std::fmt::{self, Display}; +/// An error that occurred while attempting to deal with the gateway. +/// +/// Note that - from a user standpoint - there should be no situation in which +/// you manually handle these. #[derive(Clone, Debug)] pub enum Error { - /// The connection closed + /// The connection unexpectedly (read: non-cleanly) closed. Closed(Option<u16>, String), /// Expected a Hello during a handshake ExpectedHello, diff --git a/src/client/gateway/shard.rs b/src/client/gateway/shard.rs index c35fee2..ab2e561 100644 --- a/src/client/gateway/shard.rs +++ b/src/client/gateway/shard.rs @@ -32,6 +32,10 @@ type CurrentPresence = (Option<Game>, OnlineStatus, bool); /// Refer to the [module-level documentation][module docs] for information on /// effectively using multiple shards, if you need to. /// +/// Note that there are additional methods available if you are manually +/// managing a shard yourself, although they are hidden from the documentation +/// since there are few use cases for doing such. +/// /// # Stand-alone shards /// /// You may instantiate a shard yourself - decoupled from the [`Client`] - if @@ -63,6 +67,8 @@ pub struct Shard { shard_info: Option<[u8; 2]>, token: String, ws_url: String, + /// The voice connections that this Shard is responsible for. The Shard will + /// update the voice connections' states. #[cfg(feature = "voice")] pub manager: VoiceManager, } @@ -159,6 +165,13 @@ impl Shard { }}, ready, receiver)) } + /// Retrieves a copy of the current shard information. + /// + /// The first element is the _current_ shard - 0-indexed - while the second + /// element is the _total number_ of shards -- 1-indexed. + /// + /// 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". pub fn shard_info(&self) -> Option<[u8; 2]> { self.shard_info } @@ -188,6 +201,9 @@ impl Shard { /// converted to [`Invisible`]. /// /// Other presence settings are maintained. + /// + /// [`Invisible`]: ../../model/enum.OnlineStatus.html#variant.Invisible + /// [`Offline`]: ../../model/enum.OnlineStatus.html#variant.Offline pub fn set_status(&mut self, online_status: OnlineStatus) { self.current_presence.1 = match online_status { OnlineStatus::Offline => OnlineStatus::Invisible, @@ -232,6 +248,17 @@ impl Shard { self.update_presence(); } + /// Handles an event from the gateway over the receiver, requiring the + /// receiver to be passed if a reconnect needs to occur. + /// + /// The best case scenario is that one of two values is returned: + /// + /// - `Ok(None)`: a heartbeat, late hello, or session invalidation was + /// received; + /// - `Ok(Some((event, None)))`: an op0 dispatch was received, and the + /// shard's voice state will be updated, _if_ the `voice` feature is + /// enabled. + #[doc(hidden)] pub fn handle_event(&mut self, event: Result<GatewayEvent>, mut receiver: &mut Receiver<WebSocketStream>) @@ -318,6 +345,9 @@ impl Shard { } } + /// Shuts down the receiver by attempting to cleanly close the + /// connection. + #[doc(hidden)] pub fn shutdown(&mut self, receiver: &mut Receiver<WebSocketStream>) -> Result<()> { let stream = receiver.get_mut().get_mut(); @@ -335,6 +365,9 @@ impl Shard { Ok(()) } + /// Syncs a number of [`Call`]s, given by their associated channel Ids. This + /// will allow the current user to know what calls are currently occurring, + /// as otherwise events will not be received. pub fn sync_calls(&self, channels: &[ChannelId]) { for &channel in channels { let msg = ObjectBuilder::new() @@ -348,6 +381,18 @@ impl Shard { } } + /// Requests that one or multiple [`Guild`]s be synced. + /// + /// 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. + /// + /// 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. pub fn sync_guilds(&self, guild_ids: &[GuildId]) { let msg = ObjectBuilder::new() .insert("op", OpCode::SyncGuild.num()) @@ -383,7 +428,8 @@ impl Shard { }} } - fn reconnect(&mut self, mut receiver: &mut Receiver<WebSocketStream>) -> Result<(Event, Receiver<WebSocketStream>)> { + fn reconnect(&mut self, mut receiver: &mut Receiver<WebSocketStream>) + -> Result<(Event, Receiver<WebSocketStream>)> { debug!("Reconnecting"); // Take a few attempts at reconnecting; otherwise fall back to diff --git a/src/client/mod.rs b/src/client/mod.rs index 274d438..21b74d4 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -76,24 +76,37 @@ use ::model::event::{ #[cfg(feature = "cache")] lazy_static! { - /// The CACHE is a mutable lazily-initialized static binding. It can be - /// accessed across any function and in any context. + /// A mutable and lazily-initialized static binding. It can be accessed + /// across any function and in any context. /// /// This [`Cache`] instance is updated for every event received, so you do /// not need to maintain your own cache. /// /// See the [cache module documentation] for more details. /// + /// The Cache itself is wrapped within an `RwLock`, which allows for + /// multiple readers or at most one writer at a time across threads. This + /// means that you may have multiple commands reading from the Cache + /// concurrently. + /// /// # Examples /// - /// Retrieve the [current user][`CurrentUser`]'s Id: + /// Retrieve the [current user][`CurrentUser`]'s Id, by opening a Read + /// guard: /// /// ```rust,ignore /// use serenity::client::CACHE; /// - /// println!("{}", CACHE.lock().unwrap().user.id); + /// println!("{}", CACHE.read().unwrap().user.id); /// ``` /// + /// By `unwrap()`ing, the thread managing an event dispatch will be blocked + /// until the guard can be opened. + /// + /// If you do not want to block the current thread, you may instead use + /// `RwLock::try_read`. Refer to `RwLock`'s documentation in the stdlib for + /// more information. + /// /// [`CurrentUser`]: ../model/struct.CurrentUser.html /// [`Cache`]: ../ext/cache/struct.Cache.html /// [cache module documentation]: ../ext/cache/index.html diff --git a/src/ext/cache/mod.rs b/src/ext/cache/mod.rs index a8d62c9..923c55a 100644 --- a/src/ext/cache/mod.rs +++ b/src/ext/cache/mod.rs @@ -1,3 +1,81 @@ +//! A cache of events received over a [`Shard`], where storing at least some +//! data from the event is possible. +//! +//! This acts as a hot cache, to avoid making requests over the REST API through +//! the [`http`] module where possible. All fields are public, and do not have +//! getters, to allow you more flexibility with the stored data. However, this +//! allows data to be "corrupted", and _may or may not_ cause misfunctions +//! within the library. Mutate data at your own discretion. +//! +//! A "globally available" instance of the Cache is available at +//! [`client::CACHE`]. This is the instance that is updated by the library, +//! meaning you should _not_ need to maintain updating it yourself in any case. +//! +//! # Use by the Context +//! +//! The [`Context`] will automatically attempt to pull from the cache for you. +//! For example, the [`Context::get_channel`] method will attempt to find the +//! channel in the cache. If it can not find it, it will perform a request +//! through the REST API, and then insert a clone of the channel - if found - +//! into the Cache, giving you the original. +//! +//! This allows you to save a step, by only needing to perform the +//! [`Context::get_channel`] call and not need to first search through the cache +//! - and if not found - _then_ perform an HTTP request through the Context or +//! [`http`] module. +//! +//! Additionally, note that some information received through events can _not_ +//! be retrieved through the REST API. This is information such as [`Role`]s in +//! [`LiveGuild`]s. +//! +//! # Use by Models +//! +//! Most models of Discord objects, such as the [`Message`], [`PublicChannel`], +//! or [`Emoji`], have methods for interacting with that single instance. This +//! feature is only compiled if the `methods` feature is enabled. An example of +//! this is [`LiveGuild::edit`], which performs a check to ensure that the +//! current user is the owner of the guild, prior to actually performing the +//! HTTP request. The cache is involved due to the function's use of unlocking +//! the cache and retrieving the Id of the current user, and comparing it to +//! the Id of the user that owns the guild. This is an inexpensive method of +//! being able to access data required by these sugary methods. +//! +//! # Do I need the Cache? +//! +//! If you're asking this, the answer is likely "definitely yes" or +//! "definitely no"; any in-between tends to be "yes". If you are low on RAM, +//! and need to run on only a couple MB, then the answer is "definitely no". If +//! you do not care about RAM and want your bot to be able to access data +//! while needing to hit the REST API as little as possible, then the answer +//! is "yes". +//! +//! # Special cases in the Cache +//! +//! Some items in the cache, notably [`Call`]s and [`Group`]s, will "always be +//! empty". The exception to this rule, is for: +//! +//! 1. Bots which used to be userbots prior to the conversion made available by +//! Discord when the official Bot API was introduced; +//! 2. For groups and calls: +//! 2a. Bots that have friends from before the conversion that have not been +//! removed, as those users can still add the bots to groups; +//! 2b. Bots that have the "Create Group" endpoint whitelisted specifically for +//! them. +//! +//! [`Call`]: ../../model/struct.Call.html +//! [`Context`]: ../../client/struct.Context.html +//! [`Context::get_channel`]: ../../client/struct.Context.html#method.get_channel +//! [`Emoji`]: ../../model/struct.Emoji.html +//! [`Group`]: ../../model/struct.Group.html +//! [`LiveGuild`]: ../../model/struct.LiveGuild.html +//! [`LiveGuild::edit`]: ../../model/struct.LiveGuild.html#method.edit +//! [`Message`]: ../../model/struct.Message.html +//! [`PublicChannel`]: ../../model/struct.PublicChannel.html +//! [`Role`]: ../../model/struct.Role.html +//! [`Shard`]: ../../client/gateway/struct.Shard.html +//! [`client::CACHE`]: ../../client/struct.CACHE.html +//! [`http`]: ../../client/http/index.html + use std::collections::hash_map::Entry; use std::collections::HashMap; use std::default::Default; @@ -42,31 +120,86 @@ pub struct Cache { /// where the key is the Id of the [`PrivateChannel`] or [`Group`] hosting /// the call. /// - /// For bot users this will almost always be empty. + /// For bot users this will always be empty, except for in [special cases]. + /// + /// [`Group`]: ../../model/struct.Group.html + /// [`PrivateChannel`]: ../../model/struct.PrivateChannel.html + /// [special cases]: index.html#special-cases-in-the-cache pub calls: HashMap<ChannelId, Call>, /// A map of the groups that the current user is in. /// - /// For bot users this will almost always be empty. + /// For bot users this will always be empty, except for in [special cases]. + /// + /// [special cases]: index.html#special-cases-in-the-cache pub groups: HashMap<ChannelId, Group>, /// Settings specific to a guild. /// - /// This will always be empty for bot accounts. + /// This will always be empty for bot users. pub guild_settings: HashMap<Option<GuildId>, UserGuildSettings>, + /// A map of guilds with full data available. This includes data like + /// [`Role`]s and [`Emoji`]s that are not available through the REST API. + /// + /// [`Emoji`]: ../../model/struct.Emoji.html + /// [`Role`]: ../../model/struct.Role.html pub guilds: HashMap<GuildId, Guild>, - /// A list of notes that a user has made for individual users. + /// A map of notes that a user has made for individual users. + /// + /// An empty note is equivilant to having no note, and creating an empty + /// note is equivilant to deleting a note. /// - /// This will always be empty for bot accounts. + /// This will always be empty for bot users. pub notes: HashMap<UserId, String>, + /// A map of users' presences. This is updated in real-time. Note that + /// status updates are often "eaten" by the gateway, and this should not + /// be treated as being entirely 100% accurate. pub presences: HashMap<UserId, Presence>, + /// A map of direct message channels that the current user has open with + /// other users. pub private_channels: HashMap<ChannelId, PrivateChannel>, + /// A map of relationships that the current user has with other users. + /// + /// For bot users this will always be empty, except for in [special cases]. + /// + /// [special cases]: index.html#special-cases-in-the-cache pub relationships: HashMap<UserId, Relationship>, /// Account-specific settings for a user account. pub settings: Option<UserSettings>, + /// A list of guilds which are "unavailable". Refer to the documentation for + /// [`Event::GuildUnavailable`] for more information on when this can occur. + /// + /// Additionally, guilds are always unavailable for bot users when a Ready + /// is received. Guilds are "sent in" over time through the receiving of + /// [`Event::GuildCreate`]s. + /// + /// [`Event::GuildCreate`]: ../../model/enum.Event.html#variant.GuildCreate + /// [`Event::GuildUnavailable`]: ../../model/enum.Event.html#variant.GuildUnavailable pub unavailable_guilds: Vec<GuildId>, + /// The current user "logged in" and for which events are being received + /// for. + /// + /// The current user contains information that a regular [`User`] does not, + /// such as whether it is a bot, whether the user is verified, etc. + /// + /// Refer to the documentation for [`CurrentUser`] for more information. + /// + /// [`CurrentUser`]: ../../model/struct.CurrentUser.html + /// [`User`]: ../../model/struct.User.html pub user: CurrentUser, } impl Cache { + /// Calculates 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 + /// multiple associated member objects that have not been received. + /// + /// This can be used in combination with [`Shard::sync_guilds`], and can be + /// used to determine how many members have not yet been downloaded. + /// + /// [`Member`]: ../../model/struct.Member.html + /// [`Shard::sync_guilds`]: ../../client/gateway/struct.Shard.html#method.sync_guilds + /// [`User`]: ../../model/struct.User.html pub fn unknown_members(&self) -> u64 { let mut total = 0; @@ -81,6 +214,16 @@ impl Cache { total } + /// Calculates a vector of all [`PrivateChannel`] and [`Group`] Ids that are + /// stored in the cache. + /// + /// # Examples + /// + /// If there are 6 private channels and 2 groups in the cache, then `8` Ids + /// will be returned. + /// + /// [`Group`]: ../../model/struct.Group.html + /// [`PrivateChannel`]: ../../model/struct.PrivateChannel.html pub fn all_private_channels(&self) -> Vec<ChannelId> { self.groups .keys() @@ -89,6 +232,15 @@ impl Cache { .collect() } + /// Calculates a vector of all [`Guild`]s' Ids that are stored in the cache. + /// + /// Note that if you are utilizing multiple [`Shard`]s, then the guilds + /// retrieved over all shards are included in this count -- not just the + /// current [`Context`]'s shard, if accessing from one. + /// + /// [`Context`]: ../../client/struct.Context.html + /// [`Guild`]: ../../model/struct.Guild.html + /// [`Shard`]: ../../client/gateway/struct.Shard.html pub fn all_guilds(&self) -> Vec<GuildId> { self.guilds .values() @@ -110,10 +262,25 @@ impl Cache { .collect::<Vec<_>>() } + /// Retrieves a reference to a [`Call`] from the cache based on the + /// associated [`Group`]'s channel Id. + /// + /// [`Call`]: ../../model/struct.Call.html + /// [`Group`]: ../../model/struct.Group.html pub fn get_call<C: Into<ChannelId>>(&self, group_id: C) -> Option<&Call> { self.calls.get(&group_id.into()) } + /// Retrieves a [`Channel`] from the cache based on the given Id. + /// + /// This will search the [`groups`] map, the [`private_channels`] map, and + /// then the map of [`guilds`] to find the channel. + /// + /// [`Channel`]: ../../model/enum.Channel.html + /// [`Guild`]: ../../model/struct.Guild.html + /// [`groups`]: #structfield.groups + /// [`private_channels`]: #structfield.private_channels + /// [`guilds`]: #structfield.guilds pub fn get_channel<C: Into<ChannelId>>(&self, id: C) -> Option<ChannelRef> { let id = id.into(); @@ -136,10 +303,38 @@ impl Cache { None } + /// Retrieves a reference to a guild from the cache based on the given Id. pub fn get_guild<G: Into<GuildId>>(&self, id: G) -> Option<&Guild> { self.guilds.get(&id.into()) } + /// Retrieves a reference to a [`Guild`]'s channel. Unlike [`get_channel`], + /// this will only search guilds for the given channel. + /// + /// # Examples + /// + /// Getting a guild's channel via the Id of the message received through a + /// [`Client::on_message`] event dispatch: + /// + /// ```rust,ignore + /// use serenity::cache::CACHE; + /// + /// let cache = CACHE.read().unwrap(); + /// + /// let channel = match cache.get_guild_channel(message.channel_id) { + /// Some(channel) => channel, + /// None => { + /// context.say("Could not find guild's channel data") + /// .map_err(|why| println!("Err sending message: {:?}", why)); + /// + /// return; + /// }, + /// }; + /// ``` + /// + /// [`Client::on_message`]: ../../client/struct.Client.html#method.on_message + /// [`Guild`]: ../../model/struct.Guild.html + /// [`get_channel`]: #method.get_channel pub fn get_guild_channel<C: Into<ChannelId>>(&self, id: C) -> Option<&GuildChannel> { let id = id.into(); @@ -152,10 +347,57 @@ impl Cache { None } + /// Retrieves a reference to a [`Group`] from the cache based on the given + /// associated channel Id. + /// + /// [`Group`]: ../../model/struct.Group.html pub fn get_group<C: Into<ChannelId>>(&self, id: C) -> Option<&Group> { self.groups.get(&id.into()) } + /// Retrieves a reference to a [`Guild`]'s member from the cache based on + /// the guild's and user's given Ids. + /// + /// # Examples + /// + /// Retrieving the member object of the user that posted a message, in a + /// [`Client::on_message`] context: + /// + /// ```rust,ignore + /// use serenity::client::CACHE; + /// + /// // assuming you are in a context + /// + /// let cache = CACHE.read().unwrap(); + /// let member = { + /// let channel = match cache.get_guild_channel(message.channel_id) { + /// Some(channel) => channel, + /// None => { + /// if let Err(why) = context.say("Err finding channel data") { + /// println!("Err sending message: {:?}", why); + /// } + /// }, + /// }; + /// + /// match cache.get_member(channel.guild_id, message.author.id) { + /// Some(member) => member, + /// None => { + /// if let Err(why) = context.say("Err finding member data") { + /// println!("Err sending message: {:?}", why); + /// } + /// }, + /// } + /// }; + /// + /// let msg = format!("You have {} roles", member.roles.len()); + /// + /// if let Err(why) = context.say(&msg) { + /// println!("Err sending message: {:?}", why); + /// } + /// ``` + /// + /// [`Client::on_message`]: ../../client/struct.Client.html#method.on_message + /// [`Guild`]: ../../model/struct.Guild.html pub fn get_member<G, U>(&self, guild_id: G, user_id: U) -> Option<&Member> where G: Into<GuildId>, U: Into<UserId> { self.guilds @@ -168,6 +410,9 @@ impl Cache { }) } + /// Retrieves a reference to a [`Guild`]'s role by their Ids. + /// + /// [`Guild`]: ../../model/struct.Guild.html pub fn get_role<G, R>(&self, guild_id: G, role_id: R) -> Option<&Role> where G: Into<GuildId>, R: Into<RoleId> { if let Some(guild) = self.guilds.get(&guild_id.into()) { @@ -180,6 +425,7 @@ impl Cache { /// Update the cache according to the changes described in the given event. #[allow(cyclomatic_complexity)] #[allow(unneeded_field_pattern)] + #[doc(hidden)] pub fn update(&mut self, event: &Event) { match *event { Event::CallCreate(ref event) => { @@ -300,6 +546,7 @@ impl Cache { } } + #[doc(hidden)] pub fn update_with_call_create(&mut self, event: &CallCreateEvent) { match self.calls.entry(event.call.channel_id) { Entry::Vacant(e) => { @@ -311,11 +558,13 @@ impl Cache { } } + #[doc(hidden)] pub fn update_with_call_delete(&mut self, event: &CallDeleteEvent) -> Option<Call> { self.calls.remove(&event.channel_id) } + #[doc(hidden)] pub fn update_with_call_update(&mut self, event: &CallUpdateEvent, old: bool) -> Option<Call> { let item = if old { @@ -334,6 +583,7 @@ impl Cache { item } + #[doc(hidden)] pub fn update_with_channel_create(&mut self, event: &ChannelCreateEvent) -> Option<Channel> { match event.channel { @@ -364,6 +614,7 @@ impl Cache { } } + #[doc(hidden)] pub fn update_with_channel_delete(&mut self, event: &ChannelDeleteEvent) -> Option<Channel> { match event.channel { @@ -387,6 +638,7 @@ impl Cache { } } + #[doc(hidden)] pub fn update_with_channel_pins_update(&mut self, event: &ChannelPinsUpdateEvent) { if let Some(channel) = self.private_channels.get_mut(&event.channel_id) { @@ -414,6 +666,7 @@ impl Cache { } } + #[doc(hidden)] pub fn update_with_channel_recipient_add(&mut self, event: &ChannelRecipientAddEvent) { self.groups @@ -422,6 +675,7 @@ impl Cache { event.user.clone())); } + #[doc(hidden)] pub fn update_with_channel_recipient_remove(&mut self, event: &ChannelRecipientRemoveEvent) { self.groups @@ -429,6 +683,7 @@ impl Cache { .map(|group| group.recipients.remove(&event.user.id)); } + #[doc(hidden)] pub fn update_with_channel_update(&mut self, event: &ChannelUpdateEvent) { match event.channel { Channel::Group(ref group) => { @@ -465,15 +720,18 @@ impl Cache { } } + #[doc(hidden)] pub fn update_with_guild_create(&mut self, event: &GuildCreateEvent) { self.guilds.insert(event.guild.id, event.guild.clone()); } + #[doc(hidden)] pub fn update_with_guild_delete(&mut self, event: &GuildDeleteEvent) -> Option<Guild> { self.guilds.remove(&event.guild.id) } + #[doc(hidden)] pub fn update_with_guild_emojis_update(&mut self, event: &GuildEmojisUpdateEvent) { self.guilds @@ -481,6 +739,7 @@ impl Cache { .map(|guild| guild.emojis.extend(event.emojis.clone())); } + #[doc(hidden)] pub fn update_with_guild_member_add(&mut self, event: &GuildMemberAddEvent) { self.guilds @@ -492,6 +751,7 @@ impl Cache { }); } + #[doc(hidden)] pub fn update_with_guild_member_remove(&mut self, event: &GuildMemberRemoveEvent) -> Option<Member> { @@ -508,6 +768,7 @@ impl Cache { } } + #[doc(hidden)] pub fn update_with_guild_member_update(&mut self, event: &GuildMemberUpdateEvent) -> Option<Member> { @@ -545,6 +806,7 @@ impl Cache { } } + #[doc(hidden)] pub fn update_with_guild_members_chunk(&mut self, event: &GuildMembersChunkEvent) { self.guilds @@ -552,6 +814,7 @@ impl Cache { .map(|guild| guild.members.extend(event.members.clone())); } + #[doc(hidden)] pub fn update_with_guild_role_create(&mut self, event: &GuildRoleCreateEvent) { self.guilds @@ -559,6 +822,7 @@ impl Cache { .map(|guild| guild.roles.insert(event.role.id, event.role.clone())); } + #[doc(hidden)] pub fn update_with_guild_role_delete(&mut self, event: &GuildRoleDeleteEvent) -> Option<Role> { @@ -572,6 +836,7 @@ impl Cache { } } + #[doc(hidden)] pub fn update_with_guild_role_update(&mut self, event: &GuildRoleUpdateEvent) -> Option<Role> { @@ -587,6 +852,7 @@ impl Cache { } } + #[doc(hidden)] pub fn update_with_guild_sync(&mut self, event: &GuildSyncEvent) { self.guilds .get_mut(&event.guild_id) @@ -597,6 +863,7 @@ impl Cache { }); } + #[doc(hidden)] pub fn update_with_guild_unavailable(&mut self, event: &GuildUnavailableEvent) { if !self.unavailable_guilds.contains(&event.guild_id) { @@ -604,6 +871,7 @@ impl Cache { } } + #[doc(hidden)] pub fn update_with_guild_update(&mut self, event: &GuildUpdateEvent) { self.guilds .get_mut(&event.guild.id) @@ -619,6 +887,7 @@ impl Cache { }); } + #[doc(hidden)] pub fn update_with_presences_replace(&mut self, event: &PresencesReplaceEvent) { self.presences.clone_from(&{ let mut p = HashMap::default(); @@ -631,6 +900,7 @@ impl Cache { }); } + #[doc(hidden)] pub fn update_with_presence_update(&mut self, event: &PresenceUpdateEvent) { if let Some(guild_id) = event.guild_id { if let Some(guild) = self.guilds.get_mut(&guild_id) { @@ -646,6 +916,7 @@ impl Cache { } } + #[doc(hidden)] pub fn update_with_ready(&mut self, ready: &ReadyEvent) { let ready = ready.ready.clone(); @@ -695,16 +966,19 @@ impl Cache { self.user = ready.user; } + #[doc(hidden)] pub fn update_with_relationship_add(&mut self, event: &RelationshipAddEvent) { self.relationships.insert(event.relationship.id, event.relationship.clone()); } + #[doc(hidden)] pub fn update_with_relationship_remove(&mut self, event: &RelationshipRemoveEvent) { self.relationships.remove(&event.user_id); } + #[doc(hidden)] pub fn update_with_user_guild_settings_update(&mut self, event: &UserGuildSettingsUpdateEvent) -> Option<UserGuildSettings> { @@ -713,6 +987,7 @@ impl Cache { .map(|guild_setting| mem::replace(guild_setting, event.settings.clone())) } + #[doc(hidden)] pub fn update_with_user_note_update(&mut self, event: &UserNoteUpdateEvent) -> Option<String> { @@ -723,6 +998,7 @@ impl Cache { } } + #[doc(hidden)] pub fn update_with_user_settings_update(&mut self, event: &UserSettingsUpdateEvent, old: bool) @@ -751,11 +1027,13 @@ impl Cache { item } + #[doc(hidden)] pub fn update_with_user_update(&mut self, event: &UserUpdateEvent) -> CurrentUser { mem::replace(&mut self.user, event.current_user.clone()) } + #[doc(hidden)] pub fn update_with_voice_state_update(&mut self, event: &VoiceStateUpdateEvent) { if let Some(guild_id) = event.guild_id { @@ -857,7 +1135,6 @@ fn update_presence(presences: &mut HashMap<UserId, Presence>, } /// A reference to a private channel, guild's channel, or group. -#[derive(Clone, Copy, Debug)] pub enum ChannelRef<'a> { /// A group's channel Group(&'a Group), diff --git a/src/ext/framework/command.rs b/src/ext/framework/command.rs index 6a9a713..0c595b3 100644 --- a/src/ext/framework/command.rs +++ b/src/ext/framework/command.rs @@ -3,6 +3,7 @@ use super::{CommandType, Configuration}; use ::client::Context; use ::model::Message; +#[doc(hidden)] pub type Command = Fn(Context, Message, Vec<String>) + Send + Sync; #[doc(hidden)] pub type InternalCommand = Arc<Command>; diff --git a/src/ext/framework/configuration.rs b/src/ext/framework/configuration.rs index 6dd4fd2..6a6c5e5 100644 --- a/src/ext/framework/configuration.rs +++ b/src/ext/framework/configuration.rs @@ -1,6 +1,28 @@ use std::default::Default; use ::client::rest; +/// The configuration to use for a [`Framework`] associated with a [`Client`] +/// instance. +/// +/// This allows setting configurations like the depth to search for commands, +/// whether to treat mentions like a command prefix, etc. +/// +/// # Examples +/// +/// Responding to mentions and setting a command prefix of `"~"`: +/// +/// ```rust,no_run +/// use serenity::Client; +/// use std::env; +/// +/// let mut client = Client::login_bot(&env::var("DISCORD_BOT_TOKEN").unwrap()); +/// +/// client.with_framework(|f| f +/// .configure(|c| c.on_mention(true).prefix("~"))); +/// ``` +/// +/// [`Client`]: ../../client/struct.Client.html +/// [`Framework`]: struct.Framework.html pub struct Configuration { #[doc(hidden)] pub depth: usize, diff --git a/src/ext/framework/mod.rs b/src/ext/framework/mod.rs index 361fdd8..2a92840 100644 --- a/src/ext/framework/mod.rs +++ b/src/ext/framework/mod.rs @@ -1,3 +1,58 @@ +//! The framework is a customizable method of separating commands, used in +//! combination with [`Client::with_framework`]. +//! +//! The framework has a number of configurations, and can have any number of +//! commands bound to it. The primary purpose of it is to offer the utility of +//! not needing to manually match message content strings to determine if a +//! message is a command. +//! +//! Additionally, "checks" can be added to commands, to ensure that a certain +//! condition is met prior to calling a command; this could be a check that the +//! user who posted a message owns the bot, for example. +//! +//! Each command has a given named, and an associated function/closure. For +//! example, you might have two commands: `"ping"` and `"weather"`. These each +//! have an associated function that are called if the framework determines +//! that a message is of that command. +//! +//! Assuming a command prefix of `"~"`, then the following would occur with the +//! two previous commands: +//! +//! ```ignore +//! ~ping // calls the ping command's function +//! ~pin // does not +//! ~ ping // _does_ call it _if_ the `allow_whitespace` option is enabled +//! ~~ping // does not +//! ``` +//! +//! # Examples +//! +//! Configuring a Client with a framework, which has a prefix of `"~"` and a +//! ping and about command: +//! +//! ```rust,no_run +//! use serenity::client::{Client, Context}; +//! use serenity::model::Message; +//! use std::env; +//! +//! let mut client = Client::login_bot(&env::var("DISCORD_BOT_TOKEN").unwrap()); +//! +//! client.with_framework(|f| f +//! .configure(|c| c.prefix("~")) +//! .on("about", about) +//! .on("ping", ping)); +//! +//! fn about(context: Context, _message: Message, _args: Vec<String>) { +//! let _ = context.say("A simple test bot"); +//! } +//! +//! fn ping(context: Context, _message: Message, _args: Vec<String>) { +//! let _ = context.say("Pong!"); +//! } +//! ``` +//! +//! [`Client::with_framework`]: ../../client/struct.Client.html#method.with_framework + mod command; mod configuration; @@ -98,6 +153,11 @@ pub enum CommandType { Prefix, } +/// A utility for easily managing dispatches to commands. +/// +/// Refer to the [module-level documentation] for more information. +/// +/// [module-level documentation]: index.html #[allow(type_complexity)] #[derive(Default)] pub struct Framework { @@ -218,6 +278,16 @@ impl Framework { } } + /// Adds a function to be associated with a command, which will be called + /// when a command is used in a message. + /// + /// This requires that a check - if one exists - passes, prior to being + /// called. + /// + /// Refer to the [module-level documentation] for more information and + /// usage. + /// + /// [module-level documentation]: index.html pub fn on<F, S>(mut self, command_name: S, f: F) -> Self where F: Fn(Context, Message, Vec<String>) + Send + Sync + 'static, S: Into<String> { @@ -227,6 +297,35 @@ impl Framework { self } + /// Adds a "check" to a command, which checks whether or not the command's + /// associated function should be called. + /// + /// # Examples + /// + /// Ensure that the user who created a message, calling a "ping" command, + /// is the owner. + /// + /// ```rust,no_run + /// use serenity::client::{Client, Context}; + /// use serenity::model::Message; + /// use std::env; + /// + /// let mut client = Client::login_bot(&env::var("DISCORD_TOKEN").unwrap()); + /// + /// client.with_framework(|f| f + /// .configure(|c| c.prefix("~")) + /// .on("ping", ping) + /// .set_check("ping", owner_check)); + /// + /// fn ping(context: Context, _message: Message, _args: Vec<String>) { + /// context.say("Pong!"); + /// } + /// + /// fn owner_check(_context: &Context, message: &Message) -> bool { + /// // replace with your user ID + /// message.author.id == 7 + /// } + /// ``` pub fn set_check<F, S>(mut self, command: S, check: F) -> Self where F: Fn(&Context, &Message) -> bool + Send + Sync + 'static, S: Into<String> { diff --git a/src/ext/mod.rs b/src/ext/mod.rs index 4484b0b..9605466 100644 --- a/src/ext/mod.rs +++ b/src/ext/mod.rs @@ -1,5 +1,5 @@ -//! The set of extensions is functionality that is not required for a -//! [`Client`] and/or [`Connection`] to properly function. +//! A set of extended functionality that is not required for a [`Client`] and/or +//! [`Shard`] to properly function. //! //! These are flagged behind feature-gates and can be enabled and disabled. //! @@ -11,7 +11,7 @@ //! to be enabled (disabled by default). //! //! [`Client`]: ../client/struct.Client.html -//! [`Connection`]: ../client/struct.Connection.html +//! [`Shard`]: ../client/gateway/struct.Shard.html #[cfg(feature = "cache")] pub mod cache; @@ -25,10 +25,10 @@ //! to avoid unnecessary HTTP requests to the Discord API. For more information, //! see the [cache's module-level documentation][cache docs]. //! -//! Note that - although this documentation will try to be as up-to-date and -//! accurate as possible - Discord hosts [official documentation][docs]. If you -//! need to be sure that some information piece is accurate, refer to their -//! docs. +//! Note that, although this documentation will try to be as up-to-date and +//! accurate as possible, Discord hosts [official documentation][docs]. If you +//! need to be sure that some information piece is sanctioned by Discord, refer +//! to their own documentation. //! //! # Dependencies //! diff --git a/src/model/gateway.rs b/src/model/gateway.rs index 101f050..cda1c59 100644 --- a/src/model/gateway.rs +++ b/src/model/gateway.rs @@ -1,6 +1,7 @@ use super::utils::*; use super::*; use ::internal::prelude::*; +use ::utils::decode_array; impl Game { #[cfg(feature="methods")] diff --git a/src/utils/builder/edit_guild.rs b/src/utils/builder/edit_guild.rs index 3ea9aaa..dd4ce84 100644 --- a/src/utils/builder/edit_guild.rs +++ b/src/utils/builder/edit_guild.rs @@ -3,20 +3,67 @@ use serde_json::Value; use std::default::Default; use ::model::{ChannelId, Region, VerificationLevel}; +/// A builder to optionally edit certain fields of a [`Guild`]. This is meant +/// for usage with [`Context::edit_guild`] and [`LiveGuild::edit`]. +/// +/// **Note**: Editing a guild requires that the current user have the +/// [Manage Guild] permission. +/// +/// [`Context::edit_guild`]: ../../client/struct.Context.html +/// [`Guild`]: ../../model/struct.Guild.html +/// [`LiveGuild::edit`]: ../../model/struct.LiveGuild.html#method.edit +/// [Manage Guild]: ../../model/permissions/constant.MANAGE_GUILD.html pub struct EditGuild(pub ObjectBuilder); impl EditGuild { + /// Set the "AFK voice channel" that users are to move to if they have been + /// AFK for an amount of time, configurable by [`afk_timeout`]. + /// + /// The given channel must be either some valid voice channel, or `None` to + /// not set an AFK channel. The library does not check if a channel is + /// valid. + /// + /// [`afk_timeout`]: #method.afk_timeout pub fn afk_channel<C: Into<ChannelId>>(self, channel: Option<C>) -> Self { - EditGuild(match channel { - Some(channel) => self.0.insert("afk_channel_id", channel.into().0), - None => self.0.insert("afk-channel_id", Value::Null), - }) + EditGuild(self.0.insert("afk_channel_id", match channel { + Some(channel) => Value::U64(channel.into().0), + None => Value::Null, + })) } + /// Set the amount of time a user is to be moved to the AFK channel - + /// configured via [`afk_channel`] - after being AFK. + /// + /// [`afk_channel`]: #method.afk_channel pub fn afk_timeout(self, timeout: u64) -> Self { EditGuild(self.0.insert("afk_timeout", timeout)) } + /// Set the icon of the guild. Pass `None` to remove the icon. + /// + /// # Examples + /// + /// 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 + /// use serenity::utils; + /// + /// // assuming a `guild` has already been bound + /// + /// let base64_icon = match utils::read_image("./guild_icon.png") { + /// Ok(base64_icon) => base64_icon, + /// Err(why) => { + /// println!("Error reading image: {:?}", why); + /// + /// return; + /// }, + /// }; + /// + /// let _ = guild.edit(|g| g.icon(base64_icon)); + /// ``` + /// + /// [`utils::read_image`]: ../../utils/fn.read_image.html pub fn icon(self, icon: Option<&str>) -> Self { EditGuild(self.0 .insert("icon", @@ -24,14 +71,41 @@ impl EditGuild { |x| Value::String(x.to_owned())))) } + /// Set the name of the guild. + /// + /// **Note**: Must be between (and including) 2-100 chracters. pub fn name(self, name: &str) -> Self { EditGuild(self.0.insert("name", name)) } + /// Set the voice region of the server. + /// + /// # Examples + /// + /// Setting the region to [`Region::UsWest`]: + /// + /// ```rust,ignore + /// 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); + /// } + /// ``` + /// + /// [`Region::UsWest`]: ../../model/enum.Region.html#variant.UsWest pub fn region(self, region: Region) -> Self { EditGuild(self.0.insert("region", region.name())) } + /// Set the splash image of the guild on the invitation page. + /// + /// Requires that the guild have the [`InviteSplash`] feature enabled. + /// You can check this through a guild's [`features`] list. + /// + /// [`InviteSplash`]: ../../model/enum.Feature.html#variant.InviteSplash + /// [`features`]: ../../model/struct.LiveGuild.html#structfield.features pub fn splash(self, splash: Option<&str>) -> Self { EditGuild(self.0 .insert("splash", @@ -39,6 +113,37 @@ impl EditGuild { |x| Value::String(x.to_owned())))) } + /// Set the verification level of the guild. This can restrict what a + /// user must have prior to being able to send messages in a guild. + /// + /// Refer to the documentation for [`VerificationLevel`] for more + /// information on each variant. + /// + /// [`VerificationLevel`]: ../../model/enum.VerificationLevel.html + /// + /// # Examples + /// + /// Setting the verification level to [`High`][`VerificationLevel::High`]: + /// + /// ```rust,ignore + /// use serenity::model::VerificationLevel; + /// + /// // assuming a `guild` has already been bound + /// + /// if let Err(why) = guild.edit(|g| g.verification_level(VerificationLevel::High)) { + /// println!("Error setting verification level: {:?}", why); + /// } + /// + /// // additionally, you may pass in just an integer of the verification + /// // level + /// + /// if let Err(why) = guild.edit(|g| g.verification_level(3)) { + /// println!("Error setting verification level: {:?}", why); + /// } + /// ``` + /// + /// [`VerificationLevel`]: ../../model/enum.VerificationLevel.html + /// [`VerificationLevel::High`]: ../../model/enum.VerificationLevel.html#variant.High pub fn verification_level<V>(self, verification_level: V) -> Self where V: Into<VerificationLevel> { EditGuild(self.0.insert("verification_level", diff --git a/src/utils/builder/edit_profile.rs b/src/utils/builder/edit_profile.rs index c83f049..257a8e5 100644 --- a/src/utils/builder/edit_profile.rs +++ b/src/utils/builder/edit_profile.rs @@ -12,7 +12,7 @@ impl EditProfile { /// /// # Examples /// - /// A utility method - [`utils::read_message`] - is provided to read an + /// 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 diff --git a/src/utils/message_builder.rs b/src/utils/message_builder.rs index e30db05..ca1ac8d 100644 --- a/src/utils/message_builder.rs +++ b/src/utils/message_builder.rs @@ -112,7 +112,6 @@ impl MessageBuilder { self } - /// Mentions the [`Role`] in the built message. /// /// This accepts anything that converts _into_ a [`RoleId`]. Refer to |