diff options
| author | Zeyla Hellyer <[email protected]> | 2017-03-25 15:41:47 -0700 |
|---|---|---|
| committer | Zeyla Hellyer <[email protected]> | 2017-03-25 15:41:47 -0700 |
| commit | 9114963daf708cfaeaf54d8c788206ccfbae5df8 (patch) | |
| tree | 08d6aba5b8ad40189b312865ac776bb6fa30c45d /src | |
| parent | Add slightly more documentation (diff) | |
| download | serenity-9114963daf708cfaeaf54d8c788206ccfbae5df8.tar.xz serenity-9114963daf708cfaeaf54d8c788206ccfbae5df8.zip | |
Rework the models directory
Diffstat (limited to 'src')
| -rw-r--r-- | src/model/channel.rs | 2723 | ||||
| -rw-r--r-- | src/model/channel/attachment.rs | 92 | ||||
| -rw-r--r-- | src/model/channel/channel_id.rs | 511 | ||||
| -rw-r--r-- | src/model/channel/embed.rs | 15 | ||||
| -rw-r--r-- | src/model/channel/group.rs | 329 | ||||
| -rw-r--r-- | src/model/channel/guild_channel.rs | 504 | ||||
| -rw-r--r-- | src/model/channel/message.rs | 393 | ||||
| -rw-r--r-- | src/model/channel/mod.rs | 415 | ||||
| -rw-r--r-- | src/model/channel/private_channel.rs | 309 | ||||
| -rw-r--r-- | src/model/channel/reaction.rs | 188 | ||||
| -rw-r--r-- | src/model/guild.rs | 2587 | ||||
| -rw-r--r-- | src/model/guild/emoji.rs | 100 | ||||
| -rw-r--r-- | src/model/guild/guild_id.rs | 525 | ||||
| -rw-r--r-- | src/model/guild/integration.rs | 8 | ||||
| -rw-r--r-- | src/model/guild/member.rs | 279 | ||||
| -rw-r--r-- | src/model/guild/mod.rs | 1052 | ||||
| -rw-r--r-- | src/model/guild/partial_guild.rs | 482 | ||||
| -rw-r--r-- | src/model/guild/role.rs | 151 | ||||
| -rw-r--r-- | src/model/mod.rs | 1 |
19 files changed, 5353 insertions, 5311 deletions
diff --git a/src/model/channel.rs b/src/model/channel.rs deleted file mode 100644 index 8824932..0000000 --- a/src/model/channel.rs +++ /dev/null @@ -1,2723 +0,0 @@ -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 std::sync::{Arc, RwLock}; -use super::utils::{ - decode_id, - into_map, - into_string, - opt, - 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="cache")] -use super::utils; -#[cfg(feature="cache")] -use ::client::CACHE; - -impl Attachment { - /// If this attachment is an image, then a tuple of the width and height - /// in pixels is returned. - pub fn dimensions(&self) -> Option<(u64, u64)> { - if let (Some(width), Some(height)) = (self.width, self.height) { - Some((width, height)) - } else { - None - } - } - - /// Downloads the attachment, returning back a vector of bytes. - /// - /// # Examples - /// - /// Download all of the attachments associated with a [`Message`]: - /// - /// ```rust,no_run - /// use serenity::Client; - /// use std::env; - /// use std::fs::File; - /// use std::io::Write; - /// use std::path::Path; - /// - /// let token = env::var("DISCORD_TOKEN").expect("token in environment"); - /// let mut client = Client::login_bot(&token); - /// - /// client.on_message(|_, message| { - /// for attachment in message.attachments { - /// let content = match attachment.download() { - /// Ok(content) => content, - /// Err(why) => { - /// println!("Error downloading attachment: {:?}", why); - /// let _ = message.channel_id.say("Error downloading attachment"); - /// - /// return; - /// }, - /// }; - /// - /// let mut file = match File::create(&attachment.filename) { - /// Ok(file) => file, - /// Err(why) => { - /// println!("Error creating file: {:?}", why); - /// let _ = message.channel_id.say("Error creating file"); - /// - /// return; - /// }, - /// }; - /// - /// if let Err(why) = file.write(&content) { - /// println!("Error writing to file: {:?}", why); - /// - /// return; - /// } - /// - /// let _ = message.channel_id.say(&format!("Saved {:?}", attachment.filename)); - /// } - /// }); - /// - /// client.on_ready(|_context, ready| { - /// println!("{} is connected!", ready.user.name); - /// }); - /// - /// let _ = client.start(); - /// ``` - /// - /// # Errors - /// - /// Returns an [`Error::Io`] when there is a problem reading the contents - /// of the HTTP response. - /// - /// Returns an [`Error::Hyper`] when there is a problem retrieving the - /// attachment. - /// - /// [`Error::Hyper`]: ../enum.Error.html#variant.Hyper - /// [`Error::Io`]: ../enum.Error.html#variant.Io - /// [`Message`]: struct.Message.html - pub fn download(&self) -> Result<Vec<u8>> { - let hyper = HyperClient::new(); - let mut response = hyper.get(&self.url).send()?; - - let mut bytes = vec![]; - response.read_to_end(&mut bytes)?; - - Ok(bytes) - } -} - -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`]: ../client/rest/fn.ack_message.html - pub fn ack<M: Into<MessageId>>(&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<M, R>(&self, message_id: M, reaction_type: R) - -> Result<()> where M: Into<MessageId>, R: Into<ReactionType> { - self.id().create_reaction(message_id, reaction_type) - } - - #[doc(hidden)] - pub fn decode(value: Value) -> Result<Channel> { - let map = into_map(value)?; - match req!(map.get("type").and_then(|x| x.as_u64())) { - 0 | 2 => GuildChannel::decode(Value::Object(map)) - .map(|x| Channel::Guild(Arc::new(RwLock::new(x)))), - 1 => PrivateChannel::decode(Value::Object(map)) - .map(|x| Channel::Private(Arc::new(RwLock::new(x)))), - 3 => Group::decode(Value::Object(map)) - .map(|x| Channel::Group(Arc::new(RwLock::new(x)))), - other => Err(Error::Decode("Expected value Channel type", - Value::U64(other))), - } - } - - /// Deletes the inner channel. - /// - /// **Note**: There is no real function as _deleting_ a [`Group`]. The - /// closest functionality is leaving it. - /// - /// [`Group`]: struct.Group.html - pub fn delete(&self) -> Result<()> { - match *self { - Channel::Group(ref group) => { - let _ = group.read().unwrap().leave()?; - }, - Channel::Guild(ref public_channel) => { - let _ = public_channel.read().unwrap().delete()?; - }, - Channel::Private(ref private_channel) => { - let _ = private_channel.read().unwrap().delete()?; - }, - } - - 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. - /// - /// [`Message`]: struct.Message.html - /// [`Message::delete`]: struct.Message.html#method.delete - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html - #[inline] - pub fn delete_message<M: Into<MessageId>>(&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<M, R>(&self, message_id: M, user_id: Option<UserId>, reaction_type: R) - -> Result<()> where M: Into<MessageId>, R: Into<ReactionType> { - self.id().delete_reaction(message_id, user_id, reaction_type) - } - - /// Edits a [`Message`] in the channel given its Id. - /// - /// Message editing preserves all unchanged message data. - /// - /// Refer to the documentation for [`CreateMessage`] for more information - /// regarding message restrictions and requirements. - /// - /// **Note**: Requires that the current user be the author of the message. - /// - /// # Errors - /// - /// Returns a [`ClientError::MessageTooLong`] if the content of the message - /// is over the [`the limit`], containing the number of unicode code points - /// over the limit. - /// - /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - /// [`CreateMessage`]: ../utils/builder/struct.CreateMessage.html - /// [`Message`]: struct.Message.html - /// [`the limit`]: ../utils/builder/struct.CreateMessage.html#method.content - #[inline] - pub fn edit_message<F, M>(&self, message_id: M, f: F) -> Result<Message> - where F: FnOnce(CreateMessage) -> CreateMessage, M: Into<MessageId> { - self.id().edit_message(message_id, f) - } - - /// Gets a message from the channel. - /// - /// Requires the [Read Message History] permission. - /// - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html - #[inline] - pub fn get_message<M: Into<MessageId>>(&self, message_id: M) -> Result<Message> { - self.id().get_message(message_id) - } - - /// Gets messages from the channel. - /// - /// Requires the [Read Message History] permission. - /// - /// # Examples - /// - /// ```rust,ignore - /// use serenity::model::MessageId; - /// - /// let id = MessageId(81392407232380928); - /// - /// // Maximum is 100. - /// let _messages = channel.get_messages(|g| g.after(id).limit(100)); - /// ``` - /// - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html - #[inline] - pub fn get_messages<F>(&self, f: F) -> Result<Vec<Message>> - 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<M, R, U>(&self, - message_id: M, - reaction_type: R, - limit: Option<u8>, - after: Option<U>) - -> Result<Vec<User>> where M: Into<MessageId>, R: Into<ReactionType>, U: Into<UserId> { - self.id().get_reaction_users(message_id, reaction_type, limit, after) - } - - /// Retrieves the Id of the inner [`Group`], [`GuildChannel`], or - /// [`PrivateChannel`]. - /// - /// [`Group`]: struct.Group.html - /// [`GuildChannel`]: struct.GuildChannel.html - /// [`PrivateChannel`]: struct.PrivateChannel.html - pub fn id(&self) -> ChannelId { - match *self { - Channel::Group(ref group) => group.read().unwrap().channel_id, - Channel::Guild(ref channel) => channel.read().unwrap().id, - Channel::Private(ref channel) => channel.read().unwrap().id, - } - } - - /// Sends a message with just the given message content in the channel. - /// - /// # 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. - /// - /// [`ChannelId`]: ../model/struct.ChannelId.html - /// [`ClientError::MessageTooLong`]: enum.ClientError.html#variant.MessageTooLong - #[inline] - pub fn say(&self, content: &str) -> Result<Message> { - self.id().say(content) - } - - /// Performs a search request to the API for the inner channel's - /// [`Message`]s. - /// - /// 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 - /// [`Message`]: struct.Message.html - /// [`Search`]: ../utils/builder/struct.Search.html - pub fn search<F>(&self, f: F) -> Result<SearchResult> - where F: FnOnce(Search) -> Search { - #[cfg(feature="cache")] - { - if CACHE.read().unwrap().user.bot { - return Err(Error::Client(ClientError::InvalidOperationAsBot)); - } - } - - self.id().search(f) - } - - /// Sends a file along with optional message contents. The filename _must_ - /// be specified. - /// - /// Refer to [`ChannelId::send_file`] for examples and more information. - /// - /// 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. - /// - /// [`ChannelId::send_file`]: struct.ChannelId.html#method.send_file - /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - /// [Attach Files]: permissions/constant.ATTACH_FILES.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html - pub fn send_file<F, R>(&self, file: R, filename: &str, f: F) -> Result<Message> - where F: FnOnce(CreateMessage) -> CreateMessage, R: Read { - self.id().send_file(file, filename, f) - } - - /// Sends a message to the channel. - /// - /// Refer to the documentation for [`CreateMessage`] for more information - /// regarding message restrictions and requirements. - /// - /// 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 - #[inline] - pub fn send_message<F>(&self, f: F) -> Result<Message> - where F: FnOnce(CreateMessage) -> CreateMessage { - self.id().send_message(f) - } - - /// 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<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { - self.id().unpin(message_id) - } -} - -impl fmt::Display for Channel { - /// Formats the channel into a "mentioned" string. - /// - /// This will return a different format for each type of channel: - /// - /// - [`Group`]s: the generated name retrievable via [`Group::name`]; - /// - [`PrivateChannel`]s: the recipient's name; - /// - [`GuildChannel`]s: a string mentioning the channel that users who can - /// see the channel can click on. - /// - /// [`Group`]: struct.Group.html - /// [`Group::name`]: struct.Group.html#method.name - /// [`GuildChannel`]: struct.GuildChannel.html - /// [`PrivateChannel`]: struct.PrivateChannel.html - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Channel::Group(ref group) => { - fmt::Display::fmt(&group.read().unwrap().name(), f) - }, - Channel::Guild(ref ch) => { - fmt::Display::fmt(&ch.read().unwrap().id.mention(), f) - }, - Channel::Private(ref ch) => { - let channel = ch.read().unwrap(); - let recipient = channel.recipient.read().unwrap(); - - fmt::Display::fmt(&recipient.name, f) - }, - } - } -} - -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<M: Into<MessageId>>(&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<M, R>(&self, message_id: M, reaction_type: R) - -> Result<()> where M: Into<MessageId>, R: Into<ReactionType> { - 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<Channel> { - 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. - /// - /// [`Message`]: struct.Message.html - /// [`Message::delete`]: struct.Message.html#method.delete - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html - #[inline] - pub fn delete_message<M: Into<MessageId>>(&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::<Vec<u64>>(); - - 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<M, R>(&self, message_id: M, user_id: Option<UserId>, reaction_type: R) - -> Result<()> where M: Into<MessageId>, R: Into<ReactionType> { - 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 - /// // assuming a `channel_id` has been bound - /// - /// channel_id.edit(|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 - /// [Manage Channel]: permissions/constant.MANAGE_CHANNELS.html - #[inline] - pub fn edit<F: FnOnce(EditChannel) -> EditChannel>(&self, f: F) -> Result<GuildChannel> { - rest::edit_channel(self.0, &f(EditChannel::default()).0.build()) - } - - /// Edits a [`Message`] in the channel given its Id. - /// - /// Message editing preserves all unchanged message data. - /// - /// Refer to the documentation for [`CreateMessage`] for more information - /// regarding message restrictions and requirements. - /// - /// **Note**: Requires that the current user be the author of the message. - /// - /// # Errors - /// - /// Returns a [`ClientError::MessageTooLong`] if the content of the message - /// is over the [`the limit`], containing the number of unicode code points - /// over the limit. - /// - /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - /// [`CreateMessage`]: ../utils/builder/struct.CreateMessage.html - /// [`Message`]: struct.Message.html - /// [`the limit`]: ../utils/builder/struct.CreateMessage.html#method.content - pub fn edit_message<F, M>(&self, message_id: M, f: F) -> Result<Message> - where F: FnOnce(CreateMessage) -> CreateMessage, M: Into<MessageId> { - let 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))); - } - } - } - - rest::edit_message(self.0, message_id.into().0, &Value::Object(map)) - } - - /// Search the cache for the channel with the Id. - #[cfg(feature="cache")] - pub fn find(&self) -> Option<Channel> { - CACHE.read().unwrap().get_channel(*self) - } - - /// Search the cache for the channel. If it can't be found, the channel is - /// requested over REST. - pub fn get(&self) -> Result<Channel> { - #[cfg(feature="cache")] - { - if let Some(channel) = CACHE.read().unwrap().get_channel(*self) { - return Ok(channel); - } - } - - 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<Vec<RichInvite>> { - rest::get_channel_invites(self.0) - } - - /// Gets a message from the channel. - /// - /// Requires the [Read Message History] permission. - /// - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html - #[inline] - pub fn get_message<M: Into<MessageId>>(&self, message_id: M) -> Result<Message> { - 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]: permissions/constant.READ_MESSAGE_HISTORY.html - pub fn get_messages<F>(&self, f: F) -> Result<Vec<Message>> - 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<M, R, U>(&self, - message_id: M, - reaction_type: R, - limit: Option<u8>, - after: Option<U>) - -> Result<Vec<User>> where M: Into<MessageId>, R: Into<ReactionType>, U: Into<UserId> { - 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)) - } - - /// Retrieves the channel's webhooks. - /// - /// **Note**: Requires the [Manage Webhooks] permission. - /// - /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html - #[inline] - pub fn get_webhooks(&self) -> Result<Vec<Webhook>> { - rest::get_channel_webhooks(self.0) - } - - /// Pins a [`Message`] to the channel. - /// - /// [`Message`]: struct.Message.html - #[inline] - pub fn pin<M: Into<MessageId>>(&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. - /// - /// [`Message`]: struct.Message.html - #[inline] - pub fn pins(&self) -> Result<Vec<Message>> { - rest::get_pins(self.0) - } - - /// Sends a message with just the given message content in the channel. - /// - /// # 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. - /// - /// [`ChannelId`]: ../model/struct.ChannelId.html - /// [`ClientError::MessageTooLong`]: enum.ClientError.html#variant.MessageTooLong - #[inline] - pub fn say(&self, content: &str) -> Result<Message> { - self.send_message(|m| m.content(content)) - } - - /// 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<F: FnOnce(Search) -> Search>(&self, f: F) -> Result<SearchResult> { - 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. - /// - /// The [Attach Files] and [Send Messages] permissions are required. - /// - /// **Note**: Message contents must be under 2000 unicode code points. - /// - /// # Examples - /// - /// Send a file with the filename `my_file.jpg`: - /// - /// ```rust,no_run - /// use serenity::model::ChannelId; - /// use std::fs::File; - /// - /// let channel_id = ChannelId(7); - /// let filename = "my_file.jpg"; - /// let file = File::open(filename).unwrap(); - /// - /// let _ = channel_id.send_file(file, filename, |m| m.content("a file")); - /// ``` - /// - /// # 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<F, R>(&self, file: R, filename: &str, f: F) -> Result<Message> - 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. - /// - /// **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<F>(&self, f: F) -> Result<Message> - 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<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { - rest::unpin_message(self.0, message_id.into().0) - } -} - -impl From<Channel> for ChannelId { - /// Gets the Id of a `Channel`. - fn from(channel: Channel) -> ChannelId { - match channel { - Channel::Group(group) => group.read().unwrap().channel_id, - Channel::Guild(ch) => ch.read().unwrap().id, - Channel::Private(ch) => ch.read().unwrap().id, - } - } -} - -impl From<PrivateChannel> for ChannelId { - /// Gets the Id of a private channel. - fn from(private_channel: PrivateChannel) -> ChannelId { - private_channel.id - } -} - -impl From<GuildChannel> 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 - #[inline] - pub fn fake<F>(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`]: ../client/rest/fn.ack_message.html - pub fn ack<M: Into<MessageId>>(&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. - /// - /// Refer to [`rest::add_group_recipient`] for more information. - /// - /// **Note**: Groups have a limit of 10 recipients, including the current - /// user. - /// - /// [`rest::add_group_recipient`]: ../client/rest/fn.add_group_recipient.html - pub fn add_recipient<U: Into<UserId>>(&self, user: U) -> Result<()> { - let user = user.into(); - - // If the group already contains the recipient, do nothing. - if self.recipients.contains_key(&user) { - return Ok(()); - } - - rest::add_group_recipient(self.channel_id.0, user.0) - } - - /// Broadcasts that the current user is typing in the group. - #[inline] - pub fn broadcast_typing(&self) -> Result<()> { - self.channel_id.broadcast_typing() - } - - /// 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<M, R>(&self, message_id: M, reaction_type: R) - -> Result<()> where M: Into<MessageId>, R: Into<ReactionType> { - self.channel_id.create_reaction(message_id, reaction_type) - } - - /// 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.channel_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.channel_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<M, R>(&self, message_id: M, user_id: Option<UserId>, reaction_type: R) - -> Result<()> where M: Into<MessageId>, R: Into<ReactionType> { - self.channel_id.delete_reaction(message_id, user_id, reaction_type) - } - - /// Edits a [`Message`] in the channel given its Id. - /// - /// Message editing preserves all unchanged message data. - /// - /// Refer to the documentation for [`CreateMessage`] for more information - /// regarding message restrictions and requirements. - /// - /// **Note**: Requires that the current user be the author of the message. - /// - /// # Errors - /// - /// Returns a [`ClientError::MessageTooLong`] if the content of the message - /// is over the [`the limit`], containing the number of unicode code points - /// over the limit. - /// - /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - /// [`CreateMessage`]: ../utils/builder/struct.CreateMessage.html - /// [`Message`]: struct.Message.html - /// [`the limit`]: ../utils/builder/struct.CreateMessage.html#method.content - #[inline] - pub fn edit_message<F, M>(&self, message_id: M, f: F) -> Result<Message> - where F: FnOnce(CreateMessage) -> CreateMessage, M: Into<MessageId> { - self.channel_id.edit_message(message_id, f) - } - - /// Gets a message from the channel. - /// - /// Requires the [Read Message History] permission. - /// - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html - #[inline] - pub fn get_message<M: Into<MessageId>>(&self, message_id: M) -> Result<Message> { - self.channel_id.get_message(message_id) - } - - /// Gets messages from the channel. - /// - /// Requires the [Read Message History] permission. - /// - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html - #[inline] - pub fn get_messages<F>(&self, f: F) -> Result<Vec<Message>> - 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<M, R, U>(&self, - message_id: M, - reaction_type: R, - limit: Option<u8>, - after: Option<U>) - -> Result<Vec<User>> where M: Into<MessageId>, R: Into<ReactionType>, U: Into<UserId> { - self.channel_id.get_reaction_users(message_id, reaction_type, limit, after) - } - - /// Returns the formatted URI of the group's icon if one exists. - pub fn icon_url(&self) -> Option<String> { - self.icon.as_ref().map(|icon| - format!(cdn!("/channel-icons/{}/{}.webp"), self.channel_id, icon)) - } - - /// Leaves the group. - #[inline] - pub fn leave(&self) -> Result<Group> { - rest::leave_group(self.channel_id.0) - } - - /// Generates a name for the group. - /// - /// If there are no recipients in the group, the name will be "Empty Group". - /// Otherwise, the name is generated in a Comma Separated Value list, such - /// as "person 1, person 2, person 3". - pub fn name(&self) -> Cow<str> { - match self.name { - Some(ref name) => Cow::Borrowed(name), - None => { - let mut name = match self.recipients.values().nth(0) { - Some(recipient) => recipient.read().unwrap().name.clone(), - None => return Cow::Borrowed("Empty Group"), - }; - - for recipient in self.recipients.values().skip(1) { - let _ = write!(name, ", {}", recipient.read().unwrap().name); - } - - Cow::Owned(name) - } - } - } - - /// Retrieves the list of messages that have been pinned in the group. - #[inline] - pub fn pins(&self) -> Result<Vec<Message>> { - 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. - pub fn remove_recipient<U: Into<UserId>>(&self, user: U) -> Result<()> { - let user = user.into(); - - // If the group does not contain the recipient already, do nothing. - if !self.recipients.contains_key(&user) { - return Ok(()); - } - - rest::remove_group_recipient(self.channel_id.0, user.0) - } - - /// Sends a message with just the given message content in the channel. - /// - /// # 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. - /// - /// [`ChannelId`]: ../model/struct.ChannelId.html - /// [`ClientError::MessageTooLong`]: enum.ClientError.html#variant.MessageTooLong - #[inline] - pub fn say(&self, content: &str) -> Result<Message> { - self.channel_id.say(content) - } - - /// Performs a search request to the API for the group's channel's - /// [`Message`]s. - /// - /// 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 - /// [`Message`]: struct.Message.html - /// [`Search`]: ../utils/builder/struct.Search.html - #[inline] - pub fn search<F: FnOnce(Search) -> Search>(&self, f: F) -> Result<SearchResult> { - self.channel_id.search(f) - } - - /// Sends a file along with optional message contents. The filename _must_ - /// be specified. - /// - /// Refer to [`ChannelId::send_file`] for examples and more information. - /// - /// 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. - /// - /// [`ChannelId::send_file`]: struct.ChannelId.html#method.send_file - /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - /// [Attach Files]: permissions/constant.ATTACH_FILES.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html - pub fn send_file<F, R>(&self, file: R, filename: &str, f: F) -> Result<Message> - where F: FnOnce(CreateMessage) -> CreateMessage, R: Read { - self.channel_id.send_file(file, filename, f) - } - - /// Sends a message to the group with the given content. - /// - /// Refer to the documentation for [`CreateMessage`] for more information - /// regarding message restrictions and requirements. - /// - /// **Note**: Requires the [Send Messages] permission. - /// - /// [`CreateMessage`]: ../utils/builder/struct.CreateMessage.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html - #[inline] - pub fn send_message<F: FnOnce(CreateMessage) -> CreateMessage>(&self, f: F) -> Result<Message> { - self.channel_id.send_message(f) - } - - /// 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<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { - self.channel_id.unpin(message_id) - } -} - -impl Message { - /// Marks the [`Channel`] as being read up to the 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`]: ../client/rest/fn.ack_message.html - pub fn ack<M: Into<MessageId>>(&self) -> Result<()> { - #[cfg(feature="cache")] - { - if CACHE.read().unwrap().user.bot { - return Err(Error::Client(ClientError::InvalidOperationAsBot)); - } - } - - self.channel_id.ack(self.id) - } - - /// Deletes the message. - /// - /// **Note**: The logged in user must either be the author of the message or - /// have the [Manage Messages] permission. - /// - /// # Errors - /// - /// If the `cache` feature is enabled, then returns a - /// [`ClientError::InvalidPermissions`] if the current user does not have - /// the required permissions. - /// - /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions - /// [`ClientError::InvalidUser`]: ../client/enum.ClientError.html#variant.InvalidUser - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html - pub fn delete(&self) -> Result<()> { - #[cfg(feature="cache")] - { - let req = permissions::MANAGE_MESSAGES; - let is_author = self.author.id == CACHE.read().unwrap().user.id; - let has_perms = utils::user_has_perms(self.channel_id, req)?; - - if !is_author && !has_perms { - return Err(Error::Client(ClientError::InvalidPermissions(req))); - } - } - - self.channel_id.delete_message(self.id) - } - - /// Deletes all of the [`Reaction`]s associated with the message. - /// - /// **Note**: Requires the [Manage Messages] permission. - /// - /// # Errors - /// - /// If the `cache` feature is enabled, then returns a - /// [`ClientError::InvalidPermissions`] if the current user does not have - /// the required permissions. - /// - /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions - /// [`Reaction`]: struct.Reaction.html - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html - pub fn delete_reactions(&self) -> Result<()> { - #[cfg(feature="cache")] - { - let req = permissions::MANAGE_MESSAGES; - - if !utils::user_has_perms(self.channel_id, req)? { - return Err(Error::Client(ClientError::InvalidPermissions(req))); - } - } - - rest::delete_message_reactions(self.channel_id.0, self.id.0) - } - - /// Edits this message, replacing the original content with new content. - /// - /// Message editing preserves all unchanged message data. - /// - /// Refer to the documentation for [`CreateMessage`] for more information - /// regarding message restrictions and requirements. - /// - /// **Note**: Requires that the current user be the author of the message. - /// - /// # Examples - /// - /// Edit a message with new content: - /// - /// ```rust,ignore - /// // assuming a `message` has already been bound - /// - /// message.edit(|m| m.content("new content")); - /// ``` - /// - /// # Errors - /// - /// If the `cache` is enabled, returns a [`ClientError::InvalidUser`] if the - /// current user is not the author. - /// - /// Returns a [`ClientError::MessageTooLong`] if the content of the message - /// is over [`the limit`], containing the number of unicode code points - /// over the limit. - /// - /// [`ClientError::InvalidUser`]: ../client/enum.ClientError.html#variant.InvalidUser - /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - /// [`CreateMessage`]: ../utils/builder/struct.CreateMessage.html - /// [`the limit`]: ../utils/builder/struct.CreateMessage.html#method.content - pub fn edit<F>(&mut self, f: F) -> Result<()> - where F: FnOnce(CreateMessage) -> CreateMessage { - #[cfg(feature="cache")] - { - if self.author.id != CACHE.read().unwrap().user.id { - return Err(Error::Client(ClientError::InvalidUser)); - } - } - - let mut builder = CreateMessage::default(); - - if !self.content.is_empty() { - builder = builder.content(&self.content); - } - - if let Some(embed) = self.embeds.get(0) { - builder = builder.embed(|_| CreateEmbed::from(embed.clone())); - } - - if self.tts { - builder = builder.tts(true); - } - - let map = f(builder).0; - - match rest::edit_message(self.channel_id.0, self.id.0, &Value::Object(map)) { - Ok(edited) => { - mem::replace(self, edited); - - Ok(()) - }, - Err(why) => Err(why), - } - } - - /// Returns message content, but with user and role mentions replaced with - /// names and everyone/here mentions cancelled. - #[cfg(feature="cache")] - pub fn content_safe(&self) -> String { - let mut result = self.content.clone(); - - // First replace all user mentions. - for u in &self.mentions { - result = result.replace(&u.mention(), &u.distinct()); - } - - // Then replace all role mentions. - for id in &self.mention_roles { - let mention = id.mention(); - - if let Some(role) = id.find() { - result = result.replace(&mention, &format!("@{}", role.name)); - } else { - result = result.replace(&mention, "@deleted-role"); - } - } - - // And finally replace everyone and here mentions. - result.replace("@everyone", "@\u{200B}everyone") - .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<R, U>(&self, reaction_type: R, limit: Option<u8>, after: Option<U>) - -> Result<Vec<User>> where R: Into<ReactionType>, U: Into<UserId> { - self.channel_id.get_reaction_users(self.id, reaction_type, limit, after) - } - - /// Returns the associated `Guild` for the message if one is in the cache. - /// - /// Returns `None` if the guild's Id could not be found via [`guild_id`] or - /// if the Guild itself is not cached. - /// - /// Requires the `cache` feature be enabled. - /// - /// [`guild_id`]: #method.guild_id - #[cfg(feature="cache")] - pub fn guild(&self) -> Option<Arc<RwLock<Guild>>> { - self.guild_id().and_then(|guild_id| CACHE.read().unwrap().get_guild(guild_id)) - } - - /// 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(feature="cache")] - pub fn guild_id(&self) -> Option<GuildId> { - match CACHE.read().unwrap().get_channel(self.channel_id) { - Some(Channel::Guild(ch)) => Some(ch.read().unwrap().guild_id), - _ => None, - } - } - - /// True if message was sent using direct messages. - #[cfg(feature="cache")] - pub fn is_private(&self) -> bool { - match CACHE.read().unwrap().get_channel(self.channel_id) { - Some(Channel::Group(_)) | Some(Channel::Private(_)) => true, - _ => false, - } - } - - /// Checks the length of a string to ensure that it is within Discord's - /// maximum message length limit. - /// - /// Returns `None` if the message is within the limit, otherwise returns - /// `Some` with an inner value of how many unicode code points the message - /// is over. - pub fn overflow_length(content: &str) -> Option<u64> { - // Check if the content is over the maximum number of unicode code - // points. - let count = content.chars().count() as i64; - let diff = count - (constants::MESSAGE_CODE_LIMIT as i64); - - if diff > 0 { - Some(diff as u64) - } else { - None - } - } - - /// Pins this message to its channel. - /// - /// **Note**: Requires the [Manage Messages] permission. - /// - /// # Errors - /// - /// If the `cache` is enabled, returns a - /// [`ClientError::InvalidPermissions`] if the current user does not have - /// the required permissions. - /// - /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html - pub fn pin(&self) -> Result<()> { - #[cfg(feature="cache")] - { - let req = permissions::MANAGE_MESSAGES; - - if !utils::user_has_perms(self.channel_id, req)? { - return Err(Error::Client(ClientError::InvalidPermissions(req))); - } - } - - self.channel_id.pin(self.id.0) - } - - /// React to the message with a custom [`Emoji`] or unicode character. - /// - /// **Note**: Requires the [Add Reactions] permission. - /// - /// # Errors - /// - /// If the `cache` is enabled, returns a - /// [`ClientError::InvalidPermissions`] if the current user does not have - /// the required [permissions]. - /// - /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions - /// [`Emoji`]: struct.Emoji.html - /// [Add Reactions]: permissions/constant.ADD_REACTIONS.html - /// [permissions]: permissions - pub fn react<R: Into<ReactionType>>(&self, reaction_type: R) -> Result<()> { - #[cfg(feature="cache")] - { - let req = permissions::ADD_REACTIONS; - - if !utils::user_has_perms(self.channel_id, req)? { - return Err(Error::Client(ClientError::InvalidPermissions(req))); - } - } - - rest::create_reaction(self.channel_id.0, - self.id.0, - &reaction_type.into()) - } - - /// Replies to the user, mentioning them prior to the content in the form - /// of: `@<USER_ID>: YOUR_CONTENT`. - /// - /// User mentions are generally around 20 or 21 characters long. - /// - /// **Note**: Requires the [Send Messages] permission. - /// - /// **Note**: Message contents must be under 2000 unicode code points. - /// - /// # Errors - /// - /// If the `cache` is enabled, returns a - /// [`ClientError::InvalidPermissions`] if the current user does not have - /// the required permissions. - /// - /// 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. - /// - /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions - /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html - pub fn reply(&self, content: &str) -> Result<Message> { - if let Some(length_over) = Message::overflow_length(content) { - return Err(Error::Client(ClientError::MessageTooLong(length_over))); - } - - #[cfg(feature="cache")] - { - let req = permissions::SEND_MESSAGES; - - if !utils::user_has_perms(self.channel_id, req)? { - return Err(Error::Client(ClientError::InvalidPermissions(req))); - } - } - - let mut gen = self.author.mention(); - gen.push_str(": "); - gen.push_str(content); - - let map = ObjectBuilder::new() - .insert("content", gen) - .insert("tts", false) - .build(); - - rest::send_message(self.channel_id.0, &map) - } - - /// Unpins the message from its channel. - /// - /// **Note**: Requires the [Manage Messages] permission. - /// - /// # Errors - /// - /// If the `cache` is enabled, returns a - /// [`ClientError::InvalidPermissions`] if the current user does not have - /// the required permissions. - /// - /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html - pub fn unpin(&self) -> Result<()> { - #[cfg(feature="cache")] - { - let req = permissions::MANAGE_MESSAGES; - - if !utils::user_has_perms(self.channel_id, req)? { - return Err(Error::Client(ClientError::InvalidPermissions(req))); - } - } - - rest::unpin_message(self.channel_id.0, self.id.0) - } -} - -impl From<Message> 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<PermissionOverwrite> { - let mut map = into_map(value)?; - let id = remove(&mut map, "id").and_then(decode_id)?; - let kind = remove(&mut map, "type").and_then(into_string)?; - let kind = match &*kind { - "member" => PermissionOverwriteType::Member(UserId(id)), - "role" => PermissionOverwriteType::Role(RoleId(id)), - _ => return Err(Error::Decode("Expected valid PermissionOverwrite type", Value::String(kind))), - }; - - Ok(PermissionOverwrite { - kind: kind, - allow: remove(&mut map, "allow").and_then(Permissions::decode)?, - deny: remove(&mut map, "deny").and_then(Permissions::decode)?, - }) - } -} - -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`]: ../client/rest/fn.ack_message.html - pub fn ack<M: Into<MessageId>>(&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) - } - - /// Broadcasts that the current user is typing to the recipient. - pub fn broadcast_typing(&self) -> Result<()> { - self.id.broadcast_typing() - } - - /// 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<M, R>(&self, message_id: M, reaction_type: R) - -> Result<()> where M: Into<MessageId>, R: Into<ReactionType> { - self.id.create_reaction(message_id, reaction_type) - } - - #[doc(hidden)] - pub fn decode(value: Value) -> Result<PrivateChannel> { - let mut map = into_map(value)?; - let mut recipients = decode_array(remove(&mut map, "recipients")?, - User::decode)?; - - Ok(PrivateChannel { - id: remove(&mut map, "id").and_then(ChannelId::decode)?, - kind: remove(&mut map, "type").and_then(ChannelType::decode)?, - last_message_id: opt(&mut map, "last_message_id", MessageId::decode)?, - last_pin_timestamp: opt(&mut map, "last_pin_timestamp", into_string)?, - recipient: Arc::new(RwLock::new(recipients.remove(0))), - }) - } - - /// 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. - #[inline] - pub fn delete(&self) -> Result<Channel> { - 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<M, R>(&self, message_id: M, user_id: Option<UserId>, reaction_type: R) - -> Result<()> where M: Into<MessageId>, R: Into<ReactionType> { - self.id.delete_reaction(message_id, user_id, reaction_type) - } - - /// Edits a [`Message`] in the channel given its Id. - /// - /// Message editing preserves all unchanged message data. - /// - /// Refer to the documentation for [`CreateMessage`] for more information - /// regarding message restrictions and requirements. - /// - /// **Note**: Requires that the current user be the author of the message. - /// - /// # Errors - /// - /// Returns a [`ClientError::MessageTooLong`] if the content of the message - /// is over the [`the limit`], containing the number of unicode code points - /// over the limit. - /// - /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - /// [`CreateMessage`]: ../utils/builder/struct.CreateMessage.html - /// [`Message`]: struct.Message.html - /// [`the limit`]: ../utils/builder/struct.CreateMessage.html#method.content - #[inline] - pub fn edit_message<F, M>(&self, message_id: M, f: F) -> Result<Message> - where F: FnOnce(CreateMessage) -> CreateMessage, M: Into<MessageId> { - self.id.edit_message(message_id, f) - } - - /// Gets a message from the channel. - /// - /// Requires the [Read Message History] permission. - /// - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html - #[inline] - pub fn get_message<M: Into<MessageId>>(&self, message_id: M) -> Result<Message> { - 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]: permissions/constant.READ_MESSAGE_HISTORY.html - #[inline] - pub fn get_messages<F>(&self, f: F) -> Result<Vec<Message>> - 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<M, R, U>(&self, - message_id: M, - reaction_type: R, - limit: Option<u8>, - after: Option<U>) - -> Result<Vec<User>> where M: Into<MessageId>, R: Into<ReactionType>, U: Into<UserId> { - self.id.get_reaction_users(message_id, reaction_type, limit, after) - } - - /// Pins a [`Message`] to the channel. - /// - /// [`Message`]: struct.Message.html - #[inline] - pub fn pin<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { - self.id.pin(message_id) - } - - /// Retrieves the list of messages that have been pinned in the private - /// channel. - #[inline] - pub fn pins(&self) -> Result<Vec<Message>> { - self.id.pins() - } - - /// Sends a message with just the given message content in the channel. - /// - /// # 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. - /// - /// [`ChannelId`]: ../model/struct.ChannelId.html - /// [`ClientError::MessageTooLong`]: enum.ClientError.html#variant.MessageTooLong - #[inline] - pub fn say(&self, content: &str) -> Result<Message> { - self.id.say(content) - } - - /// Performs a search request to the API for the channel's [`Message`]s. - /// - /// 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 - /// [`Message`]: struct.Message.html - /// [`Search`]: ../utils/builder/struct.Search.html - pub fn search<F>(&self, f: F) -> Result<SearchResult> - where F: FnOnce(Search) -> Search { - #[cfg(feature="cache")] - { - if CACHE.read().unwrap().user.bot { - return Err(Error::Client(ClientError::InvalidOperationAsBot)); - } - } - - self.id.search(f) - } - - /// Sends a file along with optional message contents. The filename _must_ - /// be specified. - /// - /// Refer to [`ChannelId::send_file`] for examples and more information. - /// - /// 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. - /// - /// [`ChannelId::send_file`]: struct.ChannelId.html#method.send_file - /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - /// [Attach Files]: permissions/constant.ATTACH_FILES.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html - pub fn send_file<F, R>(&self, file: R, filename: &str, f: F) -> Result<Message> - where F: FnOnce(CreateMessage) -> CreateMessage, R: Read { - self.id.send_file(file, filename, f) - } - - /// Sends a message to the channel with the given content. - /// - /// Refer to the documentation for [`CreateMessage`] for more information - /// regarding message restrictions and requirements. - /// - /// # 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. - /// - /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - /// [`CreateMessage`]: ../utils/builder/struct.CreateMessage.html - /// [`Message`]: struct.Message.html - #[inline] - pub fn send_message<F: FnOnce(CreateMessage) -> CreateMessage>(&self, f: F) -> Result<Message> { - self.id.send_message(f) - } - - /// 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<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { - self.id.unpin(message_id) - } -} - -impl fmt::Display for PrivateChannel { - /// Formats the private channel, displaying the recipient's username. - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(&self.recipient.read().unwrap().name) - } -} - -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`]: ../client/rest/fn.ack_message.html - pub fn ack<M: Into<MessageId>>(&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. - /// - /// **Note**: Requires the [Send Messages] permission. - /// - /// # Errors - /// - /// Returns a [`ClientError::InvalidPermissions`] if the current user does - /// not have the required permissions. - /// - /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html - pub fn broadcast_typing(&self) -> Result<()> { - self.id.broadcast_typing() - } - - /// Creates an invite leading to the given channel. - /// - /// # Examples - /// - /// Create an invite that can only be used 5 times: - /// - /// ```rust,ignore - /// let invite = channel.create_invite(|i| i.max_uses(5)); - /// ``` - pub fn create_invite<F>(&self, f: F) -> Result<RichInvite> - where F: FnOnce(CreateInvite) -> CreateInvite { - #[cfg(feature="cache")] - { - let req = permissions::CREATE_INVITE; - - if !utils::user_has_perms(self.id, req)? { - return Err(Error::Client(ClientError::InvalidPermissions(req))); - } - } - - let map = f(CreateInvite::default()).0.build(); - - 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::client::CACHE; - /// use serenity::model::{ChannelId, PermissionOverwrite, permissions}; - /// - /// 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 cache = CACHE.read().unwrap(); - /// let channel = cache.get_guild_channel(channel_id).unwrap(); - /// - /// let _ = channel.create_permission(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::client::CACHE; - /// use serenity::model::{ChannelId, PermissionOverwrite, permissions}; - /// - /// 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 cache = CACHE.read().unwrap(); - /// let channel = cache.get_guild_channel(channel_id).unwrap(); - /// - /// let _ = channel.create_permission(overwrite); - /// ``` - /// - /// [`Channel`]: enum.Channel.html - /// [`Member`]: struct.Member.html - /// [`PermissionOverwrite`]: struct.PermissionOverwrite.html - /// [`PermissionOverwrite::Member`]: struct.PermissionOverwrite.html#variant.Member - /// [`PermissionOverwrite::Role`]: struct.PermissionOverwrite.html#variant.Role - /// [`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<GuildChannel> { - let mut map = into_map(value)?; - - let id = remove(&mut map, "guild_id").and_then(GuildId::decode)?; - - GuildChannel::decode_guild(Value::Object(map), id) - } - - #[doc(hidden)] - pub fn decode_guild(value: Value, guild_id: GuildId) -> Result<GuildChannel> { - let mut map = into_map(value)?; - - Ok(GuildChannel { - id: remove(&mut map, "id").and_then(ChannelId::decode)?, - name: remove(&mut map, "name").and_then(into_string)?, - guild_id: guild_id, - topic: opt(&mut map, "topic", into_string)?, - position: req!(remove(&mut map, "position")?.as_i64()), - kind: remove(&mut map, "type").and_then(ChannelType::decode)?, - last_message_id: opt(&mut map, "last_message_id", MessageId::decode)?, - permission_overwrites: decode_array(remove(&mut map, "permission_overwrites")?, PermissionOverwrite::decode)?, - bitrate: remove(&mut map, "bitrate").ok().and_then(|v| v.as_u64()), - user_limit: remove(&mut map, "user_limit").ok().and_then(|v| v.as_u64()), - last_pin_timestamp: opt(&mut map, "last_pin_timestamp", into_string)?, - }) - } - - /// Deletes this channel, returning the channel on a successful deletion. - pub fn delete(&self) -> Result<Channel> { - #[cfg(feature="cache")] - { - let req = permissions::MANAGE_CHANNELS; - - if !utils::user_has_perms(self.id, req)? { - return Err(Error::Client(ClientError::InvalidPermissions(req))); - } - } - - 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<M, R>(&self, message_id: M, user_id: Option<UserId>, reaction_type: R) - -> Result<()> where M: Into<MessageId>, R: Into<ReactionType> { - self.id.delete_reaction(message_id, user_id, reaction_type) - } - - /// Modifies a channel's settings, such as its position or name. - /// - /// Refer to `EditChannel`s documentation for a full list of methods. - /// - /// # Examples - /// - /// Change a voice channels name and bitrate: - /// - /// ```rust,ignore - /// channel.edit(|c| c.name("test").bitrate(86400)); - /// ``` - pub fn edit<F>(&mut self, f: F) -> Result<()> - where F: FnOnce(EditChannel) -> EditChannel { - - #[cfg(feature="cache")] - { - let req = permissions::MANAGE_CHANNELS; - - if !utils::user_has_perms(self.id, req)? { - return Err(Error::Client(ClientError::InvalidPermissions(req))); - } - } - - let map = ObjectBuilder::new() - .insert("name", &self.name) - .insert("position", self.position) - .insert("type", self.kind.name()); - - let edited = f(EditChannel(map)).0.build(); - - match rest::edit_channel(self.id.0, &edited) { - Ok(channel) => { - mem::replace(self, channel); - - Ok(()) - }, - Err(why) => Err(why), - } - } - - /// Edits a [`Message`] in the channel given its Id. - /// - /// Message editing preserves all unchanged message data. - /// - /// Refer to the documentation for [`CreateMessage`] for more information - /// regarding message restrictions and requirements. - /// - /// **Note**: Requires that the current user be the author of the message. - /// - /// # Errors - /// - /// Returns a [`ClientError::MessageTooLong`] if the content of the message - /// is over the [`the limit`], containing the number of unicode code points - /// over the limit. - /// - /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - /// [`CreateMessage`]: ../utils/builder/struct.CreateMessage.html - /// [`Message`]: struct.Message.html - /// [`the limit`]: ../utils/builder/struct.CreateMessage.html#method.content - #[inline] - pub fn edit_message<F, M>(&self, message_id: M, f: F) -> Result<Message> - where F: FnOnce(CreateMessage) -> CreateMessage, M: Into<MessageId> { - self.id.edit_message(message_id, f) - } - - /// 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<Vec<RichInvite>> { - self.id.get_invites() - } - - /// Gets a message from the channel. - /// - /// Requires the [Read Message History] permission. - /// - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html - #[inline] - pub fn get_message<M: Into<MessageId>>(&self, message_id: M) -> Result<Message> { - 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]: permissions/constant.READ_MESSAGE_HISTORY.html - #[inline] - pub fn get_messages<F>(&self, f: F) -> Result<Vec<Message>> - 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<M, R, U>(&self, - message_id: M, - reaction_type: R, - limit: Option<u8>, - after: Option<U>) - -> Result<Vec<User>> where M: Into<MessageId>, R: Into<ReactionType>, U: Into<UserId> { - self.id.get_reaction_users(message_id, reaction_type, limit, after) - } - - /// Retrieves the channel's webhooks. - /// - /// **Note**: Requires the [Manage Webhooks] permission. - /// - /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html - #[inline] - pub fn get_webhooks(&self) -> Result<Vec<Webhook>> { - self.id.get_webhooks() - } - - /// 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(feature="cache")] - pub fn guild(&self) -> Option<Arc<RwLock<Guild>>> { - CACHE.read().unwrap().get_guild(self.guild_id) - } - - /// Pins a [`Message`] to the channel. - #[inline] - pub fn pin<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { - self.id.pin(message_id) - } - - /// Gets all channel's pins. - #[inline] - pub fn pins(&self) -> Result<Vec<Message>> { - self.id.pins() - } - - /// Sends a message with just the given message content in the channel. - /// - /// # 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. - /// - /// [`ChannelId`]: ../model/struct.ChannelId.html - /// [`ClientError::MessageTooLong`]: enum.ClientError.html#variant.MessageTooLong - #[inline] - pub fn say(&self, content: &str) -> Result<Message> { - self.id.say(content) - } - - /// Performs a search request for the channel's [`Message`]s. - /// - /// 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 - /// [`Message`]: struct.Message.html - /// [`Search`]: ../utils/builder/struct.Search.html - pub fn search<F: FnOnce(Search) -> Search>(&self, f: F) -> Result<SearchResult> { - #[cfg(feature="cache")] - { - if CACHE.read().unwrap().user.bot { - return Err(Error::Client(ClientError::InvalidOperationAsBot)); - } - } - - self.id.search(f) - } - - /// Sends a file along with optional message contents. The filename _must_ - /// be specified. - /// - /// Refer to [`ChannelId::send_file`] for examples and more information. - /// - /// 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. - /// - /// [`ChannelId::send_file`]: struct.ChannelId.html#method.send_file - /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - /// [Attach Files]: permissions/constant.ATTACH_FILES.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html - pub fn send_file<F, R>(&self, file: R, filename: &str, f: F) -> Result<Message> - where F: FnOnce(CreateMessage) -> CreateMessage, R: Read { - self.id.send_file(file, filename, f) - } - - /// Sends a message to the channel with the given content. - /// - /// **Note**: This will only work when a [`Message`] is received. - /// - /// **Note**: Requires the [Send Messages] permission. - /// - /// # 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. - /// - /// Returns a [`ClientError::InvalidPermissions`] if the current user does - /// not have the required permissions. - /// - /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions - /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - /// [`Message`]: struct.Message.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html - pub fn send_message<F: FnOnce(CreateMessage) -> CreateMessage>(&self, f: F) -> Result<Message> { - #[cfg(feature="cache")] - { - let req = permissions::SEND_MESSAGES; - - if !utils::user_has_perms(self.id, req)? { - return Err(Error::Client(ClientError::InvalidPermissions(req))); - } - } - - self.id.send_message(f) - } - - /// 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<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { - self.id.unpin(message_id) - } -} - -impl fmt::Display for GuildChannel { - /// Formats the channel, creating a mention of it. - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.id.mention(), f) - } -} - -impl Reaction { - /// Deletes the reaction, but only if the current user is the user who made - /// the reaction or has permission to. - /// - /// **Note**: Requires the [Manage Messages] permission, _if_ the current - /// user did not perform the reaction. - /// - /// # Errors - /// - /// If the `cache` is enabled, then returns a - /// [`ClientError::InvalidPermissions`] if the current user does not have - /// the required [permissions]. - /// - /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html - /// [permissions]: permissions - pub fn delete(&self) -> Result<()> { - let user_id = feature_cache! {{ - let user = if self.user_id == CACHE.read().unwrap().user.id { - None - } else { - Some(self.user_id.0) - }; - - // If the reaction is one _not_ made by the current user, then ensure - // that the current user has permission* to delete the reaction. - // - // Normally, users can only delete their own reactions. - // - // * The `Manage Messages` permission. - if user.is_some() { - let req = permissions::MANAGE_MESSAGES; - - if !utils::user_has_perms(self.channel_id, req).unwrap_or(true) { - return Err(Error::Client(ClientError::InvalidPermissions(req))); - } - } - - user - } else { - Some(self.user_id.0) - }}; - - rest::delete_reaction(self.channel_id.0, - self.message_id.0, - user_id, - &self.emoji) - } - - /// Retrieves 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. - /// - /// # Errors - /// - /// Returns a [`ClientError::InvalidPermissions`] if the current user does - /// not have the required [permissions]. - /// - /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions - /// [`Emoji`]: struct.Emoji.html - /// [`Message`]: struct.Message.html - /// [`User`]: struct.User.html - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html - /// [permissions]: permissions - pub fn users<R, U>(&self, - reaction_type: R, - limit: Option<u8>, - after: Option<U>) - -> Result<Vec<User>> - where R: Into<ReactionType>, - U: Into<UserId> { - rest::get_reaction_users(self.channel_id.0, - self.message_id.0, - &reaction_type.into(), - limit.unwrap_or(50), - after.map(|u| u.into().0)) - } -} - -/// The type of a [`Reaction`] sent. -/// -/// [`Reaction`]: struct.Reaction.html -#[derive(Clone, Debug)] -pub enum ReactionType { - /// A reaction with a [`Guild`]s custom [`Emoji`], which is unique to the - /// guild. - /// - /// [`Emoji`]: struct.Emoji.html - /// [`Guild`]: struct.Guild.html - Custom { - /// The Id of the custom [`Emoji`]. - /// - /// [`Emoji`]: struct.Emoji.html - id: EmojiId, - /// The name of the custom emoji. This is primarily used for decoration - /// and distinguishing the emoji client-side. - name: String, - }, - /// A reaction with a twemoji. - Unicode(String), -} - -impl ReactionType { - /// Creates a data-esque display of the type. This is not very useful for - /// displaying, as the primary client can not render it, but can be useful - /// for debugging. - /// - /// **Note**: This is mainly for use internally. There is otherwise most - /// likely little use for it. - pub fn as_data(&self) -> String { - match *self { - ReactionType::Custom { id, ref name } => { - format!("{}:{}", name, id) - }, - ReactionType::Unicode(ref unicode) => unicode.clone(), - } - } - - #[doc(hidden)] - pub fn decode(value: Value) -> Result<Self> { - let mut map = into_map(value)?; - let name = remove(&mut map, "name").and_then(into_string)?; - - // Only custom emoji reactions (`ReactionType::Custom`) have an Id. - Ok(match opt(&mut map, "id", EmojiId::decode)? { - Some(id) => ReactionType::Custom { - id: id, - name: name, - }, - None => ReactionType::Unicode(name), - }) - } -} - -impl From<Emoji> for ReactionType { - fn from(emoji: Emoji) -> ReactionType { - ReactionType::Custom { - id: emoji.id, - name: emoji.name, - } - } -} - -impl From<String> for ReactionType { - fn from(unicode: String) -> ReactionType { - ReactionType::Unicode(unicode) - } -} - -impl fmt::Display for ReactionType { - /// Formats the reaction type, displaying the associated emoji in a - /// way that clients can understand. - /// - /// If the type is a [custom][`ReactionType::Custom`] emoji, then refer to - /// the documentation for [emoji's formatter][`Emoji::fmt`] on how this is - /// displayed. Otherwise, if the type is a - /// [unicode][`ReactionType::Unicode`], then the inner unicode is displayed. - /// - /// [`Emoji::fmt`]: struct.Emoji.html#method.fmt - /// [`ReactionType::Custom`]: enum.ReactionType.html#variant.Custom - /// [`ReactionType::Unicode`]: enum.ReactionType.html#variant.Unicode - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - ReactionType::Custom { id, ref name } => { - f.write_char('<')?; - f.write_char(':')?; - f.write_str(name)?; - f.write_char(':')?; - fmt::Display::fmt(&id, f)?; - f.write_char('>') - }, - ReactionType::Unicode(ref unicode) => f.write_str(unicode), - } - } -} diff --git a/src/model/channel/attachment.rs b/src/model/channel/attachment.rs new file mode 100644 index 0000000..387edaf --- /dev/null +++ b/src/model/channel/attachment.rs @@ -0,0 +1,92 @@ +use hyper::Client as HyperClient; +use std::io::Read; +use ::internal::prelude::*; +use ::model::Attachment; + +impl Attachment { + /// If this attachment is an image, then a tuple of the width and height + /// in pixels is returned. + pub fn dimensions(&self) -> Option<(u64, u64)> { + if let (Some(width), Some(height)) = (self.width, self.height) { + Some((width, height)) + } else { + None + } + } + + /// Downloads the attachment, returning back a vector of bytes. + /// + /// # Examples + /// + /// Download all of the attachments associated with a [`Message`]: + /// + /// ```rust,no_run + /// use serenity::Client; + /// use std::env; + /// use std::fs::File; + /// use std::io::Write; + /// use std::path::Path; + /// + /// let token = env::var("DISCORD_TOKEN").expect("token in environment"); + /// let mut client = Client::login_bot(&token); + /// + /// client.on_message(|_, message| { + /// for attachment in message.attachments { + /// let content = match attachment.download() { + /// Ok(content) => content, + /// Err(why) => { + /// println!("Error downloading attachment: {:?}", why); + /// let _ = message.channel_id.say("Error downloading attachment"); + /// + /// return; + /// }, + /// }; + /// + /// let mut file = match File::create(&attachment.filename) { + /// Ok(file) => file, + /// Err(why) => { + /// println!("Error creating file: {:?}", why); + /// let _ = message.channel_id.say("Error creating file"); + /// + /// return; + /// }, + /// }; + /// + /// if let Err(why) = file.write(&content) { + /// println!("Error writing to file: {:?}", why); + /// + /// return; + /// } + /// + /// let _ = message.channel_id.say(&format!("Saved {:?}", attachment.filename)); + /// } + /// }); + /// + /// client.on_ready(|_context, ready| { + /// println!("{} is connected!", ready.user.name); + /// }); + /// + /// let _ = client.start(); + /// ``` + /// + /// # Errors + /// + /// Returns an [`Error::Io`] when there is a problem reading the contents + /// of the HTTP response. + /// + /// Returns an [`Error::Hyper`] when there is a problem retrieving the + /// attachment. + /// + /// [`Error::Hyper`]: ../enum.Error.html#variant.Hyper + /// [`Error::Io`]: ../enum.Error.html#variant.Io + /// [`Message`]: struct.Message.html + pub fn download(&self) -> Result<Vec<u8>> { + let hyper = HyperClient::new(); + let mut response = hyper.get(&self.url).send()?; + + let mut bytes = vec![]; + response.read_to_end(&mut bytes)?; + + Ok(bytes) + } +} diff --git a/src/model/channel/channel_id.rs b/src/model/channel/channel_id.rs new file mode 100644 index 0000000..2d09367 --- /dev/null +++ b/src/model/channel/channel_id.rs @@ -0,0 +1,511 @@ +use serde_json::builder::ObjectBuilder; +use std::fmt::{Display, Formatter, Result as FmtResult, Write as FmtWrite}; +use std::io::Read; +use ::client::{CACHE, rest}; +use ::model::*; +use ::utils::builder::{CreateMessage, EditChannel, GetMessages, Search}; + +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<M: Into<MessageId>>(&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<M, R>(&self, message_id: M, reaction_type: R) + -> Result<()> where M: Into<MessageId>, R: Into<ReactionType> { + 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<Channel> { + 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. + /// + /// [`Message`]: struct.Message.html + /// [`Message::delete`]: struct.Message.html#method.delete + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + #[inline] + pub fn delete_message<M: Into<MessageId>>(&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::<Vec<u64>>(); + + 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<M, R>(&self, message_id: M, user_id: Option<UserId>, reaction_type: R) + -> Result<()> where M: Into<MessageId>, R: Into<ReactionType> { + 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 + /// // assuming a `channel_id` has been bound + /// + /// channel_id.edit(|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 + /// [Manage Channel]: permissions/constant.MANAGE_CHANNELS.html + #[inline] + pub fn edit<F: FnOnce(EditChannel) -> EditChannel>(&self, f: F) -> Result<GuildChannel> { + rest::edit_channel(self.0, &f(EditChannel::default()).0.build()) + } + + /// Edits a [`Message`] in the channel given its Id. + /// + /// Message editing preserves all unchanged message data. + /// + /// Refer to the documentation for [`CreateMessage`] for more information + /// regarding message restrictions and requirements. + /// + /// **Note**: Requires that the current user be the author of the message. + /// + /// # Errors + /// + /// Returns a [`ClientError::MessageTooLong`] if the content of the message + /// is over the [`the limit`], containing the number of unicode code points + /// over the limit. + /// + /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong + /// [`CreateMessage`]: ../utils/builder/struct.CreateMessage.html + /// [`Message`]: struct.Message.html + /// [`the limit`]: ../utils/builder/struct.CreateMessage.html#method.content + pub fn edit_message<F, M>(&self, message_id: M, f: F) -> Result<Message> + where F: FnOnce(CreateMessage) -> CreateMessage, M: Into<MessageId> { + let 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))); + } + } + } + + rest::edit_message(self.0, message_id.into().0, &Value::Object(map)) + } + + /// Search the cache for the channel with the Id. + #[cfg(feature="cache")] + pub fn find(&self) -> Option<Channel> { + CACHE.read().unwrap().get_channel(*self) + } + + /// Search the cache for the channel. If it can't be found, the channel is + /// requested over REST. + pub fn get(&self) -> Result<Channel> { + #[cfg(feature="cache")] + { + if let Some(channel) = CACHE.read().unwrap().get_channel(*self) { + return Ok(channel); + } + } + + 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<Vec<RichInvite>> { + rest::get_channel_invites(self.0) + } + + /// Gets a message from the channel. + /// + /// Requires the [Read Message History] permission. + /// + /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + #[inline] + pub fn get_message<M: Into<MessageId>>(&self, message_id: M) -> Result<Message> { + 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]: permissions/constant.READ_MESSAGE_HISTORY.html + pub fn get_messages<F>(&self, f: F) -> Result<Vec<Message>> + 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<M, R, U>(&self, + message_id: M, + reaction_type: R, + limit: Option<u8>, + after: Option<U>) + -> Result<Vec<User>> where M: Into<MessageId>, R: Into<ReactionType>, U: Into<UserId> { + 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)) + } + + /// Retrieves the channel's webhooks. + /// + /// **Note**: Requires the [Manage Webhooks] permission. + /// + /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html + #[inline] + pub fn get_webhooks(&self) -> Result<Vec<Webhook>> { + rest::get_channel_webhooks(self.0) + } + + /// Pins a [`Message`] to the channel. + /// + /// [`Message`]: struct.Message.html + #[inline] + pub fn pin<M: Into<MessageId>>(&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. + /// + /// [`Message`]: struct.Message.html + #[inline] + pub fn pins(&self) -> Result<Vec<Message>> { + rest::get_pins(self.0) + } + + /// Sends a message with just the given message content in the channel. + /// + /// # 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. + /// + /// [`ChannelId`]: ../model/struct.ChannelId.html + /// [`ClientError::MessageTooLong`]: enum.ClientError.html#variant.MessageTooLong + #[inline] + pub fn say(&self, content: &str) -> Result<Message> { + self.send_message(|m| m.content(content)) + } + + /// 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<F: FnOnce(Search) -> Search>(&self, f: F) -> Result<SearchResult> { + 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. + /// + /// The [Attach Files] and [Send Messages] permissions are required. + /// + /// **Note**: Message contents must be under 2000 unicode code points. + /// + /// # Examples + /// + /// Send a file with the filename `my_file.jpg`: + /// + /// ```rust,no_run + /// use serenity::model::ChannelId; + /// use std::fs::File; + /// + /// let channel_id = ChannelId(7); + /// let filename = "my_file.jpg"; + /// let file = File::open(filename).unwrap(); + /// + /// let _ = channel_id.send_file(file, filename, |m| m.content("a file")); + /// ``` + /// + /// # 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<F, R>(&self, file: R, filename: &str, f: F) -> Result<Message> + 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. + /// + /// **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<F>(&self, f: F) -> Result<Message> + 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<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { + rest::unpin_message(self.0, message_id.into().0) + } +} + +impl From<Channel> for ChannelId { + /// Gets the Id of a `Channel`. + fn from(channel: Channel) -> ChannelId { + match channel { + Channel::Group(group) => group.read().unwrap().channel_id, + Channel::Guild(ch) => ch.read().unwrap().id, + Channel::Private(ch) => ch.read().unwrap().id, + } + } +} + +impl From<PrivateChannel> for ChannelId { + /// Gets the Id of a private channel. + fn from(private_channel: PrivateChannel) -> ChannelId { + private_channel.id + } +} + +impl From<GuildChannel> for ChannelId { + /// Gets the Id of a guild channel. + fn from(public_channel: GuildChannel) -> ChannelId { + public_channel.id + } +} + +impl Display for ChannelId { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + Display::fmt(&self.0, f) + } +} diff --git a/src/model/channel/embed.rs b/src/model/channel/embed.rs new file mode 100644 index 0000000..32c3722 --- /dev/null +++ b/src/model/channel/embed.rs @@ -0,0 +1,15 @@ +use serde_json::Value; +use ::model::Embed; +use ::utils::builder::CreateEmbed; + +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 + #[inline] + pub fn fake<F>(f: F) -> Value where F: FnOnce(CreateEmbed) -> CreateEmbed { + Value::Object(f(CreateEmbed::default()).0) + } +} diff --git a/src/model/channel/group.rs b/src/model/channel/group.rs new file mode 100644 index 0000000..9d3335f --- /dev/null +++ b/src/model/channel/group.rs @@ -0,0 +1,329 @@ +use std::borrow::Cow; +use std::fmt::Write as FmtWrite; +use std::io::Read; +use ::client::{CACHE, rest}; +use ::model::*; +use ::utils::builder::{CreateMessage, GetMessages, Search}; + +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`]: ../client/rest/fn.ack_message.html + pub fn ack<M: Into<MessageId>>(&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. + /// + /// Refer to [`rest::add_group_recipient`] for more information. + /// + /// **Note**: Groups have a limit of 10 recipients, including the current + /// user. + /// + /// [`rest::add_group_recipient`]: ../client/rest/fn.add_group_recipient.html + pub fn add_recipient<U: Into<UserId>>(&self, user: U) -> Result<()> { + let user = user.into(); + + // If the group already contains the recipient, do nothing. + if self.recipients.contains_key(&user) { + return Ok(()); + } + + rest::add_group_recipient(self.channel_id.0, user.0) + } + + /// Broadcasts that the current user is typing in the group. + #[inline] + pub fn broadcast_typing(&self) -> Result<()> { + self.channel_id.broadcast_typing() + } + + /// 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<M, R>(&self, message_id: M, reaction_type: R) + -> Result<()> where M: Into<MessageId>, R: Into<ReactionType> { + self.channel_id.create_reaction(message_id, reaction_type) + } + + /// 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.channel_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.channel_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<M, R>(&self, message_id: M, user_id: Option<UserId>, reaction_type: R) + -> Result<()> where M: Into<MessageId>, R: Into<ReactionType> { + self.channel_id.delete_reaction(message_id, user_id, reaction_type) + } + + /// Edits a [`Message`] in the channel given its Id. + /// + /// Message editing preserves all unchanged message data. + /// + /// Refer to the documentation for [`CreateMessage`] for more information + /// regarding message restrictions and requirements. + /// + /// **Note**: Requires that the current user be the author of the message. + /// + /// # Errors + /// + /// Returns a [`ClientError::MessageTooLong`] if the content of the message + /// is over the [`the limit`], containing the number of unicode code points + /// over the limit. + /// + /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong + /// [`CreateMessage`]: ../utils/builder/struct.CreateMessage.html + /// [`Message`]: struct.Message.html + /// [`the limit`]: ../utils/builder/struct.CreateMessage.html#method.content + #[inline] + pub fn edit_message<F, M>(&self, message_id: M, f: F) -> Result<Message> + where F: FnOnce(CreateMessage) -> CreateMessage, M: Into<MessageId> { + self.channel_id.edit_message(message_id, f) + } + + /// Gets a message from the channel. + /// + /// Requires the [Read Message History] permission. + /// + /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + #[inline] + pub fn get_message<M: Into<MessageId>>(&self, message_id: M) -> Result<Message> { + self.channel_id.get_message(message_id) + } + + /// Gets messages from the channel. + /// + /// Requires the [Read Message History] permission. + /// + /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + #[inline] + pub fn get_messages<F>(&self, f: F) -> Result<Vec<Message>> + 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<M, R, U>(&self, + message_id: M, + reaction_type: R, + limit: Option<u8>, + after: Option<U>) + -> Result<Vec<User>> where M: Into<MessageId>, R: Into<ReactionType>, U: Into<UserId> { + self.channel_id.get_reaction_users(message_id, reaction_type, limit, after) + } + + /// Returns the formatted URI of the group's icon if one exists. + pub fn icon_url(&self) -> Option<String> { + self.icon.as_ref().map(|icon| + format!(cdn!("/channel-icons/{}/{}.webp"), self.channel_id, icon)) + } + + /// Leaves the group. + #[inline] + pub fn leave(&self) -> Result<Group> { + rest::leave_group(self.channel_id.0) + } + + /// Generates a name for the group. + /// + /// If there are no recipients in the group, the name will be "Empty Group". + /// Otherwise, the name is generated in a Comma Separated Value list, such + /// as "person 1, person 2, person 3". + pub fn name(&self) -> Cow<str> { + match self.name { + Some(ref name) => Cow::Borrowed(name), + None => { + let mut name = match self.recipients.values().nth(0) { + Some(recipient) => recipient.read().unwrap().name.clone(), + None => return Cow::Borrowed("Empty Group"), + }; + + for recipient in self.recipients.values().skip(1) { + let _ = write!(name, ", {}", recipient.read().unwrap().name); + } + + Cow::Owned(name) + } + } + } + + /// Retrieves the list of messages that have been pinned in the group. + #[inline] + pub fn pins(&self) -> Result<Vec<Message>> { + 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. + pub fn remove_recipient<U: Into<UserId>>(&self, user: U) -> Result<()> { + let user = user.into(); + + // If the group does not contain the recipient already, do nothing. + if !self.recipients.contains_key(&user) { + return Ok(()); + } + + rest::remove_group_recipient(self.channel_id.0, user.0) + } + + /// Sends a message with just the given message content in the channel. + /// + /// # 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. + /// + /// [`ChannelId`]: ../model/struct.ChannelId.html + /// [`ClientError::MessageTooLong`]: enum.ClientError.html#variant.MessageTooLong + #[inline] + pub fn say(&self, content: &str) -> Result<Message> { + self.channel_id.say(content) + } + + /// Performs a search request to the API for the group's channel's + /// [`Message`]s. + /// + /// 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 + /// [`Message`]: struct.Message.html + /// [`Search`]: ../utils/builder/struct.Search.html + #[inline] + pub fn search<F: FnOnce(Search) -> Search>(&self, f: F) -> Result<SearchResult> { + self.channel_id.search(f) + } + + /// Sends a file along with optional message contents. The filename _must_ + /// be specified. + /// + /// Refer to [`ChannelId::send_file`] for examples and more information. + /// + /// 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. + /// + /// [`ChannelId::send_file`]: struct.ChannelId.html#method.send_file + /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong + /// [Attach Files]: permissions/constant.ATTACH_FILES.html + /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + pub fn send_file<F, R>(&self, file: R, filename: &str, f: F) -> Result<Message> + where F: FnOnce(CreateMessage) -> CreateMessage, R: Read { + self.channel_id.send_file(file, filename, f) + } + + /// Sends a message to the group with the given content. + /// + /// Refer to the documentation for [`CreateMessage`] for more information + /// regarding message restrictions and requirements. + /// + /// **Note**: Requires the [Send Messages] permission. + /// + /// [`CreateMessage`]: ../utils/builder/struct.CreateMessage.html + /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + #[inline] + pub fn send_message<F: FnOnce(CreateMessage) -> CreateMessage>(&self, f: F) -> Result<Message> { + self.channel_id.send_message(f) + } + + /// 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<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { + self.channel_id.unpin(message_id) + } +} diff --git a/src/model/channel/guild_channel.rs b/src/model/channel/guild_channel.rs new file mode 100644 index 0000000..677e77b --- /dev/null +++ b/src/model/channel/guild_channel.rs @@ -0,0 +1,504 @@ +use serde_json::builder::ObjectBuilder; +use std::fmt::{Display, Formatter, Result as FmtResult}; +use std::io::Read; +use std::mem; +use ::client::{CACHE, rest}; +use ::internal::prelude::*; +use ::model::*; +use ::utils::builder::{CreateInvite, CreateMessage, EditChannel, GetMessages, Search}; + +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`]: ../client/rest/fn.ack_message.html + pub fn ack<M: Into<MessageId>>(&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. + /// + /// **Note**: Requires the [Send Messages] permission. + /// + /// # Errors + /// + /// Returns a [`ClientError::InvalidPermissions`] if the current user does + /// not have the required permissions. + /// + /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions + /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + pub fn broadcast_typing(&self) -> Result<()> { + self.id.broadcast_typing() + } + + /// Creates an invite leading to the given channel. + /// + /// # Examples + /// + /// Create an invite that can only be used 5 times: + /// + /// ```rust,ignore + /// let invite = channel.create_invite(|i| i.max_uses(5)); + /// ``` + pub fn create_invite<F>(&self, f: F) -> Result<RichInvite> + where F: FnOnce(CreateInvite) -> CreateInvite { + #[cfg(feature="cache")] + { + let req = permissions::CREATE_INVITE; + + if !utils::user_has_perms(self.id, req)? { + return Err(Error::Client(ClientError::InvalidPermissions(req))); + } + } + + let map = f(CreateInvite::default()).0.build(); + + 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::client::CACHE; + /// use serenity::model::{ChannelId, PermissionOverwrite, permissions}; + /// + /// 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 cache = CACHE.read().unwrap(); + /// let channel = cache.get_guild_channel(channel_id).unwrap(); + /// + /// let _ = channel.create_permission(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::client::CACHE; + /// use serenity::model::{ChannelId, PermissionOverwrite, permissions}; + /// + /// 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 cache = CACHE.read().unwrap(); + /// let channel = cache.get_guild_channel(channel_id).unwrap(); + /// + /// let _ = channel.create_permission(overwrite); + /// ``` + /// + /// [`Channel`]: enum.Channel.html + /// [`Member`]: struct.Member.html + /// [`PermissionOverwrite`]: struct.PermissionOverwrite.html + /// [`PermissionOverwrite::Member`]: struct.PermissionOverwrite.html#variant.Member + /// [`PermissionOverwrite::Role`]: struct.PermissionOverwrite.html#variant.Role + /// [`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<GuildChannel> { + let mut map = into_map(value)?; + + let id = remove(&mut map, "guild_id").and_then(GuildId::decode)?; + + GuildChannel::decode_guild(Value::Object(map), id) + } + + #[doc(hidden)] + pub fn decode_guild(value: Value, guild_id: GuildId) -> Result<GuildChannel> { + let mut map = into_map(value)?; + + Ok(GuildChannel { + id: remove(&mut map, "id").and_then(ChannelId::decode)?, + name: remove(&mut map, "name").and_then(into_string)?, + guild_id: guild_id, + topic: opt(&mut map, "topic", into_string)?, + position: req!(remove(&mut map, "position")?.as_i64()), + kind: remove(&mut map, "type").and_then(ChannelType::decode)?, + last_message_id: opt(&mut map, "last_message_id", MessageId::decode)?, + permission_overwrites: decode_array(remove(&mut map, "permission_overwrites")?, PermissionOverwrite::decode)?, + bitrate: remove(&mut map, "bitrate").ok().and_then(|v| v.as_u64()), + user_limit: remove(&mut map, "user_limit").ok().and_then(|v| v.as_u64()), + last_pin_timestamp: opt(&mut map, "last_pin_timestamp", into_string)?, + }) + } + + /// Deletes this channel, returning the channel on a successful deletion. + pub fn delete(&self) -> Result<Channel> { + #[cfg(feature="cache")] + { + let req = permissions::MANAGE_CHANNELS; + + if !utils::user_has_perms(self.id, req)? { + return Err(Error::Client(ClientError::InvalidPermissions(req))); + } + } + + 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<M, R>(&self, message_id: M, user_id: Option<UserId>, reaction_type: R) + -> Result<()> where M: Into<MessageId>, R: Into<ReactionType> { + self.id.delete_reaction(message_id, user_id, reaction_type) + } + + /// Modifies a channel's settings, such as its position or name. + /// + /// Refer to `EditChannel`s documentation for a full list of methods. + /// + /// # Examples + /// + /// Change a voice channels name and bitrate: + /// + /// ```rust,ignore + /// channel.edit(|c| c.name("test").bitrate(86400)); + /// ``` + pub fn edit<F>(&mut self, f: F) -> Result<()> + where F: FnOnce(EditChannel) -> EditChannel { + + #[cfg(feature="cache")] + { + let req = permissions::MANAGE_CHANNELS; + + if !utils::user_has_perms(self.id, req)? { + return Err(Error::Client(ClientError::InvalidPermissions(req))); + } + } + + let map = ObjectBuilder::new() + .insert("name", &self.name) + .insert("position", self.position) + .insert("type", self.kind.name()); + + let edited = f(EditChannel(map)).0.build(); + + match rest::edit_channel(self.id.0, &edited) { + Ok(channel) => { + mem::replace(self, channel); + + Ok(()) + }, + Err(why) => Err(why), + } + } + + /// Edits a [`Message`] in the channel given its Id. + /// + /// Message editing preserves all unchanged message data. + /// + /// Refer to the documentation for [`CreateMessage`] for more information + /// regarding message restrictions and requirements. + /// + /// **Note**: Requires that the current user be the author of the message. + /// + /// # Errors + /// + /// Returns a [`ClientError::MessageTooLong`] if the content of the message + /// is over the [`the limit`], containing the number of unicode code points + /// over the limit. + /// + /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong + /// [`CreateMessage`]: ../utils/builder/struct.CreateMessage.html + /// [`Message`]: struct.Message.html + /// [`the limit`]: ../utils/builder/struct.CreateMessage.html#method.content + #[inline] + pub fn edit_message<F, M>(&self, message_id: M, f: F) -> Result<Message> + where F: FnOnce(CreateMessage) -> CreateMessage, M: Into<MessageId> { + self.id.edit_message(message_id, f) + } + + /// 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<Vec<RichInvite>> { + self.id.get_invites() + } + + /// Gets a message from the channel. + /// + /// Requires the [Read Message History] permission. + /// + /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + #[inline] + pub fn get_message<M: Into<MessageId>>(&self, message_id: M) -> Result<Message> { + 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]: permissions/constant.READ_MESSAGE_HISTORY.html + #[inline] + pub fn get_messages<F>(&self, f: F) -> Result<Vec<Message>> + 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<M, R, U>(&self, + message_id: M, + reaction_type: R, + limit: Option<u8>, + after: Option<U>) + -> Result<Vec<User>> where M: Into<MessageId>, R: Into<ReactionType>, U: Into<UserId> { + self.id.get_reaction_users(message_id, reaction_type, limit, after) + } + + /// Retrieves the channel's webhooks. + /// + /// **Note**: Requires the [Manage Webhooks] permission. + /// + /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html + #[inline] + pub fn get_webhooks(&self) -> Result<Vec<Webhook>> { + self.id.get_webhooks() + } + + /// 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(feature="cache")] + pub fn guild(&self) -> Option<Arc<RwLock<Guild>>> { + CACHE.read().unwrap().get_guild(self.guild_id) + } + + /// Pins a [`Message`] to the channel. + #[inline] + pub fn pin<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { + self.id.pin(message_id) + } + + /// Gets all channel's pins. + #[inline] + pub fn pins(&self) -> Result<Vec<Message>> { + self.id.pins() + } + + /// Sends a message with just the given message content in the channel. + /// + /// # 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. + /// + /// [`ChannelId`]: ../model/struct.ChannelId.html + /// [`ClientError::MessageTooLong`]: enum.ClientError.html#variant.MessageTooLong + #[inline] + pub fn say(&self, content: &str) -> Result<Message> { + self.id.say(content) + } + + /// Performs a search request for the channel's [`Message`]s. + /// + /// 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 + /// [`Message`]: struct.Message.html + /// [`Search`]: ../utils/builder/struct.Search.html + pub fn search<F: FnOnce(Search) -> Search>(&self, f: F) -> Result<SearchResult> { + #[cfg(feature="cache")] + { + if CACHE.read().unwrap().user.bot { + return Err(Error::Client(ClientError::InvalidOperationAsBot)); + } + } + + self.id.search(f) + } + + /// Sends a file along with optional message contents. The filename _must_ + /// be specified. + /// + /// Refer to [`ChannelId::send_file`] for examples and more information. + /// + /// 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. + /// + /// [`ChannelId::send_file`]: struct.ChannelId.html#method.send_file + /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong + /// [Attach Files]: permissions/constant.ATTACH_FILES.html + /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + pub fn send_file<F, R>(&self, file: R, filename: &str, f: F) -> Result<Message> + where F: FnOnce(CreateMessage) -> CreateMessage, R: Read { + self.id.send_file(file, filename, f) + } + + /// Sends a message to the channel with the given content. + /// + /// **Note**: This will only work when a [`Message`] is received. + /// + /// **Note**: Requires the [Send Messages] permission. + /// + /// # 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. + /// + /// Returns a [`ClientError::InvalidPermissions`] if the current user does + /// not have the required permissions. + /// + /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions + /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong + /// [`Message`]: struct.Message.html + /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + pub fn send_message<F: FnOnce(CreateMessage) -> CreateMessage>(&self, f: F) -> Result<Message> { + #[cfg(feature="cache")] + { + let req = permissions::SEND_MESSAGES; + + if !utils::user_has_perms(self.id, req)? { + return Err(Error::Client(ClientError::InvalidPermissions(req))); + } + } + + self.id.send_message(f) + } + + /// 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<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { + self.id.unpin(message_id) + } +} + +impl Display for GuildChannel { + /// Formats the channel, creating a mention of it. + fn fmt(&self, f: &mut Formatter) -> FmtResult { + Display::fmt(&self.id.mention(), f) + } +} diff --git a/src/model/channel/message.rs b/src/model/channel/message.rs new file mode 100644 index 0000000..84d726d --- /dev/null +++ b/src/model/channel/message.rs @@ -0,0 +1,393 @@ +use serde_json::builder::ObjectBuilder; +use std::mem; +use ::constants; +use ::client::{CACHE, rest}; +use ::model::*; +use ::utils::builder::{CreateEmbed, CreateMessage}; + +impl Message { + /// Marks the [`Channel`] as being read up to the 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`]: ../client/rest/fn.ack_message.html + pub fn ack<M: Into<MessageId>>(&self) -> Result<()> { + #[cfg(feature="cache")] + { + if CACHE.read().unwrap().user.bot { + return Err(Error::Client(ClientError::InvalidOperationAsBot)); + } + } + + self.channel_id.ack(self.id) + } + + /// Deletes the message. + /// + /// **Note**: The logged in user must either be the author of the message or + /// have the [Manage Messages] permission. + /// + /// # Errors + /// + /// If the `cache` feature is enabled, then returns a + /// [`ClientError::InvalidPermissions`] if the current user does not have + /// the required permissions. + /// + /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions + /// [`ClientError::InvalidUser`]: ../client/enum.ClientError.html#variant.InvalidUser + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + pub fn delete(&self) -> Result<()> { + #[cfg(feature="cache")] + { + let req = permissions::MANAGE_MESSAGES; + let is_author = self.author.id == CACHE.read().unwrap().user.id; + let has_perms = utils::user_has_perms(self.channel_id, req)?; + + if !is_author && !has_perms { + return Err(Error::Client(ClientError::InvalidPermissions(req))); + } + } + + self.channel_id.delete_message(self.id) + } + + /// Deletes all of the [`Reaction`]s associated with the message. + /// + /// **Note**: Requires the [Manage Messages] permission. + /// + /// # Errors + /// + /// If the `cache` feature is enabled, then returns a + /// [`ClientError::InvalidPermissions`] if the current user does not have + /// the required permissions. + /// + /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions + /// [`Reaction`]: struct.Reaction.html + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + pub fn delete_reactions(&self) -> Result<()> { + #[cfg(feature="cache")] + { + let req = permissions::MANAGE_MESSAGES; + + if !utils::user_has_perms(self.channel_id, req)? { + return Err(Error::Client(ClientError::InvalidPermissions(req))); + } + } + + rest::delete_message_reactions(self.channel_id.0, self.id.0) + } + + /// Edits this message, replacing the original content with new content. + /// + /// Message editing preserves all unchanged message data. + /// + /// Refer to the documentation for [`CreateMessage`] for more information + /// regarding message restrictions and requirements. + /// + /// **Note**: Requires that the current user be the author of the message. + /// + /// # Examples + /// + /// Edit a message with new content: + /// + /// ```rust,ignore + /// // assuming a `message` has already been bound + /// + /// message.edit(|m| m.content("new content")); + /// ``` + /// + /// # Errors + /// + /// If the `cache` is enabled, returns a [`ClientError::InvalidUser`] if the + /// current user is not the author. + /// + /// Returns a [`ClientError::MessageTooLong`] if the content of the message + /// is over [`the limit`], containing the number of unicode code points + /// over the limit. + /// + /// [`ClientError::InvalidUser`]: ../client/enum.ClientError.html#variant.InvalidUser + /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong + /// [`CreateMessage`]: ../utils/builder/struct.CreateMessage.html + /// [`the limit`]: ../utils/builder/struct.CreateMessage.html#method.content + pub fn edit<F>(&mut self, f: F) -> Result<()> + where F: FnOnce(CreateMessage) -> CreateMessage { + #[cfg(feature="cache")] + { + if self.author.id != CACHE.read().unwrap().user.id { + return Err(Error::Client(ClientError::InvalidUser)); + } + } + + let mut builder = CreateMessage::default(); + + if !self.content.is_empty() { + builder = builder.content(&self.content); + } + + if let Some(embed) = self.embeds.get(0) { + builder = builder.embed(|_| CreateEmbed::from(embed.clone())); + } + + if self.tts { + builder = builder.tts(true); + } + + let map = f(builder).0; + + match rest::edit_message(self.channel_id.0, self.id.0, &Value::Object(map)) { + Ok(edited) => { + mem::replace(self, edited); + + Ok(()) + }, + Err(why) => Err(why), + } + } + + /// Returns message content, but with user and role mentions replaced with + /// names and everyone/here mentions cancelled. + #[cfg(feature="cache")] + pub fn content_safe(&self) -> String { + let mut result = self.content.clone(); + + // First replace all user mentions. + for u in &self.mentions { + result = result.replace(&u.mention(), &u.distinct()); + } + + // Then replace all role mentions. + for id in &self.mention_roles { + let mention = id.mention(); + + if let Some(role) = id.find() { + result = result.replace(&mention, &format!("@{}", role.name)); + } else { + result = result.replace(&mention, "@deleted-role"); + } + } + + // And finally replace everyone and here mentions. + result.replace("@everyone", "@\u{200B}everyone") + .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<R, U>(&self, reaction_type: R, limit: Option<u8>, after: Option<U>) + -> Result<Vec<User>> where R: Into<ReactionType>, U: Into<UserId> { + self.channel_id.get_reaction_users(self.id, reaction_type, limit, after) + } + + /// Returns the associated `Guild` for the message if one is in the cache. + /// + /// Returns `None` if the guild's Id could not be found via [`guild_id`] or + /// if the Guild itself is not cached. + /// + /// Requires the `cache` feature be enabled. + /// + /// [`guild_id`]: #method.guild_id + #[cfg(feature="cache")] + pub fn guild(&self) -> Option<Arc<RwLock<Guild>>> { + self.guild_id().and_then(|guild_id| CACHE.read().unwrap().get_guild(guild_id)) + } + + /// 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(feature="cache")] + pub fn guild_id(&self) -> Option<GuildId> { + match CACHE.read().unwrap().get_channel(self.channel_id) { + Some(Channel::Guild(ch)) => Some(ch.read().unwrap().guild_id), + _ => None, + } + } + + /// True if message was sent using direct messages. + #[cfg(feature="cache")] + pub fn is_private(&self) -> bool { + match CACHE.read().unwrap().get_channel(self.channel_id) { + Some(Channel::Group(_)) | Some(Channel::Private(_)) => true, + _ => false, + } + } + + /// Checks the length of a string to ensure that it is within Discord's + /// maximum message length limit. + /// + /// Returns `None` if the message is within the limit, otherwise returns + /// `Some` with an inner value of how many unicode code points the message + /// is over. + pub fn overflow_length(content: &str) -> Option<u64> { + // Check if the content is over the maximum number of unicode code + // points. + let count = content.chars().count() as i64; + let diff = count - (constants::MESSAGE_CODE_LIMIT as i64); + + if diff > 0 { + Some(diff as u64) + } else { + None + } + } + + /// Pins this message to its channel. + /// + /// **Note**: Requires the [Manage Messages] permission. + /// + /// # Errors + /// + /// If the `cache` is enabled, returns a + /// [`ClientError::InvalidPermissions`] if the current user does not have + /// the required permissions. + /// + /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + pub fn pin(&self) -> Result<()> { + #[cfg(feature="cache")] + { + let req = permissions::MANAGE_MESSAGES; + + if !utils::user_has_perms(self.channel_id, req)? { + return Err(Error::Client(ClientError::InvalidPermissions(req))); + } + } + + self.channel_id.pin(self.id.0) + } + + /// React to the message with a custom [`Emoji`] or unicode character. + /// + /// **Note**: Requires the [Add Reactions] permission. + /// + /// # Errors + /// + /// If the `cache` is enabled, returns a + /// [`ClientError::InvalidPermissions`] if the current user does not have + /// the required [permissions]. + /// + /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions + /// [`Emoji`]: struct.Emoji.html + /// [Add Reactions]: permissions/constant.ADD_REACTIONS.html + /// [permissions]: permissions + pub fn react<R: Into<ReactionType>>(&self, reaction_type: R) -> Result<()> { + #[cfg(feature="cache")] + { + let req = permissions::ADD_REACTIONS; + + if !utils::user_has_perms(self.channel_id, req)? { + return Err(Error::Client(ClientError::InvalidPermissions(req))); + } + } + + rest::create_reaction(self.channel_id.0, + self.id.0, + &reaction_type.into()) + } + + /// Replies to the user, mentioning them prior to the content in the form + /// of: `@<USER_ID>: YOUR_CONTENT`. + /// + /// User mentions are generally around 20 or 21 characters long. + /// + /// **Note**: Requires the [Send Messages] permission. + /// + /// **Note**: Message contents must be under 2000 unicode code points. + /// + /// # Errors + /// + /// If the `cache` is enabled, returns a + /// [`ClientError::InvalidPermissions`] if the current user does not have + /// the required permissions. + /// + /// 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. + /// + /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions + /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong + /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + pub fn reply(&self, content: &str) -> Result<Message> { + if let Some(length_over) = Message::overflow_length(content) { + return Err(Error::Client(ClientError::MessageTooLong(length_over))); + } + + #[cfg(feature="cache")] + { + let req = permissions::SEND_MESSAGES; + + if !utils::user_has_perms(self.channel_id, req)? { + return Err(Error::Client(ClientError::InvalidPermissions(req))); + } + } + + let mut gen = self.author.mention(); + gen.push_str(": "); + gen.push_str(content); + + let map = ObjectBuilder::new() + .insert("content", gen) + .insert("tts", false) + .build(); + + rest::send_message(self.channel_id.0, &map) + } + + /// Unpins the message from its channel. + /// + /// **Note**: Requires the [Manage Messages] permission. + /// + /// # Errors + /// + /// If the `cache` is enabled, returns a + /// [`ClientError::InvalidPermissions`] if the current user does not have + /// the required permissions. + /// + /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + pub fn unpin(&self) -> Result<()> { + #[cfg(feature="cache")] + { + let req = permissions::MANAGE_MESSAGES; + + if !utils::user_has_perms(self.channel_id, req)? { + return Err(Error::Client(ClientError::InvalidPermissions(req))); + } + } + + rest::unpin_message(self.channel_id.0, self.id.0) + } +} + +impl From<Message> for MessageId { + /// Gets the Id of a `Message`. + fn from(message: Message) -> MessageId { + message.id + } +} diff --git a/src/model/channel/mod.rs b/src/model/channel/mod.rs new file mode 100644 index 0000000..ff12084 --- /dev/null +++ b/src/model/channel/mod.rs @@ -0,0 +1,415 @@ +mod attachment; +mod channel_id; +mod embed; +mod group; +mod guild_channel; +mod message; +mod private_channel; +mod reaction; + +pub use self::attachment::*; +pub use self::channel_id::*; +pub use self::embed::*; +pub use self::group::*; +pub use self::guild_channel::*; +pub use self::message::*; +pub use self::private_channel::*; +pub use self::reaction::*; + +use std::fmt::{Display, Formatter, Result as FmtResult}; +use std::io::Read; +use ::client::CACHE; +use ::model::*; +use ::utils::builder::{CreateMessage, GetMessages, Search}; + +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`]: ../client/rest/fn.ack_message.html + pub fn ack<M: Into<MessageId>>(&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<M, R>(&self, message_id: M, reaction_type: R) + -> Result<()> where M: Into<MessageId>, R: Into<ReactionType> { + self.id().create_reaction(message_id, reaction_type) + } + + #[doc(hidden)] + pub fn decode(value: Value) -> Result<Channel> { + let map = into_map(value)?; + match req!(map.get("type").and_then(|x| x.as_u64())) { + 0 | 2 => GuildChannel::decode(Value::Object(map)) + .map(|x| Channel::Guild(Arc::new(RwLock::new(x)))), + 1 => PrivateChannel::decode(Value::Object(map)) + .map(|x| Channel::Private(Arc::new(RwLock::new(x)))), + 3 => Group::decode(Value::Object(map)) + .map(|x| Channel::Group(Arc::new(RwLock::new(x)))), + other => Err(Error::Decode("Expected value Channel type", + Value::U64(other))), + } + } + + /// Deletes the inner channel. + /// + /// **Note**: There is no real function as _deleting_ a [`Group`]. The + /// closest functionality is leaving it. + /// + /// [`Group`]: struct.Group.html + pub fn delete(&self) -> Result<()> { + match *self { + Channel::Group(ref group) => { + let _ = group.read().unwrap().leave()?; + }, + Channel::Guild(ref public_channel) => { + let _ = public_channel.read().unwrap().delete()?; + }, + Channel::Private(ref private_channel) => { + let _ = private_channel.read().unwrap().delete()?; + }, + } + + 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. + /// + /// [`Message`]: struct.Message.html + /// [`Message::delete`]: struct.Message.html#method.delete + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + #[inline] + pub fn delete_message<M: Into<MessageId>>(&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<M, R>(&self, message_id: M, user_id: Option<UserId>, reaction_type: R) + -> Result<()> where M: Into<MessageId>, R: Into<ReactionType> { + self.id().delete_reaction(message_id, user_id, reaction_type) + } + + /// Edits a [`Message`] in the channel given its Id. + /// + /// Message editing preserves all unchanged message data. + /// + /// Refer to the documentation for [`CreateMessage`] for more information + /// regarding message restrictions and requirements. + /// + /// **Note**: Requires that the current user be the author of the message. + /// + /// # Errors + /// + /// Returns a [`ClientError::MessageTooLong`] if the content of the message + /// is over the [`the limit`], containing the number of unicode code points + /// over the limit. + /// + /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong + /// [`CreateMessage`]: ../utils/builder/struct.CreateMessage.html + /// [`Message`]: struct.Message.html + /// [`the limit`]: ../utils/builder/struct.CreateMessage.html#method.content + #[inline] + pub fn edit_message<F, M>(&self, message_id: M, f: F) -> Result<Message> + where F: FnOnce(CreateMessage) -> CreateMessage, M: Into<MessageId> { + self.id().edit_message(message_id, f) + } + + /// Gets a message from the channel. + /// + /// Requires the [Read Message History] permission. + /// + /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + #[inline] + pub fn get_message<M: Into<MessageId>>(&self, message_id: M) -> Result<Message> { + self.id().get_message(message_id) + } + + /// Gets messages from the channel. + /// + /// Requires the [Read Message History] permission. + /// + /// # Examples + /// + /// ```rust,ignore + /// use serenity::model::MessageId; + /// + /// let id = MessageId(81392407232380928); + /// + /// // Maximum is 100. + /// let _messages = channel.get_messages(|g| g.after(id).limit(100)); + /// ``` + /// + /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + #[inline] + pub fn get_messages<F>(&self, f: F) -> Result<Vec<Message>> + 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<M, R, U>(&self, + message_id: M, + reaction_type: R, + limit: Option<u8>, + after: Option<U>) + -> Result<Vec<User>> where M: Into<MessageId>, R: Into<ReactionType>, U: Into<UserId> { + self.id().get_reaction_users(message_id, reaction_type, limit, after) + } + + /// Retrieves the Id of the inner [`Group`], [`GuildChannel`], or + /// [`PrivateChannel`]. + /// + /// [`Group`]: struct.Group.html + /// [`GuildChannel`]: struct.GuildChannel.html + /// [`PrivateChannel`]: struct.PrivateChannel.html + pub fn id(&self) -> ChannelId { + match *self { + Channel::Group(ref group) => group.read().unwrap().channel_id, + Channel::Guild(ref channel) => channel.read().unwrap().id, + Channel::Private(ref channel) => channel.read().unwrap().id, + } + } + + /// Sends a message with just the given message content in the channel. + /// + /// # 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. + /// + /// [`ChannelId`]: ../model/struct.ChannelId.html + /// [`ClientError::MessageTooLong`]: enum.ClientError.html#variant.MessageTooLong + #[inline] + pub fn say(&self, content: &str) -> Result<Message> { + self.id().say(content) + } + + /// Performs a search request to the API for the inner channel's + /// [`Message`]s. + /// + /// 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 + /// [`Message`]: struct.Message.html + /// [`Search`]: ../utils/builder/struct.Search.html + pub fn search<F>(&self, f: F) -> Result<SearchResult> + where F: FnOnce(Search) -> Search { + #[cfg(feature="cache")] + { + if CACHE.read().unwrap().user.bot { + return Err(Error::Client(ClientError::InvalidOperationAsBot)); + } + } + + self.id().search(f) + } + + /// Sends a file along with optional message contents. The filename _must_ + /// be specified. + /// + /// Refer to [`ChannelId::send_file`] for examples and more information. + /// + /// 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. + /// + /// [`ChannelId::send_file`]: struct.ChannelId.html#method.send_file + /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong + /// [Attach Files]: permissions/constant.ATTACH_FILES.html + /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + pub fn send_file<F, R>(&self, file: R, filename: &str, f: F) -> Result<Message> + where F: FnOnce(CreateMessage) -> CreateMessage, R: Read { + self.id().send_file(file, filename, f) + } + + /// Sends a message to the channel. + /// + /// Refer to the documentation for [`CreateMessage`] for more information + /// regarding message restrictions and requirements. + /// + /// 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 + #[inline] + pub fn send_message<F>(&self, f: F) -> Result<Message> + where F: FnOnce(CreateMessage) -> CreateMessage { + self.id().send_message(f) + } + + /// 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<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { + self.id().unpin(message_id) + } +} + +impl Display for Channel { + /// Formats the channel into a "mentioned" string. + /// + /// This will return a different format for each type of channel: + /// + /// - [`Group`]s: the generated name retrievable via [`Group::name`]; + /// - [`PrivateChannel`]s: the recipient's name; + /// - [`GuildChannel`]s: a string mentioning the channel that users who can + /// see the channel can click on. + /// + /// [`Group`]: struct.Group.html + /// [`Group::name`]: struct.Group.html#method.name + /// [`GuildChannel`]: struct.GuildChannel.html + /// [`PrivateChannel`]: struct.PrivateChannel.html + fn fmt(&self, f: &mut Formatter) -> FmtResult { + match *self { + Channel::Group(ref group) => { + Display::fmt(&group.read().unwrap().name(), f) + }, + Channel::Guild(ref ch) => { + Display::fmt(&ch.read().unwrap().id.mention(), f) + }, + Channel::Private(ref ch) => { + let channel = ch.read().unwrap(); + let recipient = channel.recipient.read().unwrap(); + + Display::fmt(&recipient.name, f) + }, + } + } +} + +impl PermissionOverwrite { + #[doc(hidden)] + pub fn decode(value: Value) -> Result<PermissionOverwrite> { + let mut map = into_map(value)?; + let id = remove(&mut map, "id").and_then(decode_id)?; + let kind = remove(&mut map, "type").and_then(into_string)?; + let kind = match &*kind { + "member" => PermissionOverwriteType::Member(UserId(id)), + "role" => PermissionOverwriteType::Role(RoleId(id)), + _ => return Err(Error::Decode("Expected valid PermissionOverwrite type", Value::String(kind))), + }; + + Ok(PermissionOverwrite { + kind: kind, + allow: remove(&mut map, "allow").and_then(Permissions::decode)?, + deny: remove(&mut map, "deny").and_then(Permissions::decode)?, + }) + } +} diff --git a/src/model/channel/private_channel.rs b/src/model/channel/private_channel.rs new file mode 100644 index 0000000..ae64d7e --- /dev/null +++ b/src/model/channel/private_channel.rs @@ -0,0 +1,309 @@ +use std::fmt::{Display, Formatter, Result as FmtResult}; +use std::io::Read; +use ::client::CACHE; +use ::model::*; +use ::utils::builder::{CreateMessage, GetMessages, Search}; + +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`]: ../client/rest/fn.ack_message.html + pub fn ack<M: Into<MessageId>>(&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) + } + + /// Broadcasts that the current user is typing to the recipient. + pub fn broadcast_typing(&self) -> Result<()> { + self.id.broadcast_typing() + } + + /// 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<M, R>(&self, message_id: M, reaction_type: R) + -> Result<()> where M: Into<MessageId>, R: Into<ReactionType> { + self.id.create_reaction(message_id, reaction_type) + } + + #[doc(hidden)] + pub fn decode(value: Value) -> Result<PrivateChannel> { + let mut map = into_map(value)?; + let mut recipients = decode_array(remove(&mut map, "recipients")?, + User::decode)?; + + Ok(PrivateChannel { + id: remove(&mut map, "id").and_then(ChannelId::decode)?, + kind: remove(&mut map, "type").and_then(ChannelType::decode)?, + last_message_id: opt(&mut map, "last_message_id", MessageId::decode)?, + last_pin_timestamp: opt(&mut map, "last_pin_timestamp", into_string)?, + recipient: Arc::new(RwLock::new(recipients.remove(0))), + }) + } + + /// 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. + #[inline] + pub fn delete(&self) -> Result<Channel> { + 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<M, R>(&self, message_id: M, user_id: Option<UserId>, reaction_type: R) + -> Result<()> where M: Into<MessageId>, R: Into<ReactionType> { + self.id.delete_reaction(message_id, user_id, reaction_type) + } + + /// Edits a [`Message`] in the channel given its Id. + /// + /// Message editing preserves all unchanged message data. + /// + /// Refer to the documentation for [`CreateMessage`] for more information + /// regarding message restrictions and requirements. + /// + /// **Note**: Requires that the current user be the author of the message. + /// + /// # Errors + /// + /// Returns a [`ClientError::MessageTooLong`] if the content of the message + /// is over the [`the limit`], containing the number of unicode code points + /// over the limit. + /// + /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong + /// [`CreateMessage`]: ../utils/builder/struct.CreateMessage.html + /// [`Message`]: struct.Message.html + /// [`the limit`]: ../utils/builder/struct.CreateMessage.html#method.content + #[inline] + pub fn edit_message<F, M>(&self, message_id: M, f: F) -> Result<Message> + where F: FnOnce(CreateMessage) -> CreateMessage, M: Into<MessageId> { + self.id.edit_message(message_id, f) + } + + /// Gets a message from the channel. + /// + /// Requires the [Read Message History] permission. + /// + /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + #[inline] + pub fn get_message<M: Into<MessageId>>(&self, message_id: M) -> Result<Message> { + 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]: permissions/constant.READ_MESSAGE_HISTORY.html + #[inline] + pub fn get_messages<F>(&self, f: F) -> Result<Vec<Message>> + 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<M, R, U>(&self, + message_id: M, + reaction_type: R, + limit: Option<u8>, + after: Option<U>) + -> Result<Vec<User>> where M: Into<MessageId>, R: Into<ReactionType>, U: Into<UserId> { + self.id.get_reaction_users(message_id, reaction_type, limit, after) + } + + /// Pins a [`Message`] to the channel. + /// + /// [`Message`]: struct.Message.html + #[inline] + pub fn pin<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { + self.id.pin(message_id) + } + + /// Retrieves the list of messages that have been pinned in the private + /// channel. + #[inline] + pub fn pins(&self) -> Result<Vec<Message>> { + self.id.pins() + } + + /// Sends a message with just the given message content in the channel. + /// + /// # 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. + /// + /// [`ChannelId`]: ../model/struct.ChannelId.html + /// [`ClientError::MessageTooLong`]: enum.ClientError.html#variant.MessageTooLong + #[inline] + pub fn say(&self, content: &str) -> Result<Message> { + self.id.say(content) + } + + /// Performs a search request to the API for the channel's [`Message`]s. + /// + /// 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 + /// [`Message`]: struct.Message.html + /// [`Search`]: ../utils/builder/struct.Search.html + pub fn search<F>(&self, f: F) -> Result<SearchResult> + where F: FnOnce(Search) -> Search { + #[cfg(feature="cache")] + { + if CACHE.read().unwrap().user.bot { + return Err(Error::Client(ClientError::InvalidOperationAsBot)); + } + } + + self.id.search(f) + } + + /// Sends a file along with optional message contents. The filename _must_ + /// be specified. + /// + /// Refer to [`ChannelId::send_file`] for examples and more information. + /// + /// 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. + /// + /// [`ChannelId::send_file`]: struct.ChannelId.html#method.send_file + /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong + /// [Attach Files]: permissions/constant.ATTACH_FILES.html + /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + pub fn send_file<F, R>(&self, file: R, filename: &str, f: F) -> Result<Message> + where F: FnOnce(CreateMessage) -> CreateMessage, R: Read { + self.id.send_file(file, filename, f) + } + + /// Sends a message to the channel with the given content. + /// + /// Refer to the documentation for [`CreateMessage`] for more information + /// regarding message restrictions and requirements. + /// + /// # 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. + /// + /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong + /// [`CreateMessage`]: ../utils/builder/struct.CreateMessage.html + /// [`Message`]: struct.Message.html + #[inline] + pub fn send_message<F: FnOnce(CreateMessage) -> CreateMessage>(&self, f: F) -> Result<Message> { + self.id.send_message(f) + } + + /// 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<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { + self.id.unpin(message_id) + } +} + +impl Display for PrivateChannel { + /// Formats the private channel, displaying the recipient's username. + fn fmt(&self, f: &mut Formatter) -> FmtResult { + f.write_str(&self.recipient.read().unwrap().name) + } +} diff --git a/src/model/channel/reaction.rs b/src/model/channel/reaction.rs new file mode 100644 index 0000000..eaeb2cc --- /dev/null +++ b/src/model/channel/reaction.rs @@ -0,0 +1,188 @@ +use std::fmt::{Display, Formatter, Result as FmtResult, Write as FmtWrite}; +use ::client::{CACHE, rest}; +use ::internal::prelude::*; +use ::model::*; + +impl Reaction { + /// Deletes the reaction, but only if the current user is the user who made + /// the reaction or has permission to. + /// + /// **Note**: Requires the [Manage Messages] permission, _if_ the current + /// user did not perform the reaction. + /// + /// # Errors + /// + /// If the `cache` is enabled, then returns a + /// [`ClientError::InvalidPermissions`] if the current user does not have + /// the required [permissions]. + /// + /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions + /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + /// [permissions]: permissions + pub fn delete(&self) -> Result<()> { + let user_id = feature_cache! {{ + let user = if self.user_id == CACHE.read().unwrap().user.id { + None + } else { + Some(self.user_id.0) + }; + + // If the reaction is one _not_ made by the current user, then ensure + // that the current user has permission* to delete the reaction. + // + // Normally, users can only delete their own reactions. + // + // * The `Manage Messages` permission. + if user.is_some() { + let req = permissions::MANAGE_MESSAGES; + + if !utils::user_has_perms(self.channel_id, req).unwrap_or(true) { + return Err(Error::Client(ClientError::InvalidPermissions(req))); + } + } + + user + } else { + Some(self.user_id.0) + }}; + + rest::delete_reaction(self.channel_id.0, + self.message_id.0, + user_id, + &self.emoji) + } + + /// Retrieves 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. + /// + /// # Errors + /// + /// Returns a [`ClientError::InvalidPermissions`] if the current user does + /// not have the required [permissions]. + /// + /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions + /// [`Emoji`]: struct.Emoji.html + /// [`Message`]: struct.Message.html + /// [`User`]: struct.User.html + /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + /// [permissions]: permissions + pub fn users<R, U>(&self, + reaction_type: R, + limit: Option<u8>, + after: Option<U>) + -> Result<Vec<User>> + where R: Into<ReactionType>, + U: Into<UserId> { + rest::get_reaction_users(self.channel_id.0, + self.message_id.0, + &reaction_type.into(), + limit.unwrap_or(50), + after.map(|u| u.into().0)) + } +} + +/// The type of a [`Reaction`] sent. +/// +/// [`Reaction`]: struct.Reaction.html +#[derive(Clone, Debug)] +pub enum ReactionType { + /// A reaction with a [`Guild`]s custom [`Emoji`], which is unique to the + /// guild. + /// + /// [`Emoji`]: struct.Emoji.html + /// [`Guild`]: struct.Guild.html + Custom { + /// The Id of the custom [`Emoji`]. + /// + /// [`Emoji`]: struct.Emoji.html + id: EmojiId, + /// The name of the custom emoji. This is primarily used for decoration + /// and distinguishing the emoji client-side. + name: String, + }, + /// A reaction with a twemoji. + Unicode(String), +} + +impl ReactionType { + /// Creates a data-esque display of the type. This is not very useful for + /// displaying, as the primary client can not render it, but can be useful + /// for debugging. + /// + /// **Note**: This is mainly for use internally. There is otherwise most + /// likely little use for it. + pub fn as_data(&self) -> String { + match *self { + ReactionType::Custom { id, ref name } => { + format!("{}:{}", name, id) + }, + ReactionType::Unicode(ref unicode) => unicode.clone(), + } + } + + #[doc(hidden)] + pub fn decode(value: Value) -> Result<Self> { + let mut map = into_map(value)?; + let name = remove(&mut map, "name").and_then(into_string)?; + + // Only custom emoji reactions (`ReactionType::Custom`) have an Id. + Ok(match opt(&mut map, "id", EmojiId::decode)? { + Some(id) => ReactionType::Custom { + id: id, + name: name, + }, + None => ReactionType::Unicode(name), + }) + } +} + +impl From<Emoji> for ReactionType { + fn from(emoji: Emoji) -> ReactionType { + ReactionType::Custom { + id: emoji.id, + name: emoji.name, + } + } +} + +impl From<String> for ReactionType { + fn from(unicode: String) -> ReactionType { + ReactionType::Unicode(unicode) + } +} + +impl Display for ReactionType { + /// Formats the reaction type, displaying the associated emoji in a + /// way that clients can understand. + /// + /// If the type is a [custom][`ReactionType::Custom`] emoji, then refer to + /// the documentation for [emoji's formatter][`Emoji::fmt`] on how this is + /// displayed. Otherwise, if the type is a + /// [unicode][`ReactionType::Unicode`], then the inner unicode is displayed. + /// + /// [`Emoji::fmt`]: struct.Emoji.html#method.fmt + /// [`ReactionType::Custom`]: enum.ReactionType.html#variant.Custom + /// [`ReactionType::Unicode`]: enum.ReactionType.html#variant.Unicode + fn fmt(&self, f: &mut Formatter) -> FmtResult { + match *self { + ReactionType::Custom { id, ref name } => { + f.write_char('<')?; + f.write_char(':')?; + f.write_str(name)?; + f.write_char(':')?; + Display::fmt(&id, f)?; + f.write_char('>') + }, + ReactionType::Unicode(ref unicode) => f.write_str(unicode), + } + } +} diff --git a/src/model/guild.rs b/src/model/guild.rs deleted file mode 100644 index b3879b1..0000000 --- a/src/model/guild.rs +++ /dev/null @@ -1,2587 +0,0 @@ -use serde_json::builder::ObjectBuilder; -use std::borrow::Cow; -use std::cmp::Ordering; -use std::collections::HashMap; -use std::sync::{Arc, RwLock}; -use std::fmt; -use super::utils::{ - decode_emojis, - decode_members, - decode_presences, - decode_roles, - decode_voice_states, - into_map, - into_string, - opt, - remove, -}; -use super::*; -use ::client::rest; -use ::constants::LARGE_THRESHOLD; -use ::internal::prelude::*; -use ::utils::builder::{EditGuild, EditMember, EditRole, Search}; -use ::utils::decode_array; - -#[cfg(feature="cache")] -use std::mem; - -#[cfg(feature="cache")] -use ::client::CACHE; -#[cfg(feature="cache")] -use ::utils::Colour; - -impl From<PartialGuild> for GuildContainer { - fn from(guild: PartialGuild) -> GuildContainer { - GuildContainer::Guild(guild) - } -} - -impl From<GuildId> for GuildContainer { - fn from(guild_id: GuildId) -> GuildContainer { - GuildContainer::Id(guild_id) - } -} - -impl From<u64> for GuildContainer { - fn from(id: u64) -> GuildContainer { - GuildContainer::Id(GuildId(id)) - } -} - -impl Emoji { - /// Deletes the emoji. - /// - /// **Note**: The [Manage Emojis] permission is required. - /// - /// **Note**: Only user accounts may use this method. - /// - /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html - #[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), - None => Err(Error::Client(ClientError::ItemMissing)), - } - } - - /// Edits the emoji by updating it with a new name. - /// - /// **Note**: The [Manage Emojis] permission is required. - /// - /// **Note**: Only user accounts may use this method. - /// - /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html - #[cfg(feature="cache")] - pub fn edit(&mut self, name: &str) -> Result<()> { - match self.find_guild_id() { - Some(guild_id) => { - let map = ObjectBuilder::new() - .insert("name", name) - .build(); - - match rest::edit_emoji(guild_id.0, self.id.0, &map) { - Ok(emoji) => { - mem::replace(self, emoji); - - Ok(()) - }, - Err(why) => Err(why), - } - }, - None => Err(Error::Client(ClientError::ItemMissing)), - } - } - - /// Finds the [`Guild`] that owns the emoji by looking through the Cache. - /// - /// [`Guild`]: struct.Guild.html - #[cfg(feature="cache")] - pub fn find_guild_id(&self) -> Option<GuildId> { - for guild in CACHE.read().unwrap().guilds.values() { - let guild = guild.read().unwrap(); - - if guild.emojis.contains_key(&self.id) { - return Some(guild.id); - } - } - - None - } - - /// Generates a URL to the emoji's image. - #[inline] - pub fn url(&self) -> String { - format!(cdn!("/emojis/{}.png"), self.id) - } -} - -impl fmt::Display for Emoji { - /// Formats the emoji into a string that will cause Discord clients to - /// render the emoji. - /// - /// This is in the format of: `<:NAME:EMOJI_ID>`. - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("<:")?; - f.write_str(&self.name)?; - fmt::Write::write_char(f, ':')?; - fmt::Display::fmt(&self.id, f)?; - fmt::Write::write_char(f, '>') - } -} - -impl fmt::Display for EmojiId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0, f) - } -} - -impl From<Emoji> for EmojiId { - /// Gets the Id of an `Emoji`. - fn from(emoji: Emoji) -> EmojiId { - emoji.id - } -} - -impl GuildInfo { - /// Returns the formatted URL of the guild's icon, if the guild has an icon. - pub fn icon_url(&self) -> Option<String> { - self.icon.as_ref().map(|icon| - format!(cdn!("/icons/{}/{}.webp"), self.id, icon)) - } -} - -impl InviteGuild { - /// Returns the formatted URL of the guild's splash image, if one exists. - pub fn splash_url(&self) -> Option<String> { - self.icon.as_ref().map(|icon| - format!(cdn!("/splashes/{}/{}.webp"), self.id, icon)) - } -} - -impl Guild { - #[cfg(feature="cache")] - fn has_perms(&self, mut permissions: Permissions) -> Result<bool> { - let member = match self.members.get(&CACHE.read().unwrap().user.id) { - Some(member) => member, - None => return Err(Error::Client(ClientError::ItemMissing)), - }; - - let perms = self.permissions_for(ChannelId(self.id.0), member.user.read().unwrap().id); - permissions.remove(perms); - - Ok(permissions.is_empty()) - } - - /// Ban a [`User`] from the guild. All messages by the - /// 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. - /// - /// # 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::InvalidPermissions`] if the current user does - /// not have permission to perform bans. - /// - /// 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 - /// [`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 - pub fn ban<U: Into<UserId>>(&self, user: U, delete_message_days: u8) - -> Result<()> { - if delete_message_days > 7 { - return Err(Error::Client(ClientError::DeleteMessageDaysAmount(delete_message_days))); - } - - #[cfg(feature="cache")] - { - let req = permissions::BAN_MEMBERS; - - if !self.has_perms(req)? { - return Err(Error::Client(ClientError::InvalidPermissions(req))); - } - } - - self.id.ban(user, delete_message_days) - } - - /// Retrieves a list of [`Ban`]s for 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. - /// - /// [`Ban`]: struct.Ban.html - /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions - /// [Ban Members]: permissions/constant.BAN_MEMBERS.html - pub fn bans(&self) -> Result<Vec<Ban>> { - #[cfg(feature="cache")] - { - let req = permissions::BAN_MEMBERS; - - if !self.has_perms(req)? { - return Err(Error::Client(ClientError::InvalidPermissions(req))); - } - } - - 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`]: ../client/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<PartialGuild> { - 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. - /// - /// **Note**: Requires the [Manage Channels] permission. - /// - /// # Examples - /// - /// ```rust,ignore - /// use serenity::model::ChannelType; - /// - /// // assuming a `guild` has already been bound - /// - /// let _ = guild.create_channel("my-test-channel", ChannelType::Text); - /// ``` - /// - /// # Errors - /// - /// If the `cache` is enabled, returns a [`ClientError::InvalidPermissions`] - /// if the current user does not have permission to perform bans. - /// - /// [`Channel`]: struct.Channel.html - /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions - /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html - pub fn create_channel(&mut self, name: &str, kind: ChannelType) -> Result<GuildChannel> { - #[cfg(feature="cache")] - { - let req = permissions::MANAGE_CHANNELS; - - if !self.has_perms(req)? { - return Err(Error::Client(ClientError::InvalidPermissions(req))); - } - } - - self.id.create_channel(name, kind) - } - - /// 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<Emoji> { - 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<I>(&self, integration_id: I, kind: &str) -> Result<()> - where I: Into<IntegrationId> { - 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 - /// // assuming a `guild` has been bound - /// - /// let role = guild.create_role(|r| r.hoist(true).name("role")); - /// ``` - /// - /// # 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 - /// [`Context::create_role`]: ../client/struct.Context.html#method.create_role - /// [`Role`]: struct.Role.html - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html - pub fn create_role<F>(&self, f: F) -> Result<Role> - where F: FnOnce(EditRole) -> EditRole { - #[cfg(feature="cache")] - { - let req = permissions::MANAGE_ROLES; - - if !self.has_perms(req)? { - return Err(Error::Client(ClientError::InvalidPermissions(req))); - } - } - - self.id.create_role(f) - } - - #[doc(hidden)] - pub fn decode(value: Value) -> Result<Guild> { - let mut map = into_map(value)?; - - let id = remove(&mut map, "id").and_then(GuildId::decode)?; - - let channels = { - let mut channels = HashMap::new(); - - let vals = decode_array(remove(&mut map, "channels")?, - |v| GuildChannel::decode_guild(v, id))?; - - for channel in vals { - channels.insert(channel.id, Arc::new(RwLock::new(channel))); - } - - channels - }; - - Ok(Guild { - afk_channel_id: opt(&mut map, "afk_channel_id", ChannelId::decode)?, - afk_timeout: req!(remove(&mut map, "afk_timeout")?.as_u64()), - channels: channels, - default_message_notifications: req!(remove(&mut map, "default_message_notifications")?.as_u64()), - emojis: remove(&mut map, "emojis").and_then(decode_emojis)?, - features: remove(&mut map, "features").and_then(|v| decode_array(v, Feature::decode_str))?, - icon: opt(&mut map, "icon", into_string)?, - id: id, - joined_at: remove(&mut map, "joined_at").and_then(into_string)?, - large: req!(remove(&mut map, "large")?.as_bool()), - member_count: req!(remove(&mut map, "member_count")?.as_u64()), - members: remove(&mut map, "members").and_then(decode_members)?, - mfa_level: req!(remove(&mut map, "mfa_level")?.as_u64()), - name: remove(&mut map, "name").and_then(into_string)?, - owner_id: remove(&mut map, "owner_id").and_then(UserId::decode)?, - presences: remove(&mut map, "presences").and_then(decode_presences)?, - region: remove(&mut map, "region").and_then(into_string)?, - roles: remove(&mut map, "roles").and_then(decode_roles)?, - splash: opt(&mut map, "splash", into_string)?, - verification_level: remove(&mut map, "verification_level").and_then(VerificationLevel::decode)?, - voice_states: remove(&mut map, "voice_states").and_then(decode_voice_states)?, - }) - } - - /// 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. - /// - /// # Errors - /// - /// If the `cache` is enabled, then returns a [`ClientError::InvalidUser`] - /// if the current user is not the guild owner. - /// - /// [`ClientError::InvalidUser`]: ../client/enum.ClientError.html#variant.InvalidUser - pub fn delete(&self) -> Result<PartialGuild> { - #[cfg(feature="cache")] - { - if self.owner_id != CACHE.read().unwrap().user.id { - let req = permissions::MANAGE_GUILD; - - return Err(Error::Client(ClientError::InvalidPermissions(req))); - } - } - - 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<E: Into<EmojiId>>(&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<I: Into<IntegrationId>>(&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<R: Into<RoleId>>(&self, role_id: R) -> Result<()> { - self.id.delete_role(role_id) - } - - /// Edits the current guild with new data where specified. - /// - /// Refer to `EditGuild`'s documentation for a full list of methods. - /// - /// **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`] - /// if the current user does not have permission to perform bans. - /// - /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions - /// [`Context::edit_guild`]: ../client/struct.Context.html#method.edit_guild - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html - pub fn edit<F>(&mut self, f: F) -> Result<()> - where F: FnOnce(EditGuild) -> EditGuild { - #[cfg(feature="cache")] - { - let req = permissions::MANAGE_GUILD; - - if !self.has_perms(req)? { - return Err(Error::Client(ClientError::InvalidPermissions(req))); - } - } - - 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<E: Into<EmojiId>>(&self, emoji_id: E, name: &str) -> Result<Emoji> { - 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<F, U>(&self, user_id: U, f: F) -> Result<()> - where F: FnOnce(EditMember) -> EditMember, U: Into<UserId> { - 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 - pub fn edit_nickname(&self, new_nickname: Option<&str>) -> Result<()> { - #[cfg(feature="cache")] - { - let req = permissions::CHANGE_NICKNAME; - - if !self.has_perms(req)? { - return Err(Error::Client(ClientError::InvalidPermissions(req))); - } - } - - 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<F, R>(&self, role_id: R, f: F) -> Result<Role> - where F: FnOnce(EditRole) -> EditRole, R: Into<RoleId> { - 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<G: Into<GuildId>>(guild_id: G) -> Result<PartialGuild> { - guild_id.into().get() - } - - /// Gets a list of the guild's bans. - /// - /// Requires the [Ban Members] permission. - #[inline] - pub fn get_bans(&self) -> Result<Vec<Ban>> { - 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<HashMap<ChannelId, GuildChannel>> { - 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<E: Into<EmojiId>>(&self, emoji_id: E) -> Result<Emoji> { - 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<Vec<Emoji>> { - 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<Vec<Integration>> { - self.id.get_integrations() - } - - /// Retrieves the active invites for the guild. - /// - /// **Note**: Requires the [Manage Guild] 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 - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html - pub fn get_invites(&self) -> Result<Vec<RichInvite>> { - #[cfg(feature="cache")] - { - let req = permissions::MANAGE_GUILD; - - if !self.has_perms(req)? { - return Err(Error::Client(ClientError::InvalidPermissions(req))); - } - } - - self.id.get_invites() - } - - /// Gets a user's [`Member`] for the guild by Id. - /// - /// [`Guild`]: struct.Guild.html - /// [`Member`]: struct.Member.html - #[inline] - pub fn get_member<U: Into<UserId>>(&self, user_id: U) -> Result<Member> { - 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<U>(&self, limit: Option<u64>, after: Option<U>) - -> Result<Vec<Member>> where U: Into<UserId> { - 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. - /// - /// The following are valid types of searches: - /// - /// - **username**: "zey" - /// - **username and discriminator**: "zey#5479" - /// - **nickname**: "zeyla" or "zeylas#nick" - /// - /// [`Member`]: struct.Member.html - pub fn get_member_named(&self, name: &str) -> Option<&Member> { - let hash_pos = name.find('#'); - - let (name, discrim) = if let Some(pos) = hash_pos { - let split = name.split_at(pos); - - (split.0, Some(split.1)) - } else { - (&name[..], None) - }; - - self.members - .values() - .find(|member| { - let name_matches = member.user.read().unwrap().name == name; - let discrim_matches = match discrim { - Some(discrim) => member.user.read().unwrap().discriminator == discrim, - None => true, - }; - - name_matches && discrim_matches - }).or_else(|| self.members.values().find(|member| { - member.nick.as_ref().map_or(false, |nick| nick == name) - })) - } - - /// 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<GuildPrune> { - #[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) - } - - /// Retrieves the guild's webhooks. - /// - /// **Note**: Requires the [Manage Webhooks] permission. - /// - /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html - #[inline] - pub fn get_webhooks(&self) -> Result<Vec<Webhook>> { - self.id.get_webhooks() - } - - /// Returns the formatted URL of the guild's icon, if one exists. - pub fn icon_url(&self) -> Option<String> { - self.icon.as_ref().map(|icon| - format!(cdn!("/icons/{}/{}.webp"), self.id, icon)) - } - - /// 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() > LARGE_THRESHOLD as usize - } - - /// 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<U: Into<UserId>>(&self, user_id: U) -> Result<()> { - self.id.kick(user_id) - } - - /// Leaves the guild. - #[inline] - pub fn leave(&self) -> Result<PartialGuild> { - 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<C, U>(&self, user_id: U, channel_id: C) -> Result<()> - where C: Into<ChannelId>, U: Into<UserId> { - self.id.move_member(user_id, channel_id) - } - - /// Calculate a [`User`]'s permissions in a given channel in the guild. - /// - /// [`User`]: struct.User.html - pub fn permissions_for<C, U>(&self, channel_id: C, user_id: U) - -> Permissions where C: Into<ChannelId>, U: Into<UserId> { - use super::permissions::*; - - let user_id = user_id.into(); - - // The owner has all permissions in all cases. - if user_id == self.owner_id { - return Permissions::all(); - } - - let channel_id = channel_id.into(); - - // Start by retrieving the @everyone role's permissions. - let everyone = match self.roles.get(&RoleId(self.id.0)) { - Some(everyone) => everyone, - None => { - error!("(╯°□°)╯︵ ┻━┻ @everyone role ({}) missing in '{}'", - self.id, - self.name); - - return Permissions::empty(); - }, - }; - - // Create a base set of permissions, starting with `@everyone`s. - let mut permissions = everyone.permissions; - - let member = match self.members.get(&user_id) { - Some(member) => member, - None => return everyone.permissions, - }; - - for &role in &member.roles { - if let Some(role) = self.roles.get(&role) { - permissions |= role.permissions; - } else { - warn!("(╯°□°)╯︵ ┻━┻ {} on {} has non-existent role {:?}", - member.user.read().unwrap().id, - self.id, - role); - } - } - - // Administrators have all permissions in any channel. - if permissions.contains(ADMINISTRATOR) { - return Permissions::all(); - } - - if let Some(channel) = self.channels.get(&channel_id) { - let channel = channel.read().unwrap(); - - // If this is a text channel, then throw out voice permissions. - if channel.kind == ChannelType::Text { - permissions &= !(CONNECT | SPEAK | MUTE_MEMBERS | - DEAFEN_MEMBERS | MOVE_MEMBERS | USE_VAD); - } - - // Apply the permission overwrites for the channel for each of the - // overwrites that - first - applies to the member's roles, and then - // the member itself. - // - // First apply the denied permission overwrites for each, then apply - // the allowed. - - // Roles - for overwrite in &channel.permission_overwrites { - if let PermissionOverwriteType::Role(role) = overwrite.kind { - if !member.roles.contains(&role) || role.0 == self.id.0 { - continue; - } - - permissions = (permissions & !overwrite.deny) | overwrite.allow; - } - } - - // Member - for overwrite in &channel.permission_overwrites { - if PermissionOverwriteType::Member(user_id) != overwrite.kind { - continue; - } - - permissions = (permissions & !overwrite.deny) | overwrite.allow; - } - } else { - warn!("(╯°□°)╯︵ ┻━┻ Guild {} does not contain channel {}", - self.id, - channel_id); - } - - // The default channel is always readable. - if channel_id.0 == self.id.0 { - permissions |= READ_MESSAGES; - } - - // No SEND_MESSAGES => no message-sending-related actions - // If the member does not have the `SEND_MESSAGES` permission, then - // throw out message-able permissions. - if !permissions.contains(SEND_MESSAGES) { - permissions &= !(SEND_TTS_MESSAGES | - MENTION_EVERYONE | - EMBED_LINKS | - ATTACH_FILES); - } - - // If the member does not have the `READ_MESSAGES` permission, then - // throw out actionable permissions. - if !permissions.contains(READ_MESSAGES) { - permissions &= KICK_MEMBERS | BAN_MEMBERS | ADMINISTRATOR | - MANAGE_GUILD | CHANGE_NICKNAME | MANAGE_NICKNAMES; - } - - permissions - } - - /// 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<F: FnOnce(Search) -> Search>(&self, f: F) -> Result<SearchResult> { - #[cfg(feature="cache")] - { - if CACHE.read().unwrap().user.bot { - return Err(Error::Client(ClientError::InvalidOperationAsBot)); - } - } - - self.id.search(f) - } - - /// 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 - pub fn search_channels<F>(&self, channel_ids: &[ChannelId], f: F) - -> Result<SearchResult> 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) - } - - /// Returns the formatted URL of the guild's splash image, if one exists. - pub fn splash_url(&self) -> Option<String> { - 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<I: Into<IntegrationId>>(&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. - /// - /// **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 start_prune(&self, days: u16) -> Result<GuildPrune> { - #[cfg(feature="cache")] - { - let req = permissions::KICK_MEMBERS; - - if !self.has_perms(req)? { - return Err(Error::Client(ClientError::InvalidPermissions(req))); - } - } - - self.id.start_prune(days) - } - - /// Unbans the given [`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 - pub fn unban<U: Into<UserId>>(&self, user_id: U) -> Result<()> { - #[cfg(feature="cache")] - { - let req = permissions::BAN_MEMBERS; - - if !self.has_perms(req)? { - return Err(Error::Client(ClientError::InvalidPermissions(req))); - } - } - - self.id.unban(user_id) - } -} - -impl GuildId { - /// Converts the guild Id into the default channel's Id. - #[inline] - pub fn as_channel_id(&self) -> ChannelId { - ChannelId(self.0) - } - - /// Ban a [`User`] from the guild. All messages by the - /// 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. - /// - /// # 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<U: Into<UserId>>(&self, user: U, delete_message_days: u8) - -> Result<()> { - if delete_message_days > 7 { - return Err(Error::Client(ClientError::DeleteMessageDaysAmount(delete_message_days))); - } - - rest::ban_user(self.0, user.into().0, delete_message_days) - } - - /// Creates a [`GuildChannel`] in the 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, GuildId}; - /// - /// let _channel = GuildId(7).create_channel("test", ChannelType::Voice); - /// ``` - /// - /// [`GuildChannel`]: struct.GuildChannel.html - /// [`rest::create_channel`]: ../client/rest/fn.create_channel.html - /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html - pub fn create_channel(&self, name: &str, kind: ChannelType) -> Result<GuildChannel> { - let map = ObjectBuilder::new() - .insert("name", name) - .insert("type", kind.name()) - .build(); - - rest::create_channel(self.0, &map) - } - - /// 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 - pub fn create_emoji(&self, name: &str, image: &str) -> Result<Emoji> { - let map = ObjectBuilder::new() - .insert("name", name) - .insert("image", image) - .build(); - - rest::create_emoji(self.0, &map) - } - - /// Creates an integration for the guild. - /// - /// Requires the [Manage Guild] permission. - /// - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html - pub fn create_integration<I>(&self, integration_id: I, kind: &str) - -> Result<()> where I: Into<IntegrationId> { - let integration_id = integration_id.into(); - let map = ObjectBuilder::new() - .insert("id", integration_id.0) - .insert("type", kind) - .build(); - - rest::create_guild_integration(self.0, integration_id.0, &map) - } - - /// 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. - /// - /// [`Guild::create_role`]: struct.Guild.html#method.create_role - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html - #[inline] - pub fn create_role<F: FnOnce(EditRole) -> EditRole>(&self, f: F) -> Result<Role> { - rest::create_role(self.0, &f(EditRole::default()).0.build()) - } - - /// 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<PartialGuild> { - rest::delete_guild(self.0) - } - - /// 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<E: Into<EmojiId>>(&self, emoji_id: E) -> Result<()> { - rest::delete_emoji(self.0, emoji_id.into().0) - } - - /// 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<I: Into<IntegrationId>>(&self, integration_id: I) -> Result<()> { - rest::delete_guild_integration(self.0, integration_id.into().0) - } - - /// 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<R: Into<RoleId>>(&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/constant.MANAGE_GUILD.html - #[inline] - pub fn edit<F: FnOnce(EditGuild) -> EditGuild>(&mut self, f: F) -> Result<PartialGuild> { - 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<E: Into<EmojiId>>(&self, emoji_id: E, name: &str) -> Result<Emoji> { - 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<F, U>(&self, user_id: U, f: F) -> Result<()> - where F: FnOnce(EditMember) -> EditMember, U: Into<UserId> { - 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<F, R>(&self, role_id: R, f: F) -> Result<Role> - where F: FnOnce(EditRole) -> EditRole, R: Into<RoleId> { - 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<Arc<RwLock<Guild>>> { - CACHE.read().unwrap().get_guild(*self) - } - - /// 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<PartialGuild> { - rest::get_guild(self.0) - } - - /// Gets a list of the guild's bans. - /// - /// Requires the [Ban Members] permission. - /// - /// [Ban Members]: permissions/constant.BAN_MEMBERS.html - #[inline] - pub fn get_bans(&self) -> Result<Vec<Ban>> { - 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<HashMap<ChannelId, GuildChannel>> { - 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<E: Into<EmojiId>>(&self, emoji_id: E) -> Result<Emoji> { - 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<Vec<Emoji>> { - 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<Vec<Integration>> { - 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<Vec<RichInvite>> { - 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<U: Into<UserId>>(&self, user_id: U) -> Result<Member> { - 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<U>(&self, limit: Option<u64>, after: Option<U>) - -> Result<Vec<Member>> where U: Into<UserId> { - 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<GuildPrune> { - let map = ObjectBuilder::new().insert("days", days).build(); - - rest::get_guild_prune_count(self.0, &map) - } - - /// Retrieves the guild's webhooks. - /// - /// **Note**: Requires the [Manage Webhooks] permission. - /// - /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html - #[inline] - pub fn get_webhooks(&self) -> Result<Vec<Webhook>> { - rest::get_guild_webhooks(self.0) - } - - /// 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<U: Into<UserId>>(&self, user_id: U) -> Result<()> { - rest::kick_member(self.0, user_id.into().0) - } - - /// Leaves the guild. - #[inline] - pub fn leave(&self) -> Result<PartialGuild> { - 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<C, U>(&self, user_id: U, channel_id: C) - -> Result<()> where C: Into<ChannelId>, U: Into<UserId> { - 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<F: FnOnce(Search) -> Search>(&self, f: F) -> Result<SearchResult> { - 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 - /// [`Search`]: ../utils/builder/struct.Search.html - pub fn search_channels<F>(&self, channel_ids: &[ChannelId], f: F) - -> Result<SearchResult> where F: FnOnce(Search) -> Search { - let ids = channel_ids.iter().map(|x| x.0).collect::<Vec<u64>>(); - - 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<I: Into<IntegrationId>>(&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 - #[inline] - pub fn start_prune(&self, days: u16) -> Result<GuildPrune> { - rest::start_guild_prune(self.0, &ObjectBuilder::new().insert("days", days).build()) - } - - /// 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<U: Into<UserId>>(&self, user_id: U) -> Result<()> { - rest::remove_ban(self.0, user_id.into().0) - } -} - -impl fmt::Display for GuildId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0, f) - } -} - -impl From<PartialGuild> for GuildId { - /// Gets the Id of a partial guild. - fn from(guild: PartialGuild) -> GuildId { - guild.id - } -} - -impl From<GuildInfo> for GuildId { - /// Gets the Id of Guild information struct. - fn from(guild_info: GuildInfo) -> GuildId { - guild_info.id - } -} - -impl From<InviteGuild> for GuildId { - /// Gets the Id of Invite Guild struct. - fn from(invite_guild: InviteGuild) -> GuildId { - invite_guild.id - } -} - -impl From<Guild> for GuildId { - /// Gets the Id of Guild. - fn from(live_guild: Guild) -> GuildId { - live_guild.id - } -} - -impl From<Integration> 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<R: Into<RoleId>>(&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.read().unwrap().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.read().unwrap().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.read().unwrap().id.0, delete_message_days) - } - - /// Determines the member's colour. - #[cfg(feature="cache")] - pub fn colour(&self) -> Option<Colour> { - 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.read().unwrap(), - None => return None, - }; - - let mut roles = self.roles - .iter() - .filter_map(|role_id| guild.roles.get(role_id)) - .collect::<Vec<&Role>>(); - 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) - } - - #[doc(hidden)] - pub fn decode_guild(guild_id: GuildId, mut value: Value) -> Result<Member> { - if let Some(v) = value.as_object_mut() { - v.insert("guild_id".to_owned(), Value::U64(guild_id.0)); - } - - Self::decode(value) - } - - /// Calculates the member's display name. - /// - /// The nickname takes priority over the member's username if it exists. - #[inline] - pub fn display_name(&self) -> Cow<String> { - self.nick.as_ref() - .map(Cow::Borrowed) - .unwrap_or_else(|| Cow::Owned(self.user.read().unwrap().name.clone())) - } - - /// Returns the DiscordTag of a Member, taking possible nickname into account. - #[inline] - pub fn distinct(&self) -> String { - format!("{}#{}", self.display_name(), self.user.read().unwrap().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<F: FnOnce(EditMember) -> EditMember>(&self, f: F) -> Result<()> { - let guild_id = self.find_guild()?; - let map = f(EditMember::default()).0.build(); - - rest::edit_member(guild_id.0, self.user.read().unwrap().id.0, &map) - } - - /// Finds the Id of the [`Guild`] that the member is in. - /// - /// If some value is present in [`guild_id`], then that value is returned. - /// - /// # Errors - /// - /// Returns a [`ClientError::GuildNotFound`] if the guild could not be - /// found. - /// - /// [`ClientError::GuildNotFound`]: ../client/enum.ClientError.html#variant.GuildNotFound - /// [`Guild`]: struct.Guild.html - /// [`guild_id`]: #structfield.guild_id - #[cfg(feature="cache")] - pub fn find_guild(&self) -> Result<GuildId> { - if let Some(guild_id) = self.guild_id { - return Ok(guild_id); - } - - for guild in CACHE.read().unwrap().guilds.values() { - let guild = guild.read().unwrap(); - - let predicate = guild.members - .values() - .any(|m| m.joined_at == self.joined_at && m.user.read().unwrap().id == self.user.read().unwrap().id); - - if predicate { - return Ok(guild.id); - } - } - - Err(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<R: Into<RoleId>>(&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.read().unwrap().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.read().unwrap().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<Vec<Role>> { - CACHE.read().unwrap() - .guilds - .values() - .find(|guild| guild - .read() - .unwrap() - .members - .values() - .any(|m| m.user.read().unwrap().id == self.user.read().unwrap().id && m.joined_at == *self.joined_at)) - .map(|guild| guild - .read() - .unwrap() - .roles - .values() - .filter(|role| self.roles.contains(&role.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.read().unwrap().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.read().unwrap().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<U: Into<UserId>>(&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, 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`]: ../client/rest/fn.create_channel.html - /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html - #[inline] - pub fn create_channel(&self, name: &str, kind: ChannelType) -> Result<GuildChannel> { - 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<Emoji> { - 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<I>(&self, integration_id: I, kind: &str) -> Result<()> - where I: Into<IntegrationId> { - 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/constant.MANAGE_ROLES.html - #[inline] - pub fn create_role<F: FnOnce(EditRole) -> EditRole>(&self, f: F) -> Result<Role> { - 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<PartialGuild> { - 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<E: Into<EmojiId>>(&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<I: Into<IntegrationId>>(&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<R: Into<RoleId>>(&self, role_id: R) -> Result<()> { - self.id.delete_role(role_id) - } - - /// Edits the current guild with new data where specified. - /// - /// **Note**: Requires the current user to have the [Manage Guild] - /// permission. - /// - /// [`Context::edit_guild`]: ../client/struct.Context.html#method.edit_guild - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html - pub fn edit<F>(&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<E: Into<EmojiId>>(&self, emoji_id: E, name: &str) -> Result<Emoji> { - 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<F, U>(&self, user_id: U, f: F) -> Result<()> - where F: FnOnce(EditMember) -> EditMember, U: Into<UserId> { - 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<G: Into<GuildId>>(guild_id: G) -> Result<PartialGuild> { - guild_id.into().get() - } - - /// Gets a list of the guild's bans. - /// - /// Requires the [Ban Members] permission. - /// - /// [Ban Members]: permissions/constant.BAN_MEMBERS.html - #[inline] - pub fn get_bans(&self) -> Result<Vec<Ban>> { - 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<HashMap<ChannelId, GuildChannel>> { - 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<E: Into<EmojiId>>(&self, emoji_id: E) -> Result<Emoji> { - 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<Vec<Emoji>> { - 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<Vec<Integration>> { - self.id.get_integrations() - } - - /// Gets all of the guild's invites. - /// - /// Requires the [Manage Guild] permission. - /// - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html - #[inline] - pub fn get_invites(&self) -> Result<Vec<RichInvite>> { - self.id.get_invites() - } - - /// Gets a user's [`Member`] for the guild by Id. - /// - /// [`Guild`]: struct.Guild.html - /// [`Member`]: struct.Member.html - pub fn get_member<U: Into<UserId>>(&self, user_id: U) -> Result<Member> { - 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 - pub fn get_members<U>(&self, limit: Option<u64>, after: Option<U>) - -> Result<Vec<Member>> where U: Into<UserId> { - self.id.get_members(limit, after) - } - - /// 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 - #[inline] - pub fn get_prune_count(&self, days: u16) -> Result<GuildPrune> { - self.id.get_prune_count(days) - } - - /// Retrieves the guild's webhooks. - /// - /// **Note**: Requires the [Manage Webhooks] permission. - /// - /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html - #[inline] - pub fn get_webhooks(&self) -> Result<Vec<Webhook>> { - self.id.get_webhooks() - } - - /// 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<U: Into<UserId>>(&self, user_id: U) -> Result<()> { - self.id.kick(user_id) - } - - /// Returns a formatted URL of the guild's icon, if the guild has an icon. - pub fn icon_url(&self) -> Option<String> { - self.icon.as_ref().map(|icon| - format!(cdn!("/icons/{}/{}.webp"), self.id, icon)) - } - - /// Leaves the guild. - #[inline] - pub fn leave(&self) -> Result<PartialGuild> { - 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<C, U>(&self, user_id: U, channel_id: C) -> Result<()> - where C: Into<ChannelId>, U: Into<UserId> { - 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<F: FnOnce(Search) -> Search>(&self, f: F) -> Result<SearchResult> { - #[cfg(feature="cache")] - { - if CACHE.read().unwrap().user.bot { - return Err(Error::Client(ClientError::InvalidOperationAsBot)); - } - } - - self.id.search(f) - } - - /// 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 - pub fn search_channels<F>(&self, channel_ids: &[ChannelId], f: F) - -> Result<SearchResult> 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) - } - - /// Returns the formatted URL of the guild's splash image, if one exists. - pub fn splash_url(&self) -> Option<String> { - 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<I: Into<IntegrationId>>(&self, integration_id: I) -> Result<()> { - self.id.start_integration_sync(integration_id) - } - - /// 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<U: Into<UserId>>(&self, user_id: U) -> Result<()> { - self.id.unban(user_id) - } -} - -impl PossibleGuild<Guild> { - #[doc(hidden)] - pub fn decode(value: Value) -> Result<Self> { - let mut value = into_map(value)?; - if remove(&mut value, "unavailable").ok().and_then(|v| v.as_bool()).unwrap_or(false) { - remove(&mut value, "id").and_then(GuildId::decode).map(PossibleGuild::Offline) - } else { - Guild::decode(Value::Object(value)).map(PossibleGuild::Online) - } - } - - /// Retrieves the Id of the inner [`Guild`]. - /// - /// [`Guild`]: struct.Guild.html - pub fn id(&self) -> GuildId { - match *self { - PossibleGuild::Offline(guild_id) => guild_id, - PossibleGuild::Online(ref live_guild) => live_guild.id, - } - } -} - -impl PossibleGuild<PartialGuild> { - #[doc(hidden)] - pub fn decode(value: Value) -> Result<Self> { - let mut value = into_map(value)?; - if remove(&mut value, "unavailable").ok().and_then(|v| v.as_bool()).unwrap_or(false) { - remove(&mut value, "id").and_then(GuildId::decode).map(PossibleGuild::Offline) - } else { - PartialGuild::decode(Value::Object(value)).map(PossibleGuild::Online) - } - } - - /// Retrieves the Id of the inner [`Guild`]. - /// - /// [`Guild`]: struct.Guild.html - pub fn id(&self) -> GuildId { - match *self { - PossibleGuild::Offline(id) => id, - PossibleGuild::Online(ref live_guild) => live_guild.id, - } - } -} - -impl Role { - /// Deletes the role. - /// - /// **Note** Requires the [Manage Roles] permission. - /// - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html - #[cfg(feature="cache")] - #[inline] - pub fn delete(&self) -> Result<()> { - rest::delete_role(self.find_guild()?.0, self.id.0) - } - - /// Edits a [`Role`], optionally setting its new fields. - /// - /// Requires the [Manage Roles] permission. - /// - /// # Examples - /// - /// Make a role hoisted: - /// - /// ```rust,ignore - /// // assuming a `guild` and `role_id` have been bound - // - /// guild.edit_role(role_id, |r| r.hoist(true)); - /// ``` - /// - /// [`Role`]: struct.Role.html - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html - #[cfg(feature="cache")] - pub fn edit_role<F: FnOnce(EditRole) -> EditRole>(&self, f: F) -> Result<Role> { - 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 - /// - /// Returns a [`ClientError::GuildNotFound`] if a guild is not in the cache - /// that contains the role. - /// - /// [`ClientError::GuildNotFound`]: ../client/enum.ClientError.html#variant.GuildNotFound - #[cfg(feature="cache")] - pub fn find_guild(&self) -> Result<GuildId> { - for guild in CACHE.read().unwrap().guilds.values() { - let guild = guild.read().unwrap(); - - if guild.roles.contains_key(&RoleId(self.id.0)) { - return Ok(guild.id); - } - } - - Err(Error::Client(ClientError::GuildNotFound)) - } - - /// Check that the role has the given permission. - #[inline] - pub fn has_permission(&self, permission: Permissions) -> bool { - self.permissions.contains(permission) - } - - /// Checks whether the role has all of the given permissions. - /// - /// The 'precise' argument is used to check if the role's permissions are - /// precisely equivalent to the given permissions. If you need only check - /// that the role has at least the given permissions, pass `false`. - pub fn has_permissions(&self, permissions: Permissions, precise: bool) - -> bool { - if precise { - self.permissions == permissions - } else { - self.permissions.contains(permissions) - } - } -} - -impl fmt::Display for Role { - /// Format a mention for the role, pinging its members. - // This is in the format of: `<@&ROLE_ID>`. - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.mention(), f) - } -} - -impl Eq for Role {} - -impl Ord for Role { - fn cmp(&self, other: &Role) -> Ordering { - if self.position == other.position { - self.id.cmp(&other.id) - } else { - self.position.cmp(&other.position) - } - } -} - -impl PartialEq for Role { - fn eq(&self, other: &Role) -> bool { - self.id == other.id - } -} - -impl PartialOrd for Role { - fn partial_cmp(&self, other: &Role) -> Option<Ordering> { - Some(self.cmp(other)) - } -} - -impl RoleId { - /// Search the cache for the role. - #[cfg(feature="cache")] - pub fn find(&self) -> Option<Role> { - let cache = CACHE.read().unwrap(); - - for guild in cache.guilds.values() { - let guild = guild.read().unwrap(); - - if !guild.roles.contains_key(self) { - continue; - } - - if let Some(role) = guild.roles.get(self) { - return Some(role.clone()); - } - } - - None - } -} - -impl fmt::Display for RoleId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0, f) - } -} - -impl From<Role> for RoleId { - /// Gets the Id of a role. - fn from(role: Role) -> RoleId { - role.id - } -} diff --git a/src/model/guild/emoji.rs b/src/model/guild/emoji.rs new file mode 100644 index 0000000..43e57a2 --- /dev/null +++ b/src/model/guild/emoji.rs @@ -0,0 +1,100 @@ +use serde_json::builder::ObjectBuilder; +use std::fmt::{Display, Formatter, Result as FmtResult, Write as FmtWrite}; +use std::mem; +use ::client::{CACHE, rest}; +use ::model::{Emoji, EmojiId, GuildId}; +use ::internal::prelude::*; + +impl Emoji { + /// Deletes the emoji. + /// + /// **Note**: The [Manage Emojis] permission is required. + /// + /// **Note**: Only user accounts may use this method. + /// + /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + #[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), + None => Err(Error::Client(ClientError::ItemMissing)), + } + } + + /// Edits the emoji by updating it with a new name. + /// + /// **Note**: The [Manage Emojis] permission is required. + /// + /// **Note**: Only user accounts may use this method. + /// + /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + #[cfg(feature="cache")] + pub fn edit(&mut self, name: &str) -> Result<()> { + match self.find_guild_id() { + Some(guild_id) => { + let map = ObjectBuilder::new() + .insert("name", name) + .build(); + + match rest::edit_emoji(guild_id.0, self.id.0, &map) { + Ok(emoji) => { + mem::replace(self, emoji); + + Ok(()) + }, + Err(why) => Err(why), + } + }, + None => Err(Error::Client(ClientError::ItemMissing)), + } + } + + /// Finds the [`Guild`] that owns the emoji by looking through the Cache. + /// + /// [`Guild`]: struct.Guild.html + #[cfg(feature="cache")] + pub fn find_guild_id(&self) -> Option<GuildId> { + for guild in CACHE.read().unwrap().guilds.values() { + let guild = guild.read().unwrap(); + + if guild.emojis.contains_key(&self.id) { + return Some(guild.id); + } + } + + None + } + + /// Generates a URL to the emoji's image. + #[inline] + pub fn url(&self) -> String { + format!(cdn!("/emojis/{}.png"), self.id) + } +} + +impl Display for Emoji { + /// Formats the emoji into a string that will cause Discord clients to + /// render the emoji. + /// + /// This is in the format of: `<:NAME:EMOJI_ID>`. + fn fmt(&self, f: &mut Formatter) -> FmtResult { + f.write_str("<:")?; + f.write_str(&self.name)?; + FmtWrite::write_char(f, ':')?; + Display::fmt(&self.id, f)?; + FmtWrite::write_char(f, '>') + } +} + +impl Display for EmojiId { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + Display::fmt(&self.0, f) + } +} + +impl From<Emoji> for EmojiId { + /// Gets the Id of an `Emoji`. + fn from(emoji: Emoji) -> EmojiId { + emoji.id + } +} diff --git a/src/model/guild/guild_id.rs b/src/model/guild/guild_id.rs new file mode 100644 index 0000000..83dc19a --- /dev/null +++ b/src/model/guild/guild_id.rs @@ -0,0 +1,525 @@ +use serde_json::builder::ObjectBuilder; +use std::fmt::{Display, Formatter, Result as FmtResult}; +use ::client::{CACHE, rest}; +use ::internal::prelude::*; +use ::model::*; +use ::utils::builder::{EditGuild, EditMember, EditRole, Search}; + +impl GuildId { + /// Converts the guild Id into the default channel's Id. + #[inline] + pub fn as_channel_id(&self) -> ChannelId { + ChannelId(self.0) + } + + /// Ban a [`User`] from the guild. All messages by the + /// 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. + /// + /// # 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<U: Into<UserId>>(&self, user: U, delete_message_days: u8) + -> Result<()> { + if delete_message_days > 7 { + return Err(Error::Client(ClientError::DeleteMessageDaysAmount(delete_message_days))); + } + + rest::ban_user(self.0, user.into().0, delete_message_days) + } + + /// Creates a [`GuildChannel`] in the 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, GuildId}; + /// + /// let _channel = GuildId(7).create_channel("test", ChannelType::Voice); + /// ``` + /// + /// [`GuildChannel`]: struct.GuildChannel.html + /// [`rest::create_channel`]: ../client/rest/fn.create_channel.html + /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html + pub fn create_channel(&self, name: &str, kind: ChannelType) -> Result<GuildChannel> { + let map = ObjectBuilder::new() + .insert("name", name) + .insert("type", kind.name()) + .build(); + + rest::create_channel(self.0, &map) + } + + /// 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 + pub fn create_emoji(&self, name: &str, image: &str) -> Result<Emoji> { + let map = ObjectBuilder::new() + .insert("name", name) + .insert("image", image) + .build(); + + rest::create_emoji(self.0, &map) + } + + /// Creates an integration for the guild. + /// + /// Requires the [Manage Guild] permission. + /// + /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + pub fn create_integration<I>(&self, integration_id: I, kind: &str) + -> Result<()> where I: Into<IntegrationId> { + let integration_id = integration_id.into(); + let map = ObjectBuilder::new() + .insert("id", integration_id.0) + .insert("type", kind) + .build(); + + rest::create_guild_integration(self.0, integration_id.0, &map) + } + + /// 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. + /// + /// [`Guild::create_role`]: struct.Guild.html#method.create_role + /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + #[inline] + pub fn create_role<F: FnOnce(EditRole) -> EditRole>(&self, f: F) -> Result<Role> { + rest::create_role(self.0, &f(EditRole::default()).0.build()) + } + + /// 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<PartialGuild> { + rest::delete_guild(self.0) + } + + /// 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<E: Into<EmojiId>>(&self, emoji_id: E) -> Result<()> { + rest::delete_emoji(self.0, emoji_id.into().0) + } + + /// 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<I: Into<IntegrationId>>(&self, integration_id: I) -> Result<()> { + rest::delete_guild_integration(self.0, integration_id.into().0) + } + + /// 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<R: Into<RoleId>>(&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/constant.MANAGE_GUILD.html + #[inline] + pub fn edit<F: FnOnce(EditGuild) -> EditGuild>(&mut self, f: F) -> Result<PartialGuild> { + 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<E: Into<EmojiId>>(&self, emoji_id: E, name: &str) -> Result<Emoji> { + 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<F, U>(&self, user_id: U, f: F) -> Result<()> + where F: FnOnce(EditMember) -> EditMember, U: Into<UserId> { + 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<F, R>(&self, role_id: R, f: F) -> Result<Role> + where F: FnOnce(EditRole) -> EditRole, R: Into<RoleId> { + 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<Arc<RwLock<Guild>>> { + CACHE.read().unwrap().get_guild(*self) + } + + /// 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<PartialGuild> { + rest::get_guild(self.0) + } + + /// Gets a list of the guild's bans. + /// + /// Requires the [Ban Members] permission. + /// + /// [Ban Members]: permissions/constant.BAN_MEMBERS.html + #[inline] + pub fn get_bans(&self) -> Result<Vec<Ban>> { + 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<HashMap<ChannelId, GuildChannel>> { + 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<E: Into<EmojiId>>(&self, emoji_id: E) -> Result<Emoji> { + 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<Vec<Emoji>> { + 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<Vec<Integration>> { + 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<Vec<RichInvite>> { + 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<U: Into<UserId>>(&self, user_id: U) -> Result<Member> { + 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<U>(&self, limit: Option<u64>, after: Option<U>) + -> Result<Vec<Member>> where U: Into<UserId> { + 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<GuildPrune> { + let map = ObjectBuilder::new().insert("days", days).build(); + + rest::get_guild_prune_count(self.0, &map) + } + + /// Retrieves the guild's webhooks. + /// + /// **Note**: Requires the [Manage Webhooks] permission. + /// + /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html + #[inline] + pub fn get_webhooks(&self) -> Result<Vec<Webhook>> { + rest::get_guild_webhooks(self.0) + } + + /// 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<U: Into<UserId>>(&self, user_id: U) -> Result<()> { + rest::kick_member(self.0, user_id.into().0) + } + + /// Leaves the guild. + #[inline] + pub fn leave(&self) -> Result<PartialGuild> { + 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<C, U>(&self, user_id: U, channel_id: C) + -> Result<()> where C: Into<ChannelId>, U: Into<UserId> { + 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<F: FnOnce(Search) -> Search>(&self, f: F) -> Result<SearchResult> { + 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 + /// [`Search`]: ../utils/builder/struct.Search.html + pub fn search_channels<F>(&self, channel_ids: &[ChannelId], f: F) + -> Result<SearchResult> where F: FnOnce(Search) -> Search { + let ids = channel_ids.iter().map(|x| x.0).collect::<Vec<u64>>(); + + 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<I: Into<IntegrationId>>(&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 + #[inline] + pub fn start_prune(&self, days: u16) -> Result<GuildPrune> { + rest::start_guild_prune(self.0, &ObjectBuilder::new().insert("days", days).build()) + } + + /// 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<U: Into<UserId>>(&self, user_id: U) -> Result<()> { + rest::remove_ban(self.0, user_id.into().0) + } +} + +impl Display for GuildId { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + Display::fmt(&self.0, f) + } +} + +impl From<PartialGuild> for GuildId { + /// Gets the Id of a partial guild. + fn from(guild: PartialGuild) -> GuildId { + guild.id + } +} + +impl From<GuildInfo> for GuildId { + /// Gets the Id of Guild information struct. + fn from(guild_info: GuildInfo) -> GuildId { + guild_info.id + } +} + +impl From<InviteGuild> for GuildId { + /// Gets the Id of Invite Guild struct. + fn from(invite_guild: InviteGuild) -> GuildId { + invite_guild.id + } +} + +impl From<Guild> for GuildId { + /// Gets the Id of Guild. + fn from(live_guild: Guild) -> GuildId { + live_guild.id + } +} diff --git a/src/model/guild/integration.rs b/src/model/guild/integration.rs new file mode 100644 index 0000000..d7f9967 --- /dev/null +++ b/src/model/guild/integration.rs @@ -0,0 +1,8 @@ +use ::model::{Integration, IntegrationId}; + +impl From<Integration> for IntegrationId { + /// Gets the Id of integration. + fn from(integration: Integration) -> IntegrationId { + integration.id + } +} diff --git a/src/model/guild/member.rs b/src/model/guild/member.rs new file mode 100644 index 0000000..67d9dae --- /dev/null +++ b/src/model/guild/member.rs @@ -0,0 +1,279 @@ +use std::borrow::Cow; +use std::fmt::{Display, Formatter, Result as FmtResult}; +use ::client::{CACHE, rest}; +use ::internal::prelude::*; +use ::model::*; +use ::utils::builder::EditMember; +use ::utils::Colour; + +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<R: Into<RoleId>>(&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.read().unwrap().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.read().unwrap().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.read().unwrap().id.0, delete_message_days) + } + + /// Determines the member's colour. + #[cfg(feature="cache")] + pub fn colour(&self) -> Option<Colour> { + 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.read().unwrap(), + None => return None, + }; + + let mut roles = self.roles + .iter() + .filter_map(|role_id| guild.roles.get(role_id)) + .collect::<Vec<&Role>>(); + 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) + } + + #[doc(hidden)] + pub fn decode_guild(guild_id: GuildId, mut value: Value) -> Result<Member> { + if let Some(v) = value.as_object_mut() { + v.insert("guild_id".to_owned(), Value::U64(guild_id.0)); + } + + Self::decode(value) + } + + /// Calculates the member's display name. + /// + /// The nickname takes priority over the member's username if it exists. + #[inline] + pub fn display_name(&self) -> Cow<String> { + self.nick.as_ref() + .map(Cow::Borrowed) + .unwrap_or_else(|| Cow::Owned(self.user.read().unwrap().name.clone())) + } + + /// Returns the DiscordTag of a Member, taking possible nickname into account. + #[inline] + pub fn distinct(&self) -> String { + format!("{}#{}", self.display_name(), self.user.read().unwrap().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<F: FnOnce(EditMember) -> EditMember>(&self, f: F) -> Result<()> { + let guild_id = self.find_guild()?; + let map = f(EditMember::default()).0.build(); + + rest::edit_member(guild_id.0, self.user.read().unwrap().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<GuildId> { + for guild in CACHE.read().unwrap().guilds.values() { + let guild = guild.read().unwrap(); + + let predicate = guild.members + .values() + .any(|m| m.joined_at == self.joined_at && m.user.read().unwrap().id == self.user.read().unwrap().id); + + if predicate { + return Ok(guild.id); + } + } + + Err(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<R: Into<RoleId>>(&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.read().unwrap().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.read().unwrap().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<Vec<Role>> { + CACHE.read().unwrap() + .guilds + .values() + .find(|guild| guild + .read() + .unwrap() + .members + .values() + .any(|m| m.user.read().unwrap().id == self.user.read().unwrap().id && m.joined_at == *self.joined_at)) + .map(|guild| guild + .read() + .unwrap() + .roles + .values() + .filter(|role| self.roles.contains(&role.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.read().unwrap().id.0) + } +} + +impl 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 Formatter) -> FmtResult { + Display::fmt(&self.user.read().unwrap().mention(), f) + } +} diff --git a/src/model/guild/mod.rs b/src/model/guild/mod.rs new file mode 100644 index 0000000..0b008c8 --- /dev/null +++ b/src/model/guild/mod.rs @@ -0,0 +1,1052 @@ +use serde_json::builder::ObjectBuilder; +use ::client::{CACHE, rest}; +use ::constants::LARGE_THRESHOLD; +use ::model::*; +use ::utils::builder::{EditGuild, EditMember, EditRole, Search}; + +mod emoji; +mod guild_id; +mod integration; +mod member; +mod partial_guild; +mod role; + +pub use self::emoji::*; +pub use self::guild_id::*; +pub use self::integration::*; +pub use self::member::*; +pub use self::partial_guild::*; +pub use self::role::*; + +impl Guild { + #[cfg(feature="cache")] + fn has_perms(&self, mut permissions: Permissions) -> Result<bool> { + let member = match self.members.get(&CACHE.read().unwrap().user.id) { + Some(member) => member, + None => return Err(Error::Client(ClientError::ItemMissing)), + }; + + let perms = self.permissions_for(ChannelId(self.id.0), member.user.read().unwrap().id); + permissions.remove(perms); + + Ok(permissions.is_empty()) + } + + /// Ban a [`User`] from the guild. All messages by the + /// 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. + /// + /// # 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::InvalidPermissions`] if the current user does + /// not have permission to perform bans. + /// + /// 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 + /// [`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 + pub fn ban<U: Into<UserId>>(&self, user: U, delete_message_days: u8) + -> Result<()> { + if delete_message_days > 7 { + return Err(Error::Client(ClientError::DeleteMessageDaysAmount(delete_message_days))); + } + + #[cfg(feature="cache")] + { + let req = permissions::BAN_MEMBERS; + + if !self.has_perms(req)? { + return Err(Error::Client(ClientError::InvalidPermissions(req))); + } + } + + self.id.ban(user, delete_message_days) + } + + /// Retrieves a list of [`Ban`]s for 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. + /// + /// [`Ban`]: struct.Ban.html + /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions + /// [Ban Members]: permissions/constant.BAN_MEMBERS.html + pub fn bans(&self) -> Result<Vec<Ban>> { + #[cfg(feature="cache")] + { + let req = permissions::BAN_MEMBERS; + + if !self.has_perms(req)? { + return Err(Error::Client(ClientError::InvalidPermissions(req))); + } + } + + 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`]: ../client/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<PartialGuild> { + 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. + /// + /// **Note**: Requires the [Manage Channels] permission. + /// + /// # Examples + /// + /// ```rust,ignore + /// use serenity::model::ChannelType; + /// + /// // assuming a `guild` has already been bound + /// + /// let _ = guild.create_channel("my-test-channel", ChannelType::Text); + /// ``` + /// + /// # Errors + /// + /// If the `cache` is enabled, returns a [`ClientError::InvalidPermissions`] + /// if the current user does not have permission to perform bans. + /// + /// [`Channel`]: struct.Channel.html + /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions + /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html + pub fn create_channel(&mut self, name: &str, kind: ChannelType) -> Result<GuildChannel> { + #[cfg(feature="cache")] + { + let req = permissions::MANAGE_CHANNELS; + + if !self.has_perms(req)? { + return Err(Error::Client(ClientError::InvalidPermissions(req))); + } + } + + self.id.create_channel(name, kind) + } + + /// 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<Emoji> { + 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<I>(&self, integration_id: I, kind: &str) -> Result<()> + where I: Into<IntegrationId> { + 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 + /// // assuming a `guild` has been bound + /// + /// let role = guild.create_role(|r| r.hoist(true).name("role")); + /// ``` + /// + /// # 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 + /// [`Context::create_role`]: ../client/struct.Context.html#method.create_role + /// [`Role`]: struct.Role.html + /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + pub fn create_role<F>(&self, f: F) -> Result<Role> + where F: FnOnce(EditRole) -> EditRole { + #[cfg(feature="cache")] + { + let req = permissions::MANAGE_ROLES; + + if !self.has_perms(req)? { + return Err(Error::Client(ClientError::InvalidPermissions(req))); + } + } + + self.id.create_role(f) + } + + #[doc(hidden)] + pub fn decode(value: Value) -> Result<Guild> { + let mut map = into_map(value)?; + + let id = remove(&mut map, "id").and_then(GuildId::decode)?; + + let channels = { + let mut channels = HashMap::new(); + + let vals = decode_array(remove(&mut map, "channels")?, + |v| GuildChannel::decode_guild(v, id))?; + + for channel in vals { + channels.insert(channel.id, Arc::new(RwLock::new(channel))); + } + + channels + }; + + Ok(Guild { + afk_channel_id: opt(&mut map, "afk_channel_id", ChannelId::decode)?, + afk_timeout: req!(remove(&mut map, "afk_timeout")?.as_u64()), + channels: channels, + default_message_notifications: req!(remove(&mut map, "default_message_notifications")?.as_u64()), + emojis: remove(&mut map, "emojis").and_then(decode_emojis)?, + features: remove(&mut map, "features").and_then(|v| decode_array(v, Feature::decode_str))?, + icon: opt(&mut map, "icon", into_string)?, + id: id, + joined_at: remove(&mut map, "joined_at").and_then(into_string)?, + large: req!(remove(&mut map, "large")?.as_bool()), + member_count: req!(remove(&mut map, "member_count")?.as_u64()), + members: remove(&mut map, "members").and_then(decode_members)?, + mfa_level: req!(remove(&mut map, "mfa_level")?.as_u64()), + name: remove(&mut map, "name").and_then(into_string)?, + owner_id: remove(&mut map, "owner_id").and_then(UserId::decode)?, + presences: remove(&mut map, "presences").and_then(decode_presences)?, + region: remove(&mut map, "region").and_then(into_string)?, + roles: remove(&mut map, "roles").and_then(decode_roles)?, + splash: opt(&mut map, "splash", into_string)?, + verification_level: remove(&mut map, "verification_level").and_then(VerificationLevel::decode)?, + voice_states: remove(&mut map, "voice_states").and_then(decode_voice_states)?, + }) + } + + /// 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. + /// + /// # Errors + /// + /// If the `cache` is enabled, then returns a [`ClientError::InvalidUser`] + /// if the current user is not the guild owner. + /// + /// [`ClientError::InvalidUser`]: ../client/enum.ClientError.html#variant.InvalidUser + pub fn delete(&self) -> Result<PartialGuild> { + #[cfg(feature="cache")] + { + if self.owner_id != CACHE.read().unwrap().user.id { + let req = permissions::MANAGE_GUILD; + + return Err(Error::Client(ClientError::InvalidPermissions(req))); + } + } + + 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<E: Into<EmojiId>>(&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<I: Into<IntegrationId>>(&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<R: Into<RoleId>>(&self, role_id: R) -> Result<()> { + self.id.delete_role(role_id) + } + + /// Edits the current guild with new data where specified. + /// + /// Refer to `EditGuild`'s documentation for a full list of methods. + /// + /// **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`] + /// if the current user does not have permission to perform bans. + /// + /// [`ClientError::InvalidPermissions`]: ../client/enum.ClientError.html#variant.InvalidPermissions + /// [`Context::edit_guild`]: ../client/struct.Context.html#method.edit_guild + /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + pub fn edit<F>(&mut self, f: F) -> Result<()> + where F: FnOnce(EditGuild) -> EditGuild { + #[cfg(feature="cache")] + { + let req = permissions::MANAGE_GUILD; + + if !self.has_perms(req)? { + return Err(Error::Client(ClientError::InvalidPermissions(req))); + } + } + + 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<E: Into<EmojiId>>(&self, emoji_id: E, name: &str) -> Result<Emoji> { + 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<F, U>(&self, user_id: U, f: F) -> Result<()> + where F: FnOnce(EditMember) -> EditMember, U: Into<UserId> { + 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 + pub fn edit_nickname(&self, new_nickname: Option<&str>) -> Result<()> { + #[cfg(feature="cache")] + { + let req = permissions::CHANGE_NICKNAME; + + if !self.has_perms(req)? { + return Err(Error::Client(ClientError::InvalidPermissions(req))); + } + } + + 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<F, R>(&self, role_id: R, f: F) -> Result<Role> + where F: FnOnce(EditRole) -> EditRole, R: Into<RoleId> { + 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<G: Into<GuildId>>(guild_id: G) -> Result<PartialGuild> { + guild_id.into().get() + } + + /// Gets a list of the guild's bans. + /// + /// Requires the [Ban Members] permission. + #[inline] + pub fn get_bans(&self) -> Result<Vec<Ban>> { + 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<HashMap<ChannelId, GuildChannel>> { + 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<E: Into<EmojiId>>(&self, emoji_id: E) -> Result<Emoji> { + 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<Vec<Emoji>> { + 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<Vec<Integration>> { + self.id.get_integrations() + } + + /// Retrieves the active invites for the guild. + /// + /// **Note**: Requires the [Manage Guild] 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 + /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + pub fn get_invites(&self) -> Result<Vec<RichInvite>> { + #[cfg(feature="cache")] + { + let req = permissions::MANAGE_GUILD; + + if !self.has_perms(req)? { + return Err(Error::Client(ClientError::InvalidPermissions(req))); + } + } + + self.id.get_invites() + } + + /// Gets a user's [`Member`] for the guild by Id. + /// + /// [`Guild`]: struct.Guild.html + /// [`Member`]: struct.Member.html + #[inline] + pub fn get_member<U: Into<UserId>>(&self, user_id: U) -> Result<Member> { + 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<U>(&self, limit: Option<u64>, after: Option<U>) + -> Result<Vec<Member>> where U: Into<UserId> { + 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. + /// + /// The following are valid types of searches: + /// + /// - **username**: "zey" + /// - **username and discriminator**: "zey#5479" + /// - **nickname**: "zeyla" or "zeylas#nick" + /// + /// [`Member`]: struct.Member.html + pub fn get_member_named(&self, name: &str) -> Option<&Member> { + let hash_pos = name.find('#'); + + let (name, discrim) = if let Some(pos) = hash_pos { + let split = name.split_at(pos); + + (split.0, Some(split.1)) + } else { + (&name[..], None) + }; + + self.members + .values() + .find(|member| { + let name_matches = member.user.read().unwrap().name == name; + let discrim_matches = match discrim { + Some(discrim) => member.user.read().unwrap().discriminator == discrim, + None => true, + }; + + name_matches && discrim_matches + }).or_else(|| self.members.values().find(|member| { + member.nick.as_ref().map_or(false, |nick| nick == name) + })) + } + + /// 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<GuildPrune> { + #[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) + } + + /// Retrieves the guild's webhooks. + /// + /// **Note**: Requires the [Manage Webhooks] permission. + /// + /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html + #[inline] + pub fn get_webhooks(&self) -> Result<Vec<Webhook>> { + self.id.get_webhooks() + } + + /// Returns the formatted URL of the guild's icon, if one exists. + pub fn icon_url(&self) -> Option<String> { + self.icon.as_ref().map(|icon| + format!(cdn!("/icons/{}/{}.webp"), self.id, icon)) + } + + /// 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() > LARGE_THRESHOLD as usize + } + + /// 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<U: Into<UserId>>(&self, user_id: U) -> Result<()> { + self.id.kick(user_id) + } + + /// Leaves the guild. + #[inline] + pub fn leave(&self) -> Result<PartialGuild> { + 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<C, U>(&self, user_id: U, channel_id: C) -> Result<()> + where C: Into<ChannelId>, U: Into<UserId> { + self.id.move_member(user_id, channel_id) + } + + /// Calculate a [`User`]'s permissions in a given channel in the guild. + /// + /// [`User`]: struct.User.html + pub fn permissions_for<C, U>(&self, channel_id: C, user_id: U) + -> Permissions where C: Into<ChannelId>, U: Into<UserId> { + use super::permissions::*; + + let user_id = user_id.into(); + + // The owner has all permissions in all cases. + if user_id == self.owner_id { + return Permissions::all(); + } + + let channel_id = channel_id.into(); + + // Start by retrieving the @everyone role's permissions. + let everyone = match self.roles.get(&RoleId(self.id.0)) { + Some(everyone) => everyone, + None => { + error!("(╯°□°)╯︵ ┻━┻ @everyone role ({}) missing in '{}'", + self.id, + self.name); + + return Permissions::empty(); + }, + }; + + // Create a base set of permissions, starting with `@everyone`s. + let mut permissions = everyone.permissions; + + let member = match self.members.get(&user_id) { + Some(member) => member, + None => return everyone.permissions, + }; + + for &role in &member.roles { + if let Some(role) = self.roles.get(&role) { + permissions |= role.permissions; + } else { + warn!("(╯°□°)╯︵ ┻━┻ {} on {} has non-existent role {:?}", + member.user.read().unwrap().id, + self.id, + role); + } + } + + // Administrators have all permissions in any channel. + if permissions.contains(ADMINISTRATOR) { + return Permissions::all(); + } + + if let Some(channel) = self.channels.get(&channel_id) { + let channel = channel.read().unwrap(); + + // If this is a text channel, then throw out voice permissions. + if channel.kind == ChannelType::Text { + permissions &= !(CONNECT | SPEAK | MUTE_MEMBERS | + DEAFEN_MEMBERS | MOVE_MEMBERS | USE_VAD); + } + + // Apply the permission overwrites for the channel for each of the + // overwrites that - first - applies to the member's roles, and then + // the member itself. + // + // First apply the denied permission overwrites for each, then apply + // the allowed. + + // Roles + for overwrite in &channel.permission_overwrites { + if let PermissionOverwriteType::Role(role) = overwrite.kind { + if !member.roles.contains(&role) || role.0 == self.id.0 { + continue; + } + + permissions = (permissions & !overwrite.deny) | overwrite.allow; + } + } + + // Member + for overwrite in &channel.permission_overwrites { + if PermissionOverwriteType::Member(user_id) != overwrite.kind { + continue; + } + + permissions = (permissions & !overwrite.deny) | overwrite.allow; + } + } else { + warn!("(╯°□°)╯︵ ┻━┻ Guild {} does not contain channel {}", + self.id, + channel_id); + } + + // The default channel is always readable. + if channel_id.0 == self.id.0 { + permissions |= READ_MESSAGES; + } + + // No SEND_MESSAGES => no message-sending-related actions + // If the member does not have the `SEND_MESSAGES` permission, then + // throw out message-able permissions. + if !permissions.contains(SEND_MESSAGES) { + permissions &= !(SEND_TTS_MESSAGES | + MENTION_EVERYONE | + EMBED_LINKS | + ATTACH_FILES); + } + + // If the member does not have the `READ_MESSAGES` permission, then + // throw out actionable permissions. + if !permissions.contains(READ_MESSAGES) { + permissions &= KICK_MEMBERS | BAN_MEMBERS | ADMINISTRATOR | + MANAGE_GUILD | CHANGE_NICKNAME | MANAGE_NICKNAMES; + } + + permissions + } + + /// 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<F: FnOnce(Search) -> Search>(&self, f: F) -> Result<SearchResult> { + #[cfg(feature="cache")] + { + if CACHE.read().unwrap().user.bot { + return Err(Error::Client(ClientError::InvalidOperationAsBot)); + } + } + + self.id.search(f) + } + + /// 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 + pub fn search_channels<F>(&self, channel_ids: &[ChannelId], f: F) + -> Result<SearchResult> 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) + } + + /// Returns the formatted URL of the guild's splash image, if one exists. + pub fn splash_url(&self) -> Option<String> { + 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<I: Into<IntegrationId>>(&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. + /// + /// **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 start_prune(&self, days: u16) -> Result<GuildPrune> { + #[cfg(feature="cache")] + { + let req = permissions::KICK_MEMBERS; + + if !self.has_perms(req)? { + return Err(Error::Client(ClientError::InvalidPermissions(req))); + } + } + + self.id.start_prune(days) + } + + /// Unbans the given [`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 + pub fn unban<U: Into<UserId>>(&self, user_id: U) -> Result<()> { + #[cfg(feature="cache")] + { + let req = permissions::BAN_MEMBERS; + + if !self.has_perms(req)? { + return Err(Error::Client(ClientError::InvalidPermissions(req))); + } + } + + self.id.unban(user_id) + } +} + +impl GuildInfo { + /// Returns the formatted URL of the guild's icon, if the guild has an icon. + pub fn icon_url(&self) -> Option<String> { + self.icon.as_ref().map(|icon| + format!(cdn!("/icons/{}/{}.webp"), self.id, icon)) + } +} + +impl From<PartialGuild> for GuildContainer { + fn from(guild: PartialGuild) -> GuildContainer { + GuildContainer::Guild(guild) + } +} + +impl From<GuildId> for GuildContainer { + fn from(guild_id: GuildId) -> GuildContainer { + GuildContainer::Id(guild_id) + } +} + +impl From<u64> for GuildContainer { + fn from(id: u64) -> GuildContainer { + GuildContainer::Id(GuildId(id)) + } +} + +impl InviteGuild { + /// Returns the formatted URL of the guild's splash image, if one exists. + pub fn splash_url(&self) -> Option<String> { + self.icon.as_ref().map(|icon| + format!(cdn!("/splashes/{}/{}.webp"), self.id, icon)) + } +} + +impl PossibleGuild<Guild> { + #[doc(hidden)] + pub fn decode(value: Value) -> Result<Self> { + let mut value = into_map(value)?; + if remove(&mut value, "unavailable").ok().and_then(|v| v.as_bool()).unwrap_or(false) { + remove(&mut value, "id").and_then(GuildId::decode).map(PossibleGuild::Offline) + } else { + Guild::decode(Value::Object(value)).map(PossibleGuild::Online) + } + } + + /// Retrieves the Id of the inner [`Guild`]. + /// + /// [`Guild`]: struct.Guild.html + pub fn id(&self) -> GuildId { + match *self { + PossibleGuild::Offline(guild_id) => guild_id, + PossibleGuild::Online(ref live_guild) => live_guild.id, + } + } +} + +impl PossibleGuild<PartialGuild> { + #[doc(hidden)] + pub fn decode(value: Value) -> Result<Self> { + let mut value = into_map(value)?; + if remove(&mut value, "unavailable").ok().and_then(|v| v.as_bool()).unwrap_or(false) { + remove(&mut value, "id").and_then(GuildId::decode).map(PossibleGuild::Offline) + } else { + PartialGuild::decode(Value::Object(value)).map(PossibleGuild::Online) + } + } + + /// Retrieves the Id of the inner [`Guild`]. + /// + /// [`Guild`]: struct.Guild.html + pub fn id(&self) -> GuildId { + match *self { + PossibleGuild::Offline(id) => id, + PossibleGuild::Online(ref live_guild) => live_guild.id, + } + } +} diff --git a/src/model/guild/partial_guild.rs b/src/model/guild/partial_guild.rs new file mode 100644 index 0000000..e5d463e --- /dev/null +++ b/src/model/guild/partial_guild.rs @@ -0,0 +1,482 @@ +use ::client::CACHE; +use ::model::*; +use ::utils::builder::{EditGuild, EditMember, EditRole, Search}; + +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<U: Into<UserId>>(&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, 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`]: ../client/rest/fn.create_channel.html + /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html + #[inline] + pub fn create_channel(&self, name: &str, kind: ChannelType) -> Result<GuildChannel> { + 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<Emoji> { + 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<I>(&self, integration_id: I, kind: &str) -> Result<()> + where I: Into<IntegrationId> { + 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/constant.MANAGE_ROLES.html + #[inline] + pub fn create_role<F: FnOnce(EditRole) -> EditRole>(&self, f: F) -> Result<Role> { + 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<PartialGuild> { + 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<E: Into<EmojiId>>(&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<I: Into<IntegrationId>>(&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<R: Into<RoleId>>(&self, role_id: R) -> Result<()> { + self.id.delete_role(role_id) + } + + /// Edits the current guild with new data where specified. + /// + /// **Note**: Requires the current user to have the [Manage Guild] + /// permission. + /// + /// [`Context::edit_guild`]: ../client/struct.Context.html#method.edit_guild + /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + pub fn edit<F>(&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<E: Into<EmojiId>>(&self, emoji_id: E, name: &str) -> Result<Emoji> { + 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<F, U>(&self, user_id: U, f: F) -> Result<()> + where F: FnOnce(EditMember) -> EditMember, U: Into<UserId> { + 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<G: Into<GuildId>>(guild_id: G) -> Result<PartialGuild> { + guild_id.into().get() + } + + /// Gets a list of the guild's bans. + /// + /// Requires the [Ban Members] permission. + /// + /// [Ban Members]: permissions/constant.BAN_MEMBERS.html + #[inline] + pub fn get_bans(&self) -> Result<Vec<Ban>> { + 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<HashMap<ChannelId, GuildChannel>> { + 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<E: Into<EmojiId>>(&self, emoji_id: E) -> Result<Emoji> { + 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<Vec<Emoji>> { + 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<Vec<Integration>> { + self.id.get_integrations() + } + + /// Gets all of the guild's invites. + /// + /// Requires the [Manage Guild] permission. + /// + /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + #[inline] + pub fn get_invites(&self) -> Result<Vec<RichInvite>> { + self.id.get_invites() + } + + /// Gets a user's [`Member`] for the guild by Id. + /// + /// [`Guild`]: struct.Guild.html + /// [`Member`]: struct.Member.html + pub fn get_member<U: Into<UserId>>(&self, user_id: U) -> Result<Member> { + 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 + pub fn get_members<U>(&self, limit: Option<u64>, after: Option<U>) + -> Result<Vec<Member>> where U: Into<UserId> { + self.id.get_members(limit, after) + } + + /// 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 + #[inline] + pub fn get_prune_count(&self, days: u16) -> Result<GuildPrune> { + self.id.get_prune_count(days) + } + + /// Retrieves the guild's webhooks. + /// + /// **Note**: Requires the [Manage Webhooks] permission. + /// + /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html + #[inline] + pub fn get_webhooks(&self) -> Result<Vec<Webhook>> { + self.id.get_webhooks() + } + + /// 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<U: Into<UserId>>(&self, user_id: U) -> Result<()> { + self.id.kick(user_id) + } + + /// Returns a formatted URL of the guild's icon, if the guild has an icon. + pub fn icon_url(&self) -> Option<String> { + self.icon.as_ref().map(|icon| + format!(cdn!("/icons/{}/{}.webp"), self.id, icon)) + } + + /// Leaves the guild. + #[inline] + pub fn leave(&self) -> Result<PartialGuild> { + 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<C, U>(&self, user_id: U, channel_id: C) -> Result<()> + where C: Into<ChannelId>, U: Into<UserId> { + 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<F: FnOnce(Search) -> Search>(&self, f: F) -> Result<SearchResult> { + #[cfg(feature="cache")] + { + if CACHE.read().unwrap().user.bot { + return Err(Error::Client(ClientError::InvalidOperationAsBot)); + } + } + + self.id.search(f) + } + + /// 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 + pub fn search_channels<F>(&self, channel_ids: &[ChannelId], f: F) + -> Result<SearchResult> 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) + } + + /// Returns the formatted URL of the guild's splash image, if one exists. + pub fn splash_url(&self) -> Option<String> { + 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<I: Into<IntegrationId>>(&self, integration_id: I) -> Result<()> { + self.id.start_integration_sync(integration_id) + } + + /// 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<U: Into<UserId>>(&self, user_id: U) -> Result<()> { + self.id.unban(user_id) + } +} diff --git a/src/model/guild/role.rs b/src/model/guild/role.rs new file mode 100644 index 0000000..79ffdf8 --- /dev/null +++ b/src/model/guild/role.rs @@ -0,0 +1,151 @@ +use std::cmp::Ordering; +use std::fmt::{Display, Formatter, Result as FmtResult}; +use ::client::{CACHE, rest}; +use ::internal::prelude::*; +use ::model::*; +use ::utils::builder::EditRole; + +impl Role { + /// Deletes the role. + /// + /// **Note** Requires the [Manage Roles] permission. + /// + /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + #[cfg(feature="cache")] + #[inline] + pub fn delete(&self) -> Result<()> { + rest::delete_role(self.find_guild()?.0, self.id.0) + } + + /// Edits a [`Role`], optionally setting its new fields. + /// + /// Requires the [Manage Roles] permission. + /// + /// # Examples + /// + /// Make a role hoisted: + /// + /// ```rust,ignore + /// // assuming a `guild` and `role_id` have been bound + // + /// guild.edit_role(role_id, |r| r.hoist(true)); + /// ``` + /// + /// [`Role`]: struct.Role.html + /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + #[cfg(feature="cache")] + pub fn edit_role<F: FnOnce(EditRole) -> EditRole>(&self, f: F) -> Result<Role> { + 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 + /// + /// Returns a [`ClientError::GuildNotFound`] if a guild is not in the cache + /// that contains the role. + /// + /// [`ClientError::GuildNotFound`]: ../client/enum.ClientError.html#variant.GuildNotFound + #[cfg(feature="cache")] + pub fn find_guild(&self) -> Result<GuildId> { + for guild in CACHE.read().unwrap().guilds.values() { + let guild = guild.read().unwrap(); + + if guild.roles.contains_key(&RoleId(self.id.0)) { + return Ok(guild.id); + } + } + + Err(Error::Client(ClientError::GuildNotFound)) + } + + /// Check that the role has the given permission. + #[inline] + pub fn has_permission(&self, permission: Permissions) -> bool { + self.permissions.contains(permission) + } + + /// Checks whether the role has all of the given permissions. + /// + /// The 'precise' argument is used to check if the role's permissions are + /// precisely equivalent to the given permissions. If you need only check + /// that the role has at least the given permissions, pass `false`. + pub fn has_permissions(&self, permissions: Permissions, precise: bool) + -> bool { + if precise { + self.permissions == permissions + } else { + self.permissions.contains(permissions) + } + } +} + +impl Display for Role { + /// Format a mention for the role, pinging its members. + // This is in the format of: `<@&ROLE_ID>`. + fn fmt(&self, f: &mut Formatter) -> FmtResult { + Display::fmt(&self.mention(), f) + } +} + +impl Eq for Role {} + +impl Ord for Role { + fn cmp(&self, other: &Role) -> Ordering { + if self.position == other.position { + self.id.cmp(&other.id) + } else { + self.position.cmp(&other.position) + } + } +} + +impl PartialEq for Role { + fn eq(&self, other: &Role) -> bool { + self.id == other.id + } +} + +impl PartialOrd for Role { + fn partial_cmp(&self, other: &Role) -> Option<Ordering> { + Some(self.cmp(other)) + } +} + +impl RoleId { + /// Search the cache for the role. + #[cfg(feature="cache")] + pub fn find(&self) -> Option<Role> { + let cache = CACHE.read().unwrap(); + + for guild in cache.guilds.values() { + let guild = guild.read().unwrap(); + + if !guild.roles.contains_key(self) { + continue; + } + + if let Some(role) = guild.roles.get(self) { + return Some(role.clone()); + } + } + + None + } +} + +impl Display for RoleId { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + Display::fmt(&self.0, f) + } +} + +impl From<Role> for RoleId { + /// Gets the Id of a role. + fn from(role: Role) -> RoleId { + role.id + } +} diff --git a/src/model/mod.rs b/src/model/mod.rs index c871f90..ad70076 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -19,7 +19,6 @@ mod utils; pub mod event; pub mod permissions; - mod channel; mod gateway; mod guild; |