diff options
| author | Austin Hellyer <[email protected]> | 2016-11-18 11:00:45 -0800 |
|---|---|---|
| committer | Austin Hellyer <[email protected]> | 2016-11-18 11:00:45 -0800 |
| commit | cf128b1a10d0636c8b4b5233d46b068cfe665688 (patch) | |
| tree | f36b1cd08d00cec69a76eb10c493ab3b86d61678 /src/client | |
| parent | Feature macros should use else as block separator (diff) | |
| download | serenity-cf128b1a10d0636c8b4b5233d46b068cfe665688.tar.xz serenity-cf128b1a10d0636c8b4b5233d46b068cfe665688.zip | |
A bit of docs
Diffstat (limited to 'src/client')
| -rw-r--r-- | src/client/context.rs | 228 | ||||
| -rw-r--r-- | src/client/http/mod.rs | 160 | ||||
| -rw-r--r-- | src/client/http/ratelimiting.rs | 81 | ||||
| -rw-r--r-- | src/client/login_type.rs | 19 | ||||
| -rw-r--r-- | src/client/mod.rs | 164 |
5 files changed, 535 insertions, 117 deletions
diff --git a/src/client/context.rs b/src/client/context.rs index 522361a..5dd3053 100644 --- a/src/client/context.rs +++ b/src/client/context.rs @@ -23,15 +23,76 @@ use ::utils; #[cfg(feature = "state")] use super::STATE; +/// The context is a general utility struct provided on event dispatches, which +/// helps with dealing with the current "context" of the event dispatch, +/// and providing helper methods where possible. The context also acts as a +/// general high-level interface over the associated [`Connection`] which +/// received the event, or the low-level [`http`] module. +/// +/// For example, when the [`Client::on_message`] handler is dispatched to, the +/// context will contain the Id of the [`Channel`] that the message was created +/// for. This allows for using shortcuts like [`say`], which will +/// post its given argument to the associated channel for you as a [`Message`]. +/// +/// Additionally, the context contains "shortcuts", like for interacting with +/// the connection. Methods like [`set_game`] will unlock the connection and +/// perform an update for you to save a bit of work. +/// +/// A context will only live for the event it was dispatched for. After the +/// event handler finished, it is destroyed and will not be re-used. +/// +/// # Automatically using the State +/// +/// The context makes use of the [`State`] being global, and will first check +/// the state for associated data before hitting the REST API. This is to save +/// Discord requests, and ultimately save your bot bandwidth and time. This also +/// acts as a clean interface for retrieving from the state without needing to +/// check it yourself first, and then performing a request if it does not exist. +/// The context ultimately acts as a means to simplify these two operations into +/// one. +/// +/// For example, if you are needing information about a +/// [channel][`PublicChannel`] within a [guild][`LiveGuild`], then you can +/// use [`get_channel`] to retrieve it. Under most circumstances, the guild and +/// its channels will be cached within the state, and `get_channel` will just +/// pull from the state. If it does not exist, it will make a request to the +/// REST API, and then insert a clone of the channel into the state, returning +/// you the channel. +/// +/// In this scenario, now that the state has the channel, performing the same +/// request to `get_channel` will instead pull from the state, as it is now +/// cached. +/// +/// [`Channel`]: ../model/enum.Channel.html +/// [`Client::on_message`]: struct.Client.html#method.on_message +/// [`Connection`]: struct.Connection.html +/// [`LiveGuild`]: ../model/struct.LiveGuild.html +/// [`Message`]: ../model/struct.Message.html +/// [`PublicChannel`]: ../model/struct.PublicChannel.html +/// [`State`]: ../ext/state/struct.State.html +/// [`get_channel`]: #method.get_channel +/// [`http`]: http/index.html +/// [`say`]: #method.say +/// [`set_game`]: #method.set_game #[derive(Clone)] pub struct Context { channel_id: Option<ChannelId>, + /// The associated connection which dispatched the event handler. + /// + /// Note that if you are sharding, in relevant terms, this is the shard + /// which received the event being dispatched. pub connection: Arc<Mutex<Connection>>, login_type: LoginType, } impl Context { /// Create a new Context to be passed to an event handler. + /// + /// There's no real reason to use this yourself. But the option is there. + /// Highly re-consider _not_ using this if you're tempted. + /// + /// Or don't do what I say. I'm just a comment hidden from the generated + /// documentation. #[doc(hidden)] pub fn new(channel_id: Option<ChannelId>, connection: Arc<Mutex<Connection>>, @@ -45,14 +106,10 @@ impl Context { /// Accepts the given invite. /// - /// Refer to the documentation for [`Invite::accept`] for restrictions on - /// accepting an invite. - /// - /// **Note**: Requires that the current user be a user account. Bots can not - /// accept invites. Instead they must be accepted via OAuth2 authorization - /// links. These are in the format of: + /// Refer to the documentation for [`http::accept_invite`] for restrictions + /// on accepting an invite. /// - /// `https://discordapp.com/oauth2/authorize?client_id=CLIENT_ID&scope=bot` + /// **Note**: Requires that the current user be a user account. /// /// # Errors /// @@ -71,19 +128,23 @@ impl Context { http::accept_invite(code) } - /// Mark a message as being read in a channel. This will mark up to the - /// given message as read. Any messages created after that message will not - /// be marked as read. + /// Mark a [`Channel`] as being read up to a certain [`Message`]. + /// + /// Refer to the documentation for [`http::ack_message`] for more + /// information. /// /// # Errors /// /// Returns a [`ClientError::InvalidOperationAsBot`] if this is a bot. /// + /// [`Channel`]: ../../model/enum.Channel.html /// [`ClientError::InvalidOperationAsBot`]: ../enum.ClientError.html#variant.InvalidOperationAsUser + /// [`Message`]: ../../model/struct.Message.html + /// [`http::ack_message`]: http/fn.ack_message.html pub fn ack<C, M>(&self, channel_id: C, message_id: M) -> Result<()> where C: Into<ChannelId>, M: Into<MessageId> { if self.login_type == LoginType::User { - return Err(Error::Client(ClientError::InvalidOperationAsUser)) + return Err(Error::Client(ClientError::InvalidOperationAsUser)); } http::ack_message(channel_id.into().0, message_id.into().0) @@ -92,8 +153,7 @@ impl Context { /// Ban a [`User`] from a [`Guild`], removing their messages sent in the /// last X number of days. /// - /// `0` days is equivilant to not removing any messages. Up to `7` days' - /// worth of messages may be deleted. + /// Refer to the documentation for [`http::ban_user`] for more information. /// /// **Note**: Requires that you have the [Ban Members] permission. /// @@ -145,7 +205,9 @@ impl Context { /// Creates a [`PublicChannel`] in the given [`Guild`]. /// - /// Requires that you have the [Manage Channels] permission. + /// Refer to [`http::create_channel`] for more information. + /// + /// **Note**: Requires the [Manage Channels] permission. /// /// # Examples /// @@ -159,6 +221,7 @@ impl Context { /// /// [`Guild`]: ../model/struct.Guild.html /// [`PublicChannel`]: ../model/struct.PublicChannel.html + /// [`http::create_channel`]: http/fn.create_channel.html /// [Manage Channels]: ../model/permissions/constant.MANAGE_CHANNELS.html pub fn create_channel<G>(&self, guild_id: G, name: &str, kind: ChannelType) -> Result<Channel> where G: Into<GuildId> { @@ -170,6 +233,22 @@ impl Context { http::create_channel(guild_id.into().0, map) } + /// Creates an emoji in the given guild with a name and base64-encoded + /// image. The [`utils::read_image`] function is provided for you as a + /// simple method to read an image and encode it into base64, if you are + /// reading from the filesystem. + /// + /// **Note**: Requires the [Manage Emojis] permission. + /// + /// # Examples + /// + /// See the [`EditProfile::avatar`] example for an in-depth example as to + /// how to read an image from the filesystem and encode it as base64. Most + /// of the example can be applied similarly for this method. + /// + /// [`EditProfile::avatar`]: ../utils/builder/struct.EditProfile.html#method.avatar + /// [`utils::read_image`]: ../utils/fn.read_image.html + /// [Manage Emojis]: ../model/permissions/constant.MANAGE_EMOJIS.html pub fn create_emoji<G>(&self, guild_id: G, name: &str, image: &str) -> Result<Emoji> where G: Into<GuildId> { let map = ObjectBuilder::new() @@ -182,9 +261,14 @@ impl Context { /// Creates a [`Guild`] with the data provided. /// + /// **Note**: This endpoint is usually only available for user accounts. + /// Refer to Discord's information for the endpoint [here][whitelist] for + /// more information. If you require this as a bot, re-think what you are + /// doing and if it _really_ needs to be doing this. + /// /// # Examples /// - /// Create a guild called `test` in the [US West region] with no icon: + /// Create a guild called `"test"` in the [US West region] with no icon: /// /// ```rust,ignore /// use serenity::model::Region; @@ -194,6 +278,7 @@ impl Context { /// /// [`Guild`]: ../model/struct.Guild.html /// [US West region]: ../model/enum.Region.html#variant.UsWest + /// [whitelist]: https://discordapp.com/developers/docs/resources/guild#create-guild pub fn create_guild(&self, name: &str, region: Region, icon: Option<&str>) -> Result<Guild> { let map = ObjectBuilder::new() @@ -205,6 +290,13 @@ impl Context { http::create_guild(map) } + /// Creates an [`Integration`] for a [`Guild`]. + /// + /// **Note**: Requires the [Manage Guild] permission. + /// + /// [`Guild`]: ../model/struct.Guild.html + /// [`Integration`]: ../model/struct.Integration.html + /// [Manage Guild]: ../model/permissions/constant.MANAGE_GUILD.html pub fn create_integration<G, I>(&self, guild_id: G, integration_id: I, @@ -220,6 +312,16 @@ impl Context { http::create_guild_integration(guild_id.into().0, integration_id.0, map) } + /// Creates an invite for the channel, providing a builder so that fields + /// may optionally be set. + /// + /// See the documentation for the [`CreateInvite`] builder for information + /// on how to use this and the default values that it provides. + /// + /// **Note**: Requires the [Create Invite] permission. + /// + /// [`CreateInvite`]: ../utils/builder/struct.CreateInvite.html + /// [Create Invite]: ../model/permissions/constant.CREATE_INVITE.html pub fn create_invite<C, F>(&self, channel_id: C, f: F) -> Result<RichInvite> where C: Into<ChannelId>, F: FnOnce(CreateInvite) -> CreateInvite { let map = f(CreateInvite::default()).0.build(); @@ -227,6 +329,72 @@ impl Context { http::create_invite(channel_id.into().0, map) } + /// Creates a [permission overwrite][`PermissionOverwrite`] for either a + /// single [`Member`] or [`Role`] within a [`Channel`]. + /// + /// Refer to the documentation for [`PermissionOverwrite`]s for more + /// information. + /// + /// **Note**: Requires the [Manage Channels] permission. + /// + /// # Examples + /// + /// Creating a permission overwrite for a member by specifying the + /// [`PermissionOverwrite::Member`] variant, allowing it the [Send Messages] + /// permission, but denying the [Send TTS Messages] and [Attach Files] + /// permissions: + /// + /// ```rust,ignore + /// use serenity::model::{ChannelId, PermissionOverwrite, permissions}; + /// + /// // assuming you are in a context + /// + /// let channel_id = 7; + /// let user_id = 8; + /// + /// let allow = permissions::SEND_MESSAGES; + /// let deny = permissions::SEND_TTS_MESSAGES | permissions::ATTACH_FILES; + /// let overwrite = PermissionOverwrite { + /// allow: allow, + /// deny: deny, + /// kind: PermissionOverwriteType::Member(user_id), + /// }; + /// + /// let _result = context.create_permission(channel_id, overwrite); + /// ``` + /// + /// Creating a permission overwrite for a role by specifying the + /// [`PermissionOverwrite::Role`] variant, allowing it the [Manage Webhooks] + /// permission, but denying the [Send TTS Messages] and [Attach Files] + /// permissions: + /// + /// ```rust,ignore + /// use serenity::model::{ChannelId, PermissionOverwrite, permissions}; + /// + /// // assuming you are in a context + /// + /// let channel_id = 7; + /// let user_id = 8; + /// + /// let allow = permissions::SEND_MESSAGES; + /// let deny = permissions::SEND_TTS_MESSAGES | permissions::ATTACH_FILES; + /// let overwrite = PermissionOverwrite { + /// allow: allow, + /// deny: deny, + /// kind: PermissionOverwriteType::Member(user_id), + /// }; + /// + /// let _result = context.create_permission(channel_id, overwrite); + /// ``` + /// + /// [`Channel`]: ../model/enum.Channel.html + /// [`Member`]: ../model/struct.Member.html + /// [`PermissionOverwrite`]: ../model/struct.PermissionOverWrite.html + /// [`PermissionOverwrite::Member`]: ../model/struct.PermissionOverwrite.html#variant.Member + /// [`Role`]: ../model/struct.Role.html + /// [Attach Files]: ../model/permissions/constant.ATTACH_FILES.html + /// [Manage Channels]: ../model/permissions/constant.MANAGE_CHANNELS.html + /// [Send TTS Messages]: ../model/permissions/constant.SEND_TTS_MESSAGES.html pub fn create_permission<C>(&self, channel_id: C, target: PermissionOverwrite) @@ -246,7 +414,12 @@ impl Context { http::create_permission(channel_id.into().0, id, map) } - pub fn create_private_channel<U>(&self, user_id: U) + /// Creates a direct message channel between the [current user] and another + /// [`User`]. This can also retrieve the channel if one already exists. + /// + /// [`User`]: ../model/struct.User.html + /// [current user]: ../model/struct.CurrentUser.html + pub fn create_direct_message_channel<U>(&self, user_id: U) -> Result<PrivateChannel> where U: Into<UserId> { let map = ObjectBuilder::new() .insert("recipient_id", user_id.into().0) @@ -257,11 +430,16 @@ impl Context { /// React to a [`Message`] with a custom [`Emoji`] or unicode character. /// - /// **Note**: Requires the [Add Reactions] permission. + /// [`Message::react`] may be a more suited method of reacting in most + /// cases. + /// + /// **Note**: Requires the [Add Reactions] permission, _if_ the current user + /// is the first user to perform a react with a certain emoji. /// - /// [`Emoji`]: ../models/struct.Emoji.html - /// [`Message`]: ../models/struct.Message.html - /// [Add Reactions]: ../models/permissions/constant.ADD_REACTIONS.html + /// [`Emoji`]: ../model/struct.Emoji.html + /// [`Message`]: ../model/struct.Message.html + /// [`Message::react`]: ../model/struct.Message.html#method.react + /// [Add Reactions]: ../model/permissions/constant.ADD_REACTIONS.html pub fn create_reaction<C, M, R>(&self, channel_id: C, message_id: M, @@ -300,6 +478,12 @@ impl Context { http::delete_channel(channel_id.into().0) } + /// Deletes an emoji in a [`Guild`] given its Id. + /// + /// **Note**: Requires the [Manage Emojis] permission. + /// + /// [`Guild`]: ../model/struct.Guild.html + /// [Manage Emojis]: ../model/permissions/constant.MANAGE_EMOJIS.html pub fn delete_emoji<E, G>(&self, guild_id: G, emoji_id: E) -> Result<()> where E: Into<EmojiId>, G: Into<GuildId> { http::delete_emoji(guild_id.into().0, emoji_id.into().0) @@ -406,8 +590,8 @@ impl Context { /// **Note**: Requires the [`Manage Messages`] permission, _if_ the current /// user did not perform the reaction. /// - /// [`Reaction`]: ../models/struct.Reaction.html - /// [Manage Messages]: ../models/permissions/constant.MANAGE_MESSAGES.html + /// [`Reaction`]: ../model/struct.Reaction.html + /// [Manage Messages]: ../model/permissions/constant.MANAGE_MESSAGES.html pub fn delete_reaction<C, M, R>(&self, channel_id: C, message_id: M, diff --git a/src/client/http/mod.rs b/src/client/http/mod.rs index 66bbb8b..ea15230 100644 --- a/src/client/http/mod.rs +++ b/src/client/http/mod.rs @@ -16,7 +16,13 @@ //! //! If a request spuriously fails, it will be retried once. //! +//! Note that you may want to perform requests through a [`Context`] or through +//! [model]s' instance methods where possible, as they each offer different +//! levels of a high-level interface to the HTTP module. +//! //! [`Client`]: ../struct.Client.html +//! [`Context`]: ../struct.Context.html +//! [model]: ../../model/index.html mod ratelimiting; @@ -45,17 +51,68 @@ lazy_static! { static ref TOKEN: Arc<Mutex<String>> = Arc::new(Mutex::new(String::default())); } +/// Sets the token to be used across all requests which require authentication. +/// +/// This is really only for internal use, and if you are reading this as a user, +/// you should _not_ use this yourself. #[doc(hidden)] pub fn set_token(token: &str) { TOKEN.lock().unwrap().clone_from(&token.to_owned()); } +/// Accepts the [`Invite`] given its code, placing the current user in the +/// [`Guild`] that the invite was for. +/// +/// Use [`utils::parse_invite`] to retrieve codes from URLs. +/// +/// Refer to the documentation for [`Context::accept_invite`] for restrictions on +/// accepting an invite. +/// +/// This will fire the [`Client::on_guild_create`] handler once the associated +/// event is received. +/// +/// **Note**: This will fail if you are already in the guild, or are banned. A +/// ban is equivilant to an IP ban. +/// +/// **Note**: Requires that the current user be a user account. Bots can not +/// accept invites. Instead, they must be accepted via OAuth2 authorization +/// links. These are in the format of: +/// +/// `https://discordapp.com/oauth2/authorize?client_id=CLIENT_ID&scope=bot` +/// +/// # Examples +/// +/// Accept an invite given a code from a URL: +/// +/// ```rust,no_run +/// use serenity::client::http; +/// use serenity::utils; +/// +/// let url = "https://discord.gg/0cDvIgU2voY8RSYL"; +/// let code = utils::parse_invite(url); +/// +/// let _result = http::accept_invite(code); +/// ``` +/// +/// [`Context::accept_invite`]: ../struct.Context.html#method.accept_invite +/// [`Invite`]: ../../model/struct.Invite.html +/// [`utils::parse_invite`]: ../../utils/fn.parse_invite.html pub fn accept_invite(code: &str) -> Result<Invite> { let response = request!(Route::InvitesCode, post, "/invites/{}", code); Invite::decode(try!(serde_json::from_reader(response))) } +/// Marks a [`Channel`] as being "read" up to a certain [`Message`]. Any +/// message past the given one will not be marked as read. +/// +/// Usually you should use this to mark the latest message as being read. +/// +/// **Note**: Bot users should not use this, as it has no bearing on them +/// whatsoever. +/// +/// [`Channel`]: ../../model/enum.Channel.html +/// [`Message`]: ../../model/struct.Message.html pub fn ack_message(channel_id: u64, message_id: u64) -> Result<()> { verify(204, request!(Route::ChannelsIdMessagesIdAck(channel_id), post, @@ -64,6 +121,13 @@ pub fn ack_message(channel_id: u64, message_id: u64) -> Result<()> { message_id)) } +/// Adds a [`User`] as a recipient to a [`Group`]. +/// +/// **Note**: Groups have a limit of 10 recipients, including the current user. +/// +/// [`Group`]: ../../model/struct.Group.html +/// [`Group::add_recipient`]: ../../model/struct.Group.html#method.add_recipient +/// [`User`]: ../../model/struct.User.html pub fn add_group_recipient(group_id: u64, user_id: u64) -> Result<()> { verify(204, request!(Route::None, @@ -91,6 +155,17 @@ pub fn add_member_role(guild_id: u64, user_id: u64, role_id: u64) -> Result<()> role_id)) } +/// Bans a [`User`] from a [`Guild`], removing their messages sent in the last +/// X number of days. +/// +/// Passing a `delete_message_days` of `0` is equivilant to not removing any +/// messages. Up to `7` days' worth of messages may be deleted. +/// +/// **Note**: Requires that you have the [Ban Members] permission. +/// +/// [`Guild`]: ../../model/struct.Guild.html +/// [`User`]: ../../model/struct.User.html +/// [Ban Members]: ../../model/permissions/constant.BAN_MEMBERS.html pub fn ban_user(guild_id: u64, user_id: u64, delete_message_days: u8) -> Result<()> { verify(204, request!(Route::GuildsIdBansUserId(guild_id), @@ -101,6 +176,15 @@ pub fn ban_user(guild_id: u64, user_id: u64, delete_message_days: u8) delete_message_days)) } +/// Broadcasts that the current user is typing in the given [`Channel`]. +/// +/// This lasts for about 10 seconds, and will then need to be renewed to +/// indicate that the current user is still typing. +/// +/// This should rarely be used for bots, although it is a good indicator that a +/// long-running command is still being processed. +/// +/// [`Channel`]: ../../model/enum.Channel.html pub fn broadcast_typing(channel_id: u64) -> Result<()> { verify(204, request!(Route::ChannelsIdTyping(channel_id), post, @@ -108,6 +192,16 @@ pub fn broadcast_typing(channel_id: u64) -> Result<()> { channel_id)) } +/// Creates a [`PublicChannel`] in the [`Guild`] given its Id. +/// +/// Refer to the Discord's [docs] for information on what fields this requires. +/// +/// **Note**: Requires the [Manage Channels] permission. +/// +/// [`Guild`]: ../../model/struct.Guild.html +/// [`PublicChannel`]: ../../model/struct.PublicChannel.html +/// [docs]: https://discordapp.com/developers/docs/resources/guild#create-guild-channel +/// [Manage Channels]: ../../model/permissions/constant.MANAGE_CHANNELS.html pub fn create_channel(guild_id: u64, map: Value) -> Result<Channel> { let body = try!(serde_json::to_string(&map)); let response = request!(Route::GuildsIdChannels(guild_id), @@ -118,6 +212,16 @@ pub fn create_channel(guild_id: u64, map: Value) -> Result<Channel> { Channel::decode(try!(serde_json::from_reader(response))) } +/// Creates an emoji in the given [`Guild`] with the given data. +/// +/// View the source code for [`Context::create_emoji`] to see what fields this +/// requires. +/// +/// **Note**: Requires the [Manage Emojis] permission. +/// +/// [`Context::create_emoji`]: ../struct.Context.html#method.create_emoji +/// [`Guild`]: ../../model/struct.Guild.html +/// [Manage Emojis]: ../../model/permissions/constant.MANAGE_EMOJIS.html pub fn create_emoji(guild_id: u64, map: Value) -> Result<Emoji> { let body = try!(serde_json::to_string(&map)); @@ -129,6 +233,35 @@ pub fn create_emoji(guild_id: u64, map: Value) Emoji::decode(try!(serde_json::from_reader(response))) } +/// Creates a [`Guild`] with the data provided. +/// +/// **Note**: This endpoint is usually only available for user accounts. Refer +/// to Discord's documentation for the endpoint [here][whitelist] for more +/// information. If your bot requires this, re-think what you are doing and +/// whether it _really_ needs to be doing this. +/// +/// # Examples +/// +/// Create a guild called `"test"` in the [US West region]: +/// +/// ```rust,ignore +/// extern crate serde_json; +/// +/// use serde_json::builder::ObjectBuilder; +/// use serde_json::Value; +/// use serenity::client::http; +/// +/// let map = ObjectBuilder::new() +/// .insert("name", "test") +/// .insert("region", "us-west") +/// .build(); +/// +/// let _result = http::create_guild(map); +/// ``` +/// +/// [`Guild`]: ../../model/struct.Guild.html +/// [US West Region]: ../../model/enum.Region.html#variant.UsWest +/// [whitelist]: https://discordapp.com/developers/docs/resources/guild#create-guild pub fn create_guild(map: Value) -> Result<Guild> { let body = try!(serde_json::to_string(&map)); let response = request!(Route::Guilds, post(body), "/guilds"); @@ -136,9 +269,18 @@ pub fn create_guild(map: Value) -> Result<Guild> { Guild::decode(try!(serde_json::from_reader(response))) } -pub fn create_guild_integration(guild_id: u64, - integration_id: u64, - map: Value) -> Result<()> { +/// Creates an [`Integration`] for a [`Guild`]. +/// +/// Refer to Discord's [docs] for field information. +/// +/// **Note**: Requires the [Manage Guild] permission. +/// +/// [`Guild`]: ../../model/struct.Guild.html +/// [`Integration`]: ../../model/struct.Integration.html +/// [Manage Guild]: ../../model/permissions/constant.MANAGE_GUILD.html +/// [docs]: https://discordapp.com/developers/docs/resources/guild#create-guild-integration +pub fn create_guild_integration(guild_id: u64, integration_id: u64, map: Value) + -> Result<()> { let body = try!(serde_json::to_string(&map)); verify(204, request!(Route::GuildsIdIntegrations(guild_id), @@ -148,6 +290,18 @@ pub fn create_guild_integration(guild_id: u64, integration_id)) } +/// Creates a [`RichInvite`] for the given [channel][`PublicChannel`]. +/// +/// Refer to Discord's [docs] for field information. +/// +/// All fields are optional. +/// +/// **Note**: Requires the [Create Invite] permission. +/// +/// [`PublicChannel`]: ../../model/struct.PublicChannel.html +/// [`RichInvite`]: ../../model/struct.RichInvite.html +/// [Create Invite]: ../../model/permissions/constant.CREATE_INVITE.html +/// [docs]: https://discordapp.com/developers/docs/resources/channel#create-channel-invite pub fn create_invite(channel_id: u64, map: Value) -> Result<RichInvite> { let body = try!(serde_json::to_string(&map)); diff --git a/src/client/http/ratelimiting.rs b/src/client/http/ratelimiting.rs index 3518bdc..9281563 100644 --- a/src/client/http/ratelimiting.rs +++ b/src/client/http/ratelimiting.rs @@ -1,3 +1,44 @@ +//! Routes are used for ratelimiting. These are to differentiate between the +//! different _types_ of routes - such as getting the current user's channels - +//! for the most part, with the exception being major parameters. +//! +//! [Taken from] the Discord docs, major parameters are: +//! +//! > Additionally, rate limits take into account major parameters in the URL. +//! > For example, `/channels/:channel_id` and +//! > `/channels/:channel_id/messages/:message_id` both take `channel_id` into +//! > account when generating rate limits since it's the major parameter. The +//! only current major parameters are `channel_id` and `guild_id`. +//! +//! This results in the two URIs of `GET /channels/4/messages/7` and +//! `GET /channels/5/messages/8` being rate limited _separately_. However, the +//! two URIs of `GET /channels/10/messages/11` and +//! `GET /channels/10/messages/12` will count towards the "same ratelimit", as +//! the major parameter - `10` is equivilant in both URIs' format. +//! +//! # Examples +//! +//! First: taking the first two URIs - `GET /channels/4/messages/7` and +//! `GET /channels/5/messages/8` - and assuming both buckets have a `limit` of +//! `10`, requesting the first URI will result in the response containing a +//! `remaining` of `9`. Immediately after - prior to buckets resetting - +//! performing a request to the _second_ URI will also contain a `remaining` of +//! `9` in the response, as the major parameter - `channel_id` - is different +//! in the two requests (`4` and `5`). +//! +//! Second: take for example the last two URIs. Assuming the bucket's `limit` is +//! `10`, requesting the first URI will return a `remaining` of `9` in the +//! response. Immediately after - prior to buckets resetting - performing a +//! request to the _second_ URI will return a `remaining` of `8` in the +//! response, as the major parameter - `channel_id` - is equivilant for the two +//! requests (`10`). +//! +//! +//! With the examples out of the way: major parameters are why some variants +//! (i.e. all of the channel/guild variants) have an associated u64 as data. +//! This is the Id of the parameter, differentiating between different +//! ratelimits. + use hyper::client::{RequestBuilder, Response}; use hyper::header::Headers; use hyper::status::StatusCode; @@ -14,46 +55,6 @@ lazy_static! { static ref ROUTES: Arc<Mutex<HashMap<Route, RateLimit>>> = Arc::new(Mutex::new(HashMap::default())); } -/// Routes are used for ratelimiting. These are to differentiate between the -/// different _types_ of routes - such as getting the current user's channels - -/// for the most part, with the exception being major parameters. -/// -/// [Taken from] the Discord docs, major parameters are: -/// -/// > Additionally, rate limits take into account major parameters in the URL. -/// > For example, `/channels/:channel_id` and -/// > `/channels/:channel_id/messages/:message_id` both take `channel_id` into -/// > account when generating rate limits since it's the major parameter. The -/// only current major parameters are `channel_id` and `guild_id`. -/// -/// This results in the two URIs of `GET /channels/4/messages/7` and -/// `GET /channels/5/messages/8` being rate limited _separately_. However, the -/// two URIs of `GET /channels/10/messages/11` and -/// `GET /channels/10/messages/12` will count towards the "same ratelimit", as -/// the major parameter - `10` is equivilant in both URIs. -/// -/// # Examples -/// -/// First: taking the first two URIs - `GET /channels/4/messages/7` and -/// `GET /channels/5/messages/8` - and assuming both buckets have a `limit` of -/// `10`, requesting the first URI will result in the response containing a -/// `remaining` of `9`. Immediately after - prior to buckets resetting - -/// performing a request to the _second_ URI will also contain a `remaining` of -/// `9` in the response, as the major parameter - `channel_id` - is different -/// in the two requests (`4` and `5`). -/// -/// Second: take for example the last two URIs. Assuming the bucket's `limit` is -/// `10`, requesting the first URI will return a `remaining` of `9` in the -/// response. Immediately after - prior to buckets resetting - performing a -/// request to the _second_ URI will return a `remaining` of `8` in the -/// response, as the major parameter - `channel_id` - is equivilant for the two -/// requests (`10`). -/// -/// -/// With the examples out of the way: major parameters are why some variants -/// (i.e. all of the channel/guild variants) have an associated u64 as data. -/// This is the Id of the parameter, differentiating between different -/// ratelimits. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum Route { ChannelsId(u64), diff --git a/src/client/login_type.rs b/src/client/login_type.rs index e62f19a..468ead5 100644 --- a/src/client/login_type.rs +++ b/src/client/login_type.rs @@ -1,5 +1,24 @@ +/// The type of login to perform. +/// +/// Use [`Bot`] if you are using a bot which responds to others, created through +/// the [applications page]. See the [`README`] for more information on using +/// bots. +/// +/// Use [`User`] if you are creating a selfbot which responds only to you. +/// +/// [`Bot`]: #variant.Bot +/// [`README`]: https://github.com/zeyla/serenity.rs/blob/master/README.md#Bots +/// [`User`]: #variant.User +/// [applications page]: https://discordapp.com/developers/applications/me #[derive(Copy, Clone, Hash, Eq, PartialEq, Debug, Ord, PartialOrd)] pub enum LoginType { + /// An indicator to login as a bot. This will automatically prefix your + /// token with `"Bot "`, which is a requirement by Discord. Bot, + /// An indicator to login under your own user account token. Only use this + /// if you are creating a "selfbot", which triggers on events from yourself. + /// + /// **Note**: _Do not_ use this for a "userbot" which responds to others, or + /// you _can_ be banned. User, } diff --git a/src/client/mod.rs b/src/client/mod.rs index 37bb6dc..7665955 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,37 +1,25 @@ -//! The Client contains information about a single bot or user's "session" with -//! Discord. Event handers and starting the connection are handled directly via -//! the client. In addition, the [http module] and [`State`] are also -//! automatically handled by the Client module for you. +//! The Client contains information about a single bot or user's token, as well +//! as event handlers. Dispatching events to configured handlers and starting +//! the connection are handled directly via the client. In addition, the +//! [`http`] module and [`State`] are also automatically handled by the Client +//! module for you. //! -//! A [`Context`] is provided for every handler. The -//! context is an ergonomic way of accessing the lower-level Http struct's -//! methods. +//! A [`Context`] is provided for every handler. The context is an ergonomic +//! method of accessing the lower-level http functions. //! -//! The Http struct is the lower-level method of accessing the Discord REST API. -//! Realistically there should be little reason to use this yourself, as the -//! Context will do this for you. A possible use case of using the Http struct -//! is if you do not have a state for purposes such as low memory requirements. +//! The `http` module is the lower-level method of interacting with the Discord +//! REST API. Realistically, there should be little reason to use this yourself, +//! as the Context will do this for you. A possible use case of using the `http` +//! module is if you do not have a State, for purposes such as low memory +//! requirements. //! -//! Creating a Client instance and adding a handler on every message -//! receive, acting as a "ping-pong" bot is simple: -//! -//! ```rust,ignore -//! use serenity::Client; -//! -//! let mut client = Client::login_bot("my token here"); -//! -//! client.on_message(|context, message| { -//! if message.content == "!ping" { -//! context.say("Pong!"); -//! } -//! }); -//! -//! client.start(); -//! ``` +//! Click [here][Client examples] for an example on how to use a `Client`. //! +//! [`Client`]: struct.Client.html#examples //! [`Context`]: struct.Context.html -//! [`State`]: ext/state/index.html -//! [http module]: client/http/index.html +//! [`State`]: ../ext/state/index.html +//! [`http`]: http/index.html +//! [Client examples]: struct.Client.html#examples pub mod http; @@ -100,30 +88,45 @@ lazy_static! { /// # Examples /// /// Matching an [`Error`] with this variant may look something like the -/// following for the [`Context::ban_user`] method: +/// following for the [`Client::ban`] method, which in this example is used to +/// re-ban all members with an odd discriminator: /// -/// ```rust,ignore -/// use serenity::client::ClientError; +/// ```rust,no_run +/// use serenity::client::{Client, ClientError}; /// use serenity::Error; +/// use std::env; +/// +/// let token = env::var("DISCORD_BOT_TOKEN").unwrap(); +/// let mut client = Client::login_bot(&token); +/// +/// client.on_member_unban(|context, guild_id, user| { +/// let discriminator = match user.discriminator.parse::<u16>() { +/// Ok(discriminator) => discriminator, +/// Err(_why) => return, +/// }; /// -/// // assuming you are in a context and a `guild_id` has been bound +/// // If the user has an even discriminator, don't re-ban them. +/// if discriminator % 2 == 0 { +/// return; +/// } /// -/// match context.ban_user(context.guild_id, context.message.author, 8) { -/// Ok(()) => { -/// // Ban successful. -/// }, -/// Err(Error::Client(ClientError::DeleteMessageDaysAmount(amount))) => { -/// println!("Tried deleting {} days' worth of messages", amount); -/// }, -/// Err(why) => { -/// println!("Unexpected error: {:?}", why); -/// }, -/// } +/// match context.ban(guild_id, user, 8) { +/// Ok(()) => { +/// // Ban successful. +/// }, +/// Err(Error::Client(ClientError::DeleteMessageDaysAmount(amount))) => { +/// println!("Failed deleting {} days' worth of messages", amount); +/// }, +/// Err(why) => { +/// println!("Unexpected error: {:?}", why); +/// }, +/// } +/// }); /// ``` /// /// [`Client`]: struct.Client.html /// [`Context`]: struct.Context.html -/// [`Context::ban_user`]: struct.Context.html#method.ban_user +/// [`Context::ban`]: struct.Context.html#method.ban /// [`Error::Client`]: ../enum.Error.html#variant.Client #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub enum ClientError { @@ -222,7 +225,50 @@ pub enum ClientError { UnknownStatus(u16), } +/// The Client is the way to "login" and be able to start sending authenticated +/// requests over the REST API, as well as initializing a WebSocket +/// [`Connection`]. Refer to `Connection`'s [information on using sharding] for +/// more information. +/// +/// # Event Handlers +/// +/// Event handlers can be configured. For example, the event handler +/// [`on_message`] will be dispatched to whenever a [`Event::MessageCreate`] is +/// received over the connection. +/// +/// Note that you do not need to manually handle events, as they are handled +/// internally and then dispatched to your event handlers. +/// +/// # Examples +/// +/// Creating a Client instance and adding a handler on every message +/// receive, acting as a "ping-pong" bot is simple: +/// +/// ```rust,ignore +/// use serenity::Client; +/// +/// let mut client = Client::login_bot("my token here"); +/// +/// client.on_message(|context, message| { +/// if message.content == "!ping" { +/// context.say("Pong!"); +/// } +/// }); +/// +/// client.start(); +/// ``` +/// +/// [`Connection`]: struct.Connection.html +/// [`on_message`]: #method.on_message +/// [`Event::MessageCreate`]: ../model/enum.Event.html#variant.MessageCreate +/// [information on using sharding]: struct.Connection.html#sharding pub struct Client { + /// A vector of all active connections that have received their + /// [`Event::Ready`] payload, and have dispatched to [`on_ready`] if an + /// event handler was configured. + /// + /// [`Event::Ready`]: ../model/enum.Event.html#variant.Ready + /// [`on_ready`]: #method.on_ready pub connections: Vec<Arc<Mutex<Connection>>>, event_store: Arc<Mutex<EventStore>>, #[cfg(feature="framework")] @@ -233,19 +279,33 @@ pub struct Client { #[allow(type_complexity)] impl Client { - /// Creates a Client for a bot. + /// Creates a Client for a bot user. + /// + /// Discord has a requirement of prefixing bot tokens with `"Bot "`, which + /// this function will automatically do for you. pub fn login_bot(bot_token: &str) -> Client { let token = format!("Bot {}", bot_token); login(&token, LoginType::Bot) } - /// Create an instance from "raw values" + + /// Create an instance from "raw values". This allows you to manually + /// specify whether to login as a [`Bot`] or [`User`], and does not modify + /// the token in any way regardless. + /// + /// [`Bot`]: enum.LoginType.html#variant.Bot + /// [`User`]: enum.LoginType.html#variant.User #[doc(hidden)] pub fn login_raw(token: &str, login_type: LoginType) -> Client { login(&token.to_owned(), login_type) } /// Creates a Client for a user. + /// + /// **Note**: Read the notes for [`LoginType::User`] prior to using this, as + /// there are restrictions on usage. + /// + /// [`LoginType::User`]: enum.LoginType.html#variant.User pub fn login_user(user_token: &str) -> Client { login(&user_token.to_owned(), LoginType::User) } @@ -376,8 +436,8 @@ impl Client { /// use serenity::Client; /// use std::env; /// - /// let mut client = Client::login_bot(&env::var("DISCORD_BOT_TOKEN") - /// .unwrap()); + /// let token = env::var("DISCORD_BOT_TOKEN").unwrap(); + /// let mut client = Client::login_bot(&token); /// /// let _ = client.start_shard_range([4, 7], 10); /// ``` @@ -699,12 +759,12 @@ impl Client { /// /// Print the [current user][`CurrentUser`]'s name on ready: /// - /// ```rust,ignore + /// ```rust,no_run /// use serenity::Client; /// use std::env; /// - /// let mut client = Client::login_bot(&env::var("DISCORD_BOT_TOKEN") - /// .unwrap()); + /// let token = env::var("DISCORD_BOT_TOKEN").unwrap(); + /// let mut client = Client::login_bot(&token); /// /// client.on_ready(|_context, ready| { /// println!("{} is connected", ready.user.name); |