From 651c618f17cb92d3ea9bbd1d5f5c92a015ff64e0 Mon Sep 17 00:00:00 2001 From: Austin Hellyer Date: Mon, 23 Jan 2017 12:16:06 -0800 Subject: [PATCH] Switch to a mostly-fully OOP approach The context is now strictly in relation to the context of the current channel related to the event, if any. See Context::say for a list of events that the context can be used for. --- Cargo.toml | 3 +- src/client/context.rs | 1438 ++++---------------- src/client/rest/mod.rs | 3 +- src/ext/framework/help_commands.rs | 21 +- src/ext/framework/mod.rs | 2 +- src/model/channel.rs | 1375 +++++++++++++++++-- src/model/gateway.rs | 2 - src/model/guild.rs | 2038 ++++++++++++++++++++++------ src/model/id.rs | 238 ---- src/model/invite.rs | 59 +- src/model/misc.rs | 7 +- src/model/mod.rs | 12 +- src/model/user.rs | 214 ++- src/model/utils.rs | 8 +- src/model/webhook.rs | 31 +- src/utils/macros.rs | 19 - 16 files changed, 3445 insertions(+), 2025 deletions(-) delete mode 100644 src/model/id.rs diff --git a/Cargo.toml b/Cargo.toml index 57a0fdf4793..270035340ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,10 +46,9 @@ version = "0.0.12" yaml-rust = "0.3" [features] -default = ["cache", "framework", "methods"] +default = ["cache", "framework"] cache = [] debug = [] framework = [] -methods = [] extras = [] voice = ["opus", "sodiumoxide"] diff --git a/src/client/context.rs b/src/client/context.rs index 73882dd70e3..aa33e5f9bc4 100644 --- a/src/client/context.rs +++ b/src/client/context.rs @@ -1,6 +1,4 @@ use serde_json::builder::ObjectBuilder; -use std::collections::HashMap; -use std::fmt::Write as FmtWrite; use std::io::Read; use std::sync::{Arc, Mutex}; use super::gateway::Shard; @@ -9,19 +7,13 @@ use super::login_type::LoginType; use typemap::ShareMap; use ::utils::builder::{ CreateEmbed, - CreateInvite, CreateMessage, EditChannel, - EditGuild, - EditMember, EditProfile, - EditRole, - GetMessages, Search, }; use ::internal::prelude::*; use ::model::*; -use ::utils; #[cfg(feature="extras")] use std::ops::ShlAssign; @@ -90,7 +82,7 @@ pub struct Context { /// A clone of [`Client::data`]. Refer to its documentation for more /// information. /// - /// [`Client::data`]: struct.Client.html#method.data + /// [`Client::data`]: struct.Client.html#structfield.data pub data: Arc>, /// The associated shard which dispatched the event handler. /// @@ -124,29 +116,7 @@ impl Context { } } - /// Accepts the given invite. - /// - /// Refer to the documentation for [`rest::accept_invite`] for restrictions - /// on accepting an invite. - /// - /// **Note**: Requires that the current user be a user account. - /// - /// # Errors - /// - /// Returns a [`ClientError::InvalidOperationAsBot`] if the current user is - /// a bot user. - /// - /// [`ClientError::InvalidOperationAsBot`]: enum.ClientError.html#variant.InvalidOperationAsBot - /// [`rest::accept_invite`]: rest/fn.accept_invite.html - pub fn accept_invite(&self, invite: &str) -> Result { - if self.login_type == LoginType::Bot { - return Err(Error::Client(ClientError::InvalidOperationAsBot)); - } - - rest::accept_invite(utils::parse_invite(invite)) - } - - /// Marks a [`Channel`] as being read up to a certain [`Message`]. + /// Marks the contextual channel as being read up to a certain [`Message`]. /// /// Refer to the documentation for [`rest::ack_message`] for more /// information. @@ -156,52 +126,25 @@ impl Context { /// Returns a [`ClientError::InvalidOperationAsBot`] if the current user is /// a bot user. /// - /// [`Channel`]: ../../model/enum.Channel.html - /// [`ClientError::InvalidOperationAsBot`]: ../enum.ClientError.html#variant.InvalidOperationAsUser - /// [`Message`]: ../../model/struct.Message.html + /// Returns a [`ClientError::NoChannelId`] if the current context is not + /// related to a channel. + /// + /// [`Channel`]: ../model/enum.Channel.html + /// [`ChannelId`]: ../model/struct.ChannelId.html + /// [`ClientError::InvalidOperationAsBot`]: enum.ClientError.html#variant.InvalidOperationAsUser + /// [`ClientError::NoChannelId`]: enum.ClientError.html#variant.NoChannelId + /// [`Message`]: ../model/struct.Message.html /// [`rest::ack_message`]: rest/fn.ack_message.html - pub fn ack(&self, channel_id: C, message_id: M) -> Result<()> - where C: Into, M: Into { + /// [`say`]: #method.say + pub fn ack>(&self, message_id: M) -> Result<()> { if self.login_type == LoginType::User { return Err(Error::Client(ClientError::InvalidOperationAsUser)); } - rest::ack_message(channel_id.into().0, message_id.into().0) - } - - /// Bans a [`User`] from a [`Guild`], removing their messages sent in the - /// last X number of days. - /// - /// Refer to the documentation for [`rest::ban_user`] for more information. - /// - /// Requires the [Ban Members] permission. - /// - /// # Examples - /// - /// Ban the user that sent a message for `7` days: - /// - /// ```rust,ignore - /// // assuming you are in a context - /// context.ban_user(context.guild_id, context.message.author, 7); - /// ``` - /// - /// # Errors - /// - /// Returns a [`ClientError::DeleteMessageDaysAmount`] if the number of days - /// given is over the maximum allowed. - /// - /// [`ClientError::DeleteMessageDaysAmount`]: enum.ClientError.html#variant.DeleteMessageDaysAmount - /// [`Guild`]: ../model/struct.Guild.html - /// [`User`]: ../model/struct.User.html - /// [`rest::ban_user`]: rest/fn.ban_user.html - /// [Ban Members]: ../model/permissions/constant.BAN_MEMBERS.html - pub fn ban(&self, guild_id: G, user_id: U, delete_message_days: u8) - -> Result<()> where G: Into, U: Into { - if delete_message_days > 7 { - return Err(Error::Client(ClientError::DeleteMessageDaysAmount(delete_message_days))); + match self.channel_id { + Some(channel_id) => channel_id.ack(message_id), + None => Err(Error::Client(ClientError::NoChannelId)), } - - rest::ban_user(guild_id.into().0, user_id.into().0, delete_message_days) } /// Broadcasts that you are typing to a channel for the next 5 seconds. @@ -221,244 +164,55 @@ impl Context { /// context.broadcast_typing(context.channel_id); /// ``` /// - /// [Send Messages]: ../model/permissions/constant.SEND_MESSAGES.html - pub fn broadcast_typing(&self, channel_id: C) -> Result<()> - where C: Into { - rest::broadcast_typing(channel_id.into().0) - } - - /// Creates a [`GuildChannel`] in the given [`Guild`]. - /// - /// Refer to [`rest::create_channel`] for more information. - /// - /// Requires the [Manage Channels] permission. - /// - /// # Examples - /// - /// Create a voice channel in a guild with the name `test`: - /// - /// ```rust,ignore - /// use serenity::model::ChannelType; - /// - /// context.create_channel(context.guild_id, "test", ChannelType::Voice); - /// ``` - /// - /// [`Guild`]: ../model/struct.Guild.html - /// [`GuildChannel`]: ../model/struct.GuildChannel.html - /// [`rest::create_channel`]: rest/fn.create_channel.html - /// [Manage Channels]: ../model/permissions/constant.MANAGE_CHANNELS.html - pub fn create_channel(&self, guild_id: G, name: &str, kind: ChannelType) - -> Result where G: Into { - let map = ObjectBuilder::new() - .insert("name", name) - .insert("type", kind.name()) - .build(); - - rest::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. - /// - /// The name of the emoji must be at least 2 characters long and can only - /// contain alphanumeric characters and underscores. - /// - /// 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(&self, guild_id: G, name: &str, image: &str) - -> Result where G: Into { - let map = ObjectBuilder::new() - .insert("name", name) - .insert("image", image) - .build(); - - rest::create_emoji(guild_id.into().0, map) - } - - /// Creates a guild with the data provided. - /// - /// Only a [`PartialGuild`] will be immediately returned, and a full - /// [`Guild`] will be received over a [`Shard`]. - /// - /// **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: - /// - /// ```rust,ignore - /// use serenity::model::Region; - /// - /// context.create_guild("test", Region::UsWest, None); - /// ``` - /// - /// [`Guild`]: ../model/struct.Guild.html - /// [`PartialGuild`]: ../model/struct.PartialGuild.html - /// [`Shard`]: ../gateway/struct.Shard.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 { - let map = ObjectBuilder::new() - .insert("icon", icon) - .insert("name", name) - .insert("region", region.name()) - .build(); - - rest::create_guild(map) - } - - /// Creates an [`Integration`] for a [`Guild`]. - /// - /// 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(&self, - guild_id: G, - integration_id: I, - kind: &str) - -> Result<()> where G: Into, - I: Into { - let integration_id = integration_id.into(); - let map = ObjectBuilder::new() - .insert("id", integration_id.0) - .insert("type", kind) - .build(); - - rest::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. + /// # Errors /// - /// See the documentation for the [`CreateInvite`] builder for information - /// on how to use this and the default values that it provides. + /// Returns a [`ClientError::InvalidOperationAsBot`] if the current user is + /// a bot user. /// - /// Requires the [Create Invite] permission. + /// Returns a [`ClientError::NoChannelId`] if the current context is not + /// related to a channel. /// - /// [`CreateInvite`]: ../utils/builder/struct.CreateInvite.html - /// [Create Invite]: ../model/permissions/constant.CREATE_INVITE.html - pub fn create_invite(&self, channel_id: C, f: F) -> Result - where C: Into, F: FnOnce(CreateInvite) -> CreateInvite { - let map = f(CreateInvite::default()).0.build(); + /// [`ChannelId`]: ../model/struct.ChannelId.html + /// [`ClientError::InvalidOperationAsBot`]: enum.ClientError.html#variant.InvalidOperationAsUser + /// [`ClientError::NoChannelId`]: enum.ClientError.html#variant.NoChannelId + /// [`say`]: #method.say + /// [Send Messages]: ../model/permissions/constant.SEND_MESSAGES.html + pub fn broadcast_typing(&self) -> Result<()> { + if self.login_type == LoginType::User { + return Err(Error::Client(ClientError::InvalidOperationAsUser)); + } - rest::create_invite(channel_id.into().0, map) + match self.channel_id { + Some(channel_id) => channel_id.broadcast_typing(), + None => Err(Error::Client(ClientError::NoChannelId)), + } } /// Creates a [permission overwrite][`PermissionOverwrite`] for either a - /// single [`Member`] or [`Role`] within a [`Channel`]. + /// single [`Member`] or [`Role`] within the channel. /// - /// Refer to the documentation for [`PermissionOverwrite`]s for more - /// information. + /// Refer to the documentation for [`GuildChannel::create_permission`] for + /// more information. /// /// 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), - /// }; + /// # Errors /// - /// let _result = context.create_permission(channel_id, overwrite); - /// ``` + /// Returns a [`ClientError::NoChannelId`] if the current context is not + /// related to a channel. /// - /// [`Channel`]: ../model/enum.Channel.html + /// [`ClientError::NoChannelId`]: enum.ClientError.html#variant.NoChannelId + /// [`GuildChannel::create_permission`]: struct.GuildChannel.html#method.create_permission /// [`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 - /// [Manage Webhooks]: ../model/permissions/constant.MANAGE_WEBHOOKS.html - /// [Send TTS Messages]: ../model/permissions/constant.SEND_TTS_MESSAGES.html - pub fn create_permission(&self, - channel_id: C, - target: PermissionOverwrite) - -> Result<()> where C: Into { - let (id, kind) = match target.kind { - PermissionOverwriteType::Member(id) => (id.0, "member"), - PermissionOverwriteType::Role(id) => (id.0, "role"), - }; - - let map = ObjectBuilder::new() - .insert("allow", target.allow.bits()) - .insert("deny", target.deny.bits()) - .insert("id", id) - .insert("type", kind) - .build(); - - rest::create_permission(channel_id.into().0, id, map) - } - - /// 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(&self, user_id: U) - -> Result where U: Into { - let map = ObjectBuilder::new() - .insert("recipient_id", user_id.into().0) - .build(); - - rest::create_private_channel(map) + pub fn create_permission(&self, target: PermissionOverwrite) + -> Result<()> { + match self.channel_id { + Some(channel_id) => channel_id.create_permission(target), + None => Err(Error::Client(ClientError::NoChannelId)), + } } /// React to a [`Message`] with a custom [`Emoji`] or unicode character. @@ -469,118 +223,49 @@ impl Context { /// Requires the [Add Reactions] permission, _if_ the current user is the /// first user to perform a react with a certain emoji. /// + /// # Errors + /// + /// Returns a [`ClientError::NoChannelId`] if the current context is not + /// related to a channel. + /// + /// [`ClientError::NoChannelId`]: enum.ClientError.html#variant.NoChannelId /// [`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(&self, - channel_id: C, - message_id: M, - reaction_type: R) - -> Result<()> - where C: Into, - M: Into, - R: Into { - rest::create_reaction(channel_id.into().0, - message_id.into().0, - reaction_type.into()) - } - - /// Creates a [`Role`] in guild with given Id. Second argument is a - /// closure, and you can use it to automatically configure role. - /// - /// Requires the [Manage Roles] permission. - /// - /// # Examples - /// - /// Create a role which can be mentioned, with the name 'test': - /// - /// ```rust,ignore - /// let role = context.create_role(guild_id, |r| r - /// .hoist(true) - /// .name("role")); - /// ``` - /// - /// [`Role`]: ../model/struct.Role.html - /// [Manage Roles]: ../model/permissions/constant.MANAGE_ROLES.html - pub fn create_role(&self, guild_id: G, f: F) -> Result - where F: FnOnce(EditRole) -> EditRole, G: Into { - rest::create_role(guild_id.into().0, f(EditRole::default()).0.build()) + pub fn create_reaction(&self, message_id: M, reaction_type: R) + -> Result<()> where M: Into, R: Into { + match self.channel_id { + Some(channel_id) => channel_id.create_reaction(message_id, reaction_type), + None => Err(Error::Client(ClientError::NoChannelId)), + } } - /// Deletes a [`Channel`] based on the Id given. + /// Deletes the contextual channel. /// /// If the channel being deleted is a [`GuildChannel`] (a [`Guild`]'s /// channel), then the [Manage Channels] permission is required. /// + /// # Errors + /// + /// Returns a [`ClientError::NoChannelId`] if the current context is not + /// related to a channel. + /// /// [`Channel`]: ../model/enum.Channel.html + /// [`ClientError::NoChannelId`]: enum.ClientError.html#variant.NoChannelId /// [`Guild`]: ../model/struct.Guild.html /// [`GuildChannel`]: ../model/struct.GuildChannel.html /// [Manage Channels]: ../model/permissions/constant.MANAGE_CHANNELS.html - pub fn delete_channel(&self, channel_id: C) -> Result - where C: Into { - rest::delete_channel(channel_id.into().0) - } - - /// Deletes an emoji in a [`Guild`] given its Id. - /// - /// Requires the [Manage Emojis] permission. - /// - /// [`Guild`]: ../model/struct.Guild.html - /// [Manage Emojis]: ../model/permissions/constant.MANAGE_EMOJIS.html - pub fn delete_emoji(&self, guild_id: G, emoji_id: E) -> Result<()> - where E: Into, G: Into { - rest::delete_emoji(guild_id.into().0, emoji_id.into().0) - } - - /// Deletes a [`Guild`]. The current user must be the guild owner to be able - /// to delete it. - /// - /// Only a [`PartialGuild`] will be immediately returned. - /// - /// [`Guild`]: ../model/struct.Guild.html - /// [`PartialGuild`]: ../model/struct.PartialGuild.html - pub fn delete_guild>(&self, guild_id: G) - -> Result { - rest::delete_guild(guild_id.into().0) - } - - /// Deletes an integration by Id from a guild which Id was given. - /// - /// Requires the [Manage Guild] permission. - /// - /// [Manage Guild]: ../model/permissions/constant.MANAGE_GUILD.html - pub fn delete_integration(&self, guild_id: G, integration_id: I) - -> Result<()> where G: Into, I: Into { - rest::delete_guild_integration(guild_id.into().0, - integration_id.into().0) - } - - /// Deletes the given invite. - /// - /// Refer to the documentation for [`Invite::delete`] for restrictions on - /// deleting an invite. - /// - /// Requires the [Manage Guild] permission. - /// - /// # Errors - /// - /// Returns a [`ClientError::InvalidPermissions`] if the current user does - /// not have the required [permission]. - /// - /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions - /// [`Invite::delete`]: ../model/struct.Invite.html#method.delete - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html - pub fn delete_invite(&self, invite: &str) -> Result { - rest::delete_invite(utils::parse_invite(invite)) + pub fn delete_channel(&self) -> Result { + match self.channel_id { + Some(channel_id) => channel_id.delete(), + None => Err(Error::Client(ClientError::NoChannelId)), + } } - /// Deletes a [`Message`] given its Id. - /// - /// Also see [`Message::delete`] if you have the `methods` feature enabled. + /// Deletes a [`Message`] given its Id from the contextual channel. /// - /// Requires the [Manage Messages] permission, if the current user is not - /// the author of the message. + /// Refer to [`Message::delete`] for more information. /// /// # Examples /// @@ -591,19 +276,27 @@ impl Context { /// use std::env; /// /// let client = Client::login_bot(&env::var("DISCORD_BOT_TOKEN").unwrap()); - /// client.on_message(|context, message| { - /// context.delete_message(message); + /// client.on_message(|ctx, message| { + /// ctx.delete_message(message); /// }); /// ``` /// /// (in practice, please do not do this) /// + /// # Errors + /// + /// Returns a [`ClientError::NoChannelId`] if the current context is not + /// related to a channel. + /// + /// [`ClientError::NoChannelId`]: enum.ClientError.html#variant.NoChannelId /// [`Message`]: ../model/struct.Message.html /// [`Message::delete`]: ../model/struct.Message.html#method.delete /// [Manage Messages]: ../model/permissions/constant.MANAGE_MESSAGES.html - pub fn delete_message(&self, channel_id: C, message_id: M) - -> Result<()> where C: Into, M: Into { - rest::delete_message(channel_id.into().0, message_id.into().0) + pub fn delete_message>(&self, message_id: M) -> Result<()> { + match self.channel_id { + Some(channel_id) => channel_id.delete_message(message_id), + None => Err(Error::Client(ClientError::NoChannelId)), + } } /// Deletes all messages by Ids from the given vector in the given channel. @@ -617,527 +310,208 @@ impl Context { /// /// **Note**: Messages that are older than 2 weeks can't be deleted using this method. /// - /// [Manage Messages]: ../model/permissions/constant.MANAGE_MESSAGES.html - pub fn delete_messages(&self, channel_id: C, message_ids: &[MessageId]) - -> Result<()> where C: Into { - if self.login_type == LoginType::User { - return Err(Error::Client(ClientError::InvalidOperationAsUser)) - } - - let ids = message_ids.into_iter() - .map(|message_id| message_id.0) - .collect::>(); - - let map = ObjectBuilder::new() - .insert("messages", ids) - .build(); - - rest::delete_messages(channel_id.into().0, map) - } - - /// Deletes a profile note from a user. - pub fn delete_note>(&self, user_id: U) -> Result<()> { - let map = ObjectBuilder::new() - .insert("note", "") - .build(); - - rest::edit_note(user_id.into().0, map) - } - - /// Deletes all permission overrides in a channel from a member or - /// a role. - /// - /// Requires the [Manage Channel] permission. + /// # Errors /// - /// [Manage Channel]: ../model/permissions/constant.MANAGE_CHANNELS.html - pub fn delete_permission(&self, - channel_id: C, - permission_type: PermissionOverwriteType) - -> Result<()> where C: Into { - let id = match permission_type { - PermissionOverwriteType::Member(id) => id.0, - PermissionOverwriteType::Role(id) => id.0, - }; - - rest::delete_permission(channel_id.into().0, id) - } - - - /// Deletes the given [`Reaction`]. + /// Returns a [`ClientError::NoChannelId`] if the current context is not + /// related to a channel. /// - /// **Note**: Requires the [Manage Messages] permission, _if_ the current - /// user did not perform the reaction. + /// Returns a [`ClientError::InvalidOperationAsUser`] if the current user is + /// not a bot user. /// - /// [`Reaction`]: ../model/struct.Reaction.html + /// [`ClientError::InvalidOperationAsUser`]: enum.ClientError.html#variant.InvalidOperationAsUser + /// [`ClientError::NoChannelId`]: enum.ClientError.html#variant.NoChannelId /// [Manage Messages]: ../model/permissions/constant.MANAGE_MESSAGES.html - pub fn delete_reaction(&self, - channel_id: C, - message_id: M, - user_id: Option, - reaction_type: R) - -> Result<()> - where C: Into, - M: Into, - R: Into { - rest::delete_reaction(channel_id.into().0, - message_id.into().0, - user_id.map(|uid| uid.0), - reaction_type.into()) - } - - /// Deletes a [`Role`] by Id from the given [`Guild`]. - /// - /// Also see [`Role::delete`] if you have the `cache` and `methods` features - /// enabled. - /// - /// Requires the [Manage Roles] permission. - /// - /// [`Guild`]: ../model/struct.Guild.html - /// [`Role`]: ../model/struct.Role.html - /// [`Role::delete`]: ../model/struct.Role.html#method.delete - /// [Manage Roles]: ../model/permissions/constant.MANAGE_ROLES.html - pub fn delete_role(&self, guild_id: G, role_id: R) -> Result<()> - where G: Into, R: Into { - rest::delete_role(guild_id.into().0, role_id.into().0) - } - - /// Sends a message to a user through a direct message channel. This is a - /// channel that can only be accessed by you and the recipient. - /// - /// # Examples - /// - /// There are three ways to send a direct message to someone, the first - /// being an unrelated, although equally helpful method. - /// - /// Sending a message via [`User::dm`]: - /// - /// ```rust,ignore - /// // assuming you are in a context - /// let _ = context.message.author.dm("Hello!"); - /// ``` - /// - /// Sending a message to a `PrivateChannel`: - /// - /// ```rust,ignore - /// assuming you are in a context - /// let private_channel = context.create_private_channel(context.message.author.id); - /// - /// let _ = context.direct_message(private_channel, "Test!"); - /// ``` - /// - /// Sending a message to a `PrivateChannel` given its ID: - /// - /// ```rust,ignore - /// use serenity::Client; - /// use std::env; - /// - /// let mut client = Client::login_bot(&env::var("DISCORD_BOT_TOKEN").unwrap()); - /// - /// client.on_message(|context, message| { - /// if message.content == "!pm-me" { - /// let channel = context.create_private_channel(message.author.id) - /// .unwrap(); - /// - /// let _ = channel.send_message("test!"); - /// } - /// }); - /// ``` - /// - /// [`PrivateChannel`]: ../model/struct.PrivateChannel.html - /// [`User::dm`]: ../model/struct.User.html#method.dm - pub fn dm>(&self, target_id: C, content: &str) - -> Result { - self.send_message(target_id.into(), |m| m.content(content)) - } - - /// Edits the settings of a [`Channel`], optionally setting new values. - /// - /// Refer to `EditChannel`'s documentation for its methods. - /// - /// Requires the [Manage Channel] permission. - /// - /// # Examples - /// - /// Change a voice channel's name and bitrate: - /// - /// ```rust,ignore - /// context.edit_channel(channel_id, |c| c - /// .name("test") - /// .bitrate(64000)); - /// ``` - /// - /// [`Channel`]: ../model/enum.Channel.html - pub fn edit_channel(&self, channel_id: C, f: F) - -> Result where C: Into, - F: FnOnce(EditChannel) -> EditChannel { - let channel_id = channel_id.into(); - - let map = match self.get_channel(channel_id)? { - Channel::Guild(channel) => { - let map = ObjectBuilder::new() - .insert("name", channel.name) - .insert("position", channel.position); - - match channel.kind { - ChannelType::Text => map.insert("topic", channel.topic), - ChannelType::Voice => { - map.insert("bitrate", channel.bitrate) - .insert("user_limit", channel.user_limit) - }, - kind => return Err(Error::Client(ClientError::UnexpectedChannelType(kind))), - } - }, - Channel::Private(channel) => { - return Err(Error::Client(ClientError::UnexpectedChannelType(channel.kind))); - }, - Channel::Group(_group) => { - return Err(Error::Client(ClientError::UnexpectedChannelType(ChannelType::Group))); - }, - }; - - let edited = f(EditChannel(map)).0.build(); - - rest::edit_channel(channel_id.0, edited) - } - - /// Edits an [`Emoji`]'s name. - /// - /// Also see [`Emoji::edit`] if you have the `cache` and `methods` features - /// enabled. - /// - /// Requires the [Manage Emojis] permission. - /// - /// [`Emoji`]: ../model/struct.Emoji.html - /// [`Emoji::edit`]: ../model/struct.Emoji.html#method.edit - /// [Manage Emojis]: ../model/permissions/constant.MANAGE_EMOJIS.html - pub fn edit_emoji(&self, guild_id: G, emoji_id: E, name: &str) - -> Result where E: Into, G: Into { - let map = ObjectBuilder::new() - .insert("name", name) - .build(); - - rest::edit_emoji(guild_id.into().0, emoji_id.into().0, map) - } - - /// Edits the settings of a [`Guild`], optionally setting new values. - /// - /// Refer to `EditGuild`'s documentation for a full list of methods. - /// - /// Also see [`Guild::edit`] if you have the `methods` feature enabled. - /// - /// Requires the [Manage Guild] permission. - /// - /// # Examples - /// - /// Change a guild's icon using a file name "icon.png": - /// - /// ```rust,ignore - /// use serenity::utils; - /// - /// // We are using read_image helper function from utils. - /// let base64_icon = utils::read_image("./icon.png") - /// .expect("Failed to read image"); - /// - /// context.edit_guild(guild_id, |g| - /// g.icon(base64_icon)); - /// ``` - /// - /// [`Guild`]: ../model/struct.Guild.html - /// [`Guild::edit`]: ../model/struct.Guild.html - /// [Manage Guild]: ../model/permissions/constant.MANAGE_GUILD.html - pub fn edit_guild(&self, guild_id: G, f: F) -> Result - where F: FnOnce(EditGuild) -> EditGuild, G: Into { - let map = f(EditGuild::default()).0.build(); + pub fn delete_messages(&self, message_ids: &[MessageId]) -> Result<()> { + if self.login_type == LoginType::User { + return Err(Error::Client(ClientError::InvalidOperationAsUser)) + } - rest::edit_guild(guild_id.into().0, map) + match self.channel_id { + Some(channel_id) => channel_id.delete_messages(message_ids), + None => Err(Error::Client(ClientError::NoChannelId)), + } } - /// Edits the properties of member of a guild, such as muting or nicknaming - /// them. - /// - /// Refer to `EditMember`'s documentation for a full list of methods and - /// permission restrictions. + /// Deletes all permission overrides in the contextual channel from a member + /// or role. /// - /// # Examples - /// - /// Mute a member and set their roles to just one role with a predefined Id: + /// **Note**: Requires the [Manage Channel] permission. /// - /// ```rust,ignore - /// context.edit_member(guild_id, user_id, |m| m - /// .mute(true) - /// .roles(&vec![role_id])); - /// ``` - pub fn edit_member(&self, guild_id: G, user_id: U, f: F) - -> Result<()> where F: FnOnce(EditMember) -> EditMember, - G: Into, - U: Into { - let map = f(EditMember::default()).0.build(); - - rest::edit_member(guild_id.into().0, user_id.into().0, map) - } - - /// Edits the current user's nickname for the provided [`Guild`] via its Id. - /// - /// Pass `None` to reset the nickname. + /// # Errors /// - /// Requires the [Change Nickname] permission. + /// Returns a [`ClientError::NoChannelId`] if the current context is not + /// related to a channel. /// - /// [`Guild`]: ../../model/struct.Guild.html - /// [Change Nickname]: permissions/constant.CHANGE_NICKNAME.html + /// [`ClientError::NoChannelId`]: enum.ClientError.html#variant.NoChannelId + /// [Manage Channel]: ../model/permissions/constant.MANAGE_CHANNELS.html #[inline] - pub fn edit_nickname(&self, guild_id: G, new_nickname: Option<&str>) - -> Result<()> where G: Into { - rest::edit_nickname(guild_id.into().0, new_nickname) + pub fn delete_permission(&self, permission_type: PermissionOverwriteType) -> Result<()> { + match self.channel_id { + Some(channel_id) => channel_id.delete_permission(permission_type), + None => Err(Error::Client(ClientError::NoChannelId)), + } } - /// Edits the current user's profile settings. + /// Deletes the given [`Reaction`] from the contextual channel. /// - /// Refer to `EditProfile`'s documentation for its methods. + /// **Note**: Requires the [Manage Messages] permission, _if_ the current + /// user did not perform the reaction. /// - /// # Examples + /// # Errors /// - /// Change the current user's username: + /// Returns a [`ClientError::NoChannelId`] if the current context is not + /// related to a channel. /// - /// ```rust,ignore - /// context.edit_profile(|p| p.username("Hakase")); - /// ``` - pub fn edit_profile EditProfile>(&self, f: F) - -> Result { - let user = rest::get_current_user()?; - - let mut map = ObjectBuilder::new() - .insert("avatar", user.avatar) - .insert("username", user.name); - - if let Some(email) = user.email.as_ref() { - map = map.insert("email", email); + /// [`ClientError::NoChannelId`]: enum.ClientError.html#variant.NoChannelId + /// [`Reaction`]: ../model/struct.Reaction.html + /// [Manage Messages]: ../model/permissions/constant.MANAGE_MESSAGES.html + pub fn delete_reaction(&self, message_id: M, user_id: Option, reaction_type: R) + -> Result<()> where M: Into, R: Into { + match self.channel_id { + Some(channel_id) => channel_id.delete_reaction(message_id, user_id, reaction_type), + None => Err(Error::Client(ClientError::NoChannelId)), } - - let edited = f(EditProfile(map)).0.build(); - - rest::edit_profile(edited) } - /// Edits a [`Role`], optionally setting its new fields. + /// Edits the settings of a [`Channel`], optionally setting new values. + /// + /// Refer to `EditChannel`'s documentation for its methods. /// - /// Requires the [Manage Roles] permission. + /// Requires the [Manage Channel] permission. /// /// # Examples /// - /// Make a role hoisted: + /// Change a voice channel's name and bitrate: /// /// ```rust,ignore - /// context.edit_role(guild_id, role_id, |r| r - /// .hoist(true)); + /// context.edit_channel(channel_id, |c| c + /// .name("test") + /// .bitrate(64000)); /// ``` /// - /// [`Role`]: ../model/struct.Role.html - /// [Manage Roles]: ../model/permissions/constant.MANAGE_ROLES.html - pub fn edit_role(&self, guild_id: G, role_id: R, f: F) - -> Result where F: FnOnce(EditRole) -> EditRole, - G: Into, - R: Into { - let guild_id = guild_id.into(); - let role_id = role_id.into(); - - let map = feature_cache! {{ - let cache = CACHE.read().unwrap(); - - let role = if let Some(role) = { - cache.get_role(guild_id.0, role_id.0) - } { - role - } else { - return Err(Error::Client(ClientError::RecordNotFound)); - }; - - f(EditRole::new(role)).0.build() - } else { - f(EditRole::default()).0.build() - }}; - - rest::edit_role(guild_id.0, role_id.0, map) - } - - /// Edits a [`Message`] given its Id and the Id of the channel it belongs - /// to. - /// - /// Pass an empty string (`""`) to `text` if you are editing a message with - /// an embed or file but no content. Otherwise, `text` must be given. + /// # Errors /// - /// **Note**: Requires that the current user be the author of the message. + /// Returns a [`ClientError::NoChannelId`] if the current context is not + /// related to a guild channel. /// - /// [`Message`]: ../model/struct.Message.html - pub fn edit_message(&self, channel_id: C, message_id: M, text: &str, f: F) - -> Result where C: Into, - F: FnOnce(CreateEmbed) -> CreateEmbed, - M: Into { - let mut map = ObjectBuilder::new() - .insert("content", text); + /// [`Channel`]: ../model/enum.Channel.html + /// [`ClientError::NoChannelId`]: enum.ClientError.html#variant.NoChannelId + pub fn edit_channel(&self, f: F) -> Result + where F: FnOnce(EditChannel) -> EditChannel { + let channel_id = match self.channel_id { + Some(channel_id) => channel_id, + None => return Err(Error::Client(ClientError::NoChannelId)), + }; - let embed = f(CreateEmbed::default()).0; + #[cfg(feature="cache")] + { - if embed.len() > 1 { - map = map.insert("embed", Value::Object(embed)); + match channel_id.get()? { + Channel::Guild(ref c) if c.kind != ChannelType::Text && + c.kind != ChannelType::Voice => { + return Err(Error::Client(ClientError::UnexpectedChannelType(c.kind))); + }, + _ => {}, + } } - rest::edit_message(channel_id.into().0, message_id.into().0, map.build()) + channel_id.edit(f) } - /// Edits the note that the current user has set for another user. - /// - /// Use [`delete_note`] to remove a note. + /// Edits the current user's profile settings. /// - /// **Note**: Requires that the current user be a user account. + /// Refer to `EditProfile`'s documentation for its methods. /// /// # Examples /// - /// Set a note for a message's author: + /// Change the current user's username: /// /// ```rust,ignore - /// // assuming a `message` has been bound - /// let _ = context.edit_note(message.author, "test note"); + /// context.edit_profile(|p| p.username("Hakase")); /// ``` - /// - /// # Errors - /// - /// Returns a [`ClientError::InvalidOperationAsBot`] if the current user is - /// a bot user. - /// - /// [`ClientError::InvalidOperationAsBot`]: enum.ClientError.html#variant.InvalidOperationAsBot - /// [`delete_note`]: #method.delete_note - pub fn edit_note>(&self, user_id: U, note: &str) - -> Result<()> { - let map = ObjectBuilder::new() - .insert("note", note) - .build(); - - rest::edit_note(user_id.into().0, map) - } - - /// Gets a list of the given [`Guild`]'s bans. - /// - /// Requires the [Ban Members] permission. - /// - /// [`Guild`]: ../model/struct.Guild.html - /// [Ban Members]: ../model/permissions/constant.BAN_MEMBERS.html - pub fn get_bans>(&self, guild_id: G) -> Result> { - rest::get_bans(guild_id.into().0) - } + pub fn edit_profile EditProfile>(&self, f: F) -> Result { + let mut map = ObjectBuilder::new(); - /// Gets all of a [`GuildChannel`]'s invites. - /// - /// Requires the [Manage Guild] permission. - /// - /// [`GuildChannel`]: ../model/struct.GuildChannel.html - /// [Manage Guild]: ../model/permissions/constant.MANAGE_GUILD.html - pub fn get_channel_invites>(&self, channel_id: C) - -> Result> { - rest::get_channel_invites(channel_id.into().0) - } + feature_cache! {{ + let cache = CACHE.read().unwrap(); - /// Gets a `Channel` by the given Id. - pub fn get_channel(&self, channel_id: C) -> Result - where C: Into { - let channel_id = channel_id.into(); + map = map.insert("avatar", &cache.user.avatar) + .insert("username", &cache.user.name); - #[cfg(feature="cache")] - { - if let Some(channel) = CACHE.read().unwrap().get_channel(channel_id) { - return Ok(channel.clone_inner()); + if let Some(email) = cache.user.email.as_ref() { + map = map.insert("email", email); } - } - - rest::get_channel(channel_id.0) - } + } else { + let user = rest::get_current_user()?; - /// Gets all of a [`Guild`]'s channels with given Id. - /// - /// [`Guild`]: ../model/struct.Guild.html - pub fn get_channels(&self, guild_id: G) - -> Result> where G: Into { - let guild_id = guild_id.into(); + map = map.insert("avatar", user.avatar) + .insert("username", user.name); - #[cfg(feature="cache")] - { - if let Some(guild) = CACHE.read().unwrap().get_guild(guild_id) { - return Ok(guild.channels.clone()); + if let Some(email) = user.email.as_ref() { + map = map.insert("email", email); } - } - - let mut channels = HashMap::new(); + }} - for channel in rest::get_channels(guild_id.0)? { - channels.insert(channel.id, channel); - } + let edited = f(EditProfile(map)).0.build(); - Ok(channels) + rest::edit_profile(edited) } - /// Gets information about the current user. + /// Edits a [`Message`] given its Id and the Id of the channel it belongs + /// to. /// - /// Note this is shorthand for retrieving the current user through the - /// cache, and will perform a clone. - #[cfg(all(feature = "cache", feature = "methods"))] - pub fn get_current_user(&self) -> CurrentUser { - CACHE.read().unwrap().user.clone() - } - - /// Gets an [`Guild`]'s emoji by Id. + /// Refer to [`Channel::edit_message`] for more information. + /// + /// **Note**: Requires that the current user be the author of the message. + /// + /// # Errors /// - /// Requires the [Manage Emojis] permission. + /// Returns a [`ClientError::NoChannelId`] if the current context is not + /// related to a channel. /// - /// [`Guild`]: ../model/struct.Guild.html - /// [Manage Emojis]: ../model/permissions/constant.MANAGE_EMOJIS.html - pub fn get_emoji(&self, guild_id: G, emoji_id: E) -> Result - where E: Into, G: Into { - rest::get_emoji(guild_id.into().0, emoji_id.into().0) + /// [`Channel::edit_message`]: ../model/enum.Channel.html#method.edit_message + /// [`ClientError::NoChannelId`]: enum.ClientError.html#variant.NoChannelId + /// [`Message`]: ../model/struct.Message.html + pub fn edit_message(&self, message_id: M, text: &str, f: F) -> Result + where F: FnOnce(CreateEmbed) -> CreateEmbed, M: Into { + match self.channel_id { + Some(channel_id) => channel_id.edit_message(message_id, text, f), + None => Err(Error::Client(ClientError::NoChannelId)), + } } - /// Gets a list of all of a [`Guild`]'s emojis. + /// Gets a fresh version of the channel over the REST API. /// - /// Requires the [Manage Emojis] permission. + /// # Errors /// - /// [`Guild`]: ../model/struct.Guild.html - /// [Manage Emojis]: ../model/permissions/constant.MANAGE_EMOJIS.html - pub fn get_emojis>(&self, guild_id: G) - -> Result> { - rest::get_emojis(guild_id.into().0) - } - - /// Gets a partial amount of guild data by its Id. + /// Returns a [`ClientError::NoChannelId`] if the current context is not + /// related to a channel. /// - /// Requires that the current user be in the guild. - pub fn get_guild>(&self, guild_id: G) - -> Result { - rest::get_guild(guild_id.into().0) + /// [`ClientError::NoChannelId`]: enum.ClientError.html#variant.NoChannelId + pub fn get_channel(&self) -> Result { + match self.channel_id { + Some(channel_id) => channel_id.get(), + None => Err(Error::Client(ClientError::NoChannelId)), + } } - /// Gets all of a guild's invites. + /// Gets all of a [`GuildChannel`]'s invites. /// /// Requires the [Manage Guild] permission. /// - /// [`RichInvite`]: ../model/struct.RichInvite.html - /// [Manage Guild]: ../model/permissions/struct.MANAGE_GUILD.html - pub fn get_guild_invites(&self, guild_id: G) -> Result> - where G: Into { - rest::get_guild_invites(guild_id.into().0) - } - - /// Gets the number of [`Member`]s that would be pruned with the given - /// number of days. + /// # Errors /// - /// Requires the [Kick Members] permission. + /// Returns a [`ClientError::NoChannelId`] if the current context is not + /// related to a channel. /// - /// [`Member`]: ../model/struct.Member.html - /// [Kick Members]: ../model/permissions/constant.KICK_MEMBERS.html - pub fn get_guild_prune_count(&self, guild_id: G, days: u16) - -> Result where G: Into { - let map = ObjectBuilder::new() - .insert("days", days) - .build(); - - rest::get_guild_prune_count(guild_id.into().0, map) + /// [`ClientError::NoChannelId`]: enum.ClientError.html#variant.NoChannelId + /// [`GuildChannel`]: ../model/struct.GuildChannel.html + /// [Manage Guild]: ../model/permissions/constant.MANAGE_GUILD.html + pub fn get_channel_invites(&self) -> Result> { + match self.channel_id { + Some(channel_id) => channel_id.get_invites(), + None => Err(Error::Client(ClientError::NoChannelId)), + } } /// Gets a paginated list of guilds that the current user is in. @@ -1161,63 +535,12 @@ impl Context { /// /// [`CurrentUser::guilds`]: ../model/struct.CurrentUser.html#method.guilds /// [`Message`]: ../model/struct.Message.html + #[inline] pub fn get_guilds(&self, target: GuildPagination, limit: u8) -> Result> { rest::get_guilds(target, limit as u64) } - /// Gets all integrations of a guild via the given Id. - pub fn get_integrations>(&self, guild_id: G) - -> Result> { - rest::get_guild_integrations(guild_id.into().0) - } - - /// Gets the information about an invite. - pub fn get_invite(&self, invite: &str) -> Result { - let code = utils::parse_invite(invite); - - rest::get_invite(code) - } - - /// Gets a user's [`Member`] instance for a [`Guild`], given by Id. - /// - /// If the `cache` feature is enabled, then the instance will be cloned from - /// the cache if it exists. - /// - /// [`Guild`]: ../model/struct.Guild.html - /// [`Member`]: ../model/struct.Member.html - pub fn get_member(&self, guild_id: G, user_id: U) -> Result - where G: Into, U: Into { - let guild_id = guild_id.into(); - let user_id = user_id.into(); - - #[cfg(feature="cache")] - { - let cache = CACHE.read().unwrap(); - - if let Some(member) = cache.get_member(guild_id, user_id) { - return Ok(member.clone()); - } - } - - rest::get_member(guild_id.0, user_id.0) - } - - /// Gets a list of a [`Guild`]'s members. - /// - /// Optionally pass in the `limit` to limit the number of results. Maximum - /// value is 1000. Optionally pass in `after` to offset the results by a - /// [`User`]'s Id. - /// - /// [`Guild`]: ../model/struct.Guild.html - /// [`User`]: ../model/struct.User.html - pub fn get_members(&self, guild_id: G, limit: Option, after: Option) - -> Result> where G: Into, U: Into { - rest::get_guild_members(guild_id.into().0, - limit, - after.map(|x| x.into().0)) - } - - /// Gets a single [`Message`] from a [`Channel`]. + /// Gets a single [`Message`] from the contextual channel. /// /// Requires the [Read Message History] permission. /// @@ -1226,50 +549,22 @@ impl Context { /// Returns a [`ClientError::InvalidOperationAsUser`] if the current user is /// not a user account. /// - /// [`Channel`]: ../model/struct.Channel.html - /// [`ClientError::InvalidOperationAsUser`]: ../enum.ClientError.html#variant.InvalidOperationAsUser + /// Returns a [`ClientError::NoChannelId`] if the current context is not + /// related to a channel. + /// + /// [`ClientError::InvalidOperationAsUser`]: enum.ClientError.html#variant.InvalidOperationAsUser + /// [`ClientError::NoChannelId`]: enum.ClientError.html#variant.NoChannelId /// [`Message`]: ../model/struct.Message.html /// [Read Message History]: ../model/permissions/constant.READ_MESSAGE_HISTORY.html - pub fn get_message(&self, channel_id: C, message_id: M) - -> Result where C: Into, M: Into { + pub fn get_message>(&self, message_id: M) -> Result { if self.login_type == LoginType::User { return Err(Error::Client(ClientError::InvalidOperationAsUser)) } - rest::get_message(channel_id.into().0, message_id.into().0) - } - - /// Gets messages from a specific channel. - /// - /// Requires the [Read Message History] permission. - /// - /// # Examples - /// - /// ```rust,ignore - /// let role = context.get_messages(channel_id, |g| g - /// .before(20) - /// .after(100)); // Maximum is 100. - /// ``` - /// - /// [Read Message History]: ../model/permission/constant.READ_MESSAGE_HISTORY.html - pub fn get_messages(&self, channel_id: C, f: F) -> Result> - where C: Into, F: FnOnce(GetMessages) -> GetMessages { - let mut map = f(GetMessages::default()).0; - let mut query = format!("?limit={}", map.remove("limit").unwrap_or(50)); - - if let Some(after) = map.remove("after") { - write!(query, "&after={}", after)?; - } - - if let Some(around) = map.remove("around") { - write!(query, "&around={}", around)?; + match self.channel_id { + Some(channel_id) => channel_id.get_message(message_id), + None => Err(Error::Client(ClientError::NoChannelId)), } - - if let Some(before) = map.remove("before") { - write!(query, "&before={}", before)?; - } - - rest::get_messages(channel_id.into().0, &query) } /// Gets the list of [`User`]s who have reacted to a [`Message`] with a @@ -1284,86 +579,46 @@ impl Context { /// /// **Note**: Requires the [Read Message History] permission. /// + /// # Errors + /// + /// Returns a [`ClientError::NoChannelId`] if the current context is not + /// related to a channel. + /// + /// [`ClientError::NoChannelId`]: enum.ClientError.html#variant.NoChannelId /// [`Emoji`]: struct.Emoji.html /// [`Message`]: struct.Message.html /// [`User`]: struct.User.html /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html - pub fn get_reaction_users(&self, - channel_id: C, - message_id: M, - reaction_type: R, - limit: Option, - after: Option) - -> Result> - where C: Into, - M: Into, - R: Into, - U: Into { - let limit = limit.map_or(50, |x| if x > 100 { 100 } else { x }); - - rest::get_reaction_users(channel_id.into().0, - message_id.into().0, - reaction_type.into(), - limit, - after.map(|u| u.into().0)) + pub fn get_reaction_users(&self, + message_id: M, + reaction_type: R, + limit: Option, + after: Option) + -> Result> where M: Into, R: Into, U: Into { + match self.channel_id { + Some(c) => c.get_reaction_users(message_id, reaction_type, limit, after), + None => Err(Error::Client(ClientError::NoChannelId)), + } } - /// Gets a [`User`] by its Id. + /// Pins a [`Message`] in the specified [`Channel`] by its Id. /// - /// [`User`]: ../model/struct.User.html + /// Requires the [Manage Messages] permission. /// /// # Errors /// - /// Returns a [`ClientError::InvalidOperationAsUser`] if the current user is - /// not a bot user. + /// Returns a [`ClientError::NoChannelId`] if the current context is not + /// related to a channel. /// - /// [`ClientError::InvalidOperationAsUser`]: enum.ClientError.html#variant.InvalidOperationAsUser - #[inline] - pub fn get_user>(&self, user_id: U) -> Result { - #[cfg(feature="cache")] - { - if !CACHE.read().unwrap().user.bot { - return Err(Error::Client(ClientError::InvalidOperationAsUser)); - } + /// [`Channel`]: ../model/enum.Channel.html + /// [`ClientError::NoChannelId`]: enum.ClientError.html#variant.NoChannelId + /// [`Message`]: ../model/struct.Message.html + /// [Manage Messages]: ../model/permissions/constant.MANAGE_MESSAGES.html + pub fn pin>(&self, message_id: M) -> Result<()> { + match self.channel_id { + Some(channel_id) => channel_id.pin(message_id), + None => Err(Error::Client(ClientError::NoChannelId)), } - - rest::get_user(user_id.into().0) - } - - /// Kicks a [`Member`] from the specified [`Guild`] if they are in it. - /// - /// Requires the [Kick Members] permission. - /// - /// [`Guild`]: ../model/struct.Guild.html - /// [`Member`]: ../model/struct.Member.html - /// [Kick Members]: ../model/permissions/constant.KICK_MEMBERS.html - pub fn kick_member(&self, guild_id: G, user_id: U) -> Result<()> - where G: Into, U: Into { - rest::kick_member(guild_id.into().0, user_id.into().0) - } - - /// Leaves a [`Guild`] by its Id. - /// - /// [`Guild`]: ../model/struct.Guild.html - pub fn leave_guild>(&self, guild_id: G) - -> Result { - rest::leave_guild(guild_id.into().0) - } - - /// Moves a member to a specific voice channel. - /// - /// Requires the [Move Members] permission. - /// - /// [Move Members]: ../model/permissions/constant.MOVE_MEMBERS.html - pub fn move_member(&self, guild_id: G, user_id: U, channel_id: C) - -> Result<()> where C: Into, - G: Into, - U: Into { - let map = ObjectBuilder::new() - .insert("channel_id", channel_id.into().0) - .build(); - - rest::edit_member(guild_id.into().0, user_id.into().0, map) } /// Gets the list of [`Message`]s which are pinned to the specified @@ -1371,22 +626,11 @@ impl Context { /// /// [`Channel`]: ../model/enum.Channel.html /// [`Message`]: ../model/struct.Message.html - pub fn get_pins(&self, channel_id: C) -> Result> - where C: Into { - rest::get_pins(channel_id.into().0) - } - - /// Pins a [`Message`] in the specified [`Channel`] by its Id. - /// - /// Requires the [Manage Messages] permission. - /// - /// [`Channel`]: ../model/enum.Channel.html - /// [`Message`]: ../model/struct.Message.html - /// - /// [Manage Messages]: ../model/permissions/constant.MANAGE_MESSAGES.html - pub fn pin(&self, channel_id: C, message_id: M) -> Result<()> - where C: Into, M: Into { - rest::pin_message(channel_id.into().0, message_id.into().0) + pub fn pins(&self) -> Result> { + match self.channel_id { + Some(channel_id) => channel_id.pins(), + None => Err(Error::Client(ClientError::NoChannelId)), + } } /// Sends a message with just the given message content in the channel that @@ -1423,7 +667,7 @@ impl Context { /// /// [`ChannelId`]: ../../model/struct.ChannelId.html /// [`ClientError::MessageTooLong`]: enum.ClientError.html#variant.MessageTooLong - /// [`ClientError::NoChannelId`]: ../enum.ClientError.html#NoChannelId + /// [`ClientError::NoChannelId`]: enum.ClientError.html#NoChannelId /// [`Event::ChannelCreate`]: ../model/event/enum.Event.html#variant.ChannelCreate /// [`Event::ChannelPinsAck`]: ../model/event/enum.Event.html#variant.ChannelPinsAck /// [`Event::ChannelPinsUpdate`]: ../model/event/enum.Event.html#variant.ChannelPinsUpdate @@ -1439,10 +683,9 @@ impl Context { /// [`Event::ReactionRemoveAll`]: ../model/event/enum.Event.html#variant.ReactionRemoveAll /// [`Message`]: ../model/struct.Message.html pub fn say(&self, content: &str) -> Result { - if let Some(channel_id) = self.channel_id { - self.send_message(channel_id, |m| m.content(content)) - } else { - Err(Error::Client(ClientError::NoChannelId)) + match self.channel_id { + Some(channel_id) => channel_id.send_message(|m| m.content(content)), + None => Err(Error::Client(ClientError::NoChannelId)), } } @@ -1478,55 +721,17 @@ impl Context { /// If the `cache` is enabled, returns a /// [`ClientError::InvalidOperationAsBot`] if the current user is a bot. /// - /// [`ClientError::InvalidOperationAsBot`]: ../client/enum.ClientError.html#variant.InvalidOperationAsBot + /// [`ClientError::InvalidOperationAsBot`]: enum.ClientError.html#variant.InvalidOperationAsBot /// [`Channel`]: ../model/enum.Channel.html /// [`Search`]: ../utils/builder/struct.Search.html - /// [search channel]: ../../utils/builder/struct.Search.html#searching-a-channel - pub fn search_channel(&self, channel_id: C, f: F) - -> Result where C: Into, - F: FnOnce(Search) -> Search { - #[cfg(feature="cache")] - { - if CACHE.read().unwrap().user.bot { - return Err(Error::Client(ClientError::InvalidOperationAsBot)); - } - } - - let map = f(Search::default()).0; - - rest::search_channel_messages(channel_id.into().0, map) - } + /// [search channel]: ../utils/builder/struct.Search.html#searching-a-channel + pub fn search_channel(&self, f: F) -> Result + where F: FnOnce(Search) -> Search { + let channel_id = match self.channel_id { + Some(channel_id) => channel_id, + None => return Err(Error::Client(ClientError::NoChannelId)), + }; - /// Searches a [`Guild`]'s messages by providing query parameters via the - /// search builder, with the ability to narrow down channels to search. - /// - /// Refer to the documentation for the [`Search`] builder for restrictions - /// and default parameters, as well as potentially advanced usage. - /// - /// **Note**: Bot users can not search. - /// - /// # Examples - /// - /// Refer to the [`Search`] builder's documentation for more examples, - /// specifically the section on - /// [searching a guild's channels][search guild]. - /// - /// # Errors - /// - /// If the `cache` is enabled, returns a - /// [`ClientError::InvalidOperationAsBot`] if the current user is a bot. - /// - /// [`ClientError::InvalidOperationAsBot`]: ../client/enum.ClientError.html#variant.InvalidOperationAsBot - /// [`Guild`]: ../model/struct.Guild.html - /// [`Search`]: ../utils/builder/struct.Search.html - /// [search guild]: ../../utils/builder/struct.Search.html#searching-a-guilds-channels - pub fn search_guild(&self, - guild_id: G, - channel_ids: Vec, - f: F) - -> Result - where F: FnOnce(Search) -> Search, - G: Into { #[cfg(feature="cache")] { if CACHE.read().unwrap().user.bot { @@ -1534,10 +739,7 @@ impl Context { } } - let map = f(Search::default()).0; - let ids = channel_ids.iter().map(|ch| ch.0).collect::>(); - - rest::search_guild_messages(guild_id.into().0, &ids, map) + channel_id.search(f) } /// Sends a file along with optional message contents. The filename _must_ @@ -1564,23 +766,12 @@ impl Context { /// [`GuildChannel`]: ../model/struct.GuildChannel.html /// [Attach Files]: ../model/permissions/constant.ATTACH_FILES.html /// [Send Messages]: ../model/permissions/constant.SEND_MESSAGES.html - pub fn send_file(&self, channel_id: C, file: R, filename: &str, f: F) - -> Result where C: Into, - F: FnOnce(CreateMessage) -> CreateMessage, - R: Read { - let mut map = f(CreateMessage::default()).0; - - if let Some(content) = map.get("content") { - if let Value::String(ref content) = *content { - if let Some(length_over) = Message::overflow_length(content) { - return Err(Error::Client(ClientError::MessageTooLong(length_over))); - } - } + pub fn send_file(&self, file: R, filename: &str, f: F) -> Result + where F: FnOnce(CreateMessage) -> CreateMessage, R: Read { + match self.channel_id { + Some(channel_id) => channel_id.send_file(file, filename, f), + None => Err(Error::Client(ClientError::NoChannelId)), } - - let _ = map.remove("embed"); - - rest::send_file(channel_id.into().0, file, filename, map) } /// Sends a message to a [`Channel`]. @@ -1692,19 +883,12 @@ impl Context { /// [Send Messages]: ../model/permissions/constant.SEND_MESSAGES.html /// [author structure]: ../utils/builder/struct.CreateEmbedAuthor.html /// [field structure]: ../utils/builder/struct.CreateEmbedField.html - pub fn send_message(&self, channel_id: C, f: F) -> Result - where C: Into, F: FnOnce(CreateMessage) -> CreateMessage { - let map = f(CreateMessage::default()).0; - - if let Some(content) = map.get(&"content".to_owned()) { - if let Value::String(ref content) = *content { - if let Some(length_over) = Message::overflow_length(content) { - return Err(Error::Client(ClientError::MessageTooLong(length_over))); - } - } + pub fn send_message(&self, f: F) -> Result + where F: FnOnce(CreateMessage) -> CreateMessage { + match self.channel_id { + Some(channel_id) => channel_id.send_message(f), + None => Err(Error::Client(ClientError::NoChannelId)), } - - rest::send_message(channel_id.into().0, Value::Object(map)) } /// Sets the current user as being [`Online`]. This maintains the current @@ -1841,58 +1025,18 @@ impl Context { .set_presence(game, status, afk) } - /// Deletes an undefined amount of members from the given guild - /// based on the amount of days they've been offline for. - /// - /// **Note**: This will trigger [`GuildMemberRemove`] events. - /// - /// **Note**: Requires the [Kick Members] permission. - /// - /// [`GuildMemberRemove`]: ../model/event/enum.Event.html#variant.GuildMemberRemove - /// [Kick Members]: ../model/permissions/constant.KICK_MEMBERS.html - pub fn start_guild_prune(&self, guild_id: G, days: u16) - -> Result where G: Into { - let map = ObjectBuilder::new() - .insert("days", days) - .build(); - - rest::start_guild_prune(guild_id.into().0, map) - } - - /// Starts integration synchronization by the given integration Id. - /// - /// Requires the [Manage Guild] permission. - /// - /// [Manage Guild]: ../model/permissions/constant.MANAGE_GUILD.html - pub fn start_integration_sync(&self, guild_id: G, integration_id: I) - -> Result<()> where G: Into, I: Into { - rest::start_integration_sync(guild_id.into().0, integration_id.into().0) - } - - /// Unbans a [`User`] from a [`Guild`]. - /// - /// Requires the [Ban Members] permission. - /// - /// [`Guild`]: ../model/struct.Guild.html - /// [`User`]: ../model/struct.User.html - /// [Ban Members]: ../model/permissions/constant.BAN_MEMBERS.html - pub fn unban(&self, guild_id: G, user_id: U) -> Result<()> - where G: Into, U: Into { - rest::remove_ban(guild_id.into().0, user_id.into().0) - } - - - /// Unpins a [`Message`] in the specified [`Channel`] given each Id. + /// Unpins a [`Message`] in the contextual channel given by its Id. /// /// Requires the [Manage Messages] permission. /// /// [`Channel`]: ../model/enum.Channel.html /// [`Message`]: ../model/struct.Message.html - /// /// [Manage Messages]: ../model/permissions/constant.MANAGE_MESSAGES.html - pub fn unpin(&self, channel_id: C, message_id: M) -> Result<()> - where C: Into, M: Into { - rest::unpin_message(channel_id.into().0, message_id.into().0) + pub fn unpin>(&self, message_id: M) -> Result<()> { + match self.channel_id { + Some(channel_id) => channel_id.unpin(message_id), + None => Err(Error::Client(ClientError::NoChannelId)), + } } } diff --git a/src/client/rest/mod.rs b/src/client/rest/mod.rs index 43f8f8c5bfe..fd4cc2f1e4f 100644 --- a/src/client/rest/mod.rs +++ b/src/client/rest/mod.rs @@ -517,7 +517,7 @@ pub fn delete_reaction(channel_id: u64, user_id: Option, reaction_type: ReactionType) -> Result<()> { - let user = user_id.map(|uid| uid.to_string()).unwrap_or("@me".to_string()); + let user = user_id.map(|uid| uid.to_string()).unwrap_or_else(|| "@me".to_string()); verify(204, request!(Route::ChannelsIdMessagesIdReactionsUserIdType(channel_id), delete, @@ -702,7 +702,6 @@ pub fn edit_note(user_id: u64, map: Value) -> Result<()> { /// **Note**: this token change may cause requests made between the actual token /// change and when the token is internally changed to be invalid requests, as /// the token may be outdated. -/// pub fn edit_profile(map: Value) -> Result { let body = serde_json::to_string(&map)?; let response = request!(Route::UsersMe, patch(body), "/users/@me"); diff --git a/src/ext/framework/help_commands.rs b/src/ext/framework/help_commands.rs index 87b69a0161f..863f3fd84a7 100644 --- a/src/ext/framework/help_commands.rs +++ b/src/ext/framework/help_commands.rs @@ -7,8 +7,8 @@ use ::client::Context; use ::model::Message; use ::utils::Colour; -fn error_embed(ctx: &mut Context, message: &Message, input: &str) { - let _ = ctx.send_message(message.channel_id, |m| m +fn error_embed(ctx: &mut Context, input: &str) { + let _ = ctx.send_message(|m| m .embed(|e| e .colour(Colour::dark_red()) .description(input))); @@ -27,7 +27,7 @@ fn remove_aliases(cmds: &HashMap) -> HashMap<&String, &I } pub fn with_embeds(ctx: &mut Context, - message: &Message, + _: &Message, groups: HashMap>, args: Vec) -> Result<(), String> { if !args.is_empty() { @@ -49,7 +49,7 @@ pub fn with_embeds(ctx: &mut Context, found = Some((command_name, cmd)); }, CommandOrAlias::Alias(ref name) => { - error_embed(ctx, message, &format!("Did you mean \"{}\"?", name)); + error_embed(ctx, &format!("Did you mean \"{}\"?", name)); return Ok(()); } } @@ -58,12 +58,12 @@ pub fn with_embeds(ctx: &mut Context, if let Some((command_name, command)) = found { if !command.help_available { - error_embed(ctx, message, "**Error**: No help available."); + error_embed(ctx, "**Error**: No help available."); return Ok(()); } - let _ = ctx.send_message(message.channel_id, |m| { + let _ = ctx.send_message(|m| { m.embed(|e| { let mut embed = e.colour(Colour::rosewater()) .title(command_name); @@ -110,13 +110,13 @@ pub fn with_embeds(ctx: &mut Context, } let error_msg = format!("**Error**: Command `{}` not found.", name); - error_embed(ctx, message, &error_msg); + error_embed(ctx, &error_msg); return Ok(()); } - let _ = ctx.send_message(message.channel_id, |m| { - m.embed(|mut e| { + let _ = ctx.send_message(|m| m + .embed(|mut e| { e = e.colour(Colour::rosewater()) .description("To get help with an individual command, pass its \ name as an argument to this command."); @@ -146,8 +146,7 @@ pub fn with_embeds(ctx: &mut Context, } e - }) - }); + })); Ok(()) } diff --git a/src/ext/framework/mod.rs b/src/ext/framework/mod.rs index ccb81f15e04..988d3a3e590 100644 --- a/src/ext/framework/mod.rs +++ b/src/ext/framework/mod.rs @@ -360,7 +360,7 @@ impl Framework { return; } - #[cfg(all(feature="cache", feature="methods"))] + #[cfg(feature="cache")] { if !self.configuration.allow_dm && message.is_private() { if let Some(ref message) = self.configuration.no_dm_message { diff --git a/src/model/channel.rs b/src/model/channel.rs index a773cc71dff..bda26e5f438 100644 --- a/src/model/channel.rs +++ b/src/model/channel.rs @@ -1,5 +1,9 @@ +use hyper::Client as HyperClient; +use serde_json::builder::ObjectBuilder; use std::borrow::Cow; use std::fmt::{self, Write}; +use std::io::Read; +use std::mem; use super::utils::{ decode_id, into_map, @@ -8,34 +12,29 @@ use super::utils::{ remove, }; use super::*; +use ::client::rest; use ::constants; use ::internal::prelude::*; +use ::utils::builder::{ + CreateEmbed, + CreateInvite, + CreateMessage, + EditChannel, + GetMessages, + Search +}; use ::utils::decode_array; -#[cfg(feature="methods")] -use hyper::Client as HyperClient; -#[cfg(feature="methods")] -use serde_json::builder::ObjectBuilder; -#[cfg(feature="methods")] -use std::io::Read; -#[cfg(feature="methods")] -use std::mem; -#[cfg(all(feature="cache", feature="methods"))] +#[cfg(feature="cache")] use super::utils; - -#[cfg(all(feature="cache", feature="methods"))] +#[cfg(feature="cache")] use ::client::CACHE; -#[cfg(feature="methods")] -use ::client::rest; -#[cfg(all(feature="cache", feature="methods"))] +#[cfg(feature="cache")] use ::ext::cache::ChannelRef; -#[cfg(feature="methods")] -use ::utils::builder::{CreateEmbed, CreateInvite, EditChannel, Search}; impl Attachment { /// If this attachment is an image, then a tuple of the width and height /// in pixels is returned. - #[cfg(feature="methods")] pub fn dimensions(&self) -> Option<(u64, u64)> { if let (Some(width), Some(height)) = (self.width, self.height) { Some((width, height)) @@ -110,7 +109,6 @@ impl Attachment { /// [`Error::Hyper`]: ../enum.Error.html#variant.Hyper /// [`Error::Io`]: ../enum.Error.html#variant.Io /// [`Message`]: struct.Message.html - #[cfg(feature="methods")] pub fn download(&self) -> Result> { let hyper = HyperClient::new(); let mut response = hyper.get(&self.url).send()?; @@ -123,6 +121,50 @@ impl Attachment { } impl Channel { + /// Marks the channel as being read up to a certain [`Message`]. + /// + /// Refer to the documentation for [`rest::ack_message`] for more + /// information. + /// + /// # Errors + /// + /// If the `cache` is enabled, returns a + /// [`ClientError::InvalidOperationAsBot`] if the current user is a bot + /// user. + /// + /// [`Channel`]: enum.Channel.html + /// [`ClientError::InvalidOperationAsBot`]: ../client/enum.ClientError.html#variant.InvalidOperationAsUser + /// [`Message`]: struct.Message.html + /// [`rest::ack_message`]: rest/fn.ack_message.html + pub fn ack>(&self, message_id: M) -> Result<()> { + #[cfg(feature="cache")] + { + if CACHE.read().unwrap().user.bot { + return Err(Error::Client(ClientError::InvalidOperationAsBot)); + } + } + + self.id().ack(message_id) + } + + /// React to a [`Message`] with a custom [`Emoji`] or unicode character. + /// + /// [`Message::react`] may be a more suited method of reacting in most + /// cases. + /// + /// Requires the [Add Reactions] permission, _if_ the current user is the + /// first user to perform a react with a certain emoji. + /// + /// [`Emoji`]: struct.Emoji.html + /// [`Message`]: struct.Message.html + /// [`Message::react`]: struct.Message.html#method.react + /// [Add Reactions]: permissions/constant.ADD_REACTIONS.html + #[inline] + pub fn create_reaction(&self, message_id: M, reaction_type: R) + -> Result<()> where M: Into, R: Into { + self.id().create_reaction(message_id, reaction_type) + } + #[doc(hidden)] pub fn decode(value: Value) -> Result { let map = into_map(value)?; @@ -144,7 +186,6 @@ impl Channel { /// closest functionality is leaving it. /// /// [`Group`]: struct.Group.html - #[cfg(feature="methods")] pub fn delete(&self) -> Result<()> { match *self { Channel::Group(ref group) => { @@ -161,6 +202,122 @@ impl Channel { Ok(()) } + /// Deletes a [`Message`] given its Id. + /// + /// Refer to [`Message::delete`] for more information. + /// + /// Requires the [Manage Messages] permission, if the current user is not + /// the author of the message. + /// + /// (in practice, please do not do this) + /// + /// [`Message`]: struct.Message.html + /// [`Message::delete`]: struct.Message.html#method.delete + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + #[inline] + pub fn delete_message>(&self, message_id: M) -> Result<()> { + self.id().delete_message(message_id) + } + + /// Deletes all messages by Ids from the given vector in the channel. + /// + /// The minimum amount of messages is 2 and the maximum amount is 100. + /// + /// Requires the [Manage Messages] permission. + /// + /// **Note**: This uses bulk delete endpoint which is not available + /// for user accounts. + /// + /// **Note**: Messages that are older than 2 weeks can't be deleted using + /// this method. + /// + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + #[inline] + pub fn delete_messages(&self, message_ids: &[MessageId]) -> Result<()> { + self.id().delete_messages(message_ids) + } + + /// Deletes all permission overrides in the channel from a member + /// or role. + /// + /// **Note**: Requires the [Manage Channel] permission. + /// + /// [Manage Channel]: permissions/constant.MANAGE_CHANNELS.html + #[inline] + pub fn delete_permission(&self, permission_type: PermissionOverwriteType) -> Result<()> { + self.id().delete_permission(permission_type) + } + + /// Deletes the given [`Reaction`] from the channel. + /// + /// **Note**: Requires the [Manage Messages] permission, _if_ the current + /// user did not perform the reaction. + /// + /// [`Reaction`]: struct.Reaction.html + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + #[inline] + pub fn delete_reaction(&self, message_id: M, user_id: Option, reaction_type: R) + -> Result<()> where M: Into, R: Into { + self.id().delete_reaction(message_id, user_id, reaction_type) + } + + /// Gets a message from the channel. + /// + /// Requires the [Read Message History] permission. + /// + /// [Read Message History]: permission/constant.READ_MESSAGE_HISTORY.html + #[inline] + pub fn get_message>(&self, message_id: M) -> Result { + self.id().get_message(message_id) + } + + /// Gets messages from the channel. + /// + /// Requires the [Read Message History] permission. + /// + /// # Examples + /// + /// ```rust,ignore + /// use serenity::model::ChannelId; + /// + /// let messages = channel.get_messages(|g| g + /// .before(20) + /// .after(100)); // Maximum is 100. + /// ``` + /// + /// [Read Message History]: permission/constant.READ_MESSAGE_HISTORY.html + #[inline] + pub fn get_messages(&self, f: F) -> Result> + where F: FnOnce(GetMessages) -> GetMessages { + self.id().get_messages(f) + } + + /// Gets the list of [`User`]s who have reacted to a [`Message`] with a + /// certain [`Emoji`]. + /// + /// The default `limit` is `50` - specify otherwise to receive a different + /// maximum number of users. The maximum that may be retrieve at a time is + /// `100`, if a greater number is provided then it is automatically reduced. + /// + /// The optional `after` attribute is to retrieve the users after a certain + /// user. This is useful for pagination. + /// + /// **Note**: Requires the [Read Message History] permission. + /// + /// [`Emoji`]: struct.Emoji.html + /// [`Message`]: struct.Message.html + /// [`User`]: struct.User.html + /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + #[inline] + pub fn get_reaction_users(&self, + message_id: M, + reaction_type: R, + limit: Option, + after: Option) + -> Result> where M: Into, R: Into, U: Into { + self.id().get_reaction_users(message_id, reaction_type, limit, after) + } + /// Retrieves the Id of the inner [`Group`], [`GuildChannel`], or /// [`PrivateChannel`]. /// @@ -191,7 +348,6 @@ impl Channel { /// [`ClientError::InvalidOperationAsBot`]: ../client/enum.ClientError.html#variant.InvalidOperationAsBot /// [`Message`]: struct.Message.html /// [`Search`]: ../utils/builder/struct.Search.html - #[cfg(feature="methods")] pub fn search(&self, f: F) -> Result where F: FnOnce(Search) -> Search { #[cfg(feature="cache")] @@ -201,13 +357,18 @@ impl Channel { } } - let id = match *self { - Channel::Group(ref group) => group.channel_id.0, - Channel::Guild(ref channel) => channel.id.0, - Channel::Private(ref channel) => channel.id.0, - }; + self.id().search(f) + } - rest::search_channel_messages(id, f(Search::default()).0) + /// Unpins a [`Message`] in the channel given by its Id. + /// + /// Requires the [Manage Messages] permission. + /// + /// [`Message`]: struct.Message.html + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + #[inline] + pub fn unpin>(&self, message_id: M) -> Result<()> { + self.id().unpin(message_id) } } @@ -236,20 +397,509 @@ impl fmt::Display for Channel { } } +impl ChannelId { + /// Marks a [`Channel`] as being read up to a certain [`Message`]. + /// + /// Refer to the documentation for [`rest::ack_message`] for more + /// information. + /// + /// [`Channel`]: enum.Channel.html + /// [`Message`]: struct.Message.html + /// [`rest::ack_message`]: rest/fn.ack_message.html + #[inline] + pub fn ack>(&self, message_id: M) -> Result<()> { + rest::ack_message(self.0, message_id.into().0) + } + + /// Broadcasts that the current user is typing to a channel for the next 5 + /// seconds. + /// + /// After 5 seconds, another request must be made to continue broadcasting + /// that the current user is typing. + /// + /// This should rarely be used for bots, and should likely only be used for + /// signifying that a long-running command is still being executed. + /// + /// **Note**: Requires the [Send Messages] permission. + /// + /// # Examples + /// + /// ```rust,ignore + /// use serenity::model::ChannelId; + /// + /// let _successful = ChannelId(7).broadcast_typing(); + /// ``` + /// + /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + #[inline] + pub fn broadcast_typing(&self) -> Result<()> { + rest::broadcast_typing(self.0) + } + + /// Creates a [permission overwrite][`PermissionOverwrite`] for either a + /// single [`Member`] or [`Role`] within the channel. + /// + /// Refer to the documentation for [`GuildChannel::create_permission`] for + /// more information. + /// + /// Requires the [Manage Channels] permission. + /// + /// [`GuildChannel::create_permission`]: struct.GuildChannel.html#method.create_permission + /// [`Member`]: struct.Member.html + /// [`PermissionOverwrite`]: struct.PermissionOverWrite.html + /// [`Role`]: struct.Role.html + /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html + pub fn create_permission(&self, target: PermissionOverwrite) + -> Result<()> { + let (id, kind) = match target.kind { + PermissionOverwriteType::Member(id) => (id.0, "member"), + PermissionOverwriteType::Role(id) => (id.0, "role"), + }; + + let map = ObjectBuilder::new() + .insert("allow", target.allow.bits()) + .insert("deny", target.deny.bits()) + .insert("id", id) + .insert("type", kind) + .build(); + + rest::create_permission(self.0, id, map) + } + + /// React to a [`Message`] with a custom [`Emoji`] or unicode character. + /// + /// [`Message::react`] may be a more suited method of reacting in most + /// cases. + /// + /// Requires the [Add Reactions] permission, _if_ the current user is the + /// first user to perform a react with a certain emoji. + /// + /// [`Emoji`]: struct.Emoji.html + /// [`Message`]: struct.Message.html + /// [`Message::react`]: struct.Message.html#method.react + /// [Add Reactions]: permissions/constant.ADD_REACTIONS.html + #[inline] + pub fn create_reaction(&self, message_id: M, reaction_type: R) + -> Result<()> where M: Into, R: Into { + rest::create_reaction(self.0, message_id.into().0, reaction_type.into()) + } + + /// Deletes this channel, returning the channel on a successful deletion. + #[inline] + pub fn delete(&self) -> Result { + rest::delete_channel(self.0) + } + + /// Deletes a [`Message`] given its Id. + /// + /// Refer to [`Message::delete`] for more information. + /// + /// Requires the [Manage Messages] permission, if the current user is not + /// the author of the message. + /// + /// (in practice, please do not do this) + /// + /// [`Message`]: struct.Message.html + /// [`Message::delete`]: struct.Message.html#method.delete + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + #[inline] + pub fn delete_message>(&self, message_id: M) -> Result<()> { + rest::delete_message(self.0, message_id.into().0) + } + + /// Deletes all messages by Ids from the given vector in the given channel. + /// + /// Refer to the documentation for [`Channel::delete_messages`] for more + /// information. + /// + /// Requires the [Manage Messages] permission. + /// + /// **Note**: This uses bulk delete endpoint which is not available + /// for user accounts. + /// + /// **Note**: Messages that are older than 2 weeks can't be deleted using this method. + /// + /// [`Channel::delete_messages`]: enum.Channel.html#method.delete_messages + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + pub fn delete_messages(&self, message_ids: &[MessageId]) -> Result<()> { + let ids = message_ids.into_iter() + .map(|message_id| message_id.0) + .collect::>(); + + let map = ObjectBuilder::new().insert("messages", ids).build(); + + rest::delete_messages(self.0, map) + } + + /// Deletes all permission overrides in the channel from a member or role. + /// + /// **Note**: Requires the [Manage Channel] permission. + /// + /// [Manage Channel]: permissions/constant.MANAGE_CHANNELS.html + pub fn delete_permission(&self, permission_type: PermissionOverwriteType) -> Result<()> { + rest::delete_permission(self.0, match permission_type { + PermissionOverwriteType::Member(id) => id.0, + PermissionOverwriteType::Role(id) => id.0, + }) + } + + /// Deletes the given [`Reaction`] from the channel. + /// + /// **Note**: Requires the [Manage Messages] permission, _if_ the current + /// user did not perform the reaction. + /// + /// [`Reaction`]: struct.Reaction.html + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + pub fn delete_reaction(&self, message_id: M, user_id: Option, reaction_type: R) + -> Result<()> where M: Into, R: Into { + rest::delete_reaction(self.0, + message_id.into().0, + user_id.map(|uid| uid.0), + reaction_type.into()) + } + + + /// Edits the settings of a [`Channel`], optionally setting new values. + /// + /// Refer to `EditChannel`'s documentation for its methods. + /// + /// Requires the [Manage Channel] permission. + /// + /// # Examples + /// + /// Change a voice channel's name and bitrate: + /// + /// ```rust,ignore + /// context.edit_channel(channel_id, |c| c + /// .name("test") + /// .bitrate(64000)); + /// ``` + /// + /// # Errors + /// + /// Returns a [`ClientError::NoChannelId`] if the current context is not + /// related to a channel. + /// + /// [`Channel`]: enum.Channel.html + /// [`ClientError::NoChannelId`]: ../client/enum.ClientError.html#variant.NoChannelId + #[inline] + pub fn edit EditChannel>(&self, f: F) -> Result { + rest::edit_channel(self.0, f(EditChannel::default()).0.build()) + } + + /// Edits a [`Message`] in the channel given its Id. + /// + /// Pass an empty string (`""`) to `text` if you are editing a message with + /// an embed or file but no content. Otherwise, `text` must be given. + /// + /// **Note**: Requires that the current user be the author of the message. + /// + /// # Errors + /// + /// Returns a [`ClientError::NoChannelId`] if the current context is not + /// related to a channel. + /// + /// [`ClientError::NoChannelId`]: ../client/enum.ClientError.html#variant.NoChannelId + /// [`Message`]: struct.Message.html + pub fn edit_message(&self, message_id: M, text: &str, f: F) -> Result + where F: FnOnce(CreateEmbed) -> CreateEmbed, M: Into { + let mut map = ObjectBuilder::new().insert("content", text); + + let embed = f(CreateEmbed::default()).0; + + if embed.len() > 1 { + map = map.insert("embed", Value::Object(embed)); + } + + rest::edit_message(self.0, message_id.into().0, map.build()) + } + + /// Search the cache for the channel with the Id. + #[cfg(feature="cache")] + pub fn find(&self) -> Option { + CACHE.read().unwrap().get_channel(*self).map(|x| x.clone_inner()) + } + + /// Search the cache for the channel. If it can't be found, the channel is + /// requested over REST. + pub fn get(&self) -> Result { + #[cfg(feature="cache")] + { + if let Some(channel) = CACHE.read().unwrap().get_channel(*self) { + return Ok(channel.clone_inner()); + } + } + + rest::get_channel(self.0) + } + + /// Gets all of the channel's invites. + /// + /// Requires the [Manage Channels] permission. + /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html + #[inline] + pub fn get_invites(&self) -> Result> { + rest::get_channel_invites(self.0) + } + + /// Gets a message from the channel. + /// + /// Requires the [Read Message History] permission. + /// + /// [Read Message History]: permission/constant.READ_MESSAGE_HISTORY.html + #[inline] + pub fn get_message>(&self, message_id: M) -> Result { + rest::get_message(self.0, message_id.into().0) + } + + /// Gets messages from the channel. + /// + /// Refer to [`Channel::get_messages`] for more information. + /// + /// Requires the [Read Message History] permission. + /// + /// [`Channel::get_messages`]: enum.Channel.html#method.get_messages + /// [Read Message History]: permission/constant.READ_MESSAGE_HISTORY.html + pub fn get_messages(&self, f: F) -> Result> + where F: FnOnce(GetMessages) -> GetMessages { + let mut map = f(GetMessages::default()).0; + let mut query = format!("?limit={}", map.remove("limit").unwrap_or(50)); + + if let Some(after) = map.remove("after") { + write!(query, "&after={}", after)?; + } else if let Some(around) = map.remove("around") { + write!(query, "&around={}", around)?; + } else if let Some(before) = map.remove("before") { + write!(query, "&before={}", before)?; + } + + rest::get_messages(self.0, &query) + } + + /// Gets the list of [`User`]s who have reacted to a [`Message`] with a + /// certain [`Emoji`]. + /// + /// Refer to [`Channel::get_reaction_users`] for more information. + /// + /// **Note**: Requires the [Read Message History] permission. + /// + /// [`Channel::get_reaction_users`]: enum.Channel.html#variant.get_reaction_users + /// [`Emoji`]: struct.Emoji.html + /// [`Message`]: struct.Message.html + /// [`User`]: struct.User.html + /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + pub fn get_reaction_users(&self, + message_id: M, + reaction_type: R, + limit: Option, + after: Option) + -> Result> where M: Into, R: Into, U: Into { + let limit = limit.map_or(50, |x| if x > 100 { 100 } else { x }); + + rest::get_reaction_users(self.0, + message_id.into().0, + reaction_type.into(), + limit, + after.map(|u| u.into().0)) + } + + /// Pins a [`Message`] to the channel. + #[inline] + pub fn pin>(&self, message_id: M) -> Result<()> { + rest::pin_message(self.0, message_id.into().0) + } + + /// Gets the list of [`Message`]s which are pinned to the channel. + #[inline] + pub fn pins(&self) -> Result> { + rest::get_pins(self.0) + } + + /// Searches the channel's messages by providing query parameters via the + /// search builder. + /// + /// Refer to the documentation for the [`Search`] builder for restrictions + /// and defaults parameters, as well as potentially advanced usage. + /// + /// **Note**: Bot users can not search. + /// + /// # Examples + /// + /// Refer to the [`Search`] builder's documentation for examples, + /// specifically the section on [searching a channel][search channel]. + /// + /// [`Search`]: ../utils/builder/struct.Search.html + #[inline] + pub fn search Search>(&self, f: F) -> Result { + rest::search_channel_messages(self.0, f(Search::default()).0) + } + + /// Sends a file along with optional message contents. The filename _must_ + /// be specified. + /// + /// Message contents may be passed by using the [`CreateMessage::content`] + /// method. + /// + /// An embed can _not_ be sent when sending a file. If you set one, it will + /// be automatically removed. + /// + /// Requires the [Attach Files] and [Send Messages] permissions are required. + /// + /// **Note**: Message contents must be under 2000 unicode code points. + /// + /// # Errors + /// + /// If the content of the message is over the above limit, then a + /// [`ClientError::MessageTooLong`] will be returned, containing the number + /// of unicode code points over the limit. + /// + /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong + /// [`CreateMessage::content`]: ../utils/builder/struct.CreateMessage.html#method.content + /// [`GuildChannel`]: struct.GuildChannel.html + /// [Attach Files]: permissions/constant.ATTACH_FILES.html + /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + pub fn send_file(&self, file: R, filename: &str, f: F) -> Result + where F: FnOnce(CreateMessage) -> CreateMessage, R: Read { + let mut map = f(CreateMessage::default()).0; + + if let Some(content) = map.get("content") { + if let Value::String(ref content) = *content { + if let Some(length_over) = Message::overflow_length(content) { + return Err(Error::Client(ClientError::MessageTooLong(length_over))); + } + } + } + + let _ = map.remove("embed"); + + rest::send_file(self.0, file, filename, map) + } + + /// Sends a message to the channel. + /// + /// Refer to the documentation for [`CreateMessage`] for more information + /// regarding message restrictions and requirements. + /// + /// Requires the [Send Messages] permission is required. + /// + /// **Note**: Message contents must be under 2000 unicode code points. + /// + /// # Errors + /// + /// Returns a [`ClientError::MessageTooLong`] if the content of the message + /// is over the above limit, containing the number of unicode code points + /// over the limit. + /// + /// [`Channel`]: enum.Channel.html + /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong + /// [`CreateMessage`]: ../utils/builder/struct.CreateMessage.html + /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + pub fn send_message(&self, f: F) -> Result + where F: FnOnce(CreateMessage) -> CreateMessage { + let map = f(CreateMessage::default()).0; + + if let Some(content) = map.get(&"content".to_owned()) { + if let Value::String(ref content) = *content { + if let Some(length_over) = Message::overflow_length(content) { + return Err(Error::Client(ClientError::MessageTooLong(length_over))); + } + } + } + + rest::send_message(self.0, Value::Object(map)) + } + + /// Unpins a [`Message`] in the channel given by its Id. + /// + /// Requires the [Manage Messages] permission. + /// + /// [`Message`]: struct.Message.html + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + #[inline] + pub fn unpin>(&self, message_id: M) -> Result<()> { + rest::unpin_message(self.0, message_id.into().0) + } + + /// Retrieves the channel's webhooks. + /// + /// **Note**: Requires the [Manage Webhooks] permission. + /// + /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html + #[inline] + pub fn webhooks(&self) -> Result> { + rest::get_channel_webhooks(self.0) + } +} + +impl From for ChannelId { + /// Gets the Id of a `Channel`. + fn from(channel: Channel) -> ChannelId { + match channel { + Channel::Group(group) => group.channel_id, + Channel::Guild(channel) => channel.id, + Channel::Private(channel) => channel.id, + } + } +} + +impl From for ChannelId { + /// Gets the Id of a private channel. + fn from(private_channel: PrivateChannel) -> ChannelId { + private_channel.id + } +} + +impl From for ChannelId { + /// Gets the Id of a guild channel. + fn from(public_channel: GuildChannel) -> ChannelId { + public_channel.id + } +} + +impl fmt::Display for ChannelId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + impl Embed { /// Creates a fake Embed, giving back a `serde_json` map. /// /// This should only be useful in conjunction with [`Webhook::execute`]. /// /// [`Webhook::execute`]: struct.Webhook.html - #[cfg(feature="methods")] - #[inline(always)] + #[inline] pub fn fake(f: F) -> Value where F: FnOnce(CreateEmbed) -> CreateEmbed { Value::Object(f(CreateEmbed::default()).0) } } impl Group { + /// Marks the group as being read up to a certain [`Message`]. + /// + /// Refer to the documentation for [`rest::ack_message`] for more + /// information. + /// + /// # Errors + /// + /// If the `cache` is enabled, returns a + /// [`ClientError::InvalidOperationAsBot`] if the current user is a bot + /// user. + /// + /// [`ClientError::InvalidOperationAsBot`]: ../client/enum.ClientError.html#variant.InvalidOperationAsUser + /// [`Message`]: struct.Message.html + /// [`rest::ack_message`]: rest/fn.ack_message.html + pub fn ack>(&self, message_id: M) -> Result<()> { + #[cfg(feature="cache")] + { + if CACHE.read().unwrap().user.bot { + return Err(Error::Client(ClientError::InvalidOperationAsBot)); + } + } + + self.channel_id.ack(message_id) + } + /// Adds the given user to the group. If the user is already in the group, /// then nothing is done. /// @@ -259,7 +909,6 @@ impl Group { /// user. /// /// [`rest::add_group_recipient`]: ../client/rest/fn.add_group_recipient.html - #[cfg(feature="methods")] pub fn add_recipient>(&self, user: U) -> Result<()> { let user = user.into(); @@ -272,43 +921,113 @@ impl Group { } /// Broadcasts that the current user is typing in the group. - #[cfg(feature="methods")] + #[inline] pub fn broadcast_typing(&self) -> Result<()> { - rest::broadcast_typing(self.channel_id.0) + self.channel_id.broadcast_typing() } - /// Deletes multiple messages in the group. + /// React to a [`Message`] with a custom [`Emoji`] or unicode character. /// - /// Refer to - /// [`Context::delete_messages`] for more information. + /// [`Message::react`] may be a more suited method of reacting in most + /// cases. /// - /// **Note**: Only 2 to 100 messages may be deleted in a single request. + /// Requires the [Add Reactions] permission, _if_ the current user is the + /// first user to perform a react with a certain emoji. /// - /// **Note**: Messages that are older than 2 weeks can't be deleted using this method. + /// [`Emoji`]: struct.Emoji.html + /// [`Message`]: struct.Message.html + /// [`Message::react`]: struct.Message.html#method.react + /// [Add Reactions]: permissions/constant.ADD_REACTIONS.html + #[inline] + pub fn create_reaction(&self, message_id: M, reaction_type: R) + -> Result<()> where M: Into, R: Into { + self.channel_id.create_reaction(message_id, reaction_type) + } + + /// Deletes all messages by Ids from the given vector in the channel. /// - /// # Errors + /// Refer to [`Channel::delete_messages`] for more information. /// - /// Returns a - /// [`ClientError::DeleteMessageDaysAmount`] if the number of messages to - /// delete is not within the valid range. + /// Requires the [Manage Messages] permission. + /// + /// **Note**: This uses bulk delete endpoint which is not available + /// for user accounts. + /// + /// **Note**: Messages that are older than 2 weeks can't be deleted using + /// this method. /// - /// [`ClientError::DeleteMessageDaysAmount`]: ../client/enum.ClientError.html#variant.DeleteMessageDaysAmount - /// [`Context::delete_messages`]: ../client/struct.Context.html#delete_messages - #[cfg(feature="methods")] + /// [`Channel::delete_messages`]: enum.Channel.html#method.delete_messages + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + #[inline] pub fn delete_messages(&self, message_ids: &[MessageId]) -> Result<()> { - if message_ids.len() < 2 || message_ids.len() > 100 { - return Err(Error::Client(ClientError::BulkDeleteAmount)); - } + self.channel_id.delete_messages(message_ids) + } - let ids: Vec = message_ids.into_iter() - .map(|message_id| message_id.0) - .collect(); + /// Deletes all permission overrides in the channel from a member + /// or role. + /// + /// **Note**: Requires the [Manage Channel] permission. + /// + /// [Manage Channel]: permissions/constant.MANAGE_CHANNELS.html + #[inline] + pub fn delete_permission(&self, permission_type: PermissionOverwriteType) -> Result<()> { + self.channel_id.delete_permission(permission_type) + } - let map = ObjectBuilder::new() - .insert("messages", ids) - .build(); + /// Deletes the given [`Reaction`] from the channel. + /// + /// **Note**: Requires the [Manage Messages] permission, _if_ the current + /// user did not perform the reaction. + /// + /// [`Reaction`]: struct.Reaction.html + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + #[inline] + pub fn delete_reaction(&self, message_id: M, user_id: Option, reaction_type: R) + -> Result<()> where M: Into, R: Into { + self.channel_id.delete_reaction(message_id, user_id, reaction_type) + } + + /// Gets a message from the channel. + /// + /// Requires the [Read Message History] permission. + /// + /// [Read Message History]: permission/constant.READ_MESSAGE_HISTORY.html + #[inline] + pub fn get_message>(&self, message_id: M) -> Result { + self.channel_id.get_message(message_id) + } - rest::delete_messages(self.channel_id.0, map) + /// Gets messages from the channel. + /// + /// Requires the [Read Message History] permission. + /// + /// [Read Message History]: permission/constant.READ_MESSAGE_HISTORY.html + #[inline] + pub fn get_messages(&self, f: F) -> Result> + where F: FnOnce(GetMessages) -> GetMessages { + self.channel_id.get_messages(f) + } + + /// Gets the list of [`User`]s who have reacted to a [`Message`] with a + /// certain [`Emoji`]. + /// + /// Refer to [`Channel::get_reaction_users`] for more information. + /// + /// **Note**: Requires the [Read Message History] permission. + /// + /// [`Channel::get_reaction_users`]: enum.Channel.html#variant.get_reaction_users + /// [`Emoji`]: struct.Emoji.html + /// [`Message`]: struct.Message.html + /// [`User`]: struct.User.html + /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + #[inline] + pub fn get_reaction_users(&self, + message_id: M, + reaction_type: R, + limit: Option, + after: Option) + -> Result> where M: Into, R: Into, U: Into { + self.channel_id.get_reaction_users(message_id, reaction_type, limit, after) } /// Returns the formatted URI of the group's icon if one exists. @@ -318,7 +1037,7 @@ impl Group { } /// Leaves the group. - #[cfg(feature="methods")] + #[inline] pub fn leave(&self) -> Result { rest::leave_group(self.channel_id.0) } @@ -347,16 +1066,15 @@ impl Group { } /// Retrieves the list of messages that have been pinned in the group. - #[cfg(feature="methods")] + #[inline] pub fn pins(&self) -> Result> { - rest::get_pins(self.channel_id.0) + self.channel_id.pins() } /// Removes a recipient from the group. If the recipient is already not in /// the group, then nothing is done. /// /// **Note**: This is only available to the group owner. - #[cfg(feature="methods")] pub fn remove_recipient>(&self, user: U) -> Result<()> { let user = user.into(); @@ -384,32 +1102,63 @@ impl Group { /// [`ClientError::InvalidOperationAsBot`]: ../client/enum.ClientError.html#variant.InvalidOperationAsBot /// [`Message`]: struct.Message.html /// [`Search`]: ../utils/builder/struct.Search.html - #[cfg(feature="methods")] + #[inline] pub fn search(&self, f: F) -> Result where F: FnOnce(Search) -> Search { - rest::search_channel_messages(self.channel_id.0, f(Search::default()).0) + self.channel_id.search(f) } /// Sends a message to the group with the given content. /// - /// Note that an @everyone mention will not be applied. + /// Note that an @everyone mention will not be applied. + /// + /// **Note**: Requires the [Send Messages] permission. + /// + /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + #[inline] + pub fn send_message(&self, content: &str) -> Result { + self.channel_id.send_message(|m| m.content(content)) + } + + /// Unpins a [`Message`] in the channel given by its Id. + /// + /// Requires the [Manage Messages] permission. + /// + /// [`Message`]: struct.Message.html + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + #[inline] + pub fn unpin>(&self, message_id: M) -> Result<()> { + self.channel_id.unpin(message_id.into().0) + } +} + +impl Message { + /// Marks the [`Channel`] as being read up to the message. + /// + /// Refer to the documentation for [`rest::ack_message`] for more + /// information. /// - /// **Note**: Requires the [Send Messages] permission. + /// # Errors /// - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html - #[cfg(feature="methods")] - pub fn send_message(&self, content: &str) -> Result { - let map = ObjectBuilder::new() - .insert("content", content) - .insert("nonce", "") - .insert("tts", false) - .build(); + /// If the `cache` is enabled, returns a + /// [`ClientError::InvalidOperationAsBot`] if the current user is a bot + /// user. + /// + /// [`Channel`]: enum.Channel.html + /// [`ClientError::InvalidOperationAsBot`]: ../client/enum.ClientError.html#variant.InvalidOperationAsUser + /// [`Message`]: struct.Message.html + /// [`rest::ack_message`]: rest/fn.ack_message.html + pub fn ack>(&self) -> Result<()> { + #[cfg(feature="cache")] + { + if CACHE.read().unwrap().user.bot { + return Err(Error::Client(ClientError::InvalidOperationAsBot)); + } + } - rest::send_message(self.channel_id.0, map) + self.channel_id.ack(self.id) } -} -impl Message { /// Deletes the message. /// /// **Note**: The logged in user must either be the author of the message or @@ -424,7 +1173,6 @@ impl Message { /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions /// [`ClientError::InvalidUser`]: ../client/enum.ClientError.html#variant.InvalidUser /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html - #[cfg(feature="methods")] pub fn delete(&self) -> Result<()> { #[cfg(feature="cache")] { @@ -437,7 +1185,7 @@ impl Message { } } - rest::delete_message(self.channel_id.0, self.id.0) + self.channel_id.delete_message(self.id) } /// Deletes all of the [`Reaction`]s associated with the message. @@ -453,7 +1201,6 @@ impl Message { /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions /// [`Reaction`]: struct.Reaction.html /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html - #[cfg(feature="methods")] pub fn delete_reactions(&self) -> Result<()> { #[cfg(feature="cache")] { @@ -490,8 +1237,7 @@ impl Message { /// over the limit. /// /// [`ClientError::InvalidUser`]: ../client/enum.ClientError.html#variant.InvalidUser - /// [`ClientError::MessageTooLong`]: enum.ClientError.html#variant.MessageTooLong - #[cfg(feature="methods")] + /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong pub fn edit(&mut self, new_content: &str, embed: F) -> Result<()> where F: FnOnce(CreateEmbed) -> CreateEmbed { if let Some(length_over) = Message::overflow_length(new_content) { @@ -525,7 +1271,7 @@ impl Message { /// Returns message content, but with user and role mentions replaced with /// names and everyone/here mentions cancelled. - #[cfg(all(feature="cache", feature="methods"))] + #[cfg(feature="cache")] pub fn content_safe(&self) -> String { let mut result = self.content.clone(); @@ -550,12 +1296,34 @@ impl Message { .replace("@here", "@\u{200B}here") } + /// Gets the list of [`User`]s who have reacted to a [`Message`] with a + /// certain [`Emoji`]. + /// + /// The default `limit` is `50` - specify otherwise to receive a different + /// maximum number of users. The maximum that may be retrieve at a time is + /// `100`, if a greater number is provided then it is automatically reduced. + /// + /// The optional `after` attribute is to retrieve the users after a certain + /// user. This is useful for pagination. + /// + /// **Note**: Requires the [Read Message History] permission. + /// + /// [`Emoji`]: struct.Emoji.html + /// [`Message`]: struct.Message.html + /// [`User`]: struct.User.html + /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + #[inline] + pub fn get_reaction_users(&self, reaction_type: R, limit: Option, after: Option) + -> Result> where R: Into, U: Into { + self.id.get_reaction_users(self.channel_id, reaction_type, limit, after) + } + /// Retrieves the Id of the guild that the message was sent in, if sent in /// one. /// /// Returns `None` if the channel data or guild data does not exist in the /// cache. - #[cfg(all(feature="cache", feature="methods"))] + #[cfg(feature="cache")] pub fn guild_id(&self) -> Option { match CACHE.read().unwrap().get_channel(self.channel_id) { Some(ChannelRef::Guild(channel)) => Some(channel.guild_id), @@ -564,7 +1332,7 @@ impl Message { } /// True if message was sent using direct messages. - #[cfg(all(feature="cache", feature="methods"))] + #[cfg(feature="cache")] pub fn is_private(&self) -> bool { match CACHE.read().unwrap().get_channel(self.channel_id) { Some(ChannelRef::Group(_)) | Some(ChannelRef::Private(_)) => true, @@ -603,7 +1371,6 @@ impl Message { /// /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html - #[cfg(feature="methods")] pub fn pin(&self) -> Result<()> { #[cfg(feature="cache")] { @@ -614,7 +1381,7 @@ impl Message { } } - rest::pin_message(self.channel_id.0, self.id.0) + self.channel_id.pin(self.id.0) } /// React to the message with a custom [`Emoji`] or unicode character. @@ -631,7 +1398,6 @@ impl Message { /// [`Emoji`]: struct.Emoji.html /// [Add Reactions]: permissions/constant.ADD_REACTIONS.html /// [permissions]: permissions - #[cfg(feature="methods")] pub fn react>(&self, reaction_type: R) -> Result<()> { #[cfg(feature="cache")] { @@ -667,9 +1433,8 @@ impl Message { /// over the limit. /// /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions - /// [`ClientError::MessageTooLong`]: enum.ClientError.html#variant.MessageTooLong + /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong /// [Send Messages]: permissions/constant.SEND_MESSAGES.html - #[cfg(feature="methods")] pub fn reply(&self, content: &str) -> Result { if let Some(length_over) = Message::overflow_length(content) { return Err(Error::Client(ClientError::MessageTooLong(length_over))); @@ -709,7 +1474,6 @@ impl Message { /// /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html - #[cfg(feature="methods")] pub fn unpin(&self) -> Result<()> { #[cfg(feature="cache")] { @@ -724,6 +1488,46 @@ impl Message { } } +impl MessageId { + /// Gets the list of [`User`]s who have reacted to a [`Message`] with a + /// certain [`Emoji`]. + /// + /// The default `limit` is `50` - specify otherwise to receive a different + /// maximum number of users. The maximum that may be retrieve at a time is + /// `100`, if a greater number is provided then it is automatically reduced. + /// + /// The optional `after` attribute is to retrieve the users after a certain + /// user. This is useful for pagination. + /// + /// **Note**: Requires the [Read Message History] permission. + /// + /// [`Emoji`]: struct.Emoji.html + /// [`Message`]: struct.Message.html + /// [`User`]: struct.User.html + /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + pub fn get_reaction_users(&self, + channel_id: C, + reaction_type: R, + limit: Option, + after: Option) + -> Result> where C: Into, R: Into, U: Into { + let limit = limit.map_or(50, |x| if x > 100 { 100 } else { x }); + + rest::get_reaction_users(channel_id.into().0, + self.0, + reaction_type.into(), + limit, + after.map(|u| u.into().0)) + } +} + +impl From for MessageId { + /// Gets the Id of a `Message`. + fn from(message: Message) -> MessageId { + message.id + } +} + impl PermissionOverwrite { #[doc(hidden)] pub fn decode(value: Value) -> Result { @@ -745,12 +1549,53 @@ impl PermissionOverwrite { } impl PrivateChannel { + /// Marks the channel as being read up to a certain [`Message`]. + /// + /// Refer to the documentation for [`rest::ack_message`] for more + /// information. + /// + /// # Errors + /// + /// If the `cache` is enabled, returns a + /// [`ClientError::InvalidOperationAsBot`] if the current user is a bot + /// user. + /// + /// [`ClientError::InvalidOperationAsBot`]: ../client/enum.ClientError.html#variant.InvalidOperationAsUser + /// [`Message`]: struct.Message.html + /// [`rest::ack_message`]: rest/fn.ack_message.html + pub fn ack>(&self, message_id: M) -> Result<()> { + #[cfg(feature="cache")] + { + if CACHE.read().unwrap().user.bot { + return Err(Error::Client(ClientError::InvalidOperationAsBot)); + } + } + + rest::ack_message(self.id.0, message_id.into().0) + } + /// Broadcasts that the current user is typing to the recipient. - #[cfg(feature="methods")] pub fn broadcast_typing(&self) -> Result<()> { rest::broadcast_typing(self.id.0) } + /// React to a [`Message`] with a custom [`Emoji`] or unicode character. + /// + /// [`Message::react`] may be a more suited method of reacting in most + /// cases. + /// + /// Requires the [Add Reactions] permission, _if_ the current user is the + /// first user to perform a react with a certain emoji. + /// + /// [`Emoji`]: struct.Emoji.html + /// [`Message`]: struct.Message.html + /// [`Message::react`]: struct.Message.html#method.react + /// [Add Reactions]: permissions/constant.ADD_REACTIONS.html + pub fn create_reaction(&self, message_id: M, reaction_type: R) + -> Result<()> where M: Into, R: Into { + self.id.create_reaction(message_id, reaction_type) + } + #[doc(hidden)] pub fn decode(value: Value) -> Result { let mut map = into_map(value)?; @@ -769,50 +1614,111 @@ impl PrivateChannel { /// Deletes the channel. This does not delete the contents of the channel, /// and is equivalent to closing a private channel on the client, which can /// be re-opened. - #[cfg(feature="methods")] + #[inline] pub fn delete(&self) -> Result { - rest::delete_channel(self.id.0) + self.id.delete() } - /// Deletes the given message Ids from the private channel. + /// Deletes all messages by Ids from the given vector in the channel. /// - /// **Note** This method is only available to bot users and they can't - /// delete their recipient's messages. + /// Refer to [`Channel::delete_messages`] for more information. + /// + /// Requires the [Manage Messages] permission. + /// + /// **Note**: This uses bulk delete endpoint which is not available + /// for user accounts. /// /// **Note**: Messages that are older than 2 weeks can't be deleted using /// this method. /// - /// # Errors + /// [`Channel::delete_messages`]: enum.Channel.html#method.delete_messages + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + #[inline] + pub fn delete_messages(&self, message_ids: &[MessageId]) -> Result<()> { + self.id.delete_messages(message_ids) + } + + /// Deletes all permission overrides in the channel from a member + /// or role. /// - /// If the `cache` is enabled, then returns a - /// [`ClientError::InvalidUser`] if the current user is not a bot user. + /// **Note**: Requires the [Manage Channel] permission. /// - /// [`ClientError::InvalidUser`]: ../client/enum.ClientError.html#variant.InvalidOperationAsUser - #[cfg(feature="methods")] - pub fn delete_messages(&self, message_ids: &[MessageId]) -> Result<()> { - #[cfg(feature="cache")] - { - if !CACHE.read().unwrap().user.bot { - return Err(Error::Client(ClientError::InvalidOperationAsUser)); - } - } + /// [Manage Channel]: permissions/constant.MANAGE_CHANNELS.html + #[inline] + pub fn delete_permission(&self, permission_type: PermissionOverwriteType) -> Result<()> { + self.id.delete_permission(permission_type) + } - let ids: Vec = message_ids.into_iter() - .map(|message_id| message_id.0) - .collect(); + /// Deletes the given [`Reaction`] from the channel. + /// + /// **Note**: Requires the [Manage Messages] permission, _if_ the current + /// user did not perform the reaction. + /// + /// [`Reaction`]: struct.Reaction.html + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + #[inline] + pub fn delete_reaction(&self, message_id: M, user_id: Option, reaction_type: R) + -> Result<()> where M: Into, R: Into { + self.id.delete_reaction(message_id, user_id, reaction_type) + } - let map = ObjectBuilder::new() - .insert("messages", ids) - .build(); + /// Gets a message from the channel. + /// + /// Requires the [Read Message History] permission. + /// + /// [Read Message History]: permission/constant.READ_MESSAGE_HISTORY.html + #[inline] + pub fn get_message>(&self, message_id: M) -> Result { + self.id.get_message(message_id) + } + + /// Gets messages from the channel. + /// + /// Refer to [`Channel::get_messages`] for more information. + /// + /// Requires the [Read Message History] permission. + /// + /// [`Channel::get_messages`]: enum.Channel.html#method.get_messages + /// [Read Message History]: permission/constant.READ_MESSAGE_HISTORY.html + #[inline] + pub fn get_messages(&self, f: F) -> Result> + where F: FnOnce(GetMessages) -> GetMessages { + self.id.get_messages(f) + } + + /// Gets the list of [`User`]s who have reacted to a [`Message`] with a + /// certain [`Emoji`]. + /// + /// Refer to [`Channel::get_reaction_users`] for more information. + /// + /// **Note**: Requires the [Read Message History] permission. + /// + /// [`Channel::get_reaction_users`]: enum.Channel.html#variant.get_reaction_users + /// [`Emoji`]: struct.Emoji.html + /// [`Message`]: struct.Message.html + /// [`User`]: struct.User.html + /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + #[inline] + pub fn get_reaction_users(&self, + message_id: M, + reaction_type: R, + limit: Option, + after: Option) + -> Result> where M: Into, R: Into, U: Into { + self.id.get_reaction_users(message_id, reaction_type, limit, after) + } - rest::delete_messages(self.id.0, map) + /// Pins a [`Message`] to the channel. + #[inline] + pub fn pin>(&self, message_id: M) -> Result<()> { + self.id.pin(message_id) } /// Retrieves the list of messages that have been pinned in the private /// channel. - #[cfg(feature="methods")] + #[inline] pub fn pins(&self) -> Result> { - rest::get_pins(self.id.0) + self.id.pins() } /// Performs a search request to the API for the channel's [`Message`]s. @@ -830,7 +1736,6 @@ impl PrivateChannel { /// [`ClientError::InvalidOperationAsBot`]: ../client/enum.ClientError.html#variant.InvalidOperationAsBot /// [`Message`]: struct.Message.html /// [`Search`]: ../utils/builder/struct.Search.html - #[cfg(feature="methods")] pub fn search(&self, f: F) -> Result where F: FnOnce(Search) -> Search { #[cfg(feature="cache")] @@ -840,7 +1745,7 @@ impl PrivateChannel { } } - rest::search_channel_messages(self.id.0, f(Search::default()).0) + self.id.search(f) } /// Sends a message to the channel with the given content. @@ -854,7 +1759,6 @@ impl PrivateChannel { /// over the limit. /// /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - #[cfg(feature="methods")] pub fn send_message(&self, content: &str) -> Result { if let Some(length_over) = Message::overflow_length(content) { return Err(Error::Client(ClientError::MessageTooLong(length_over))); @@ -868,6 +1772,17 @@ impl PrivateChannel { rest::send_message(self.id.0, map) } + + /// Unpins a [`Message`] in the channel given by its Id. + /// + /// Requires the [Manage Messages] permission. + /// + /// [`Message`]: struct.Message.html + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + #[inline] + pub fn unpin>(&self, message_id: M) -> Result<()> { + self.id.unpin(message_id.into().0) + } } impl fmt::Display for PrivateChannel { @@ -878,6 +1793,31 @@ impl fmt::Display for PrivateChannel { } impl GuildChannel { + /// Marks the channel as being read up to a certain [`Message`]. + /// + /// Refer to the documentation for [`rest::ack_message`] for more + /// information. + /// + /// # Errors + /// + /// If the `cache` is enabled, returns a + /// [`ClientError::InvalidOperationAsBot`] if the current user is a bot + /// user. + /// + /// [`ClientError::InvalidOperationAsBot`]: ../client/enum.ClientError.html#variant.InvalidOperationAsUser + /// [`Message`]: struct.Message.html + /// [`rest::ack_message`]: rest/fn.ack_message.html + pub fn ack>(&self, message_id: M) -> Result<()> { + #[cfg(feature="cache")] + { + if CACHE.read().unwrap().user.bot { + return Err(Error::Client(ClientError::InvalidOperationAsBot)); + } + } + + rest::ack_message(self.id.0, message_id.into().0) + } + /// Broadcasts to the channel that the current user is typing. /// /// For bots, this is a good indicator for long-running commands. @@ -892,7 +1832,6 @@ impl GuildChannel { /// /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions /// [Send Messages]: permissions/constants.SEND_MESSAGES.html - #[cfg(feature="methods")] pub fn broadcast_typing(&self) -> Result<()> { rest::broadcast_typing(self.id.0) } @@ -907,7 +1846,6 @@ impl GuildChannel { /// let invite = channel.create_invite(|i| i /// .max_uses(5)); /// ``` - #[cfg(feature="methods")] pub fn create_invite(&self, f: F) -> Result where F: FnOnce(CreateInvite) -> CreateInvite { #[cfg(feature="cache")] @@ -924,6 +1862,78 @@ impl GuildChannel { rest::create_invite(self.id.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. + /// + /// 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`]: enum.Channel.html + /// [`Member`]: struct.Member.html + /// [`PermissionOverwrite`]: struct.PermissionOverWrite.html + /// [`PermissionOverwrite::Member`]: struct.PermissionOverwrite.html#variant.Member + /// [`Role`]: struct.Role.html + /// [Attach Files]: permissions/constant.ATTACH_FILES.html + /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html + /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html + /// [Send TTS Messages]: permissions/constant.SEND_TTS_MESSAGES.html + #[inline] + pub fn create_permission(&self, target: PermissionOverwrite) -> Result<()> { + self.id.create_permission(target) + } + #[doc(hidden)] pub fn decode(value: Value) -> Result { let mut map = into_map(value)?; @@ -953,7 +1963,6 @@ impl GuildChannel { } /// Deletes this channel, returning the channel on a successful deletion. - #[cfg(feature="methods")] pub fn delete(&self) -> Result { #[cfg(feature="cache")] { @@ -964,7 +1973,50 @@ impl GuildChannel { } } - rest::delete_channel(self.id.0) + self.id.delete() + } + + /// Deletes all messages by Ids from the given vector in the channel. + /// + /// Refer to [`Channel::delete_messages`] for more information. + /// + /// Requires the [Manage Messages] permission. + /// + /// **Note**: This uses bulk delete endpoint which is not available + /// for user accounts. + /// + /// **Note**: Messages that are older than 2 weeks can't be deleted using + /// this method. + /// + /// [`Channel::delete_messages`]: enum.Channel.html#method.delete_messages + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + #[inline] + pub fn delete_messages(&self, message_ids: &[MessageId]) -> Result<()> { + self.id.delete_messages(message_ids) + } + + /// Deletes all permission overrides in the channel from a member + /// or role. + /// + /// **Note**: Requires the [Manage Channel] permission. + /// + /// [Manage Channel]: permissions/constant.MANAGE_CHANNELS.html + #[inline] + pub fn delete_permission(&self, permission_type: PermissionOverwriteType) -> Result<()> { + self.id.delete_permission(permission_type) + } + + /// Deletes the given [`Reaction`] from the channel. + /// + /// **Note**: Requires the [Manage Messages] permission, _if_ the current + /// user did not perform the reaction. + /// + /// [`Reaction`]: struct.Reaction.html + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + #[inline] + pub fn delete_reaction(&self, message_id: M, user_id: Option, reaction_type: R) + -> Result<()> where M: Into, R: Into { + self.id.delete_reaction(message_id, user_id, reaction_type) } /// Modifies a channel's settings, such as its position or name. @@ -980,7 +2032,6 @@ impl GuildChannel { /// .name("test") /// .bitrate(86400)); /// ``` - #[cfg(feature="methods")] pub fn edit(&mut self, f: F) -> Result<()> where F: FnOnce(EditChannel) -> EditChannel { @@ -1010,19 +2061,79 @@ impl GuildChannel { } } + /// Gets all of the channel's invites. + /// + /// Requires the [Manage Channels] permission. + /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html + #[inline] + pub fn get_invites(&self) -> Result> { + self.id.get_invites() + } + + /// Gets a message from the channel. + /// + /// Requires the [Read Message History] permission. + /// + /// [Read Message History]: permission/constant.READ_MESSAGE_HISTORY.html + #[inline] + pub fn get_message>(&self, message_id: M) -> Result { + self.id.get_message(message_id) + } + + /// Gets messages from the channel. + /// + /// Refer to [`Channel::get_messages`] for more information. + /// + /// Requires the [Read Message History] permission. + /// + /// [`Channel::get_messages`]: enum.Channel.html#method.get_messages + /// [Read Message History]: permission/constant.READ_MESSAGE_HISTORY.html + #[inline] + pub fn get_messages(&self, f: F) -> Result> + where F: FnOnce(GetMessages) -> GetMessages { + self.id.get_messages(f) + } + + /// Gets the list of [`User`]s who have reacted to a [`Message`] with a + /// certain [`Emoji`]. + /// + /// Refer to [`Channel::get_reaction_users`] for more information. + /// + /// **Note**: Requires the [Read Message History] permission. + /// + /// [`Channel::get_reaction_users`]: enum.Channel.html#variant.get_reaction_users + /// [`Emoji`]: struct.Emoji.html + /// [`Message`]: struct.Message.html + /// [`User`]: struct.User.html + /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + pub fn get_reaction_users(&self, + message_id: M, + reaction_type: R, + limit: Option, + after: Option) + -> Result> where M: Into, R: Into, U: Into { + self.id.get_reaction_users(message_id, reaction_type, limit, after) + } + /// Attempts to find this channel's guild in the Cache. /// /// **Note**: Right now this performs a clone of the guild. This will be /// optimized in the future. - #[cfg(all(feature="cache", feature="methods"))] + #[cfg(feature="cache")] pub fn guild(&self) -> Option { CACHE.read().unwrap().get_guild(self.guild_id).cloned() } + /// Pins a [`Message`] to the channel. + #[inline] + pub fn pin>(&self, message_id: M) -> Result<()> { + self.id.pin(message_id) + } + /// Gets all channel's pins. - #[cfg(feature="methods")] + #[inline] pub fn pins(&self) -> Result> { - rest::get_pins(self.id.0) + self.id.pins() } /// Performs a search request for the channel's [`Message`]s. @@ -1040,7 +2151,6 @@ impl GuildChannel { /// [`ClientError::InvalidOperationAsBot`]: ../client/enum.ClientError.html#variant.InvalidOperationAsBot /// [`Message`]: struct.Message.html /// [`Search`]: ../utils/builder/struct.Search.html - #[cfg(feature="methods")] pub fn search(&self, f: F) -> Result where F: FnOnce(Search) -> Search { #[cfg(feature="cache")] @@ -1072,7 +2182,6 @@ impl GuildChannel { /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong /// [`Message`]: struct.Message.html /// [Send Messages]: permissions/constant.SEND_MESSAGES.html - #[cfg(feature="methods")] pub fn send_message(&self, content: &str) -> Result { if let Some(length_over) = Message::overflow_length(content) { return Err(Error::Client(ClientError::MessageTooLong(length_over))); @@ -1096,12 +2205,22 @@ impl GuildChannel { rest::send_message(self.id.0, map) } + /// Unpins a [`Message`] in the channel given by its Id. + /// + /// Requires the [Manage Messages] permission. + /// + /// [`Message`]: struct.Message.html + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + #[inline] + pub fn unpin>(&self, message_id: M) -> Result<()> { + self.id.unpin(message_id.into().0) + } + /// Retrieves the channel's webhooks. /// /// **Note**: Requires the [Manage Webhooks] permission. /// /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html - #[cfg(feature="methods")] #[inline] pub fn webhooks(&self) -> Result> { rest::get_channel_webhooks(self.id.0) @@ -1131,7 +2250,6 @@ impl Reaction { /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html /// [permissions]: permissions - #[cfg(feature="methods")] pub fn delete(&self) -> Result<()> { let user_id = feature_cache! {{ let user = if self.user_id == CACHE.read().unwrap().user.id { @@ -1188,7 +2306,6 @@ impl Reaction { /// [`User`]: struct.User.html /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html /// [permissions]: permissions - #[cfg(feature="methods")] pub fn users(&self, reaction_type: R, limit: Option, diff --git a/src/model/gateway.rs b/src/model/gateway.rs index 769045c22d7..9758e4b64b7 100644 --- a/src/model/gateway.rs +++ b/src/model/gateway.rs @@ -26,7 +26,6 @@ impl Game { /// Creates a `Game` struct that appears as a `Playing ` status. /// /// **Note**: Maximum `name` length is 128. - #[cfg(feature="methods")] pub fn playing(name: &str) -> Game { Game { kind: GameType::Playing, @@ -38,7 +37,6 @@ impl Game { /// Creates a `Game` struct that appears as a `Streaming ` status. /// /// **Note**: Maximum `name` length is 128. - #[cfg(feature="methods")] pub fn streaming(name: &str, url: &str) -> Game { Game { kind: GameType::Streaming, diff --git a/src/model/guild.rs b/src/model/guild.rs index fa8df32ebb9..9af9e00837c 100644 --- a/src/model/guild.rs +++ b/src/model/guild.rs @@ -1,3 +1,4 @@ +use serde_json::builder::ObjectBuilder; use std::cmp::Ordering; use std::collections::HashMap; use std::fmt; @@ -13,23 +14,17 @@ use super::utils::{ remove, }; use super::*; +use ::client::rest; use ::internal::prelude::*; +use ::utils::builder::{EditGuild, EditMember, EditRole, Search}; use ::utils::decode_array; -#[cfg(feature="methods")] -use serde_json::builder::ObjectBuilder; -#[cfg(all(feature="cache", feature = "methods"))] +#[cfg(feature="cache")] use std::mem; -#[cfg(all(feature="cache", feature="methods"))] -use ::utils::builder::EditMember; -#[cfg(feature="methods")] -use ::utils::builder::{EditGuild, EditRole, Search}; -#[cfg(feature = "methods")] -use ::client::rest; -#[cfg(all(feature="cache", feature="methods"))] +#[cfg(feature="cache")] use ::client::CACHE; -#[cfg(all(feature="cache", feature="methods"))] +#[cfg(feature="cache")] use ::utils::Colour; impl From for GuildContainer { @@ -58,7 +53,7 @@ impl Emoji { /// **Note**: Only user accounts may use this method. /// /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html - #[cfg(all(feature="cache", feature="methods"))] + #[cfg(feature="cache")] pub fn delete(&self) -> Result<()> { match self.find_guild_id() { Some(guild_id) => rest::delete_emoji(guild_id.0, self.id.0), @@ -73,7 +68,7 @@ impl Emoji { /// **Note**: Only user accounts may use this method. /// /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html - #[cfg(all(feature="cache", feature="methods"))] + #[cfg(feature="cache")] pub fn edit(&mut self, name: &str) -> Result<()> { match self.find_guild_id() { Some(guild_id) => { @@ -97,7 +92,7 @@ impl Emoji { /// Finds the [`Guild`] that owns the emoji by looking through the Cache. /// /// [`Guild`]: struct.Guild.html - #[cfg(all(feature="cache", feature="methods"))] + #[cfg(feature="cache")] pub fn find_guild_id(&self) -> Option { CACHE.read() .unwrap() @@ -108,7 +103,6 @@ impl Emoji { } /// Generates a URL to the emoji's image. - #[cfg(feature="methods")] #[inline] pub fn url(&self) -> String { format!(cdn!("/emojis/{}.png"), self.id) @@ -129,141 +123,37 @@ impl fmt::Display for Emoji { } } -impl GuildInfo { - /// Returns the formatted URL of the guild's icon, if the guild has an icon. - pub fn icon_url(&self) -> Option { - self.icon.as_ref().map(|icon| - format!(cdn!("/icons/{}/{}.webp"), self.id, icon)) +impl fmt::Display for EmojiId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) } } -impl InviteGuild { - /// Returns the formatted URL of the guild's splash image, if one exists. - #[cfg(feature="methods")] - pub fn splash_url(&self) -> Option { - self.icon.as_ref().map(|icon| - format!(cdn!("/splashes/{}/{}.webp"), self.id, icon)) +impl From for EmojiId { + /// Gets the Id of an `Emoji`. + fn from(emoji: Emoji) -> EmojiId { + emoji.id } } -impl PartialGuild { - /// Edits the current user's nickname for the guild. - /// - /// Pass `None` to reset the nickname. - /// - /// **Note**: Requires the [Change Nickname] permission. - /// - /// [Change Nickname]: permissions/constant.CHANGE_NICKNAME.html - #[cfg(feature="methods")] - #[inline] - pub fn edit_nickname(&self, new_nickname: Option<&str>) -> Result<()> { - rest::edit_nickname(self.id.0, new_nickname) - } - - /// Returns a formatted URL of the guild's icon, if the guild has an icon. +impl GuildInfo { + /// Returns the formatted URL of the guild's icon, if the guild has an icon. pub fn icon_url(&self) -> Option { self.icon.as_ref().map(|icon| format!(cdn!("/icons/{}/{}.webp"), self.id, icon)) } +} - /// Performs a search request to the API for the guild's [`Message`]s. - /// - /// This will search all of the guild's [`Channel`]s at once, that you have - /// the [Read Message History] permission to. Use [`search_channels`] to - /// specify a list of [channel][`GuildChannel`]s to search, where all other - /// channels will be excluded. - /// - /// Refer to the documentation for the [`Search`] builder for examples and - /// more information. - /// - /// **Note**: Bot users can not search. - /// - /// # Errors - /// - /// If the `cache` is enabled, returns a - /// [`ClientError::InvalidOperationAsBot`] if the current user is a bot. - /// - /// [`ClientError::InvalidOperationAsBot`]: ../client/enum.ClientError.html#variant.InvalidOperationAsBot - /// [`Channel`]: enum.Channel.html - /// [`GuildChannel`]: struct.GuildChannel.html - /// [`Message`]: struct.Message.html - /// [`Search`]: ../utils/builder/struct.Search.html - /// [`search_channels`]: #method.search_channels - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html - #[cfg(feature="methods")] - pub fn search(&self, f: F) -> Result - where F: FnOnce(Search) -> Search { - #[cfg(feature="cache")] - { - if CACHE.read().unwrap().user.bot { - return Err(Error::Client(ClientError::InvalidOperationAsBot)); - } - } - - rest::search_guild_messages(self.id.0, &[], f(Search::default()).0) - } - - /// Performs a search request to the API for the guild's [`Message`]s in - /// given channels. - /// - /// This will search all of the messages in the guild's provided - /// [`Channel`]s by Id that you have the [Read Message History] permission - /// to. Use [`search`] to search all of a guild's [channel][`GuildChannel`]s - /// at once. - /// - /// Refer to the documentation for the [`Search`] builder for examples and - /// more information. - /// - /// **Note**: Bot users can not search. - /// - /// # Errors - /// - /// If the `cache` is enabled, returns a - /// [`ClientError::InvalidOperationAsBot`] if the current user is a bot. - /// - /// [`ClientError::InvalidOperationAsBot`]: ../client/enum.ClientError.html#variant.InvalidOperationAsBot - /// [`Channel`]: enum.Channel.html - /// [`GuildChannel`]: struct.GuildChannel.html - /// [`Message`]: struct.Message.html - /// [`Search`]: ../utils/builder/struct.Search.html - /// [`search`]: #method.search - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html - #[cfg(feature="methods")] - pub fn search_channels(&self, channel_ids: &[ChannelId], f: F) - -> Result where F: FnOnce(Search) -> Search { - #[cfg(feature="cache")] - { - if CACHE.read().unwrap().user.bot { - return Err(Error::Client(ClientError::InvalidOperationAsBot)); - } - } - - let ids = channel_ids.iter().map(|x| x.0).collect::>(); - - rest::search_guild_messages(self.id.0, &ids, f(Search::default()).0) - } - +impl InviteGuild { /// Returns the formatted URL of the guild's splash image, if one exists. - #[cfg(feature="methods")] pub fn splash_url(&self) -> Option { self.icon.as_ref().map(|icon| format!(cdn!("/splashes/{}/{}.webp"), self.id, icon)) } - - /// Retrieves the guild's webhooks. - /// - /// **Note**: Requires the [Manage Webhooks] permission. - /// - /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html - #[cfg(feature="methods")] - #[inline] - pub fn webhooks(&self) -> Result> { - rest::get_guild_webhooks(self.id.0) - } } impl Guild { - #[cfg(all(feature="cache", feature="methods"))] + #[cfg(feature="cache")] fn has_perms(&self, mut permissions: Permissions) -> Result { let member = match self.members.get(&CACHE.read().unwrap().user.id) { Some(member) => member, @@ -277,8 +167,9 @@ impl Guild { } /// Ban a [`User`] from the guild. All messages by the - /// user within the last given number of days given will be deleted. This - /// may be a range between `0` and `7`. + /// user within the last given number of days given will be deleted. + /// + /// Refer to the documentation for [`Guild::ban`] for more information. /// /// **Note**: Requires the [Ban Members] permission. /// @@ -301,9 +192,9 @@ impl Guild { /// /// [`ClientError::DeleteMessageDaysAmount`]: ../client/enum.ClientError.html#variant.DeleteMessageDaysAmount /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions + /// [`Guild::ban`]: struct.Guild.html#method.ban /// [`User`]: struct.User.html /// [Ban Members]: permissions/constant.BAN_MEMBERS.html - #[cfg(feature="methods")] pub fn ban>(&self, user: U, delete_message_days: u8) -> Result<()> { if delete_message_days > 7 { @@ -319,7 +210,7 @@ impl Guild { } } - rest::ban_user(self.id.0, user.into().0, delete_message_days) + self.id.ban(user.into(), delete_message_days) } /// Retrieves a list of [`Ban`]s for the guild. @@ -334,7 +225,6 @@ impl Guild { /// [`Ban`]: struct.Ban.html /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions /// [Ban Members]: permissions/constant.BAN_MEMBERS.html - #[cfg(feature="methods")] pub fn bans(&self) -> Result> { #[cfg(feature="cache")] { @@ -345,7 +235,43 @@ impl Guild { } } - rest::get_bans(self.id.0) + self.id.get_bans() + } + + /// Creates a guild with the data provided. + /// + /// Only a [`PartialGuild`] will be immediately returned, and a full + /// [`Guild`] will be received over a [`Shard`]. + /// + /// **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: + /// + /// ```rust,ignore + /// use serenity::model::{Guild, Region}; + /// + /// let _guild = Guild::create_guild("test", Region::UsWest, None); + /// ``` + /// + /// [`Guild`]: struct.Guild.html + /// [`PartialGuild`]: struct.PartialGuild.html + /// [`Shard`]: ../gateway/struct.Shard.html + /// [US West region]: enum.Region.html#variant.UsWest + /// [whitelist]: https://discordapp.com/developers/docs/resources/guild#create-guild + pub fn create(name: &str, region: Region, icon: Option<&str>) + -> Result { + let map = ObjectBuilder::new() + .insert("icon", icon) + .insert("name", name) + .insert("region", region.name()) + .build(); + + rest::create_guild(map) } /// Creates a new [`Channel`] in the guild. @@ -370,7 +296,6 @@ impl Guild { /// [`Channel`]: struct.Channel.html /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions /// [Manage Channels]: permissions/constants.MANAGE_CHANNELS.html - #[cfg(feature="methods")] pub fn create_channel(&mut self, name: &str, kind: ChannelType) -> Result { #[cfg(feature="cache")] @@ -382,20 +307,58 @@ impl Guild { } } - let map = ObjectBuilder::new() - .insert("name", name) - .insert("type", kind.name()) - .build(); + self.id.create_channel(name, kind) + } - rest::create_channel(self.id.0, map) + /// Creates an emoji in the 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. + /// + /// The name of the emoji must be at least 2 characters long and can only + /// contain alphanumeric characters and underscores. + /// + /// 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]: permissions/constant.MANAGE_EMOJIS.html + #[inline] + pub fn create_emoji(&self, name: &str, image: &str) -> Result { + self.id.create_emoji(name, image) } - /// Creates a new [`Role`] in the guild with the data set, if any. + /// Creates an integration for the guild. /// - /// See the documentation for [`Context::create_role`] on how to use this. + /// Requires the [Manage Guild] permission. + /// + /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + #[inline] + pub fn create_integration(&self, integration_id: I, kind: &str) -> Result<()> + where I: Into { + self.id.create_integration(integration_id, kind) + } + + /// Creates a new role in the guild with the data set, if any. /// /// **Note**: Requires the [Manage Roles] permission. /// + /// # Examples + /// + /// Create a role which can be mentioned, with the name 'test': + /// + /// ```rust,ignore + /// let role = context.create_role(guild_id, |r| r + /// .hoist(true) + /// .name("role")); + /// ``` + /// /// # Errors /// /// If the `cache` is enabled, returns a [`ClientError::InvalidPermissions`] @@ -405,7 +368,6 @@ impl Guild { /// [`Context::create_role`]: ../client/struct.Context.html#method.create_role /// [`Role`]: struct.Role.html /// [Manage Roles]: permissions/constants.MANAGE_ROLES.html - #[cfg(feature="methods")] pub fn create_role(&self, f: F) -> Result where F: FnOnce(EditRole) -> EditRole { #[cfg(feature="cache")] @@ -417,7 +379,7 @@ impl Guild { } } - rest::create_role(self.id.0, f(EditRole::default()).0.build()) + self.id.create_role(f) } #[doc(hidden)] @@ -464,8 +426,7 @@ impl Guild { }) } - - /// Deletes the current guild if the current account is the owner of the + /// Deletes the current guild if the current user is the owner of the /// guild. /// /// **Note**: Requires the current user to be the owner of the guild. @@ -476,7 +437,6 @@ impl Guild { /// if the current user is not the guild owner. /// /// [`ClientError::InvalidUser`]: ../client/enum.ClientError.html#variant.InvalidUser - #[cfg(feature="methods")] pub fn delete(&self) -> Result { #[cfg(feature="cache")] { @@ -487,15 +447,68 @@ impl Guild { } } - rest::delete_guild(self.id.0) + self.id.delete() + } + + /// Deletes an [`Emoji`] from the guild. + /// + /// Requires the [Manage Emojis] permission. + /// + /// [`Emoji`]: struct.Emoji.html + /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + #[inline] + pub fn delete_emoji>(&self, emoji_id: E) -> Result<()> { + self.id.delete_emoji(emoji_id) + } + + /// Deletes an integration by Id from the guild. + /// + /// Requires the [Manage Guild] permission. + /// + /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + #[inline] + pub fn delete_integration>(&self, integration_id: I) -> Result<()> { + self.id.delete_integration(integration_id) + } + + /// Deletes a [`Role`] by Id from the guild. + /// + /// Also see [`Role::delete`] if you have the `cache` and `methods` features + /// enabled. + /// + /// Requires the [Manage Roles] permission. + /// + /// [`Role`]: struct.Role.html + /// [`Role::delete`]: struct.Role.html#method.delete + /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + #[inline] + pub fn delete_role>(&self, role_id: R) -> Result<()> { + self.id.delete_role(role_id) } - /// Edits the current guild with new data where specified. See the - /// documentation for [`Context::edit_guild`] on how to use this. + /// Edits the current guild with new data where specified. + /// + /// Refer to `EditGuild`'s documentation for a full list of methods. + /// + /// Also see [`Guild::edit`] if you have the `methods` feature enabled. /// /// **Note**: Requires the current user to have the [Manage Guild] /// permission. /// + /// # Examples + /// + /// Change a guild's icon using a file name "icon.png": + /// + /// ```rust,ignore + /// use serenity::utils; + /// + /// // We are using read_image helper function from utils. + /// let base64_icon = utils::read_image("./icon.png") + /// .expect("Failed to read image"); + /// + /// guild.edit(|g| g.icon(base64_icon)); + /// ``` + /// /// # Errors /// /// If the `cache` is enabled, returns a [`ClientError::InvalidPermissions`] @@ -504,7 +517,6 @@ impl Guild { /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions /// [`Context::edit_guild`]: ../client/struct.Context.html#method.edit_guild /// [Manage Guild]: permissions/constants.MANAGE_GUILD.html - #[cfg(feature="methods")] pub fn edit(&mut self, f: F) -> Result<()> where F: FnOnce(EditGuild) -> EditGuild { #[cfg(feature="cache")] @@ -516,9 +528,7 @@ impl Guild { } } - let map = f(EditGuild::default()).0.build(); - - match rest::edit_guild(self.id.0, map) { + match self.id.edit(f) { Ok(guild) => { self.afk_channel_id = guild.afk_channel_id; self.afk_timeout = guild.afk_timeout; @@ -540,6 +550,42 @@ impl Guild { } } + /// Edits an [`Emoji`]'s name in the guild. + /// + /// Also see [`Emoji::edit`] if you have the `cache` and `methods` features + /// enabled. + /// + /// Requires the [Manage Emojis] permission. + /// + /// [`Emoji`]: struct.Emoji.html + /// [`Emoji::edit`]: struct.Emoji.html#method.edit + /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + #[inline] + pub fn edit_emoji>(&self, emoji_id: E, name: &str) -> Result { + self.id.edit_emoji(emoji_id, name) + } + + /// Edits the properties of member of the guild, such as muting or + /// nicknaming them. + /// + /// Refer to `EditMember`'s documentation for a full list of methods and + /// permission restrictions. + /// + /// # Examples + /// + /// Mute a member and set their roles to just one role with a predefined Id: + /// + /// ```rust,ignore + /// guild.edit_member(user_id, |m| m + /// .mute(true) + /// .roles(&vec![role_id])); + /// ``` + #[inline] + pub fn edit_member(&self, user_id: U, f: F) -> Result<()> + where F: FnOnce(EditMember) -> EditMember, U: Into { + self.id.edit_member(user_id, f) + } + /// Edits the current user's nickname for the guild. /// /// Pass `None` to reset the nickname. @@ -554,7 +600,6 @@ impl Guild { /// /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions /// [Change Nickname]: permissions/constant.CHANGE_NICKNAME.html - #[cfg(feature="methods")] pub fn edit_nickname(&self, new_nickname: Option<&str>) -> Result<()> { #[cfg(feature="cache")] { @@ -565,7 +610,78 @@ impl Guild { } } - rest::edit_nickname(self.id.0, new_nickname) + self.id.edit_nickname(new_nickname) + } + + /// Edits a role, optionally setting its fields. + /// + /// Requires the [Manage Roles] permission. + /// + /// # Examples + /// + /// Make a role hoisted: + /// + /// ```rust,ignore + /// guild.edit_role(RoleId(7), |r| r.hoist(true)); + /// ``` + /// + /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + #[inline] + pub fn edit_role(&self, role_id: R, f: F) -> Result + where F: FnOnce(EditRole) -> EditRole, R: Into { + self.id.edit_role(role_id, f) + } + + /// Gets a partial amount of guild data by its Id. + /// + /// Requires that the current user be in the guild. + #[inline] + pub fn get>(guild_id: G) -> Result { + guild_id.into().get() + } + + /// Gets a list of the guild's bans. + /// + /// Requires the [Ban Members] permission. + #[inline] + pub fn get_bans(&self) -> Result> { + self.id.get_bans() + } + + /// Gets all of the guild's channels over the REST API. + /// + /// [`Guild`]: struct.Guild.html + #[inline] + pub fn get_channels(&self) -> Result> { + self.id.get_channels() + } + + /// Gets an emoji in the guild by Id. + /// + /// Requires the [Manage Emojis] permission. + /// + /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + #[inline] + pub fn get_emoji>(&self, emoji_id: E) -> Result { + self.id.get_emoji(emoji_id) + } + + /// Gets a list of all of the guild's emojis. + /// + /// Requires the [Manage Emojis] permission. + /// + /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + #[inline] + pub fn get_emojis(&self) -> Result> { + self.id.get_emojis() + } + + /// Gets all integration of the guild. + /// + /// This performs a request over the REST API. + #[inline] + pub fn get_integrations(&self) -> Result> { + self.id.get_integrations() } /// Retrieves the active invites for the guild. @@ -579,7 +695,6 @@ impl Guild { /// /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html - #[cfg(feature="methods")] pub fn get_invites(&self) -> Result> { #[cfg(feature="cache")] { @@ -590,16 +705,38 @@ impl Guild { } } - rest::get_guild_invites(self.id.0) + self.id.get_invites() } - /// Retrieves the first [`Member`] found that matches the name - with an - /// optional discriminator - provided. + /// Gets a user's [`Member`] for the guild by Id. /// - /// Searching with a discriminator given is the most precise form of lookup, - /// as no two people can share the same username *and* discriminator. - /// - /// If a member can not be found by username or username#discriminator, + /// [`Guild`]: struct.Guild.html + /// [`Member`]: struct.Member.html + #[inline] + pub fn get_member>(&self, user_id: U) -> Result { + self.id.get_member(user_id) + } + + /// Gets a list of the guild's members. + /// + /// Optionally pass in the `limit` to limit the number of results. Maximum + /// value is 1000. Optionally pass in `after` to offset the results by a + /// [`User`]'s Id. + /// + /// [`User`]: struct.User.html + #[inline] + pub fn get_members(&self, limit: Option, after: Option) + -> Result> where U: Into { + self.id.get_members(limit, after) + } + + /// Retrieves the first [`Member`] found that matches the name - with an + /// optional discriminator - provided. + /// + /// Searching with a discriminator given is the most precise form of lookup, + /// as no two people can share the same username *and* discriminator. + /// + /// If a member can not be found by username or username#discriminator, /// then a search will be done for the nickname. When searching by nickname, /// the hash (`#`) and everything after it is included in the search. /// @@ -631,11 +768,40 @@ impl Guild { }; name_matches && discrim_matches - }).or(self.members.iter().find(|&(_member_id, member)| { + }).or_else(|| self.members.iter().find(|&(_member_id, member)| { member.nick.as_ref().map_or(false, |nick| nick == name) })).map(|(_member_id, member)| member) } + /// Retrieves the count of the number of [`Member`]s that would be pruned + /// with the number of given days. + /// + /// See the documentation on [`GuildPrune`] for more information. + /// + /// **Note**: Requires the [Kick Members] permission. + /// + /// # Errors + /// + /// If the `cache` is enabled, returns a [`ClientError::InvalidPermissions`] + /// if the current user does not have permission to perform bans. + /// + /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions + /// [`GuildPrune`]: struct.GuildPrune.html + /// [`Member`]: struct.Member.html + /// [Kick Members]: permissions/constant.KICK_MEMBERS.html + pub fn get_prune_count(&self, days: u16) -> Result { + #[cfg(feature="cache")] + { + let req = permissions::KICK_MEMBERS; + + if !self.has_perms(req)? { + return Err(Error::Client(ClientError::InvalidPermissions(req))); + } + } + + self.id.get_prune_count(days) + } + /// Returns the formatted URL of the guild's icon, if one exists. pub fn icon_url(&self) -> Option { self.icon.as_ref().map(|icon| @@ -644,14 +810,37 @@ impl Guild { /// Checks if the guild is 'large'. A guild is considered large if it has /// more than 250 members. + #[inline] pub fn is_large(&self) -> bool { self.members.len() > 250 } + /// Kicks a [`Member`] from the guild. + /// + /// Requires the [Kick Members] permission. + /// + /// [`Member`]: struct.Member.html + /// [Kick Members]: permissions/constant.KICK_MEMBERS.html + #[inline] + pub fn kick>(&self, user_id: U) -> Result<()> { + self.id.kick(user_id) + } + /// Leaves the guild. - #[cfg(feature="methods")] + #[inline] pub fn leave(&self) -> Result { - rest::leave_guild(self.id.0) + self.id.leave() + } + + /// Moves a member to a specific voice channel. + /// + /// Requires the [Move Members] permission. + /// + /// [Move Members]: permissions/constant.MOVE_MEMBERS.html + #[inline] + pub fn move_member(&self, user_id: U, channel_id: C) + -> Result<()> where C: Into, U: Into { + self.id.move_member(user_id, channel_id) } /// Calculate a [`User`]'s permissions in a given channel in the guild. @@ -770,40 +959,6 @@ impl Guild { permissions } - /// Retrieves the count of the number of [`Member`]s that would be pruned - /// with the number of given days. - /// - /// See the documentation on [`GuildPrune`] for more information. - /// - /// **Note**: Requires the [Kick Members] permission. - /// - /// # Errors - /// - /// If the `cache` is enabled, returns a [`ClientError::InvalidPermissions`] - /// if the current user does not have permission to perform bans. - /// - /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions - /// [`GuildPrune`]: struct.GuildPrune.html - /// [`Member`]: struct.Member.html - /// [Kick Members]: permissions/constant.KICK_MEMBERS.html - #[cfg(feature="methods")] - pub fn prune_count(&self, days: u16) -> Result { - #[cfg(feature="cache")] - { - let req = permissions::KICK_MEMBERS; - - if !self.has_perms(req)? { - return Err(Error::Client(ClientError::InvalidPermissions(req))); - } - } - - let map = ObjectBuilder::new() - .insert("days", days) - .build(); - - rest::get_guild_prune_count(self.id.0, map) - } - /// Performs a search request to the API for the guild's [`Message`]s. /// /// This will search all of the guild's [`Channel`]s at once, that you have @@ -828,9 +983,7 @@ impl Guild { /// [`Search`]: ../utils/builder/struct.Search.html /// [`search_channels`]: #method.search_channels /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html - #[cfg(feature="methods")] - pub fn search(&self, f: F) -> Result - where F: FnOnce(Search) -> Search { + pub fn search Search>(&self, f: F) -> Result { #[cfg(feature="cache")] { if CACHE.read().unwrap().user.bot { @@ -866,7 +1019,6 @@ impl Guild { /// [`Search`]: ../utils/builder/struct.Search.html /// [`search`]: #method.search /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html - #[cfg(feature = "methods")] pub fn search_channels(&self, channel_ids: &[ChannelId], f: F) -> Result where F: FnOnce(Search) -> Search { #[cfg(feature="cache")] @@ -876,18 +1028,25 @@ impl Guild { } } - let ids = channel_ids.iter().map(|x| x.0).collect::>(); - - rest::search_guild_messages(self.id.0, &ids, f(Search::default()).0) + self.id.search_channels(channel_ids, f) } /// Returns the formatted URL of the guild's splash image, if one exists. - #[cfg(feature="methods")] pub fn splash_url(&self) -> Option { self.icon.as_ref().map(|icon| format!(cdn!("/splashes/{}/{}.webp"), self.id, icon)) } + /// Starts an integration sync for the given integration Id. + /// + /// Requires the [Manage Guild] permission. + /// + /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + #[inline] + pub fn start_integration_sync>(&self, integration_id: I) -> Result<()> { + self.id.start_integration_sync(integration_id) + } + /// Starts a prune of [`Member`]s. /// /// See the documentation on [`GuildPrune`] for more information. @@ -903,7 +1062,6 @@ impl Guild { /// [`GuildPrune`]: struct.GuildPrune.html /// [`Member`]: struct.Member.html /// [Kick Members]: permissions/constant.KICK_MEMBERS.html - #[cfg(feature="methods")] pub fn start_prune(&self, days: u16) -> Result { #[cfg(feature="cache")] { @@ -914,11 +1072,7 @@ impl Guild { } } - let map = ObjectBuilder::new() - .insert("days", days) - .build(); - - rest::start_guild_prune(self.id.0, map) + self.id.start_prune(days) } /// Unbans the given [`User`] from the guild. @@ -933,8 +1087,7 @@ impl Guild { /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions /// [`User`]: struct.User.html /// [Ban Members]: permissions/constant.BAN_MEMBERS.html - #[cfg(feature="methods")] - pub fn unban>(&self, user: U) -> Result<()> { + pub fn unban>(&self, user_id: U) -> Result<()> { #[cfg(feature="cache")] { let req = permissions::BAN_MEMBERS; @@ -944,7 +1097,7 @@ impl Guild { } } - rest::remove_ban(self.id.0, user.into().0) + self.id.unban(user_id) } /// Retrieves the guild's webhooks. @@ -952,241 +1105,1279 @@ impl Guild { /// **Note**: Requires the [Manage Webhooks] permission. /// /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html - #[cfg(feature="methods")] #[inline] pub fn webhooks(&self) -> Result> { - rest::get_guild_webhooks(self.id.0) + self.id.webhooks() } } -impl Member { - /// Adds a [`Role`] to the member, editing its roles in-place if the request - /// was successful. +impl GuildId { + /// Ban a [`User`] from the guild. All messages by the + /// user within the last given number of days given will be deleted. /// - /// **Note**: Requires the [Manage Roles] permission. + /// Refer to the documentation for [`Guild::ban`] for more information. /// - /// [`Role`]: struct.Role.html - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html - #[cfg(all(feature="cache", feature="methods"))] - pub fn add_role>(&mut self, role_id: R) -> Result<()> { - let role_id = role_id.into(); - - if self.roles.contains(&role_id) { - return Ok(()); + /// **Note**: Requires the [Ban Members] permission. + /// + /// # Examples + /// + /// Ban a member and remove all messages they've sent in the last 4 days: + /// + /// ```rust,ignore + /// use serenity::model::GuildId; + /// + /// // assuming a `user` has already been bound + /// let _ = GuildId(81384788765712384).ban(user, 4); + /// ``` + /// + /// # Errors + /// + /// Returns a [`ClientError::DeleteMessageDaysAmount`] if the number of + /// days' worth of messages to delete is over the maximum. + /// + /// [`ClientError::DeleteMessageDaysAmount`]: ../client/enum.ClientError.html#variant.DeleteMessageDaysAmount + /// [`Guild::ban`]: struct.Guild.html#method.ban + /// [`User`]: struct.User.html + /// [Ban Members]: permissions/constant.BAN_MEMBERS.html + pub fn ban>(&self, user: U, delete_message_days: u8) + -> Result<()> { + if delete_message_days > 7 { + return Err(Error::Client(ClientError::DeleteMessageDaysAmount(delete_message_days))); } - let guild_id = self.find_guild()?; - - match rest::add_member_role(guild_id.0, self.user.id.0, role_id.0) { - Ok(()) => { - self.roles.push(role_id); - - Ok(()) - }, - Err(why) => Err(why), - } + rest::ban_user(self.0, user.into().0, delete_message_days) } - /// Adds one or multiple [`Role`]s to the member, editing - /// its roles in-place if the request was successful. + /// Creates a [`GuildChannel`] in the the guild. /// - /// **Note**: Requires the [Manage Roles] permission. + /// Refer to [`rest::create_channel`] for more information. /// - /// [`Role`]: struct.Role.html - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html - #[cfg(all(feature="cache", feature="methods"))] - pub fn add_roles(&mut self, role_ids: &[RoleId]) -> Result<()> { - let guild_id = self.find_guild()?; - self.roles.extend_from_slice(role_ids); - - let map = EditMember::default().roles(&self.roles).0.build(); - - match rest::edit_member(guild_id.0, self.user.id.0, map) { - Ok(()) => Ok(()), - Err(why) => { - self.roles.retain(|r| !role_ids.contains(r)); + /// Requires the [Manage Channels] permission. + /// + /// # Examples + /// + /// Create a voice channel in a guild with the name `test`: + /// + /// ```rust,ignore + /// use serenity::model::{ChannelType, GuildId}; + /// + /// let _channel = GuildId(7).create_channel("test", ChannelType::Voice); + /// ``` + /// + /// [`GuildChannel`]: struct.GuildChannel.html + /// [`rest::create_channel`]: rest/fn.create_channel.html + /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html + pub fn create_channel(&self, name: &str, kind: ChannelType) -> Result { + let map = ObjectBuilder::new() + .insert("name", name) + .insert("type", kind.name()) + .build(); - Err(why) - } - } + rest::create_channel(self.0, map) } - /// Ban the member from its guild, deleting the last X number of - /// days' worth of messages. + /// Creates an emoji in the guild with a name and base64-encoded image. /// - /// **Note**: Requires the [Ban Members] role. + /// Refer to the documentation for [`Guild::create_emoji`] for more + /// information. /// - /// [Ban Members]: permissions/constant.BAN_MEMBERS.html - #[cfg(all(feature="cache", feature="methods"))] - pub fn ban(&self, delete_message_days: u8) -> Result<()> { - let guild_id = self.find_guild()?; + /// 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 + /// [`Guild::create_emoji`]: struct.Guild.html#method.create_emoji + /// [`utils::read_image`]: ../utils/fn.read_image.html + /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + pub fn create_emoji(&self, name: &str, image: &str) -> Result { + let map = ObjectBuilder::new() + .insert("name", name) + .insert("image", image) + .build(); - rest::ban_user(guild_id.0, - self.user.id.0, - delete_message_days) + rest::create_emoji(self.0, map) } - /// Determines the member's colour. - #[cfg(all(feature="cache", feature="methods"))] - pub fn colour(&self) -> Option { - let default = Colour::default(); - let guild_id = match self.find_guild() { - Ok(guild_id) => guild_id, - Err(_why) => return None, - }; - - let cache = CACHE.read().unwrap(); - let guild = match cache.guilds.get(&guild_id) { - Some(guild) => guild, - None => return None, - }; - - let mut roles = self.roles - .iter() - .filter_map(|id| guild.roles.get(id)) - .collect::>(); - roles.sort_by(|a, b| b.cmp(a)); + /// Creates an integration for the guild. + /// + /// Requires the [Manage Guild] permission. + /// + /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + pub fn create_integration(&self, integration_id: I, kind: &str) + -> Result<()> where I: Into { + let integration_id = integration_id.into(); + let map = ObjectBuilder::new() + .insert("id", integration_id.0) + .insert("type", kind) + .build(); - roles.iter().find(|r| r.colour.0 != default.0).map(|r| r.colour) + rest::create_guild_integration(self.0, integration_id.0, map) } - /// Calculates the member's display name. + /// Creates a new role in the guild with the data set, if any. /// - /// The nickname takes priority over the member's username if it exists. - pub fn display_name(&self) -> &str { - self.nick.as_ref().unwrap_or(&self.user.name) + /// See the documentation for [`Guild::create_role`] on how to use this. + /// + /// **Note**: Requires the [Manage Roles] permission. + /// + /// [`Guild::create_role`]: struct.Guild.html#method.create_role + /// [Manage Roles]: permissions/constants.MANAGE_ROLES.html + #[inline] + pub fn create_role EditRole>(&self, f: F) -> Result { + rest::create_role(self.0, f(EditRole::default()).0.build()) } - /// Returns the DiscordTag of a Member, taking possible nickname into account. - #[cfg(feature="methods")] - pub fn distinct(&self) -> String { - format!("{}#{}", self.display_name(), self.user.discriminator) + /// Deletes the current guild if the current account is the owner of the + /// guild. + /// + /// Refer to [`Guild::delete`] for more information. + /// + /// **Note**: Requires the current user to be the owner of the guild. + /// + /// [`Guild::delete`]: struct.Guild.html#method.delete + #[inline] + pub fn delete(&self) -> Result { + rest::delete_guild(self.0) } - /// Edits the member with the given data. See [`Context::edit_member`] for - /// more information. + /// Deletes an [`Emoji`] from the guild. /// - /// See [`EditMember`] for the permission(s) required for separate builder - /// methods, as well as usage of this. + /// Requires the [Manage Emojis] permission. /// - /// [`Context::edit_member`]: ../client/struct.Context.html#method.edit_member - /// [`EditMember`]: ../builder/struct.EditMember.html - #[cfg(all(feature="cache", feature="methods"))] - pub fn edit(&self, f: F) -> Result<()> - where F: FnOnce(EditMember) -> EditMember { - let guild_id = self.find_guild()?; - let map = f(EditMember::default()).0.build(); + /// [`Emoji`]: struct.Emoji.html + /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + #[inline] + pub fn delete_emoji>(&self, emoji_id: E) -> Result<()> { + rest::delete_emoji(self.0, emoji_id.into().0) + } - rest::edit_member(guild_id.0, self.user.id.0, map) + /// Deletes an integration by Id from the guild. + /// + /// Requires the [Manage Guild] permission. + /// + /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + #[inline] + pub fn delete_integration>(&self, integration_id: I) -> Result<()> { + rest::delete_guild_integration(self.0, integration_id.into().0) } - /// Finds the Id of the [`Guild`] that the member is in. + /// Deletes a [`Role`] by Id from the guild. + /// + /// Also see [`Role::delete`] if you have the `cache` and `methods` features + /// enabled. + /// + /// Requires the [Manage Roles] permission. + /// + /// [`Role`]: struct.Role.html + /// [`Role::delete`]: struct.Role.html#method.delete + /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + #[inline] + pub fn delete_role>(&self, role_id: R) -> Result<()> { + rest::delete_role(self.0, role_id.into().0) + } + + /// Edits the current guild with new data where specified. + /// + /// Refer to [`Guild::edit`] for more information. + /// + /// **Note**: Requires the current user to have the [Manage Guild] + /// permission. + /// + /// [`Guild::edit`]: struct.Guild.html#method.edit + /// [Manage Guild]: permissions/constants.MANAGE_GUILD.html + #[inline] + pub fn edit EditGuild>(&mut self, f: F) -> Result { + rest::edit_guild(self.0, f(EditGuild::default()).0.build()) + } + + /// Edits an [`Emoji`]'s name in the guild. + /// + /// Also see [`Emoji::edit`] if you have the `cache` and `methods` features + /// enabled. + /// + /// Requires the [Manage Emojis] permission. + /// + /// [`Emoji`]: struct.Emoji.html + /// [`Emoji::edit`]: struct.Emoji.html#method.edit + /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + pub fn edit_emoji>(&self, emoji_id: E, name: &str) -> Result { + let map = ObjectBuilder::new() + .insert("name", name) + .build(); + + rest::edit_emoji(self.0, emoji_id.into().0, map) + } + + /// Edits the properties of member of the guild, such as muting or + /// nicknaming them. + /// + /// Refer to `EditMember`'s documentation for a full list of methods and + /// permission restrictions. + /// + /// # Examples + /// + /// Mute a member and set their roles to just one role with a predefined Id: + /// + /// ```rust,ignore + /// guild.edit_member(user_id, |m| m + /// .mute(true) + /// .roles(&vec![role_id])); + /// ``` + #[inline] + pub fn edit_member(&self, user_id: U, f: F) -> Result<()> + where F: FnOnce(EditMember) -> EditMember, U: Into { + rest::edit_member(self.0, user_id.into().0, f(EditMember::default()).0.build()) + } + + /// Edits the current user's nickname for the guild. + /// + /// Pass `None` to reset the nickname. + /// + /// Requires the [Change Nickname] permission. + /// + /// [Change Nickname]: permissions/constant.CHANGE_NICKNAME.html + #[inline] + pub fn edit_nickname(&self, new_nickname: Option<&str>) -> Result<()> { + rest::edit_nickname(self.0, new_nickname) + } + + /// Edits a [`Role`], optionally setting its new fields. + /// + /// Requires the [Manage Roles] permission. + /// + /// # Examples + /// + /// Make a role hoisted: + /// + /// ```rust,ignore + /// use serenity::model::{GuildId, RoleId}; + /// + /// GuildId(7).edit_role(RoleId(8), |r| r.hoist(true)); + /// ``` + /// + /// [`Role`]: struct.Role.html + /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + #[inline] + pub fn edit_role(&self, role_id: R, f: F) -> Result + where F: FnOnce(EditRole) -> EditRole, R: Into { + rest::edit_role(self.0, role_id.into().0, f(EditRole::default()).0.build()) + } + + /// Search the cache for the guild. + #[cfg(feature="cache")] + pub fn find(&self) -> Option { + CACHE.read().unwrap().get_guild(*self).cloned() + } + + /// Requests the guild over REST. + /// + /// Note that this will not be a complete guild, as REST does not send + /// all data with a guild retrieval. + #[inline] + pub fn get(&self) -> Result { + rest::get_guild(self.0) + } + + /// Gets a list of the guild's bans. + /// + /// Requires the [Ban Members] permission. + #[inline] + pub fn get_bans(&self) -> Result> { + rest::get_bans(self.0) + } + + /// Gets all of the guild's channels over the REST API. + /// + /// [`Guild`]: struct.Guild.html + pub fn get_channels(&self) -> Result> { + let mut channels = HashMap::new(); + + for channel in rest::get_channels(self.0)? { + channels.insert(channel.id, channel); + } + + Ok(channels) + } + + /// Gets an emoji in the guild by Id. + /// + /// Requires the [Manage Emojis] permission. + /// + /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + #[inline] + pub fn get_emoji>(&self, emoji_id: E) -> Result { + rest::get_emoji(self.0, emoji_id.into().0) + } + + /// Gets a list of all of the guild's emojis. + /// + /// Requires the [Manage Emojis] permission. + /// + /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + #[inline] + pub fn get_emojis(&self) -> Result> { + rest::get_emojis(self.0) + } + + /// Gets all integration of the guild. + /// + /// This performs a request over the REST API. + #[inline] + pub fn get_integrations(&self) -> Result> { + rest::get_guild_integrations(self.0) + } + + /// Gets all of the guild's invites. + /// + /// Requires the [Manage Guild] permission. + /// + /// [Manage Guild]: permissions/struct.MANAGE_GUILD.html + #[inline] + pub fn get_invites(&self) -> Result> { + rest::get_guild_invites(self.0) + } + + /// Gets a user's [`Member`] for the guild by Id. + /// + /// [`Guild`]: struct.Guild.html + /// [`Member`]: struct.Member.html + #[inline] + pub fn get_member>(&self, user_id: U) -> Result { + rest::get_member(self.0, user_id.into().0) + } + + /// Gets a list of the guild's members. + /// + /// Optionally pass in the `limit` to limit the number of results. Maximum + /// value is 1000. Optionally pass in `after` to offset the results by a + /// [`User`]'s Id. + /// + /// [`User`]: struct.User.html + #[inline] + pub fn get_members(&self, limit: Option, after: Option) + -> Result> where U: Into { + rest::get_guild_members(self.0, limit, after.map(|x| x.into().0)) + } + + /// Gets the number of [`Member`]s that would be pruned with the given + /// number of days. + /// + /// Requires the [Kick Members] permission. + /// + /// [`Member`]: struct.Member.html + /// [Kick Members]: permissions/constant.KICK_MEMBERS.html + pub fn get_prune_count(&self, days: u16) -> Result { + let map = ObjectBuilder::new() + .insert("days", days) + .build(); + + rest::get_guild_prune_count(self.0, map) + } + + /// Kicks a [`Member`] from the guild. + /// + /// Requires the [Kick Members] permission. + /// + /// [`Member`]: struct.Member.html + /// [Kick Members]: permissions/constant.KICK_MEMBERS.html + #[inline] + pub fn kick>(&self, user_id: U) -> Result<()> { + rest::kick_member(self.0, user_id.into().0) + } + + /// Leaves the guild. + #[inline] + pub fn leave(&self) -> Result { + rest::leave_guild(self.0) + } + + /// Moves a member to a specific voice channel. + /// + /// Requires the [Move Members] permission. + /// + /// [Move Members]: permissions/constant.MOVE_MEMBERS.html + pub fn move_member(&self, user_id: U, channel_id: C) + -> Result<()> where C: Into, U: Into { + let map = ObjectBuilder::new() + .insert("channel_id", channel_id.into().0) + .build(); + + rest::edit_member(self.0, user_id.into().0, map) + } + + /// Performs a search request to the API for the guild's [`Message`]s. + /// + /// This will search all of the guild's [`Channel`]s at once, that you have + /// the [Read Message History] permission to. Use [`search_channels`] to + /// specify a list of [channel][`GuildChannel`]s to search, where all other + /// channels will be excluded. + /// + /// Refer to the documentation for the [`Search`] builder for examples and + /// more information. + /// + /// [`Channel`]: enum.Channel.html + /// [`GuildChannel`]: struct.GuildChannel.html + /// [`Message`]: struct.Message.html + /// [`Search`]: ../utils/builder/struct.Search.html + /// [`search_channels`]: #method.search_channels + /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + #[inline] + pub fn search Search>(&self, f: F) -> Result { + rest::search_guild_messages(self.0, &[], f(Search::default()).0) + } + + /// Performs a search request to the API for the guild's [`Message`]s in + /// given channels. + /// + /// Refer to [`Guild::search_channels`] for more information. + /// + /// Refer to the documentation for the [`Search`] builder for examples and + /// more information. + /// + /// **Note**: Bot users can not search. + /// + /// [`Guild::search_channels`]: struct.Guild.html#method.search_channels + /// [`Message`]: struct.Message.html + pub fn search_channels(&self, channel_ids: &[ChannelId], f: F) + -> Result where F: FnOnce(Search) -> Search { + let ids = channel_ids.iter().map(|x| x.0).collect::>(); + + rest::search_guild_messages(self.0, &ids, f(Search::default()).0) + } + + /// Starts an integration sync for the given integration Id. + /// + /// Requires the [Manage Guild] permission. + /// + /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + #[inline] + pub fn start_integration_sync>(&self, integration_id: I) -> Result<()> { + rest::start_integration_sync(self.0, integration_id.into().0) + } + + /// Starts a prune of [`Member`]s. + /// + /// See the documentation on [`GuildPrune`] for more information. + /// + /// **Note**: Requires the [Kick Members] permission. + /// + /// [`GuildPrune`]: struct.GuildPrune.html + /// [`Member`]: struct.Member.html + /// [Kick Members]: permissions/constant.KICK_MEMBERS.html + pub fn start_prune(&self, days: u16) -> Result { + let map = ObjectBuilder::new() + .insert("days", days) + .build(); + + rest::start_guild_prune(self.0, map) + } + + /// Unbans a [`User`] from the guild. + /// + /// Requires the [Ban Members] permission. + /// + /// [`User`]: struct.User.html + /// [Ban Members]: permissions/constant.BAN_MEMBERS.html + #[inline] + pub fn unban>(&self, user_id: U) -> Result<()> { + rest::remove_ban(self.0, user_id.into().0) + } + + /// Retrieves the guild's webhooks. + /// + /// **Note**: Requires the [Manage Webhooks] permission. + /// + /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html + #[inline] + pub fn webhooks(&self) -> Result> { + rest::get_guild_webhooks(self.0) + } +} + +impl fmt::Display for GuildId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +impl From for GuildId { + /// Gets the Id of a partial guild. + fn from(guild: PartialGuild) -> GuildId { + guild.id + } +} + +impl From for GuildId { + /// Gets the Id of Guild information struct. + fn from(guild_info: GuildInfo) -> GuildId { + guild_info.id + } +} + +impl From for GuildId { + /// Gets the Id of Invite Guild struct. + fn from(invite_guild: InviteGuild) -> GuildId { + invite_guild.id + } +} + +impl From for GuildId { + /// Gets the Id of Guild. + fn from(live_guild: Guild) -> GuildId { + live_guild.id + } +} + +impl From for IntegrationId { + /// Gets the Id of integration. + fn from(integration: Integration) -> IntegrationId { + integration.id + } +} + +impl Member { + /// Adds a [`Role`] to the member, editing its roles in-place if the request + /// was successful. + /// + /// **Note**: Requires the [Manage Roles] permission. + /// + /// [`Role`]: struct.Role.html + /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + #[cfg(feature="cache")] + pub fn add_role>(&mut self, role_id: R) -> Result<()> { + let role_id = role_id.into(); + + if self.roles.contains(&role_id) { + return Ok(()); + } + + let guild_id = self.find_guild()?; + + match rest::add_member_role(guild_id.0, self.user.id.0, role_id.0) { + Ok(()) => { + self.roles.push(role_id); + + Ok(()) + }, + Err(why) => Err(why), + } + } + + /// Adds one or multiple [`Role`]s to the member, editing + /// its roles in-place if the request was successful. + /// + /// **Note**: Requires the [Manage Roles] permission. + /// + /// [`Role`]: struct.Role.html + /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + #[cfg(feature="cache")] + pub fn add_roles(&mut self, role_ids: &[RoleId]) -> Result<()> { + let guild_id = self.find_guild()?; + self.roles.extend_from_slice(role_ids); + + let map = EditMember::default().roles(&self.roles).0.build(); + + match rest::edit_member(guild_id.0, self.user.id.0, map) { + Ok(()) => Ok(()), + Err(why) => { + self.roles.retain(|r| !role_ids.contains(r)); + + Err(why) + } + } + } + + /// Ban the member from its guild, deleting the last X number of + /// days' worth of messages. + /// + /// **Note**: Requires the [Ban Members] role. + /// + /// # Errors + /// + /// Returns a [`ClientError::GuildNotFound`] if the guild could not be + /// found. + /// + /// [`ClientError::GuildNotFound`]: ../client/enum.ClientError.html#variant.GuildNotFound + /// + /// [Ban Members]: permissions/constant.BAN_MEMBERS.html + #[cfg(feature="cache")] + pub fn ban(&self, delete_message_days: u8) -> Result<()> { + rest::ban_user(self.find_guild()?.0, self.user.id.0, delete_message_days) + } + + /// Determines the member's colour. + #[cfg(feature="cache")] + pub fn colour(&self) -> Option { + let guild_id = match self.find_guild() { + Ok(guild_id) => guild_id, + Err(_) => return None, + }; + + let cache = CACHE.read().unwrap(); + let guild = match cache.guilds.get(&guild_id) { + Some(guild) => guild, + None => return None, + }; + + let mut roles = self.roles + .iter() + .filter_map(|id| guild.roles.get(id)) + .collect::>(); + roles.sort_by(|a, b| b.cmp(a)); + + let default = Colour::default(); + + roles.iter().find(|r| r.colour.0 != default.0).map(|r| r.colour) + } + + /// Calculates the member's display name. + /// + /// The nickname takes priority over the member's username if it exists. + #[inline] + pub fn display_name(&self) -> &str { + self.nick.as_ref().unwrap_or(&self.user.name) + } + + /// Returns the DiscordTag of a Member, taking possible nickname into account. + #[inline] + pub fn distinct(&self) -> String { + format!("{}#{}", self.display_name(), self.user.discriminator) + } + + /// Edits the member with the given data. See [`Context::edit_member`] for + /// more information. + /// + /// See [`EditMember`] for the permission(s) required for separate builder + /// methods, as well as usage of this. + /// + /// [`Context::edit_member`]: ../client/struct.Context.html#method.edit_member + /// [`EditMember`]: ../builder/struct.EditMember.html + #[cfg(feature="cache")] + pub fn edit(&self, f: F) -> Result<()> + where F: FnOnce(EditMember) -> EditMember { + let guild_id = self.find_guild()?; + let map = f(EditMember::default()).0.build(); + + rest::edit_member(guild_id.0, self.user.id.0, map) + } + + /// Finds the Id of the [`Guild`] that the member is in. + /// + /// # Errors + /// + /// Returns a [`ClientError::GuildNotFound`] if the guild could not be + /// found. + /// + /// [`ClientError::GuildNotFound`]: ../client/enum.ClientError.html#variant.GuildNotFound + /// [`Guild`]: struct.Guild.html + #[cfg(feature="cache")] + pub fn find_guild(&self) -> Result { + CACHE.read() + .unwrap() + .guilds + .values() + .find(|guild| { + guild.members + .iter() + .any(|(_member_id, member)| { + let joined_at = member.joined_at == self.joined_at; + let user_id = member.user.id == self.user.id; + + joined_at && user_id + }) + }) + .map(|x| x.id) + .ok_or(Error::Client(ClientError::GuildNotFound)) + } + + /// Removes a [`Role`] from the member, editing its roles in-place if the + /// request was successful. + /// + /// **Note**: Requires the [Manage Roles] permission. + /// + /// [`Role`]: struct.Role.html + /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + #[cfg(feature="cache")] + pub fn remove_role>(&mut self, role_id: R) -> Result<()> { + let role_id = role_id.into(); + + if !self.roles.contains(&role_id) { + return Ok(()); + } + + let guild_id = self.find_guild()?; + + match rest::remove_member_role(guild_id.0, self.user.id.0, role_id.0) { + Ok(()) => { + self.roles.retain(|r| r.0 != role_id.0); + + Ok(()) + }, + Err(why) => Err(why), + } + } + + /// Removes one or multiple [`Role`]s from the member. + /// + /// **Note**: Requires the [Manage Roles] permission. + /// + /// [`Role`]: struct.Role.html + /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + #[cfg(feature="cache")] + pub fn remove_roles(&mut self, role_ids: &[RoleId]) -> Result<()> { + let guild_id = self.find_guild()?; + self.roles.retain(|r| !role_ids.contains(r)); + + let map = EditMember::default().roles(&self.roles).0.build(); + + match rest::edit_member(guild_id.0, self.user.id.0, map) { + Ok(()) => Ok(()), + Err(why) => { + self.roles.extend_from_slice(role_ids); + + Err(why) + }, + } + } + + /// Retrieves the full role data for the user's roles. + /// + /// This is shorthand for manually searching through the CACHE. + /// + /// If role data can not be found for the member, then `None` is returned. + #[cfg(feature="cache")] + pub fn roles(&self) -> Option> { + CACHE.read().unwrap() + .guilds + .values() + .find(|g| g.members + .values() + .any(|m| m.user.id == self.user.id && m.joined_at == *self.joined_at)) + .map(|g| g.roles + .values() + .filter(|r| self.roles.contains(&r.id)) + .cloned() + .collect()) + } + + /// Unbans the [`User`] from the guild. + /// + /// **Note**: Requires the [Ban Members] permission. + /// + /// # Errors + /// + /// If the `cache` is enabled, returns a [`ClientError::InvalidPermissions`] + /// if the current user does not have permission to perform bans. + /// + /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions + /// [`User`]: struct.User.html + /// [Ban Members]: permissions/constant.BAN_MEMBERS.html + #[cfg(feature="cache")] + pub fn unban(&self) -> Result<()> { + rest::remove_ban(self.find_guild()?.0, self.user.id.0) + } +} + +impl fmt::Display for Member { + /// Mentions the user so that they receive a notification. + /// + /// # Examples + /// + /// ```rust,ignore + /// // assumes a `member` has already been bound + /// println!("{} is a member!", member); + /// ``` + /// + // This is in the format of `<@USER_ID>`. + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.user.mention(), f) + } +} + +impl PartialGuild { + /// Ban a [`User`] from the guild. All messages by the + /// user within the last given number of days given will be deleted. This + /// may be a range between `0` and `7`. + /// + /// **Note**: Requires the [Ban Members] permission. + /// + /// # Examples + /// + /// Ban a member and remove all messages they've sent in the last 4 days: + /// + /// ```rust,ignore + /// // assumes a `user` and `guild` have already been bound + /// let _ = guild.ban(user, 4); + /// ``` + /// + /// # Errors + /// + /// Returns a [`ClientError::DeleteMessageDaysAmount`] if the number of + /// days' worth of messages to delete is over the maximum. + /// + /// [`ClientError::DeleteMessageDaysAmount`]: ../client/enum.ClientError.html#variant.DeleteMessageDaysAmount + /// [`User`]: struct.User.html + /// [Ban Members]: permissions/constant.BAN_MEMBERS.html + pub fn ban>(&self, user: U, delete_message_days: u8) + -> Result<()> { + if delete_message_days > 7 { + return Err(Error::Client(ClientError::DeleteMessageDaysAmount(delete_message_days))); + } + + self.id.ban(user.into(), delete_message_days) + } + + /// Creates a [`GuildChannel`] in the guild. + /// + /// Refer to [`rest::create_channel`] for more information. + /// + /// Requires the [Manage Channels] permission. + /// + /// # Examples + /// + /// Create a voice channel in a guild with the name `test`: + /// + /// ```rust,ignore + /// use serenity::model::ChannelType; + /// + /// guild.create_channel("test", ChannelType::Voice); + /// ``` + /// + /// [`GuildChannel`]: struct.GuildChannel.html + /// [`rest::create_channel`]: rest/fn.create_channel.html + /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html + #[inline] + pub fn create_channel(&self, name: &str, kind: ChannelType) -> Result { + self.id.create_channel(name, kind) + } + + /// Creates an emoji in the guild with a name and base64-encoded image. + /// + /// Refer to the documentation for [`Guild::create_emoji`] for more + /// information. + /// + /// 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 + /// [`Guild::create_emoji`]: struct.Guild.html#method.create_emoji + /// [`utils::read_image`]: ../utils/fn.read_image.html + /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + #[inline] + pub fn create_emoji(&self, name: &str, image: &str) -> Result { + self.id.create_emoji(name, image) + } + + /// Creates an integration for the guild. + /// + /// Requires the [Manage Guild] permission. + /// + /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + #[inline] + pub fn create_integration(&self, integration_id: I, kind: &str) -> Result<()> + where I: Into { + self.id.create_integration(integration_id, kind) + } + + /// Creates a new role in the guild with the data set, if any. + /// + /// See the documentation for [`Guild::create_role`] on how to use this. + /// + /// **Note**: Requires the [Manage Roles] permission. + /// + /// # Errors + /// + /// If the `cache` is enabled, returns a [`ClientError::InvalidPermissions`] + /// if the current user does not have permission to perform bans. + /// + /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions + /// [`Guild::create_role`]: struct.Guild.html#method.create_role + /// [Manage Roles]: permissions/constants.MANAGE_ROLES.html + #[inline] + pub fn create_role EditRole>(&self, f: F) -> Result { + self.id.create_role(f) + } + + /// Deletes the current guild if the current user is the owner of the + /// guild. + /// + /// **Note**: Requires the current user to be the owner of the guild. + #[inline] + pub fn delete(&self) -> Result { + self.id.delete() + } + + /// Deletes an [`Emoji`] from the guild. + /// + /// Requires the [Manage Emojis] permission. + /// + /// [`Emoji`]: struct.Emoji.html + /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + #[inline] + pub fn delete_emoji>(&self, emoji_id: E) -> Result<()> { + self.id.delete_emoji(emoji_id) + } + + /// Deletes an integration by Id from the guild. + /// + /// Requires the [Manage Guild] permission. + /// + /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + #[inline] + pub fn delete_integration>(&self, integration_id: I) -> Result<()> { + self.id.delete_integration(integration_id) + } + + /// Deletes a [`Role`] by Id from the guild. + /// + /// Also see [`Role::delete`] if you have the `cache` and `methods` features + /// enabled. + /// + /// Requires the [Manage Roles] permission. + /// + /// [`Role`]: struct.Role.html + /// [`Role::delete`]: struct.Role.html#method.delete + /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + #[inline] + pub fn delete_role>(&self, role_id: R) -> Result<()> { + self.id.delete_role(role_id) + } + + /// Edits the current guild with new data where specified. + /// + /// Refer to [`Guild::edit`] for more information. + /// + /// **Note**: Requires the current user to have the [Manage Guild] + /// permission. + /// + /// [`Context::edit_guild`]: ../client/struct.Context.html#method.edit_guild + /// [Manage Guild]: permissions/constants.MANAGE_GUILD.html + pub fn edit(&mut self, f: F) -> Result<()> + where F: FnOnce(EditGuild) -> EditGuild { + match self.id.edit(f) { + Ok(guild) => { + self.afk_channel_id = guild.afk_channel_id; + self.afk_timeout = guild.afk_timeout; + self.default_message_notifications = guild.default_message_notifications; + self.emojis = guild.emojis; + self.features = guild.features; + self.icon = guild.icon; + self.mfa_level = guild.mfa_level; + self.name = guild.name; + self.owner_id = guild.owner_id; + self.region = guild.region; + self.roles = guild.roles; + self.splash = guild.splash; + self.verification_level = guild.verification_level; + + Ok(()) + }, + Err(why) => Err(why), + } + } + + /// Edits an [`Emoji`]'s name in the guild. + /// + /// Also see [`Emoji::edit`] if you have the `cache` and `methods` features + /// enabled. + /// + /// Requires the [Manage Emojis] permission. + /// + /// [`Emoji`]: struct.Emoji.html + /// [`Emoji::edit`]: struct.Emoji.html#method.edit + /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + #[inline] + pub fn edit_emoji>(&self, emoji_id: E, name: &str) -> Result { + self.id.edit_emoji(emoji_id, name) + } + + /// Edits the properties of member of the guild, such as muting or + /// nicknaming them. + /// + /// Refer to `EditMember`'s documentation for a full list of methods and + /// permission restrictions. + /// + /// # Examples + /// + /// Mute a member and set their roles to just one role with a predefined Id: + /// + /// ```rust,ignore + /// use serenity::model::GuildId; + /// + /// GuildId(7).edit_member(user_id, |m| m + /// .mute(true) + /// .roles(&vec![role_id])); + /// ``` + #[inline] + pub fn edit_member(&self, user_id: U, f: F) -> Result<()> + where F: FnOnce(EditMember) -> EditMember, U: Into { + self.id.edit_member(user_id, f) + } + + /// Edits the current user's nickname for the guild. + /// + /// Pass `None` to reset the nickname. + /// + /// **Note**: Requires the [Change Nickname] permission. + /// + /// # Errors + /// + /// If the `cache` is enabled, returns a [`ClientError::InvalidPermissions`] + /// if the current user does not have permission to change their own + /// nickname. + /// + /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions + /// [Change Nickname]: permissions/constant.CHANGE_NICKNAME.html + #[inline] + pub fn edit_nickname(&self, new_nickname: Option<&str>) -> Result<()> { + self.id.edit_nickname(new_nickname) + } + + /// Gets a partial amount of guild data by its Id. + /// + /// Requires that the current user be in the guild. + #[inline] + pub fn get>(guild_id: G) -> Result { + guild_id.into().get() + } + + /// Gets a list of the guild's bans. + /// + /// Requires the [Ban Members] permission. + #[inline] + pub fn get_bans(&self) -> Result> { + self.id.get_bans() + } + + /// Gets all of the guild's channels over the REST API. /// /// [`Guild`]: struct.Guild.html - #[cfg(all(feature="cache", feature="methods"))] - pub fn find_guild(&self) -> Result { - CACHE.read() - .unwrap() - .guilds - .values() - .find(|guild| { - guild.members - .iter() - .any(|(_member_id, member)| { - let joined_at = member.joined_at == self.joined_at; - let user_id = member.user.id == self.user.id; + #[inline] + pub fn get_channels(&self) -> Result> { + self.id.get_channels() + } - joined_at && user_id - }) - }) - .map(|x| x.id) - .ok_or(Error::Client(ClientError::GuildNotFound)) + /// Gets an emoji in the guild by Id. + /// + /// Requires the [Manage Emojis] permission. + /// + /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + #[inline] + pub fn get_emoji>(&self, emoji_id: E) -> Result { + self.id.get_emoji(emoji_id) } - /// Removes a [`Role`] from the member, editing its roles in-place if the - /// request was successful. + /// Gets a list of all of the guild's emojis. /// - /// **Note**: Requires the [Manage Roles] permission. + /// Requires the [Manage Emojis] permission. /// - /// [`Role`]: struct.Role.html - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html - #[cfg(all(feature="cache", feature="methods"))] - pub fn remove_role>(&mut self, role_id: R) -> Result<()> { - let role_id = role_id.into(); + /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + #[inline] + pub fn get_emojis(&self) -> Result> { + self.id.get_emojis() + } - if !self.roles.contains(&role_id) { - return Ok(()); - } + /// Gets all integration of the guild. + /// + /// This performs a request over the REST API. + #[inline] + pub fn get_integrations(&self) -> Result> { + self.id.get_integrations() + } - let guild_id = self.find_guild()?; + /// Gets all of the guild's invites. + /// + /// Requires the [Manage Guild] permission. + /// + /// [Manage Guild]: permissions/struct.MANAGE_GUILD.html + #[inline] + pub fn get_invites(&self) -> Result> { + self.id.get_invites() + } - match rest::remove_member_role(guild_id.0, self.user.id.0, role_id.0) { - Ok(()) => { - self.roles.retain(|r| r.0 != role_id.0); + /// Gets a user's [`Member`] for the guild by Id. + /// + /// [`Guild`]: struct.Guild.html + /// [`Member`]: struct.Member.html + pub fn get_member>(&self, user_id: U) -> Result { + self.id.get_member(user_id) + } - Ok(()) - }, - Err(why) => Err(why), - } + /// Gets a list of the guild's members. + /// + /// Optionally pass in the `limit` to limit the number of results. Maximum + /// value is 1000. Optionally pass in `after` to offset the results by a + /// [`User`]'s Id. + /// + /// [`User`]: struct.User.html + pub fn get_members(&self, limit: Option, after: Option) + -> Result> where U: Into { + self.id.get_members(limit, after) } - /// Removes one or multiple [`Role`]s from the member. + /// Gets the number of [`Member`]s that would be pruned with the given + /// number of days. /// - /// **Note**: Requires the [Manage Roles] permission. + /// Requires the [Kick Members] permission. /// - /// [`Role`]: struct.Role.html - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html - #[cfg(all(feature="cache", feature="methods"))] - pub fn remove_roles(&mut self, role_ids: &[RoleId]) -> Result<()> { - let guild_id = self.find_guild()?; - self.roles.retain(|r| !role_ids.contains(r)); + /// [`Member`]: struct.Member.html + /// [Kick Members]: permissions/constant.KICK_MEMBERS.html + #[inline] + pub fn get_prune_count(&self, days: u16) -> Result { + self.id.get_prune_count(days) + } - let map = EditMember::default().roles(&self.roles).0.build(); + /// Kicks a [`Member`] from the guild. + /// + /// Requires the [Kick Members] permission. + /// + /// [`Member`]: struct.Member.html + /// [Kick Members]: permissions/constant.KICK_MEMBERS.html + #[inline] + pub fn kick>(&self, user_id: U) -> Result<()> { + self.id.kick(user_id) + } - match rest::edit_member(guild_id.0, self.user.id.0, map) { - Ok(()) => Ok(()), - Err(why) => { - self.roles.extend_from_slice(role_ids); + /// Returns a formatted URL of the guild's icon, if the guild has an icon. + pub fn icon_url(&self) -> Option { + self.icon.as_ref().map(|icon| + format!(cdn!("/icons/{}/{}.webp"), self.id, icon)) + } - Err(why) - }, + /// Leaves the guild. + #[inline] + pub fn leave(&self) -> Result { + self.id.leave() + } + + /// Moves a member to a specific voice channel. + /// + /// Requires the [Move Members] permission. + /// + /// [Move Members]: permissions/constant.MOVE_MEMBERS.html + #[inline] + pub fn move_member(&self, user_id: U, channel_id: C) + -> Result<()> where C: Into, U: Into { + self.id.move_member(user_id, channel_id) + } + + /// Performs a search request to the API for the guild's [`Message`]s. + /// + /// This will search all of the guild's [`Channel`]s at once, that you have + /// the [Read Message History] permission to. Use [`search_channels`] to + /// specify a list of [channel][`GuildChannel`]s to search, where all other + /// channels will be excluded. + /// + /// Refer to the documentation for the [`Search`] builder for examples and + /// more information. + /// + /// **Note**: Bot users can not search. + /// + /// # Errors + /// + /// If the `cache` is enabled, returns a + /// [`ClientError::InvalidOperationAsBot`] if the current user is a bot. + /// + /// [`ClientError::InvalidOperationAsBot`]: ../client/enum.ClientError.html#variant.InvalidOperationAsBot + /// [`Channel`]: enum.Channel.html + /// [`GuildChannel`]: struct.GuildChannel.html + /// [`Message`]: struct.Message.html + /// [`Search`]: ../utils/builder/struct.Search.html + /// [`search_channels`]: #method.search_channels + /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + pub fn search(&self, f: F) -> Result + where F: FnOnce(Search) -> Search { + #[cfg(feature="cache")] + { + if CACHE.read().unwrap().user.bot { + return Err(Error::Client(ClientError::InvalidOperationAsBot)); + } } + + self.id.search(f) } - /// Retrieves the full role data for the user's roles. + /// Performs a search request to the API for the guild's [`Message`]s in + /// given channels. /// - /// This is shorthand for manually searching through the CACHE. + /// This will search all of the messages in the guild's provided + /// [`Channel`]s by Id that you have the [Read Message History] permission + /// to. Use [`search`] to search all of a guild's [channel][`GuildChannel`]s + /// at once. /// - /// If role data can not be found for the member, then `None` is returned. - #[cfg(all(feature="cache", feature="methods"))] - pub fn roles(&self) -> Option> { - CACHE.read().unwrap() - .guilds - .values() - .find(|g| g.members - .values() - .any(|m| m.user.id == self.user.id && m.joined_at == *self.joined_at)) - .map(|g| g.roles - .values() - .filter(|r| self.roles.contains(&r.id)) - .cloned() - .collect()) + /// Refer to the documentation for the [`Search`] builder for examples and + /// more information. + /// + /// **Note**: Bot users can not search. + /// + /// # Errors + /// + /// If the `cache` is enabled, returns a + /// [`ClientError::InvalidOperationAsBot`] if the current user is a bot. + /// + /// [`ClientError::InvalidOperationAsBot`]: ../client/enum.ClientError.html#variant.InvalidOperationAsBot + /// [`Channel`]: enum.Channel.html + /// [`GuildChannel`]: struct.GuildChannel.html + /// [`Message`]: struct.Message.html + /// [`Search`]: ../utils/builder/struct.Search.html + /// [`search`]: #method.search + /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + pub fn search_channels(&self, channel_ids: &[ChannelId], f: F) + -> Result where F: FnOnce(Search) -> Search { + #[cfg(feature="cache")] + { + if CACHE.read().unwrap().user.bot { + return Err(Error::Client(ClientError::InvalidOperationAsBot)); + } + } + + self.id.search_channels(channel_ids, f) } -} -impl fmt::Display for Member { - /// Mentions the user so that they receive a notification. + /// Returns the formatted URL of the guild's splash image, if one exists. + pub fn splash_url(&self) -> Option { + self.icon.as_ref().map(|icon| + format!(cdn!("/splashes/{}/{}.webp"), self.id, icon)) + } + + /// Starts an integration sync for the given integration Id. /// - /// # Examples + /// Requires the [Manage Guild] permission. /// - /// ```rust,ignore - /// // assumes a `member` has already been bound - /// println!("{} is a member!", member); - /// ``` + /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + #[inline] + pub fn start_integration_sync>(&self, integration_id: I) -> Result<()> { + self.id.start_integration_sync(integration_id) + } + + /// Unbans a [`User`] from the guild. /// - // This is in the format of `<@USER_ID>`. - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.user.mention(), f) + /// Requires the [Ban Members] permission. + /// + /// [`User`]: struct.User.html + /// [Ban Members]: permissions/constant.BAN_MEMBERS.html + #[inline] + pub fn unban>(&self, user_id: U) -> Result<()> { + self.id.unban(user_id) + } + + /// Retrieves the guild's webhooks. + /// + /// **Note**: Requires the [Manage Webhooks] permission. + /// + /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html + #[inline] + pub fn webhooks(&self) -> Result> { + self.id.webhooks() } } @@ -1240,13 +2431,36 @@ impl Role { /// **Note** Requires the [Manage Roles] permission. /// /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html - #[cfg(all(feature="cache", feature="methods"))] + #[cfg(feature="cache")] pub fn delete(&self) -> Result<()> { let guild_id = self.find_guild()?; rest::delete_role(guild_id.0, self.id.0) } + /// Edits a [`Role`], optionally setting its new fields. + /// + /// Requires the [Manage Roles] permission. + /// + /// # Examples + /// + /// Make a role hoisted: + /// + /// ```rust,ignore + /// context.edit_role(guild_id, role_id, |r| r + /// .hoist(true)); + /// ``` + /// + /// [`Role`]: struct.Role.html + /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + #[cfg(feature="cache")] + pub fn edit_role EditRole>(&self, f: F) -> Result { + match self.find_guild() { + Ok(guild_id) => guild_id.edit_role(self.id, f), + Err(why) => Err(why), + } + } + /// Searches the cache for the guild that owns the role. /// /// # Errors @@ -1255,7 +2469,7 @@ impl Role { /// that contains the role. /// /// [`ClientError::GuildNotFound`]: ../client/enum.ClientError.html#variant.GuildNotFound - #[cfg(all(feature="cache", feature="methods"))] + #[cfg(feature="cache")] pub fn find_guild(&self) -> Result { CACHE.read() .unwrap() @@ -1267,6 +2481,7 @@ impl Role { } /// Check that the role has the given permission. + #[inline] pub fn has_permission(&self, permission: Permissions) -> bool { self.permissions.contains(permission) } @@ -1317,3 +2532,34 @@ impl PartialOrd for Role { Some(self.cmp(other)) } } + +impl RoleId { + /// Search the cache for the role. + #[cfg(feature="cache")] + pub fn find(&self) -> Option { + CACHE.read() + .unwrap() + .guilds + .values() + .find(|guild| guild.roles.contains_key(self)) + .map(|guild| guild.roles.get(self)) + .and_then(|v| match v { + Some(v) => Some(v), + None => None, + }) + .cloned() + } +} + +impl fmt::Display for RoleId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +impl From for RoleId { + /// Gets the Id of a role. + fn from(role: Role) -> RoleId { + role.id + } +} diff --git a/src/model/id.rs b/src/model/id.rs deleted file mode 100644 index 17fd29c2cea..00000000000 --- a/src/model/id.rs +++ /dev/null @@ -1,238 +0,0 @@ -use super::*; -use std::fmt; - -#[cfg(all(feature="cache", feature="methods"))] -use ::client::CACHE; -#[cfg(feature="methods")] -use ::client::rest; -#[cfg(feature="methods")] -use ::internal::prelude::*; - -impl ChannelId { - /// Search the cache for the channel with the Id. - #[cfg(all(feature="cache", feature="methods"))] - pub fn find(&self) -> Option { - CACHE.read().unwrap().get_channel(*self).map(|x| x.clone_inner()) - } - - /// Search the cache for the channel. If it can't be found, the channel is - /// requested over REST. - #[cfg(feature="methods")] - pub fn get(&self) -> Result { - #[cfg(feature="cache")] - { - if let Some(channel) = CACHE.read().unwrap().get_channel(*self) { - return Ok(channel.clone_inner()); - } - } - - rest::get_channel(self.0) - } - - /// Retrieves the channel's webhooks. - /// - /// **Note**: Requires the [Manage Webhooks] permission. - /// - /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html - #[cfg(feature="methods")] - pub fn webhooks(&self) -> Result> { - rest::get_channel_webhooks(self.0) - } -} - -impl From for ChannelId { - /// Gets the Id of a `Channel`. - fn from(channel: Channel) -> ChannelId { - match channel { - Channel::Group(group) => group.channel_id, - Channel::Guild(channel) => channel.id, - Channel::Private(channel) => channel.id, - } - } -} - -impl From for ChannelId { - /// Gets the Id of a private channel. - fn from(private_channel: PrivateChannel) -> ChannelId { - private_channel.id - } -} - -impl From for ChannelId { - /// Gets the Id of a guild channel. - fn from(public_channel: GuildChannel) -> ChannelId { - public_channel.id - } -} - -impl From for EmojiId { - /// Gets the Id of an `Emoji`. - fn from(emoji: Emoji) -> EmojiId { - emoji.id - } -} - -impl GuildId { - /// Search the cache for the guild. - #[cfg(all(feature="cache", feature="methods"))] - pub fn find(&self) -> Option { - CACHE.read().unwrap().get_guild(*self).cloned() - } - - /// Requests the guild over REST. - /// - /// Note that this will not be a complete guild, as REST does not send - /// all data with a guild retrieval. - #[cfg(feature="methods")] - pub fn get(&self) -> Result { - rest::get_guild(self.0) - } - - /// Retrieves the guild's webhooks. - /// - /// **Note**: Requires the [Manage Webhooks] permission. - /// - /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html - #[cfg(feature="methods")] - pub fn webhooks(&self) -> Result> { - rest::get_guild_webhooks(self.0) - } -} - -impl From for GuildId { - /// Gets the Id of a partial guild. - fn from(guild: PartialGuild) -> GuildId { - guild.id - } -} - -impl From for GuildId { - /// Gets the Id of Guild information struct. - fn from(guild_info: GuildInfo) -> GuildId { - guild_info.id - } -} - -impl From for GuildId { - /// Gets the Id of Invite Guild struct. - fn from(invite_guild: InviteGuild) -> GuildId { - invite_guild.id - } -} - -impl From for GuildId { - /// Gets the Id of Guild. - fn from(live_guild: Guild) -> GuildId { - live_guild.id - } -} - -impl From for IntegrationId { - /// Gets the Id of integration. - fn from(integration: Integration) -> IntegrationId { - integration.id - } -} - -impl From for MessageId { - /// Gets the Id of a `Message`. - fn from(message: Message) -> MessageId { - message.id - } -} - -impl From for RoleId { - /// Gets the Id of a `Role`. - fn from(role: Role) -> RoleId { - role.id - } -} - -impl RoleId { - /// Search the cache for the role. - #[cfg(all(feature="cache", feature="methods"))] - pub fn find(&self) -> Option { - CACHE.read() - .unwrap() - .guilds - .values() - .find(|guild| guild.roles.contains_key(self)) - .map(|guild| guild.roles.get(self)) - .and_then(|v| match v { - Some(v) => Some(v), - None => None, - }) - .cloned() - } -} - -impl UserId { - /// Search the cache for the user with the Id. - #[cfg(all(feature="cache", feature="methods"))] - pub fn find(&self) -> Option { - CACHE.read().unwrap().get_user(*self).cloned() - } -} - -impl From for UserId { - /// Gets the Id of a `CurrentUser` struct. - fn from(current_user: CurrentUser) -> UserId { - current_user.id - } -} - -impl From for UserId { - /// Gets the Id of a `Member`. - fn from(member: Member) -> UserId { - member.user.id - } -} - -impl From for UserId { - /// Gets the Id of a `User`. - fn from(user: User) -> UserId { - user.id - } -} - -impl fmt::Display for UserId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0, f) - } -} - -impl fmt::Display for RoleId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0, f) - } -} - -impl fmt::Display for ChannelId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0, f) - } -} - -impl fmt::Display for GuildId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0, f) - } -} - -impl fmt::Display for EmojiId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0, f) - } -} - -impl WebhookId { - /// Retrieves the webhook by the Id. - /// - /// **Note**: Requires the [Manage Webhooks] permission. - /// - /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html - #[cfg(feature="methods")] - pub fn webhooks(&self) -> Result { - rest::get_webhook(self.0) - } -} diff --git a/src/model/invite.rs b/src/model/invite.rs index adfba606f71..47ae28a11bd 100644 --- a/src/model/invite.rs +++ b/src/model/invite.rs @@ -1,12 +1,15 @@ use super::{Invite, RichInvite}; use ::client::rest; use ::internal::prelude::*; +use ::model::ChannelId; +use ::utils::builder::CreateInvite; +use ::utils; #[cfg(feature="cache")] use super::permissions; -#[cfg(all(feature="cache", feature="methods"))] -use super::utils; -#[cfg(feature = "cache")] +#[cfg(feature="cache")] +use super::utils as model_utils; +#[cfg(feature="cache")] use ::client::CACHE; impl Invite { @@ -26,11 +29,10 @@ impl Invite { /// [`ClientError::InvalidOperationAsBot`] if the current user does not have /// the required [permission]. /// - /// [`ClientError::InvalidOperationAsBot`]: enum.ClientError.html#variant.InvalidOperationAsBot + /// [`ClientError::InvalidOperationAsBot`]: ../client/enum.ClientError.html#variant.InvalidOperationAsBot /// [`Guild`]: struct.Guild.html /// [`rest::accept_invite`]: ../client/rest/fn.accept_invite.html /// [permission]: permissions/index.html - #[cfg(feature="methods")] pub fn accept(&self) -> Result { #[cfg(feature="cache")] { @@ -42,6 +44,39 @@ impl Invite { rest::accept_invite(&self.code) } + /// Creates an invite for a [`GuildChannel`], 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. + /// + /// Requires the [Create Invite] permission. + /// + /// # Errors + /// + /// If the `cache` is enabled, returns a [`ClientError::InvalidPermissions`] + /// if the current user does not have the required [permission]. + /// + /// [`CreateInvite`]: ../utils/builder/struct.CreateInvite.html + /// [`GuildChannel`]: struct.GuildChannel.html + /// [Create Invite]: permissions/constant.CREATE_INVITE.html + /// [permission]: permissions/index.html + pub fn create(channel_id: C, f: F) -> Result + where C: Into, F: FnOnce(CreateInvite) -> CreateInvite { + let channel_id = channel_id.into(); + + #[cfg(feature="cache")] + { + let req = permissions::CREATE_INVITE; + + if !model_utils::user_has_perms(channel_id, req)? { + return Err(Error::Client(ClientError::InvalidPermissions(req))); + } + } + + rest::create_invite(channel_id.0, f(CreateInvite::default()).0.build()) + } + /// Deletes the invite. /// /// **Note**: Requires the [Manage Guild] permission. @@ -54,19 +89,23 @@ impl Invite { /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html /// [permission]: permissions/index.html - #[cfg(feature="methods")] pub fn delete(&self) -> Result { #[cfg(feature="cache")] { let req = permissions::MANAGE_GUILD; - if !utils::user_has_perms(self.channel.id, req)? { + if !model_utils::user_has_perms(self.channel.id, req)? { return Err(Error::Client(ClientError::InvalidPermissions(req))); } } rest::delete_invite(&self.code) } + + /// Gets the information about an invite. + pub fn get(code: &str) -> Result { + rest::get_invite(utils::parse_invite(code)) + } } impl RichInvite { @@ -86,10 +125,9 @@ impl RichInvite { /// [`ClientError::InvalidOperationAsBot`] if the current user is a bot /// user. /// - /// [`ClientError::InvalidOperationAsBot`]: enum.ClientError.html#variant.InvalidOperationAsBot + /// [`ClientError::InvalidOperationAsBot`]: ../client/enum.ClientError.html#variant.InvalidOperationAsBot /// [`Guild`]: struct.Guild.html /// [`rest::accept_invite`]: ../client/rest/fn.accept_invite.html - #[cfg(feature="methods")] pub fn accept(&self) -> Result { #[cfg(feature="cache")] { @@ -118,13 +156,12 @@ impl RichInvite { /// [`rest::delete_invite`]: ../client/rest/fn.delete_invite.html /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html /// [permission]: permissions/index.html - #[cfg(feature="methods")] pub fn delete(&self) -> Result { #[cfg(feature="cache")] { let req = permissions::MANAGE_GUILD; - if !utils::user_has_perms(self.channel.id, req)? { + if !model_utils::user_has_perms(self.channel.id, req)? { return Err(Error::Client(ClientError::InvalidPermissions(req))); } } diff --git a/src/model/misc.rs b/src/model/misc.rs index f3632a5d2af..eaae8904f0d 100644 --- a/src/model/misc.rs +++ b/src/model/misc.rs @@ -78,7 +78,7 @@ impl Mentionable for User { } } -#[cfg(all(feature="cache", feature="methods"))] +#[cfg(feature="cache")] impl FromStr for User { type Err = (); @@ -103,7 +103,7 @@ impl FromStr for UserId { } } -#[cfg(all(feature="cache", feature="methods"))] +#[cfg(feature="cache")] impl FromStr for Role { type Err = (); @@ -130,7 +130,6 @@ impl FromStr for RoleId { impl EmojiIdentifier { /// Generates a URL to the emoji's image. - #[cfg(feature="methods")] #[inline] pub fn url(&self) -> String { format!(cdn!("/emojis/{}.png"), self.id) @@ -153,7 +152,7 @@ impl FromStr for ChannelId { } } -#[cfg(all(feature="cache", feature="methods"))] +#[cfg(feature="cache")] impl FromStr for Channel { type Err = (); diff --git a/src/model/mod.rs b/src/model/mod.rs index 5aad290ac68..c2883cef482 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -23,26 +23,18 @@ pub mod permissions; mod channel; mod gateway; mod guild; -mod id; +mod invite; mod misc; mod user; - -#[cfg(feature="methods")] -mod invite; -#[cfg(feature="methods")] mod webhook; pub use self::channel::*; pub use self::gateway::*; pub use self::guild::*; -pub use self::id::*; +pub use self::invite::*; pub use self::misc::*; pub use self::permissions::Permissions; pub use self::user::*; - -#[cfg(feature="methods")] -pub use self::invite::*; -#[cfg(feature="methods")] pub use self::webhook::*; use self::utils::*; diff --git a/src/model/user.rs b/src/model/user.rs index 8a2dd45a5d4..4eaf65e367f 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -1,32 +1,26 @@ -use std::fmt; +use serde_json::builder::ObjectBuilder; +use std::{fmt, mem}; use super::utils::{into_map, into_string, remove}; use super::{ CurrentUser, FriendSourceFlags, GuildContainer, GuildId, + GuildInfo, + Member, + Message, + PrivateChannel, RoleId, UserSettings, User, + UserId, }; -use ::internal::prelude::*; -use ::utils::decode_array; -use ::model::misc::Mentionable; - -#[cfg(feature="methods")] -use serde_json::builder::ObjectBuilder; -#[cfg(feature="methods")] -use std::mem; -#[cfg(feature="methods")] -use super::Message; -#[cfg(feature="methods")] use time::Timespec; -#[cfg(feature="methods")] use ::client::rest::{self, GuildPagination}; -#[cfg(feature="methods")] -use super::GuildInfo; -#[cfg(feature="methods")] +use ::internal::prelude::*; +use ::model::misc::Mentionable; use ::utils::builder::EditProfile; +use ::utils::decode_array; #[cfg(feature="cache")] use ::client::CACHE; @@ -49,7 +43,6 @@ impl CurrentUser { } /// Returns the DiscordTag of a User. - #[cfg(feature="methods")] pub fn distinct(&self) -> String { format!("{}#{}", self.name, self.discriminator) } @@ -75,7 +68,6 @@ impl CurrentUser { /// .edit(|p| p /// .avatar(Some(&avatar))); /// ``` - #[cfg(feature="methods")] pub fn edit(&mut self, f: F) -> Result<()> where F: FnOnce(EditProfile) -> EditProfile { let mut map = ObjectBuilder::new() @@ -86,11 +78,9 @@ impl CurrentUser { map = map.insert("email", email) } - let edited = f(EditProfile(map)).0.build(); - - match rest::edit_profile(edited) { + match rest::edit_profile(f(EditProfile(map)).0.build()) { Ok(new) => { - mem::replace(self, new); + let _ = mem::replace(self, new); Ok(()) }, @@ -99,9 +89,9 @@ impl CurrentUser { } /// Gets a list of guilds that the current user is in. - #[cfg(feature="methods")] + #[inline] pub fn guilds(&self) -> Result> { - rest::get_guilds(GuildPagination::After(GuildId(0)), 100) + rest::get_guilds(GuildPagination::After(GuildId(1)), 100) } /// Returns a static formatted URL of the user's icon, if one exists. @@ -130,14 +120,22 @@ impl User { }) } + /// Creates a direct message channel between the [current user] and the + /// user. This can also retrieve the channel if one already exists. + /// + /// [current user]: struct.CurrentUser.html + #[inline] + pub fn create_dm_channel(&self) -> Result { + self.id.create_dm_channel() + } + /// Returns the DiscordTag of a User. - #[cfg(feature="methods")] + #[inline] pub fn distinct(&self) -> String { format!("{}#{}", self.name, self.discriminator) } /// Retrieves the time that this user was created at. - #[cfg(feature="methods")] #[inline] pub fn created_at(&self) -> Timespec { self.id.created_at() @@ -162,10 +160,26 @@ impl User { Ok(cdn!("/embed/avatars/{}.png", self.discriminator.parse::()? % 5u16).to_owned()) } - /// Send a direct message to a user. This will create or retrieve the - /// PrivateChannel over REST if one is not already in the cache, and then - /// send a message to it. - #[cfg(feature="methods")] + /// Deletes a profile note from a user. + #[inline] + pub fn delete_note(&self) -> Result<()> { + self.id.delete_note() + } + + /// Sends a message to a user through a direct message channel. This is a + /// channel that can only be accessed by you and the recipient. + /// + /// # Examples + /// + /// Sending a message: + /// + /// ```rust,ignore + /// // assuming you are in a context + /// let _ = context.message.author.dm("Hello!"); + /// ``` + /// + /// [`PrivateChannel`]: struct.PrivateChannel.html + /// [`User::dm`]: struct.User.html#method.dm pub fn direct_message(&self, content: &str) -> Result { let private_channel_id = feature_cache! {{ @@ -205,12 +219,61 @@ impl User { /// This is an alias of [direct_message]. /// /// [direct_message]: #method.direct_message - #[cfg(feature="methods")] #[inline] pub fn dm(&self, content: &str) -> Result { self.direct_message(content) } + /// Edits the note that the current user has set for another user. + /// + /// Use [`delete_note`] to remove a note. + /// + /// **Note**: Requires that the current user be a user account. + /// + /// # Examples + /// + /// Set a note for a message's author: + /// + /// ```rust,ignore + /// // assuming a `message` has been bound + /// let _ = context.edit_note(message.author, "test note"); + /// ``` + /// + /// # Errors + /// + /// If the `cache` is enabled, returns a + /// [`ClientError::InvalidOperationAsBot`] if the current user is a bot + /// user. + /// + /// [`ClientError::InvalidOperationAsBot`]: ../client/enum.ClientError.html#variant.InvalidOperationAsBot + /// [`delete_note`]: #method.delete_note + #[inline] + pub fn edit_note(&self, note: &str) -> Result<()> { + self.id.edit_note(note) + } + + /// Gets a user by its Id over the REST API. + /// + /// **Note**: The current user must be a bot user. + /// + /// # Errors + /// + /// If the `cache` is enabled, returns a + /// [`ClientError::InvalidOperationAsUser`] if the current user is not a bot + /// user. + /// + /// [`ClientError::InvalidOperationAsUser`]: ../client/enum.ClientError.html#variant.InvalidOperationAsUser + pub fn get>(user_id: U) -> Result { + #[cfg(feature="cache")] + { + if !CACHE.read().unwrap().user.bot { + return Err(Error::Client(ClientError::InvalidOperationAsUser)); + } + } + + user_id.into().get() + } + /// Check if a user has a [`Role`]. This will retrieve the /// [`Guild`] from the [`Cache`] if it is available, and then check if that /// guild has the given [`Role`]. @@ -244,13 +307,16 @@ impl User { match guild.into() { GuildContainer::Guild(guild) => { - guild.roles.get(&role_id).is_some() + guild.roles.contains_key(&role_id) }, - GuildContainer::Id(guild_id) => { + GuildContainer::Id(_guild_id) => { feature_cache! {{ - let cache = CACHE.read().unwrap(); - - cache.get_role(guild_id, role_id).is_some() + CACHE.read() + .unwrap() + .guilds + .get(&_guild_id) + .map(|g| g.roles.contains_key(&role_id)) + .unwrap_or(false) } else { true }} @@ -275,6 +341,82 @@ impl fmt::Display for User { } } +impl UserId { + /// Creates a direct message channel between the [current user] and the + /// user. This can also retrieve the channel if one already exists. + /// + /// [current user]: struct.CurrentUser.html + pub fn create_dm_channel(&self) -> Result { + let map = ObjectBuilder::new().insert("recipient_id", self.0).build(); + + rest::create_private_channel(map) + } + + /// Deletes a profile note from a user. + pub fn delete_note(&self) -> Result<()> { + let map = ObjectBuilder::new().insert("note", "").build(); + + rest::edit_note(self.0, map) + } + + /// Edits the note that the current user has set for another user. + /// + /// Use [`delete_note`] to remove a note. + /// + /// Refer to the documentation for [`User::edit_note`] for more information. + /// + /// **Note**: Requires that the current user be a user account. + /// + /// [`delete_note`]: #method.delete_note + /// [`User::edit_note`]: struct.User.html#method.edit_note + pub fn edit_note(&self, note: &str) -> Result<()> { + let map = ObjectBuilder::new().insert("note", note).build(); + + rest::edit_note(self.0, map) + } + + /// Search the cache for the user with the Id. + #[cfg(feature="cache")] + pub fn find(&self) -> Option { + CACHE.read().unwrap().get_user(*self).cloned() + } + + /// Gets a user by its Id over the REST API. + /// + /// **Note**: The current user must be a bot user. + #[inline] + pub fn get(&self) -> Result { + rest::get_user(self.0) + } +} + +impl From for UserId { + /// Gets the Id of a `CurrentUser` struct. + fn from(current_user: CurrentUser) -> UserId { + current_user.id + } +} + +impl From for UserId { + /// Gets the Id of a `Member`. + fn from(member: Member) -> UserId { + member.user.id + } +} + +impl From for UserId { + /// Gets the Id of a `User`. + fn from(user: User) -> UserId { + user.id + } +} + +impl fmt::Display for UserId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + impl UserSettings { #[doc(hidden)] pub fn decode(value: Value) -> Result> { diff --git a/src/model/utils.rs b/src/model/utils.rs index 71b4a527c93..dee9a08b21c 100644 --- a/src/model/utils.rs +++ b/src/model/utils.rs @@ -18,11 +18,11 @@ use super::{ use ::internal::prelude::*; use ::utils::{decode_array, into_array}; -#[cfg(all(feature="cache", feature="methods"))] +#[cfg(feature="cache")] use super::permissions::{self, Permissions}; -#[cfg(all(feature="cache", feature="methods"))] +#[cfg(feature="cache")] use ::client::CACHE; -#[cfg(all(feature="cache", feature="methods"))] +#[cfg(feature="cache")] use ::ext::cache::ChannelRef; #[macro_escape] @@ -293,7 +293,7 @@ pub fn remove(map: &mut BTreeMap, key: &str) -> Result { }) } -#[cfg(all(feature="cache", feature="methods"))] +#[cfg(feature="cache")] pub fn user_has_perms(channel_id: ChannelId, mut permissions: Permissions) -> Result { diff --git a/src/model/webhook.rs b/src/model/webhook.rs index 9d3a393f198..f1a664db799 100644 --- a/src/model/webhook.rs +++ b/src/model/webhook.rs @@ -1,6 +1,6 @@ use serde_json::builder::ObjectBuilder; use std::mem; -use super::{Message, Webhook}; +use super::{Message, Webhook, WebhookId}; use ::utils::builder::ExecuteWebhook; use ::client::rest; use ::internal::prelude::*; @@ -12,7 +12,7 @@ impl Webhook { /// authentication is not required. /// /// [`rest::delete_webhook_with_token`]: ../client/rest/fn.delete_webhook_with_token.html - #[cfg(feature="methods")] + #[inline] pub fn delete(&self) -> Result<()> { rest::delete_webhook_with_token(self.id.0, &self.token) } @@ -63,7 +63,6 @@ impl Webhook { /// /// [`rest::edit_webhook`]: ../client/rest/fn.edit_webhook.html /// [`rest::edit_webhook_with_token`]: ../client/rest/fn.edit_webhook_with_token.html - #[cfg(feature="methods")] pub fn edit(&mut self, name: Option<&str>, avatar: Option<&str>) -> Result<()> { if name.is_none() && avatar.is_none() { @@ -84,9 +83,7 @@ impl Webhook { map = map.insert("name", name); } - let map = map.build(); - - match rest::edit_webhook_with_token(self.id.0, &self.token, map) { + match rest::edit_webhook_with_token(self.id.0, &self.token, map.build()) { Ok(replacement) => { mem::replace(self, replacement); @@ -143,12 +140,9 @@ impl Webhook { /// .embeds(vec![embed])) /// .expect("Error executing"); /// ``` - #[cfg(feature="methods")] - pub fn execute(&self, f: F) -> Result - where F: FnOnce(ExecuteWebhook) -> ExecuteWebhook { - let map = f(ExecuteWebhook::default()).0.build(); - - rest::execute_webhook(self.id.0, &self.token, map) + #[inline] + pub fn execute ExecuteWebhook>(&self, f: F) -> Result { + rest::execute_webhook(self.id.0, &self.token, f(ExecuteWebhook::default()).0.build()) } /// Retrieves the latest information about the webhook, editing the @@ -158,7 +152,6 @@ impl Webhook { /// authentication is not required. /// /// [`rest::get_webhook_with_token`]: ../client/rest/fn.get_webhook_with_token.html - #[cfg(feature="methods")] pub fn refresh(&mut self) -> Result<()> { match rest::get_webhook_with_token(self.id.0, &self.token) { Ok(replacement) => { @@ -170,3 +163,15 @@ impl Webhook { } } } + +impl WebhookId { + /// Retrieves the webhook by the Id. + /// + /// **Note**: Requires the [Manage Webhooks] permission. + /// + /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html + #[inline] + pub fn get(&self) -> Result { + rest::get_webhook(self.0) + } +} diff --git a/src/utils/macros.rs b/src/utils/macros.rs index e4aaa14d963..484638f9a18 100644 --- a/src/utils/macros.rs +++ b/src/utils/macros.rs @@ -94,25 +94,6 @@ macro_rules! feature_framework { } } -// Enable/disable check for methods -#[cfg(feature="methods")] -macro_rules! feature_methods { - ($enabled:block else $disabled:block) => { - { - $enabled - } - } -} - -#[cfg(not(feature="methods"))] -macro_rules! feature_methods { - ($enabled:block else $disabled:block) => { - { - $disabled - } - } -} - // Enable/disable check for voice #[cfg(feature="voice")] macro_rules! feature_voice {