diff options
| author | Zeyla Hellyer <[email protected]> | 2018-05-28 16:34:38 -0700 |
|---|---|---|
| committer | Zeyla Hellyer <[email protected]> | 2018-05-28 16:34:38 -0700 |
| commit | 6b5f3b98084b86b00e3f7e78b5eb9512e75e78a0 (patch) | |
| tree | 4011d56b63d88999eb8169e332c54f3eafe972ae /src/model | |
| parent | Make Message Builder use &mut self instead of self (diff) | |
| parent | Futures shard manager #298 (WIP) (#300) (diff) | |
| download | serenity-6b5f3b98084b86b00e3f7e78b5eb9512e75e78a0.tar.xz serenity-6b5f3b98084b86b00e3f7e78b5eb9512e75e78a0.zip | |
Merge branch 'futures' into v0.6.x
Diffstat (limited to 'src/model')
| -rw-r--r-- | src/model/channel/attachment.rs | 88 | ||||
| -rw-r--r-- | src/model/channel/channel_category.rs | 95 | ||||
| -rw-r--r-- | src/model/channel/channel_id.rs | 580 | ||||
| -rw-r--r-- | src/model/channel/embed.rs | 7 | ||||
| -rw-r--r-- | src/model/channel/group.rs | 273 | ||||
| -rw-r--r-- | src/model/channel/guild_channel.rs | 568 | ||||
| -rw-r--r-- | src/model/channel/message.rs | 421 | ||||
| -rw-r--r-- | src/model/channel/mod.rs | 333 | ||||
| -rw-r--r-- | src/model/channel/private_channel.rs | 266 | ||||
| -rw-r--r-- | src/model/channel/reaction.rs | 135 | ||||
| -rw-r--r-- | src/model/error.rs | 4 | ||||
| -rw-r--r-- | src/model/event.rs | 628 | ||||
| -rw-r--r-- | src/model/gateway.rs | 23 | ||||
| -rw-r--r-- | src/model/guild/emoji.rs | 142 | ||||
| -rw-r--r-- | src/model/guild/guild_id.rs | 526 | ||||
| -rw-r--r-- | src/model/guild/member.rs | 407 | ||||
| -rw-r--r-- | src/model/guild/mod.rs | 963 | ||||
| -rw-r--r-- | src/model/guild/partial_guild.rs | 391 | ||||
| -rw-r--r-- | src/model/guild/role.rs | 89 | ||||
| -rw-r--r-- | src/model/invite.rs | 136 | ||||
| -rw-r--r-- | src/model/misc.rs | 73 | ||||
| -rw-r--r-- | src/model/mod.rs | 14 | ||||
| -rw-r--r-- | src/model/permissions.rs | 1 | ||||
| -rw-r--r-- | src/model/user.rs | 498 | ||||
| -rw-r--r-- | src/model/utils.rs | 142 | ||||
| -rw-r--r-- | src/model/webhook.rs | 200 |
26 files changed, 335 insertions, 6668 deletions
diff --git a/src/model/channel/attachment.rs b/src/model/channel/attachment.rs index 0530b6f..8e3f149 100644 --- a/src/model/channel/attachment.rs +++ b/src/model/channel/attachment.rs @@ -1,10 +1,3 @@ -#[cfg(feature = "model")] -use hyper::Client as HyperClient; -#[cfg(feature = "model")] -use internal::prelude::*; -#[cfg(feature = "model")] -use std::io::Read; - /// A file uploaded with a message. Not to be confused with [`Embed`]s. /// /// [`Embed`]: struct.Embed.html @@ -27,7 +20,6 @@ pub struct Attachment { pub width: Option<u64>, } -#[cfg(feature = "model")] impl Attachment { /// If this attachment is an image, then a tuple of the width and height /// in pixels is returned. @@ -35,84 +27,4 @@ impl Attachment { self.width .and_then(|width| self.height.map(|height| (width, height))) } - - /// Downloads the attachment, returning back a vector of bytes. - /// - /// # Examples - /// - /// Download all of the attachments associated with a [`Message`]: - /// - /// ```rust,no_run - /// use serenity::model::prelude::*; - /// use serenity::prelude::*; - /// use std::env; - /// use std::fs::File; - /// use std::io::Write; - /// use std::path::Path; - /// - /// struct Handler; - /// - /// impl EventHandler for Handler { - /// fn message(&self, _: Context, 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)); - /// } - /// } - /// - /// fn ready(&self, _: Context, ready: Ready) { - /// println!("{} is connected!", ready.user.name); - /// } - /// } - /// let token = env::var("DISCORD_TOKEN").expect("token in environment"); - /// let mut client = Client::new(&token, Handler).unwrap(); - /// - /// client.start().unwrap(); - /// ``` - /// - /// # 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 = request_client!(); - 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_category.rs b/src/model/channel/channel_category.rs index 7a49c7e..0fd1a69 100644 --- a/src/model/channel/channel_category.rs +++ b/src/model/channel/channel_category.rs @@ -1,11 +1,7 @@ use model::prelude::*; -#[cfg(all(feature = "builder", feature = "model"))] -use builder::EditChannel; -#[cfg(all(feature = "builder", feature = "model"))] -use http; -#[cfg(all(feature = "model", feature = "utils"))] -use utils::{self as serenity_utils, VecMap}; +#[cfg(feature = "utils")] +use utils as serenity_utils; /// A category of [`GuildChannel`]s. /// @@ -37,94 +33,7 @@ pub struct ChannelCategory { pub permission_overwrites: Vec<PermissionOverwrite>, } -#[cfg(feature = "model")] impl ChannelCategory { - /// Adds a permission overwrite to the category's channels. - #[inline] - pub fn create_permission(&self, target: &PermissionOverwrite) -> Result<()> { - self.id.create_permission(target) - } - - /// Deletes all permission overrides in the category from the channels. - /// - /// **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 this category. - #[inline] - pub fn delete(&self) -> Result<()> { - #[cfg(feature = "cache")] - { - let req = Permissions::MANAGE_CHANNELS; - - if !utils::user_has_perms(self.id, req)? { - return Err(Error::Model(ModelError::InvalidPermissions(req))); - } - } - - self.id.delete().map(|_| ()) - } - - /// Modifies the category'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 - /// category.edit(|c| c.name("test").bitrate(86400)); - /// ``` - #[cfg(all(feature = "builder", feature = "model", feature = "utils"))] - 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::Model(ModelError::InvalidPermissions(req))); - } - } - - let mut map = VecMap::new(); - map.insert("name", Value::String(self.name.clone())); - map.insert("position", Value::Number(Number::from(self.position))); - map.insert("type", Value::String(self.kind.name().to_string())); - - let map = serenity_utils::vecmap_to_json_map(f(EditChannel(map)).0); - - http::edit_channel(self.id.0, &map).map(|channel| { - let GuildChannel { - id, - category_id, - permission_overwrites, - nsfw, - name, - position, - kind, - .. - } = channel; - - *self = ChannelCategory { - id, - category_id, - permission_overwrites, - nsfw, - name, - position, - kind, - }; - () - }) - } - #[cfg(feature = "utils")] #[inline] pub fn is_nsfw(&self) -> bool { diff --git a/src/model/channel/channel_id.rs b/src/model/channel/channel_id.rs index f4336fc..fc6d748 100644 --- a/src/model/channel/channel_id.rs +++ b/src/model/channel/channel_id.rs @@ -1,577 +1,13 @@ -use internal::RwLockExt; use model::prelude::*; -#[cfg(feature = "model")] -use std::borrow::Cow; -#[cfg(feature = "model")] -use std::fmt::Write as FmtWrite; -#[cfg(feature = "model")] -use builder::{ - CreateMessage, - EditChannel, - EditMessage, - GetMessages -}; -#[cfg(all(feature = "cache", feature = "model"))] -use CACHE; -#[cfg(feature = "model")] -use http::{self, AttachmentType}; -#[cfg(feature = "model")] -use utils; - -#[cfg(feature = "model")] -impl ChannelId { - /// 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<()> { http::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 = json!({ - "allow": target.allow.bits(), - "deny": target.deny.bits(), - "id": id, - "type": kind, - }); - - http::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> { - http::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> { http::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<()> { - http::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**: Messages that are older than 2 weeks can't be deleted using - /// this method. - /// - /// # Errors - /// - /// Returns [`ModelError::BulkDeleteAmount`] if an attempt was made to - /// delete either 0 or more than 100 messages. - /// - /// [`Channel::delete_messages`]: enum.Channel.html#method.delete_messages - /// [`ModelError::BulkDeleteAmount`]: ../enum.ModelError.html#variant.BulkDeleteAmount - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html - pub fn delete_messages<T: AsRef<MessageId>, It: IntoIterator<Item=T>>(&self, message_ids: It) -> Result<()> { - let ids = message_ids - .into_iter() - .map(|message_id| message_id.as_ref().0) - .collect::<Vec<u64>>(); - let len = ids.len(); - - if len == 0 || len > 100 { - return Err(Error::Model(ModelError::BulkDeleteAmount)); - } else if ids.len() == 1 { - self.delete_message(ids[0]) - } else { - let map = json!({ "messages": ids }); - - http::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<()> { - http::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> { - http::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)); - /// ``` - /// - /// [`Channel`]: enum.Channel.html - /// [Manage Channel]: permissions/constant.MANAGE_CHANNELS.html - #[cfg(feature = "utils")] - #[inline] - pub fn edit<F: FnOnce(EditChannel) -> EditChannel>(&self, f: F) -> Result<GuildChannel> { - let map = utils::vecmap_to_json_map(f(EditChannel::default()).0); - - http::edit_channel(self.0, &map) - } - - /// Edits a [`Message`] in the channel given its Id. - /// - /// Message editing preserves all unchanged message data. - /// - /// Refer to the documentation for [`EditMessage`] for more information - /// regarding message restrictions and requirements. - /// - /// **Note**: Requires that the current user be the author of the message. - /// - /// # Errors - /// - /// Returns a [`ModelError::MessageTooLong`] if the content of the message - /// is over the [`the limit`], containing the number of unicode code points - /// over the limit. - /// - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - /// [`EditMessage`]: ../builder/struct.EditMessage.html - /// [`Message`]: struct.Message.html - /// [`the limit`]: ../builder/struct.EditMessage.html#method.content - #[cfg(feature = "utils")] - pub fn edit_message<F, M>(&self, message_id: M, f: F) -> Result<Message> - where F: FnOnce(EditMessage) -> EditMessage, M: Into<MessageId> { - let msg = f(EditMessage::default()); - - if let Some(content) = msg.0.get(&"content") { - if let Value::String(ref content) = *content { - if let Some(length_over) = Message::overflow_length(content) { - return Err(Error::Model(ModelError::MessageTooLong(length_over))); - } - } - } - - let map = utils::vecmap_to_json_map(msg.0); - - http::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().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().channel(*self) { - return Ok(channel); - } - } - - http::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 invites(&self) -> Result<Vec<RichInvite>> { http::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 message<M: Into<MessageId>>(&self, message_id: M) -> Result<Message> { - http::get_message(self.0, message_id.into().0) - .map(|mut msg| { - msg.transform_content(); - - msg - }) - } - - /// Gets messages from the channel. - /// - /// Refer to [`Channel::messages`] for more information. - /// - /// Requires the [Read Message History] permission. - /// - /// [`Channel::messages`]: enum.Channel.html#method.messages - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html - pub fn 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)?; - } - - http::get_messages(self.0, &query).map(|msgs| { - msgs.into_iter() - .map(|mut msg| { - msg.transform_content(); - - msg - }) - .collect::<Vec<Message>>() - }) - } - - /// Returns the name of whatever channel this id holds. - #[cfg(feature = "model")] - pub fn name(&self) -> Option<String> { - use self::Channel::*; - - let finding = feature_cache! {{ - Some(self.find()) - } else { - None - }}; - - let channel = if let Some(Some(c)) = finding { - c - } else { - return None; - }; - - Some(match channel { - Guild(channel) => channel.read().name().to_string(), - Group(channel) => match channel.read().name() { - Cow::Borrowed(name) => name.to_string(), - Cow::Owned(name) => name, - }, - Category(category) => category.read().name().to_string(), - Private(channel) => channel.read().name(), - }) - } - - /// Pins a [`Message`] to the channel. - /// - /// [`Message`]: struct.Message.html - #[inline] - pub fn pin<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { - http::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>> { http::get_pins(self.0) } - - /// Gets the list of [`User`]s who have reacted to a [`Message`] with a - /// certain [`Emoji`]. - /// - /// Refer to [`Channel::reaction_users`] for more information. - /// - /// **Note**: Requires the [Read Message History] permission. - /// - /// [`Channel::reaction_users`]: enum.Channel.html#method.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 reaction_users<M, R, U>(&self, - message_id: M, - reaction_type: R, - limit: Option<u8>, - after: U, - ) -> Result<Vec<User>> where M: Into<MessageId>, - R: Into<ReactionType>, - U: Into<Option<UserId>> { - let limit = limit.map_or(50, |x| if x > 100 { 100 } else { x }); - - http::get_reaction_users( - self.0, - message_id.into().0, - &reaction_type.into(), - limit, - after.into().map(|x| x.0), - ) - } - - /// Sends a message with just the given message content in the channel. - /// - /// # Errors - /// - /// Returns a [`ModelError::MessageTooLong`] if the content of the message - /// is over the above limit, containing the number of unicode code points - /// over the limit. - /// - /// [`ChannelId`]: struct.ChannelId.html - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - #[inline] - pub fn say<D: ::std::fmt::Display>(&self, content: D) -> Result<Message> { - self.send_message(|mut m| { - m.content(content); - - m - }) - } - - /// Sends a file along with optional message contents. The filename _must_ - /// be specified. - /// - /// Message contents may be passed by using the [`CreateMessage::content`] - /// method. - /// - /// The [Attach Files] and [Send Messages] permissions are required. - /// - /// **Note**: Message contents must be under 2000 unicode code points. - /// - /// # Examples - /// - /// Send files with the paths `/path/to/file.jpg` and `/path/to/file2.jpg`: - /// - /// ```rust,no_run - /// use serenity::model::id::ChannelId; - /// - /// let channel_id = ChannelId(7); - /// - /// let paths = vec!["/path/to/file.jpg", "path/to/file2.jpg"]; - /// - /// let _ = channel_id.send_files(paths, |mut m| { - /// m.content("a file"); - /// - /// m - /// }); - /// ``` - /// - /// Send files using `File`: - /// - /// ```rust,no_run - /// use serenity::model::id::ChannelId; - /// use std::fs::File; - /// - /// let channel_id = ChannelId(7); - /// - /// let f1 = File::open("my_file.jpg").unwrap(); - /// let f2 = File::open("my_file2.jpg").unwrap(); - /// - /// let files = vec![(&f1, "my_file.jpg"), (&f2, "my_file2.jpg")]; - /// - /// let _ = channel_id.send_files(files, |mut m| { - /// m.content("a file"); - /// - /// m - /// }); - /// ``` - /// - /// # 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. - /// - /// Returns an - /// [`HttpError::InvalidRequest(PayloadTooLarge)`][`HttpError::InvalidRequest`] - /// if the file is too large to send. - /// - /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - /// [`HttpError::InvalidRequest`]: ../http/enum.HttpError.html#variant.InvalidRequest - /// [`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 - #[cfg(feature = "utils")] - pub fn send_files<'a, F, T, It: IntoIterator<Item=T>>(&self, files: It, f: F) -> Result<Message> - where F: FnOnce(CreateMessage) -> CreateMessage, T: Into<AttachmentType<'a>> { - let mut msg = f(CreateMessage::default()); - - if let Some(content) = msg.0.get(&"content") { - if let Value::String(ref content) = *content { - if let Some(length_over) = Message::overflow_length(content) { - return Err(Error::Model(ModelError::MessageTooLong(length_over))); - } - } - } - - if let Some(e) = msg.0.remove(&"embed") { - msg.0.insert("payload_json", json!({ "embed": e })); - } - - let map = utils::vecmap_to_json_map(msg.0); - http::send_files(self.0, files, 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 [`ModelError::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 - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - /// [`CreateMessage`]: ../builder/struct.CreateMessage.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html - #[cfg(feature = "utils")] - pub fn send_message<F>(&self, f: F) -> Result<Message> - where F: FnOnce(CreateMessage) -> CreateMessage { - let mut msg = f(CreateMessage::default()); - - if !msg.2.is_empty() { - if let Some(e) = msg.0.remove(&"embed") { - msg.0.insert("payload_json", json!({ "embed": e })); - } - } - - let map = utils::vecmap_to_json_map(msg.0); - - Message::check_content_length(&map)?; - Message::check_embed_length(&map)?; - - let message = if msg.2.is_empty() { - http::send_message(self.0, &Value::Object(map))? - } else { - http::send_files(self.0, msg.2, map)? - }; - - if let Some(reactions) = msg.1 { - for reaction in reactions { - self.create_reaction(message.id, reaction)?; - } - } - - Ok(message) - } - - /// 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<()> { - http::unpin_message(self.0, message_id.into().0) - } - - /// Retrieves the channel's webhooks. - /// - /// **Note**: Requires the [Manage Webhooks] permission. - /// - /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html - #[inline] - pub fn webhooks(&self) -> Result<Vec<Webhook>> { http::get_channel_webhooks(self.0) } -} - impl From<Channel> for ChannelId { /// Gets the Id of a `Channel`. fn from(channel: Channel) -> ChannelId { match channel { - Channel::Group(group) => group.with(|g| g.channel_id), - Channel::Guild(ch) => ch.with(|c| c.id), - Channel::Private(ch) => ch.with(|c| c.id), - Channel::Category(ch) => ch.with(|c| c.id), + Channel::Group(group) => group.borrow().channel_id, + Channel::Guild(ch) => ch.borrow().id, + Channel::Private(ch) => ch.borrow().id, + Channel::Category(ch) => ch.borrow().id, } } } @@ -580,10 +16,10 @@ impl<'a> From<&'a Channel> for ChannelId { /// Gets the Id of a `Channel`. fn from(channel: &Channel) -> ChannelId { match *channel { - Channel::Group(ref group) => group.with(|g| g.channel_id), - Channel::Guild(ref ch) => ch.with(|c| c.id), - Channel::Private(ref ch) => ch.with(|c| c.id), - Channel::Category(ref ch) => ch.with(|c| c.id), + Channel::Group(ref group) => group.borrow().channel_id, + Channel::Guild(ref ch) => ch.borrow().id, + Channel::Private(ref ch) => ch.borrow().id, + Channel::Category(ref ch) => ch.borrow().id, } } } diff --git a/src/model/channel/embed.rs b/src/model/channel/embed.rs index 8792d18..4baf622 100644 --- a/src/model/channel/embed.rs +++ b/src/model/channel/embed.rs @@ -1,11 +1,9 @@ -#[cfg(feature = "model")] use builder::CreateEmbed; -#[cfg(feature = "model")] use internal::prelude::*; +use utils; + #[cfg(feature = "utils")] use utils::Colour; -#[cfg(feature = "model")] -use utils; /// Represents a rich embed which allows using richer markdown, multiple fields /// and more. This was heavily inspired by [slack's attachments]. @@ -65,7 +63,6 @@ pub struct Embed { pub video: Option<EmbedVideo>, } -#[cfg(feature = "model")] impl Embed { /// Creates a fake Embed, giving back a `serde_json` map. /// diff --git a/src/model/channel/group.rs b/src/model/channel/group.rs index 2fc062b..db15e8f 100644 --- a/src/model/channel/group.rs +++ b/src/model/channel/group.rs @@ -1,20 +1,9 @@ use chrono::{DateTime, FixedOffset}; use model::prelude::*; - -#[cfg(feature = "model")] -use builder::{ - CreateMessage, - EditMessage, - GetMessages -}; -#[cfg(feature = "model")] -use http::{self, AttachmentType}; -#[cfg(feature = "model")] -use internal::RwLockExt; -#[cfg(feature = "model")] use std::borrow::Cow; -#[cfg(feature = "model")] +use std::cell::RefCell; use std::fmt::Write as FmtWrite; +use std::rc::Rc; /// A group channel - potentially including other [`User`]s - separate from a /// [`Guild`]. @@ -39,129 +28,10 @@ pub struct Group { /// A map of the group's recipients. #[serde(deserialize_with = "deserialize_users", serialize_with = "serialize_users")] - pub recipients: HashMap<UserId, Arc<RwLock<User>>>, + pub recipients: HashMap<UserId, Rc<RefCell<User>>>, } -#[cfg(feature = "model")] impl Group { - /// Adds the given user to the group. If the user is already in the group, - /// then nothing is done. - /// - /// Refer to [`http::add_group_recipient`] for more information. - /// - /// **Note**: Groups have a limit of 10 recipients, including the current - /// user. - /// - /// [`http::add_group_recipient`]: ../http/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(()); - } - - http::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**: Messages that are older than 2 weeks can't be deleted using - /// this method. - /// - /// # Errors - /// - /// Returns [`ModelError::BulkDeleteAmount`] if an attempt was made to - /// delete either 0 or more than 100 messages. - /// - /// [`Channel::delete_messages`]: enum.Channel.html#method.delete_messages - /// [`ModelError::BulkDeleteAmount`]: ../enum.ModelError.html#variant.BulkDeleteAmount - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html - #[inline] - pub fn delete_messages<T: AsRef<MessageId>, It: IntoIterator<Item=T>>(&self, message_ids: It) -> 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 [`EditMessage`] for more information - /// regarding message restrictions and requirements. - /// - /// **Note**: Requires that the current user be the author of the message. - /// - /// # Errors - /// - /// Returns a [`ModelError::MessageTooLong`] if the content of the message - /// is over the [`the limit`], containing the number of unicode code points - /// over the limit. - /// - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - /// [`EditMessage`]: ../builder/struct.EditMessage.html - /// [`Message`]: struct.Message.html - /// [`the limit`]: ../builder/struct.EditMessage.html#method.content - #[inline] - pub fn edit_message<F, M>(&self, message_id: M, f: F) -> Result<Message> - where F: FnOnce(EditMessage) -> EditMessage, M: Into<MessageId> { - self.channel_id.edit_message(message_id, f) - } - /// 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| { @@ -180,31 +50,6 @@ impl Group { #[inline] pub fn is_nsfw(&self) -> bool { false } - /// Leaves the group. - #[inline] - pub fn leave(&self) -> Result<Group> { http::leave_group(self.channel_id.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 message<M: Into<MessageId>>(&self, message_id: M) -> Result<Message> { - self.channel_id.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 messages<F>(&self, f: F) -> Result<Vec<Message>> - where F: FnOnce(GetMessages) -> GetMessages { - self.channel_id.messages(f) - } - /// Generates a name for the group. /// /// If there are no recipients in the group, the name will be "Empty Group". @@ -215,122 +60,18 @@ impl Group { Some(ref name) => Cow::Borrowed(name), None => { let mut name = match self.recipients.values().nth(0) { - Some(recipient) => recipient.with(|c| c.name.clone()), + Some(recipient) => recipient.borrow().name.clone(), None => return Cow::Borrowed("Empty Group"), }; for recipient in self.recipients.values().skip(1) { - let _ = write!(name, ", {}", recipient.with(|r| r.name.clone())); + let recipient = recipient.borrow(); + + let _ = write!(name, ", {}", recipient.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() } - - /// Gets the list of [`User`]s who have reacted to a [`Message`] with a - /// certain [`Emoji`]. - /// - /// Refer to [`Channel::reaction_users`] for more information. - /// - /// **Note**: Requires the [Read Message History] permission. - /// - /// [`Channel::reaction_users`]: enum.Channel.html#method.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 reaction_users<M, R, U>( - &self, - message_id: M, - reaction_type: R, - limit: Option<u8>, - after: U, - ) -> Result<Vec<User>> where M: Into<MessageId>, - R: Into<ReactionType>, - U: Into<Option<UserId>> { - self.channel_id.reaction_users(message_id, reaction_type, limit, after) - } - - /// 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(()); - } - - http::remove_group_recipient(self.channel_id.0, user.0) - } - - /// Sends a message with just the given message content in the channel. - /// - /// # Errors - /// - /// Returns a [`ModelError::MessageTooLong`] if the content of the message - /// is over the above limit, containing the number of unicode code points - /// over the limit. - /// - /// [`ChannelId`]: ../model/id/struct.ChannelId.html - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - #[inline] - pub fn say(&self, content: &str) -> Result<Message> { self.channel_id.say(content) } - - /// Sends (a) file(s) along with optional message contents. - /// - /// Refer to [`ChannelId::send_files`] 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_files`]: struct.ChannelId.html#method.send_files - /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - /// [Attach Files]: permissions/constant.ATTACH_FILES.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html - #[inline] - pub fn send_files<'a, F, T, It: IntoIterator<Item=T>>(&self, files: It, f: F) -> Result<Message> - where F: FnOnce(CreateMessage) -> CreateMessage, T: Into<AttachmentType<'a>> { - self.channel_id.send_files(files, 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`]: ../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 index c6d9031..3c976d1 100644 --- a/src/model/channel/guild_channel.rs +++ b/src/model/channel/guild_channel.rs @@ -1,30 +1,9 @@ use chrono::{DateTime, FixedOffset}; use model::prelude::*; -#[cfg(all(feature = "cache", feature = "model"))] -use CACHE; -#[cfg(feature = "model")] -use builder::{ - CreateInvite, - CreateMessage, - EditChannel, - EditMessage, - GetMessages -}; -#[cfg(feature = "model")] -use http::{self, AttachmentType}; -#[cfg(all(feature = "cache", feature = "model"))] -use internal::prelude::*; -#[cfg(feature = "model")] -use std::fmt::{ - Display, - Formatter, - Result as FmtResult -}; -#[cfg(feature = "model")] -use std::mem; -#[cfg(all(feature = "model", feature = "utils"))] -use utils::{self as serenity_utils, VecMap}; +use std::fmt::{Display, Formatter, Result as FmtResult}; +#[cfg(feature = "utils")] +use utils as serenity_utils; /// Represents a guild's text or voice channel. Some methods are available only /// for voice channels and some are only available for text channels. @@ -89,300 +68,7 @@ pub struct GuildChannel { pub nsfw: bool, } -#[cfg(feature = "model")] impl GuildChannel { - /// 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 [`ModelError::InvalidPermissions`] if the current user does - /// not have the required permissions. - /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.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)); - /// ``` - #[cfg(feature = "utils")] - 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::Model(ModelError::InvalidPermissions(req))); - } - } - - let map = serenity_utils::vecmap_to_json_map(f(CreateInvite::default()).0); - - http::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,no_run - /// # use serenity::model::id::{ChannelId, UserId}; - /// # use std::error::Error; - /// # - /// # fn try_main() -> Result<(), Box<Error>> { - /// # let (channel_id, user_id) = (ChannelId(0), UserId(0)); - /// # - /// use serenity::model::channel::{ - /// PermissionOverwrite, - /// PermissionOverwriteType, - /// }; - /// use serenity::model::{ModelError, Permissions}; - /// use serenity::CACHE; - /// - /// 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(); - /// let channel = cache - /// .guild_channel(channel_id) - /// .ok_or(ModelError::ItemMissing)?; - /// - /// channel.read().create_permission(&overwrite)?; - /// # Ok(()) - /// # } - /// # - /// # fn main() { - /// # try_main().unwrap(); - /// # } - /// ``` - /// - /// 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,no_run - /// # use serenity::model::id::{ChannelId, UserId}; - /// # use std::error::Error; - /// # - /// # fn try_main() -> Result<(), Box<Error>> { - /// # let (channel_id, user_id) = (ChannelId(0), UserId(0)); - /// # - /// use serenity::model::channel::{ - /// PermissionOverwrite, - /// PermissionOverwriteType, - /// }; - /// use serenity::model::{ModelError, Permissions}; - /// use serenity::CACHE; - /// - /// 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(); - /// let channel = cache - /// .guild_channel(channel_id) - /// .ok_or(ModelError::ItemMissing)?; - /// - /// channel.read().create_permission(&overwrite)?; - /// # Ok(()) - /// # } - /// # - /// # fn main() { - /// # try_main().unwrap(); - /// # } - /// ``` - /// - /// [`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 Messages]: permissions/constant.SEND_MESSAGES.html - /// [Send TTS Messages]: permissions/constant.SEND_TTS_MESSAGES.html - #[inline] - pub fn create_permission(&self, target: &PermissionOverwrite) -> Result<()> { - self.id.create_permission(target) - } - - /// 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::Model(ModelError::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**: Messages that are older than 2 weeks can't be deleted using - /// this method. - /// - /// # Errors - /// - /// Returns [`ModelError::BulkDeleteAmount`] if an attempt was made to - /// delete either 0 or more than 100 messages. - /// - /// [`Channel::delete_messages`]: enum.Channel.html#method.delete_messages - /// [`ModelError::BulkDeleteAmount`]: ../enum.ModelError.html#variant.BulkDeleteAmount - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html - #[inline] - pub fn delete_messages<T: AsRef<MessageId>, It: IntoIterator<Item=T>>(&self, message_ids: It) -> 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)); - /// ``` - #[cfg(feature = "utils")] - 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::Model(ModelError::InvalidPermissions(req))); - } - } - - let mut map = VecMap::new(); - map.insert("name", Value::String(self.name.clone())); - map.insert("position", Value::Number(Number::from(self.position))); - map.insert("type", Value::String(self.kind.name().to_string())); - - let edited = serenity_utils::vecmap_to_json_map(f(EditChannel(map)).0); - - match http::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 [`EditMessage`] for more information - /// regarding message restrictions and requirements. - /// - /// **Note**: Requires that the current user be the author of the message. - /// - /// # Errors - /// - /// Returns a [`ModelError::MessageTooLong`] if the content of the message - /// is over the [`the limit`], containing the number of unicode code points - /// over the limit. - /// - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - /// [`EditMessage`]: ../builder/struct.EditMessage.html - /// [`Message`]: struct.Message.html - /// [`the limit`]: ../builder/struct.EditMessage.html#method.content - #[inline] - pub fn edit_message<F, M>(&self, message_id: M, f: F) -> Result<Message> - where F: FnOnce(EditMessage) -> EditMessage, M: Into<MessageId> { - self.id.edit_message(message_id, f) - } - - /// 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().guild(self.guild_id) } - - /// Gets all of the channel's invites. - /// - /// Requires the [Manage Channels] permission. - /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html - #[inline] - pub fn invites(&self) -> Result<Vec<RichInvite>> { self.id.invites() } - /// Determines if the channel is NSFW. /// /// Refer to [`utils::is_nsfw`] for more details. @@ -399,258 +85,10 @@ impl GuildChannel { self.kind == ChannelType::Text && (self.nsfw || serenity_utils::is_nsfw(&self.name)) } - /// Gets a message from the channel. - /// - /// Requires the [Read Message History] permission. - /// - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html - #[inline] - pub fn message<M: Into<MessageId>>(&self, message_id: M) -> Result<Message> { - self.id.message(message_id) - } - - /// Gets messages from the channel. - /// - /// Refer to [`Channel::messages`] for more information. - /// - /// Requires the [Read Message History] permission. - /// - /// [`Channel::messages`]: enum.Channel.html#method.messages - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html - #[inline] - pub fn messages<F>(&self, f: F) -> Result<Vec<Message>> - where F: FnOnce(GetMessages) -> GetMessages { - self.id.messages(f) - } - /// Returns the name of the guild channel. pub fn name(&self) -> &str { &self.name } - - /// Calculates the permissions of a member. - /// - /// The Id of the argument must be a [`Member`] of the [`Guild`] that the - /// channel is in. - /// - /// # Examples - /// - /// Calculate the permissions of a [`User`] who posted a [`Message`] in a - /// channel: - /// - /// ```rust,no_run - /// use serenity::prelude::*; - /// use serenity::model::prelude::*; - /// struct Handler; - /// - /// use serenity::CACHE; - /// - /// impl EventHandler for Handler { - /// fn message(&self, _: Context, msg: Message) { - /// let channel = match CACHE.read().guild_channel(msg.channel_id) { - /// Some(channel) => channel, - /// None => return, - /// }; - /// - /// let permissions = channel.read().permissions_for(&msg.author).unwrap(); - /// - /// println!("The user's permissions: {:?}", permissions); - /// } - /// } - /// let mut client = Client::new("token", Handler).unwrap(); - /// - /// client.start().unwrap(); - /// ``` - /// - /// Check if the current user has the [Attach Files] and [Send Messages] - /// permissions (note: serenity will automatically check this for; this is - /// for demonstrative purposes): - /// - /// ```rust,no_run - /// use serenity::CACHE; - /// use serenity::prelude::*; - /// use serenity::model::prelude::*; - /// use std::fs::File; - /// - /// struct Handler; - /// - /// impl EventHandler for Handler { - /// fn message(&self, _: Context, msg: Message) { - /// let channel = match CACHE.read().guild_channel(msg.channel_id) { - /// Some(channel) => channel, - /// None => return, - /// }; - /// - /// let current_user_id = CACHE.read().user.id; - /// let permissions = - /// channel.read().permissions_for(current_user_id).unwrap(); - /// - /// if !permissions.contains(Permissions::ATTACH_FILES | Permissions::SEND_MESSAGES) { - /// return; - /// } - /// - /// let file = match File::open("./cat.png") { - /// Ok(file) => file, - /// Err(why) => { - /// println!("Err opening file: {:?}", why); - /// - /// return; - /// }, - /// }; - /// - /// let _ = msg.channel_id.send_files(vec![(&file, "cat.png")], |mut m| { - /// m.content("here's a cat"); - /// - /// m - /// }); - /// } - /// } - /// - /// let mut client = Client::new("token", Handler).unwrap(); - /// - /// client.start().unwrap(); - /// ``` - /// - /// # Errors - /// - /// Returns a [`ModelError::GuildNotFound`] if the channel's guild could - /// not be found in the [`Cache`]. - /// - /// [`Cache`]: ../cache/struct.Cache.html - /// [`ModelError::GuildNotFound`]: enum.ModelError.html#variant.GuildNotFound - /// [`Guild`]: struct.Guild.html - /// [`Member`]: struct.Member.html - /// [`Message`]: struct.Message.html - /// [`User`]: struct.User.html - /// [Attach Files]: permissions/constant.ATTACH_FILES.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html - #[cfg(feature = "cache")] - pub fn permissions_for<U: Into<UserId>>(&self, user_id: U) -> Result<Permissions> { - self.guild() - .ok_or_else(|| Error::Model(ModelError::GuildNotFound)) - .map(|g| g.read().permissions_in(self.id, user_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() } - - /// Gets the list of [`User`]s who have reacted to a [`Message`] with a - /// certain [`Emoji`]. - /// - /// Refer to [`Channel::reaction_users`] for more information. - /// - /// **Note**: Requires the [Read Message History] permission. - /// - /// [`Channel::reaction_users`]: enum.Channel.html#method.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 reaction_users<M, R, U>( - &self, - message_id: M, - reaction_type: R, - limit: Option<u8>, - after: U, - ) -> Result<Vec<User>> where M: Into<MessageId>, - R: Into<ReactionType>, - U: Into<Option<UserId>> { - self.id.reaction_users(message_id, reaction_type, limit, after) - } - - /// Sends a message with just the given message content in the channel. - /// - /// # Errors - /// - /// Returns a [`ModelError::MessageTooLong`] if the content of the message - /// is over the above limit, containing the number of unicode code points - /// over the limit. - /// - /// [`ChannelId`]: struct.ChannelId.html - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - #[inline] - pub fn say(&self, content: &str) -> Result<Message> { self.id.say(content) } - - /// Sends (a) file(s) along with optional message contents. - /// - /// Refer to [`ChannelId::send_files`] 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_files`]: struct.ChannelId.html#method.send_files - /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - /// [Attach Files]: permissions/constant.ATTACH_FILES.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html - #[inline] - pub fn send_files<'a, F, T, It: IntoIterator<Item=T>>(&self, files: It, f: F) -> Result<Message> - where F: FnOnce(CreateMessage) -> CreateMessage, T: Into<AttachmentType<'a>> { - self.id.send_files(files, 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 [`ModelError::MessageTooLong`] if the content of the message - /// is over the above limit, containing the number of unicode code points - /// over the limit. - /// - /// Returns a [`ModelError::InvalidPermissions`] if the current user does - /// not have the required permissions. - /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [`ModelError::MessageTooLong`]: enum.ModelError.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::Model(ModelError::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) - } - - /// Retrieves the channel's webhooks. - /// - /// **Note**: Requires the [Manage Webhooks] permission. - /// - /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html - #[inline] - pub fn webhooks(&self) -> Result<Vec<Webhook>> { self.id.webhooks() } } -#[cfg(feature = "model")] 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 index fdc7787..d0268b0 100644 --- a/src/model/channel/message.rs +++ b/src/model/channel/message.rs @@ -1,20 +1,10 @@ //! Models relating to Discord channels. use chrono::{DateTime, FixedOffset}; +use constants; use model::prelude::*; use serde_json::Value; -#[cfg(feature = "model")] -use builder::{CreateEmbed, EditMessage}; -#[cfg(all(feature = "cache", feature = "model"))] -use CACHE; -#[cfg(all(feature = "cache", feature = "model"))] -use std::fmt::Write; -#[cfg(feature = "model")] -use std::mem; -#[cfg(feature = "model")] -use {constants, http, utils as serenity_utils}; - /// A representation of a message over a guild's text channel, a group, or a /// private channel. #[derive(Clone, Debug, Deserialize, Serialize)] @@ -70,177 +60,8 @@ pub struct Message { pub webhook_id: Option<WebhookId>, } -#[cfg(feature = "model")] impl Message { - /// Retrieves the related channel located in the cache. - /// - /// Returns `None` if the channel is not in the cache. - /// - /// # Examples - /// - /// On command, print the name of the channel that a message took place in: - /// - /// ```rust,no_run - /// # #[macro_use] extern crate serenity; - /// # - /// # fn main() { - /// # use serenity::prelude::*; - /// # struct Handler; - /// # - /// # impl EventHandler for Handler {} - /// # let mut client = Client::new("token", Handler).unwrap(); - /// # - /// use serenity::model::channel::Channel; - /// use serenity::framework::StandardFramework; - /// - /// client.with_framework(StandardFramework::new() - /// .configure(|c| c.prefix("~")) - /// .cmd("channelname", channel_name)); - /// - /// command!(channel_name(_ctx, msg) { - /// let _ = match msg.channel() { - /// Some(Channel::Category(c)) => msg.reply(&c.read().name), - /// Some(Channel::Group(c)) => msg.reply(&c.read().name()), - /// Some(Channel::Guild(c)) => msg.reply(&c.read().name), - /// Some(Channel::Private(c)) => { - /// let channel = c.read(); - /// let user = channel.recipient.read(); - /// - /// msg.reply(&format!("DM with {}", user.name.clone())) - /// }, - /// None => msg.reply("Unknown"), - /// }; - /// }); - /// # } - /// ``` - #[cfg(feature = "cache")] - #[inline] - pub fn channel(&self) -> Option<Channel> { CACHE.read().channel(self.channel_id) } - - /// A util function for determining whether this message was sent by someone else, or the - /// bot. - #[cfg(all(feature = "cache", feature = "utils"))] - pub fn is_own(&self) -> bool { self.author.id == CACHE.read().user.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 - /// [`ModelError::InvalidPermissions`] if the current user does not have - /// the required permissions. - /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [`ModelError::InvalidUser`]: enum.ModelError.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().user.id; - let has_perms = utils::user_has_perms(self.channel_id, req)?; - - if !is_author && !has_perms { - return Err(Error::Model(ModelError::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 - /// [`ModelError::InvalidPermissions`] if the current user does not have - /// the required permissions. - /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.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::Model(ModelError::InvalidPermissions(req))); - } - } - - http::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 [`EditMessage`] 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 [`ModelError::InvalidUser`] if the - /// current user is not the author. - /// - /// Returns a [`ModelError::MessageTooLong`] if the content of the message - /// is over [`the limit`], containing the number of unicode code points - /// over the limit. - /// - /// [`ModelError::InvalidUser`]: enum.ModelError.html#variant.InvalidUser - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - /// [`EditMessage`]: ../builder/struct.EditMessage.html - /// [`the limit`]: ../builder/struct.EditMessage.html#method.content - pub fn edit<F>(&mut self, f: F) -> Result<()> - where F: FnOnce(EditMessage) -> EditMessage { - #[cfg(feature = "cache")] - { - if self.author.id != CACHE.read().user.id { - return Err(Error::Model(ModelError::InvalidUser)); - } - } - - let mut builder = EditMessage::default(); - - if !self.content.is_empty() { - builder.content(&self.content); - } - - if let Some(embed) = self.embeds.get(0) { - builder.embed(|_| CreateEmbed::from(embed.clone())); - } - - let map = serenity_utils::vecmap_to_json_map(f(builder).0); - - match http::edit_message(self.channel_id.0, self.id.0, &Value::Object(map)) { - Ok(edited) => { - mem::replace(self, edited); - - Ok(()) - }, - Err(why) => Err(why), - } - } - - pub(crate) fn transform_content(&mut self) { + pub fn transform_content(&mut self) { match self.kind { MessageType::PinsAdd => { self.content = format!( @@ -262,115 +83,6 @@ impl Message { } } - /// 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 { - let mut at_distinct = String::with_capacity(38); - at_distinct.push('@'); - at_distinct.push_str(&u.name); - at_distinct.push('#'); - let _ = write!(at_distinct, "{:04}", u.discriminator); - result = result.replace(&u.mention(), &at_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 reaction_users<R, U>( - &self, - reaction_type: R, - limit: Option<u8>, - after: U, - ) -> Result<Vec<User>> where R: Into<ReactionType>, - U: Into<Option<UserId>> { - self.channel_id.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().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().channel(self.channel_id) { - Some(Channel::Guild(ch)) => Some(ch.read().guild_id), - _ => None, - } - } - - /// True if message was sent using direct messages. - #[cfg(feature = "cache")] - pub fn is_private(&self) -> bool { - match CACHE.read().channel(self.channel_id) { - Some(Channel::Group(_)) | Some(Channel::Private(_)) => true, - _ => false, - } - } - - /// Retrieves a clone of the author's Member instance, if this message was - /// sent in a guild. - /// - /// Note that since this clones, it is preferable performance-wise to - /// manually retrieve the guild from the cache and access - /// [`Guild::members`]. - /// - /// [`Guild::members`]: ../guild/struct.Guild.html#structfield.members - #[cfg(feature = "cache")] - pub fn member(&self) -> Option<Member> { - self.guild().and_then(|g| g.read().members.get(&self.author.id).cloned()) - } - /// Checks the length of a string to ensure that it is within Discord's /// maximum message length limit. /// @@ -390,132 +102,7 @@ impl Message { } } - /// Pins this message to its channel. - /// - /// **Note**: Requires the [Manage Messages] permission. - /// - /// # Errors - /// - /// If the `cache` is enabled, returns a - /// [`ModelError::InvalidPermissions`] if the current user does not have - /// the required permissions. - /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.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::Model(ModelError::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 - /// [`ModelError::InvalidPermissions`] if the current user does not have the - /// required [permissions]. - /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.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::Model(ModelError::InvalidPermissions(req))); - } - } - - http::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 - /// [`ModelError::InvalidPermissions`] if the current user does not have - /// the required permissions. - /// - /// Returns a [`ModelError::MessageTooLong`] if the content of the message - /// is over the above limit, containing the number of unicode code points - /// over the limit. - /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [`ModelError::MessageTooLong`]: enum.ModelError.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::Model(ModelError::MessageTooLong(length_over))); - } - - #[cfg(feature = "cache")] - { - let req = Permissions::SEND_MESSAGES; - - if !utils::user_has_perms(self.channel_id, req)? { - return Err(Error::Model(ModelError::InvalidPermissions(req))); - } - } - - let mut gen = self.author.mention(); - gen.push_str(": "); - gen.push_str(content); - - let map = json!({ - "content": gen, - "tts": false, - }); - - http::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 - /// [`ModelError::InvalidPermissions`] if the current user does not have - /// the required permissions. - /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.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::Model(ModelError::InvalidPermissions(req))); - } - } - - http::unpin_message(self.channel_id.0, self.id.0) - } - - pub(crate) fn check_content_length(map: &JsonMap) -> Result<()> { + pub fn check_content_length(map: &JsonMap) -> Result<()> { if let Some(content) = map.get("content") { if let Value::String(ref content) = *content { if let Some(length_over) = Message::overflow_length(content) { @@ -527,7 +114,7 @@ impl Message { Ok(()) } - pub(crate) fn check_embed_length(map: &JsonMap) -> Result<()> { + pub fn check_embed_length(map: &JsonMap) -> Result<()> { let embed = match map.get("embed") { Some(&Value::Object(ref value)) => value, _ => return Ok(()), diff --git a/src/model/channel/mod.rs b/src/model/channel/mod.rs index 8d586a6..384bb34 100644 --- a/src/model/channel/mod.rs +++ b/src/model/channel/mod.rs @@ -20,48 +20,38 @@ pub use self::private_channel::*; pub use self::reaction::*; pub use self::channel_category::*; -use internal::RwLockExt; use model::prelude::*; use serde::de::Error as DeError; use serde::ser::{SerializeStruct, Serialize, Serializer}; use serde_json; -use super::utils::deserialize_u64; - -#[cfg(feature = "model")] -use builder::{CreateMessage, EditMessage, GetMessages}; -#[cfg(feature = "model")] -use http::AttachmentType; -#[cfg(feature = "model")] +use std::cell::RefCell; use std::fmt::{Display, Formatter, Result as FmtResult}; +use std::rc::Rc; +use super::utils::deserialize_u64; /// A container for any channel. #[derive(Clone, Debug)] pub enum Channel { /// A group. A group comprises of only one channel. - Group(Arc<RwLock<Group>>), + Group(Rc<RefCell<Group>>), /// A [text] or [voice] channel within a [`Guild`]. /// /// [`Guild`]: struct.Guild.html /// [text]: enum.ChannelType.html#variant.Text /// [voice]: enum.ChannelType.html#variant.Voice - Guild(Arc<RwLock<GuildChannel>>), + Guild(Rc<RefCell<GuildChannel>>), /// A private channel to another [`User`]. No other users may access the /// channel. For multi-user "private channels", use a group. /// /// [`User`]: struct.User.html - Private(Arc<RwLock<PrivateChannel>>), + Private(Rc<RefCell<PrivateChannel>>), /// A category of [`GuildChannel`]s /// /// [`GuildChannel`]: struct.GuildChannel.html - Category(Arc<RwLock<ChannelCategory>>), + Category(Rc<RefCell<ChannelCategory>>), } impl Channel { - - ///////////////////////////////////////////////////////////////////////// - // Adapter for each variant - ///////////////////////////////////////////////////////////////////////// - /// Converts from `Channel` to `Option<Arc<RwLock<Group>>>`. /// /// Converts `self` into an `Option<Arc<RwLock<Group>>>`, consuming `self`, @@ -88,9 +78,7 @@ impl Channel { /// } /// # } /// ``` - - - pub fn group(self) -> Option<Arc<RwLock<Group>>> { + pub fn group(self) -> Option<Rc<RefCell<Group>>> { match self { Channel::Group(lock) => Some(lock), _ => None, @@ -119,8 +107,7 @@ impl Channel { /// } /// # } /// ``` - - pub fn guild(self) -> Option<Arc<RwLock<GuildChannel>>> { + pub fn guild(self) -> Option<Rc<RefCell<GuildChannel>>> { match self { Channel::Guild(lock) => Some(lock), _ => None, @@ -152,8 +139,7 @@ impl Channel { /// } /// # } /// ``` - - pub fn private(self) -> Option<Arc<RwLock<PrivateChannel>>> { + pub fn private(self) -> Option<Rc<RefCell<PrivateChannel>>> { match self { Channel::Private(lock) => Some(lock), _ => None, @@ -182,124 +168,13 @@ impl Channel { /// } /// # } /// ``` - - pub fn category(self) -> Option<Arc<RwLock<ChannelCategory>>> { + pub fn category(self) -> Option<Rc<RefCell<ChannelCategory>>> { match self { Channel::Category(lock) => Some(lock), _ => None, } } - /// 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 - #[cfg(feature = "model")] - #[deprecated(since = "0.4.2", note = "Use the inner channel's method")] - #[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) - } - - /// Deletes the inner channel. - /// - /// **Note**: There is no real function as _deleting_ a [`Group`]. The - /// closest functionality is leaving it. - /// - /// [`Group`]: struct.Group.html - #[cfg(feature = "model")] - pub fn delete(&self) -> Result<()> { - match *self { - Channel::Group(ref group) => { - let _ = group.read().leave()?; - }, - Channel::Guild(ref public_channel) => { - let _ = public_channel.read().delete()?; - }, - Channel::Private(ref private_channel) => { - let _ = private_channel.read().delete()?; - }, - Channel::Category(ref category) => { - category.read().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 - #[cfg(feature = "model")] - #[deprecated(since = "0.4.2", note = "Use the inner channel's method")] - #[inline] - pub fn delete_message<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { - self.id().delete_message(message_id) - } - - /// 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 - #[cfg(feature = "model")] - #[deprecated(since = "0.4.2", note = "Use the inner channel's method")] - #[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 [`EditMessage`] for more information - /// regarding message restrictions and requirements. - /// - /// **Note**: Requires that the current user be the author of the message. - /// - /// # Errors - /// - /// Returns a [`ModelError::MessageTooLong`] if the content of the message - /// is over the [`the limit`], containing the number of unicode code points - /// over the limit. - /// - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - /// [`EditMessage`]: ../builder/struct.EditMessage.html - /// [`Message`]: struct.Message.html - /// [`the limit`]: ../builder/struct.EditMessage.html#method.content - #[cfg(feature = "model")] - #[deprecated(since = "0.4.2", note = "Use the inner channel's method")] - #[inline] - pub fn edit_message<F, M>(&self, message_id: M, f: F) -> Result<Message> - where F: FnOnce(EditMessage) -> EditMessage, M: Into<MessageId> { - self.id().edit_message(message_id, f) - } - /// Determines if the channel is NSFW. /// /// Refer to [`utils::is_nsfw`] for more details. @@ -309,78 +184,12 @@ impl Channel { #[inline] pub fn is_nsfw(&self) -> bool { match *self { - Channel::Guild(ref channel) => channel.with(|c| c.is_nsfw()), - Channel::Category(ref category) => category.with(|c| c.is_nsfw()), + Channel::Guild(ref channel) => channel.borrow().is_nsfw(), + Channel::Category(ref category) => category.borrow().is_nsfw(), Channel::Group(_) | Channel::Private(_) => false, } } - /// Gets a message from the channel. - /// - /// Requires the [Read Message History] permission. - /// - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html - #[cfg(feature = "model")] - #[deprecated(since = "0.4.2", note = "Use the inner channel's method")] - #[inline] - pub fn message<M: Into<MessageId>>(&self, message_id: M) -> Result<Message> { - self.id().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.messages(|g| g.after(id).limit(100)); - /// ``` - /// - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html - #[cfg(feature = "model")] - #[deprecated(since = "0.4.2", note = "Use the inner channel's method")] - #[inline] - pub fn messages<F>(&self, f: F) -> Result<Vec<Message>> - where F: FnOnce(GetMessages) -> GetMessages { - self.id().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 - #[cfg(feature = "model")] - #[deprecated(since = "0.4.2", note = "Use the inner channel's method")] - #[inline] - pub fn reaction_users<M, R, U>(&self, - message_id: M, - reaction_type: R, - limit: Option<u8>, - after: U, - ) -> Result<Vec<User>> where M: Into<MessageId>, - R: Into<ReactionType>, - U: Into<Option<UserId>> { - self.id().reaction_users(message_id, reaction_type, limit, after) - } - /// Retrieves the Id of the inner [`Group`], [`GuildChannel`], or /// [`PrivateChannel`]. /// @@ -389,93 +198,12 @@ impl Channel { /// [`PrivateChannel`]: struct.PrivateChannel.html pub fn id(&self) -> ChannelId { match *self { - Channel::Group(ref group) => group.with(|g| g.channel_id), - Channel::Guild(ref ch) => ch.with(|c| c.id), - Channel::Private(ref ch) => ch.with(|c| c.id), - Channel::Category(ref category) => category.with(|c| c.id), + Channel::Group(ref group) => group.borrow().channel_id, + Channel::Guild(ref ch) => ch.borrow().id, + Channel::Private(ref ch) => ch.borrow().id, + Channel::Category(ref category) => category.borrow().id, } } - - /// Sends a message with just the given message content in the channel. - /// - /// # Errors - /// - /// Returns a [`ModelError::MessageTooLong`] if the content of the message - /// is over the above limit, containing the number of unicode code points - /// over the limit. - /// - /// [`ChannelId`]: struct.ChannelId.html - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - #[cfg(feature = "model")] - #[deprecated(since = "0.4.2", note = "Use the inner channel's method")] - #[inline] - pub fn say(&self, content: &str) -> Result<Message> { self.id().say(content) } - - /// Sends (a) file(s) along with optional message contents. - /// - /// Refer to [`ChannelId::send_files`] 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_files`]: struct.ChannelId.html#method.send_files - /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - /// [Attach Files]: permissions/constant.ATTACH_FILES.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html - #[cfg(feature = "model")] - #[deprecated(since = "0.4.2", note = "Use the inner channel's method")] - #[inline] - pub fn send_files<'a, F, T, It: IntoIterator<Item=T>>(&self, files: It, f: F) -> Result<Message> - where F: FnOnce(CreateMessage) -> CreateMessage, T: Into<AttachmentType<'a>> { - self.id().send_files(files, 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 [`ModelError::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 - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - /// [`CreateMessage`]: ../builder/struct.CreateMessage.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html - #[cfg(feature = "model")] - #[deprecated(since = "0.4.2", note = "Use the inner channel's method")] - #[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 - #[cfg(feature = "model")] - #[deprecated(since = "0.4.2", note = "Use the inner channel's method")] - #[inline] - pub fn unpin<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { - self.id().unpin(message_id) - } } impl<'de> Deserialize<'de> for Channel { @@ -489,16 +217,16 @@ impl<'de> Deserialize<'de> for Channel { match kind { 0 | 2 => serde_json::from_value::<GuildChannel>(Value::Object(v)) - .map(|x| Channel::Guild(Arc::new(RwLock::new(x)))) + .map(|x| Channel::Guild(Rc::new(RefCell::new(x)))) .map_err(DeError::custom), 1 => serde_json::from_value::<PrivateChannel>(Value::Object(v)) - .map(|x| Channel::Private(Arc::new(RwLock::new(x)))) + .map(|x| Channel::Private(Rc::new(RefCell::new(x)))) .map_err(DeError::custom), 3 => serde_json::from_value::<Group>(Value::Object(v)) - .map(|x| Channel::Group(Arc::new(RwLock::new(x)))) + .map(|x| Channel::Group(Rc::new(RefCell::new(x)))) .map_err(DeError::custom), 4 => serde_json::from_value::<ChannelCategory>(Value::Object(v)) - .map(|x| Channel::Category(Arc::new(RwLock::new(x)))) + .map(|x| Channel::Category(Rc::new(RefCell::new(x)))) .map_err(DeError::custom), _ => Err(DeError::custom("Unknown channel type")), } @@ -510,22 +238,21 @@ impl Serialize for Channel { where S: Serializer { match *self { Channel::Category(ref c) => { - ChannelCategory::serialize(&*c.read(), serializer) + ChannelCategory::serialize(&*c.borrow(), serializer) }, Channel::Group(ref c) => { - Group::serialize(&*c.read(), serializer) + Group::serialize(&*c.borrow(), serializer) }, Channel::Guild(ref c) => { - GuildChannel::serialize(&*c.read(), serializer) + GuildChannel::serialize(&*c.borrow(), serializer) }, Channel::Private(ref c) => { - PrivateChannel::serialize(&*c.read(), serializer) + PrivateChannel::serialize(&*c.borrow(), serializer) }, } } } -#[cfg(feature = "model")] impl Display for Channel { /// Formats the channel into a "mentioned" string. /// @@ -542,15 +269,15 @@ impl Display for Channel { /// [`PrivateChannel`]: struct.PrivateChannel.html fn fmt(&self, f: &mut Formatter) -> FmtResult { match *self { - Channel::Group(ref group) => Display::fmt(&group.read().name(), f), - Channel::Guild(ref ch) => Display::fmt(&ch.read().id.mention(), f), + Channel::Group(ref group) => Display::fmt(&group.borrow().name(), f), + Channel::Guild(ref ch) => Display::fmt(&ch.borrow().id.mention(), f), Channel::Private(ref ch) => { - let channel = ch.read(); - let recipient = channel.recipient.read(); + let channel = ch.borrow(); + let recipient = channel.recipient.borrow(); Display::fmt(&recipient.name, f) }, - Channel::Category(ref category) => Display::fmt(&category.read().name, f), + Channel::Category(ref category) => Display::fmt(&category.borrow().name, f), } } } diff --git a/src/model/channel/private_channel.rs b/src/model/channel/private_channel.rs index 91a019c..74f6785 100644 --- a/src/model/channel/private_channel.rs +++ b/src/model/channel/private_channel.rs @@ -1,23 +1,10 @@ use chrono::{DateTime, FixedOffset}; use model::prelude::*; -use std::fmt::{ - Display, - Formatter, - Result as FmtResult -}; +use std::cell::RefCell; +use std::fmt::{Display, Formatter, Result as FmtResult}; +use std::rc::Rc; use super::deserialize_single_recipient; -#[cfg(feature = "model")] -use builder::{ - CreateMessage, - EditMessage, - GetMessages -}; -#[cfg(feature = "model")] -use http::AttachmentType; -#[cfg(feature = "model")] -use internal::RwLockExt; - /// A Direct Message text channel with another user. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct PrivateChannel { @@ -41,257 +28,20 @@ pub struct PrivateChannel { /// The recipient to the private channel. #[serde(deserialize_with = "deserialize_single_recipient", rename = "recipients", - serialize_with = "serialize_sync_user")] - pub recipient: Arc<RwLock<User>>, + serialize_with = "serialize_user")] + pub recipient: Rc<RefCell<User>>, } -#[cfg(feature = "model")] impl PrivateChannel { - /// 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) - } - - /// 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**: Messages that are older than 2 weeks can't be deleted using - /// this method. - /// - /// # Errors - /// - /// Returns [`ModelError::BulkDeleteAmount`] if an attempt was made to - /// delete either 0 or more than 100 messages. - /// - /// [`Channel::delete_messages`]: enum.Channel.html#method.delete_messages - /// [`ModelError::BulkDeleteAmount`]: ../enum.ModelError.html#variant.BulkDeleteAmount - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html - #[inline] - pub fn delete_messages<T: AsRef<MessageId>, It: IntoIterator<Item=T>>(&self, message_ids: It) -> 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 [`EditMessage`] for more information - /// regarding message restrictions and requirements. - /// - /// **Note**: Requires that the current user be the author of the message. - /// - /// # Errors - /// - /// Returns a [`ModelError::MessageTooLong`] if the content of the message - /// is over the [`the limit`], containing the number of unicode code points - /// over the limit. - /// - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - /// [`EditMessage`]: ../builder/struct.EditMessage.html - /// [`Message`]: struct.Message.html - /// [`the limit`]: ../builder/struct.EditMessage.html#method.content - #[inline] - pub fn edit_message<F, M>(&self, message_id: M, f: F) -> Result<Message> - where F: FnOnce(EditMessage) -> EditMessage, M: Into<MessageId> { - self.id.edit_message(message_id, f) - } - - /// Determines if the channel is NSFW. - /// - /// Refer to [`utils::is_nsfw`] for more details. - /// - /// **Note**: This method is for consistency. This will always return - /// `false`, due to DMs not being considered NSFW. - /// - /// [`utils::is_nsfw`]: ../../utils/fn.is_nsfw.html - #[inline] - pub fn is_nsfw(&self) -> bool { false } - - /// Gets a message from the channel. - /// - /// Requires the [Read Message History] permission. - /// - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html - #[inline] - pub fn message<M: Into<MessageId>>(&self, message_id: M) -> Result<Message> { - self.id.message(message_id) - } - - /// Gets messages from the channel. - /// - /// Refer to [`Channel::messages`] for more information. - /// - /// Requires the [Read Message History] permission. - /// - /// [`Channel::messages`]: enum.Channel.html#method.messages - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html - #[inline] - pub fn messages<F>(&self, f: F) -> Result<Vec<Message>> - where F: FnOnce(GetMessages) -> GetMessages { - self.id.messages(f) - } - /// Returns "DM with $username#discriminator". - pub fn name(&self) -> String { format!("DM with {}", self.recipient.with(|r| r.tag())) } - - /// Gets the list of [`User`]s who have reacted to a [`Message`] with a - /// certain [`Emoji`]. - /// - /// Refer to [`Channel::reaction_users`] for more information. - /// - /// **Note**: Requires the [Read Message History] permission. - /// - /// [`Channel::reaction_users`]: enum.Channel.html#method.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 reaction_users<M, R, U>(&self, - message_id: M, - reaction_type: R, - limit: Option<u8>, - after: U, - ) -> Result<Vec<User>> where M: Into<MessageId>, - R: Into<ReactionType>, - U: Into<Option<UserId>> { - self.id.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 [`ModelError::MessageTooLong`] if the content of the message - /// is over the above limit, containing the number of unicode code points - /// over the limit. - /// - /// [`ChannelId`]: ../model/id/struct.ChannelId.html - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - #[inline] - pub fn say<D: ::std::fmt::Display>(&self, content: D) -> Result<Message> { self.id.say(content) } - - /// Sends (a) file(s) along with optional message contents. - /// - /// Refer to [`ChannelId::send_files`] 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_files`]: struct.ChannelId.html#method.send_files - /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - /// [Attach Files]: permissions/constant.ATTACH_FILES.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html - #[inline] - pub fn send_files<'a, F, T, It: IntoIterator<Item=T>>(&self, files: It, f: F) -> Result<Message> - where F: FnOnce(CreateMessage) -> CreateMessage, T: Into<AttachmentType<'a>> { - self.id.send_files(files, 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 [`ModelError::MessageTooLong`] if the content of the message - /// is over the above limit, containing the number of unicode code points - /// over the limit. - /// - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - /// [`CreateMessage`]: ../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) + pub fn name(&self) -> String { + format!("DM with {}", self.recipient.borrow().tag()) } } 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().name) + f.write_str(&self.recipient.borrow().name) } } diff --git a/src/model/channel/reaction.rs b/src/model/channel/reaction.rs index fcffa20..d10d715 100644 --- a/src/model/channel/reaction.rs +++ b/src/model/channel/reaction.rs @@ -13,11 +13,6 @@ use std::{ }; use internal::prelude::*; -#[cfg(all(feature = "cache", feature = "model"))] -use CACHE; -#[cfg(feature = "model")] -use http; - /// An emoji reaction to a message. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Reaction { @@ -38,135 +33,6 @@ pub struct Reaction { pub user_id: UserId, } -#[cfg(feature = "model")] -impl Reaction { - /// Retrieves the associated the reaction was made in. - /// - /// If the cache is enabled, this will search for the already-cached - /// channel. If not - or the channel was not found - this will perform a - /// request over the REST API for the channel. - /// - /// Requires the [Read Message History] permission. - /// - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html - #[inline] - pub fn channel(&self) -> Result<Channel> { - self.channel_id.get() - } - - /// Deletes the reaction, but only if the current user is the user who made - /// the reaction or has permission to. - /// - /// Requires the [Manage Messages] permission, _if_ the current - /// user did not perform the reaction. - /// - /// # Errors - /// - /// If the `cache` is enabled, then returns a - /// [`ModelError::InvalidPermissions`] if the current user does not have - /// the required [permissions]. - /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.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().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::Model(ModelError::InvalidPermissions(req))); - } - } - - user - } else { - Some(self.user_id.0) - } - }; - - http::delete_reaction(self.channel_id.0, self.message_id.0, user_id, &self.emoji) - } - - /// Retrieves the [`Message`] associated with this reaction. - /// - /// Requires the [Read Message History] permission. - /// - /// **Note**: This will send a request to the REST API. Prefer maintaining - /// your own message cache or otherwise having the message available if - /// possible. - /// - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html - /// [`Message`]: struct.Message.html - #[inline] - pub fn message(&self) -> Result<Message> { - self.channel_id.message(self.message_id) - } - - /// Retrieves the user that made the reaction. - /// - /// If the cache is enabled, this will search for the already-cached user. - /// If not - or the user was not found - this will perform a request over - /// the REST API for the user. - #[inline] - pub fn user(&self) -> Result<User> { - self.user_id.get() - } - - /// 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. - /// - /// Requires the [Read Message History] permission. - /// - /// **Note**: This will send a request to the REST API. - /// - /// # Errors - /// - /// Returns a [`ModelError::InvalidPermissions`] if the current user does - /// not have the required [permissions]. - /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.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> { - http::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 @@ -307,7 +173,6 @@ impl ReactionType { } } -#[cfg(feature = "model")] impl From<char> for ReactionType { /// Creates a `ReactionType` from a `char`. /// diff --git a/src/model/error.rs b/src/model/error.rs index c0cd609..bd877bf 100644 --- a/src/model/error.rs +++ b/src/model/error.rs @@ -79,6 +79,9 @@ pub enum Error { /// When attempting to delete below or above the minimum and maximum allowed /// number of messages. BulkDeleteAmount, + /// The client wasn't present on the model when it was expected to be, e.g. + /// when performing a cache or HTTP operation on it. + ClientNotPresent, /// When attempting to delete a number of days' worth of messages that is /// not allowed. DeleteMessageDaysAmount(u8), @@ -137,6 +140,7 @@ impl StdError for Error { fn description(&self) -> &str { match *self { Error::BulkDeleteAmount => "Too few/many messages to bulk delete", + Error::ClientNotPresent => "The client wasn't present on the model", Error::DeleteMessageDaysAmount(_) => "Invalid delete message days", Error::EmbedTooLarge(_) => "Embed too large", Error::GuildNotFound => "Guild not found in the cache", diff --git a/src/model/event.rs b/src/model/event.rs index 093adcd..7922b23 100644 --- a/src/model/event.rs +++ b/src/model/event.rs @@ -14,15 +14,6 @@ use super::prelude::*; use constants::{OpCode, VoiceOpCode}; use internal::prelude::*; -#[cfg(feature = "cache")] -use cache::{Cache, CacheUpdate}; -#[cfg(feature = "cache")] -use internal::RwLockExt; -#[cfg(feature = "cache")] -use std::collections::hash_map::Entry; -#[cfg(feature = "cache")] -use std::mem; - /// Event data for the channel creation event. /// /// This is fired when: @@ -56,107 +47,11 @@ impl Serialize for ChannelCreateEvent { } } -#[cfg(feature = "cache")] -impl CacheUpdate for ChannelCreateEvent { - type Output = Channel; - - fn update(&mut self, cache: &mut Cache) -> Option<Self::Output> { - match self.channel { - Channel::Group(ref group) => { - let group = Arc::clone(group); - - let channel_id = group.with_mut(|writer| { - for (recipient_id, recipient) in &mut writer.recipients { - cache.update_user_entry(&recipient.read()); - - *recipient = Arc::clone(&cache.users[recipient_id]); - } - - writer.channel_id - }); - - let ch = cache.groups.insert(channel_id, group); - - ch.map(Channel::Group) - }, - Channel::Guild(ref channel) => { - let (guild_id, channel_id) = channel.with(|channel| (channel.guild_id, channel.id)); - - cache.channels.insert(channel_id, Arc::clone(channel)); - - cache - .guilds - .get_mut(&guild_id) - .and_then(|guild| { - guild - .with_mut(|guild| guild.channels.insert(channel_id, Arc::clone(channel))) - }) - .map(Channel::Guild) - }, - Channel::Private(ref channel) => { - if let Some(channel) = cache.private_channels.get(&channel.with(|c| c.id)) { - return Some(Channel::Private(Arc::clone(&(*channel)))); - } - - let channel = Arc::clone(channel); - - let id = channel.with_mut(|writer| { - let user_id = writer.recipient.with_mut(|user| { - cache.update_user_entry(user); - - user.id - }); - - writer.recipient = Arc::clone(&cache.users[&user_id]); - writer.id - }); - - let ch = cache.private_channels.insert(id, Arc::clone(&channel)); - ch.map(Channel::Private) - }, - Channel::Category(ref category) => cache - .categories - .insert(category.read().id, Arc::clone(category)) - .map(Channel::Category), - } - } -} - #[derive(Clone, Debug)] pub struct ChannelDeleteEvent { pub channel: Channel, } -#[cfg(feature = "cache")] -impl CacheUpdate for ChannelDeleteEvent { - type Output = (); - - fn update(&mut self, cache: &mut Cache) -> Option<()> { - match self.channel { - Channel::Guild(ref channel) => { - let (guild_id, channel_id) = channel.with(|channel| (channel.guild_id, channel.id)); - - cache.channels.remove(&channel_id); - - cache - .guilds - .get_mut(&guild_id) - .and_then(|guild| guild.with_mut(|g| g.channels.remove(&channel_id))); - }, - Channel::Category(ref category) => { - let channel_id = category.with(|cat| cat.id); - - cache.categories.remove(&channel_id); - }, - // We ignore these two due to the fact that the delete event for dms/groups - // will _not_ fire anymore. - Channel::Private(_) | Channel::Group(_) => unreachable!(), - }; - - None - } -} - impl<'de> Deserialize<'de> for ChannelDeleteEvent { fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> { Ok(Self { @@ -178,143 +73,23 @@ pub struct ChannelPinsUpdateEvent { pub last_pin_timestamp: Option<DateTime<FixedOffset>>, } -#[cfg(feature = "cache")] -impl CacheUpdate for ChannelPinsUpdateEvent { - type Output = (); - - fn update(&mut self, cache: &mut Cache) -> Option<()> { - if let Some(channel) = cache.channels.get(&self.channel_id) { - channel.with_mut(|c| { - c.last_pin_timestamp = self.last_pin_timestamp; - }); - - return None; - } - - if let Some(channel) = cache.private_channels.get_mut(&self.channel_id) { - channel.with_mut(|c| { - c.last_pin_timestamp = self.last_pin_timestamp; - }); - - return None; - } - - if let Some(group) = cache.groups.get_mut(&self.channel_id) { - group.with_mut(|c| { - c.last_pin_timestamp = self.last_pin_timestamp; - }); - - return None; - } - - None - } -} - - #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ChannelRecipientAddEvent { pub channel_id: ChannelId, pub user: User, } -#[cfg(feature = "cache")] -impl CacheUpdate for ChannelRecipientAddEvent { - type Output = (); - - fn update(&mut self, cache: &mut Cache) -> Option<()> { - cache.update_user_entry(&self.user); - let user = Arc::clone(&cache.users[&self.user.id]); - - cache.groups.get_mut(&self.channel_id).map(|group| { - group.write().recipients.insert(self.user.id, user); - }); - - None - } -} - - #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ChannelRecipientRemoveEvent { pub channel_id: ChannelId, pub user: User, } -#[cfg(feature = "cache")] -impl CacheUpdate for ChannelRecipientRemoveEvent { - type Output = (); - - fn update(&mut self, cache: &mut Cache) -> Option<()> { - cache.groups.get_mut(&self.channel_id).map(|group| { - group.with_mut(|g| g.recipients.remove(&self.user.id)) - }); - - None - } -} - #[derive(Clone, Debug)] pub struct ChannelUpdateEvent { pub channel: Channel, } -#[cfg(feature = "cache")] -impl CacheUpdate for ChannelUpdateEvent { - type Output = (); - - fn update(&mut self, cache: &mut Cache) -> Option<()> { - match self.channel { - Channel::Group(ref group) => { - let (ch_id, no_recipients) = - group.with(|g| (g.channel_id, g.recipients.is_empty())); - - match cache.groups.entry(ch_id) { - Entry::Vacant(e) => { - e.insert(Arc::clone(group)); - }, - Entry::Occupied(mut e) => { - let mut dest = e.get_mut().write(); - - if no_recipients { - let recipients = mem::replace(&mut dest.recipients, HashMap::new()); - - dest.clone_from(&group.read()); - - dest.recipients = recipients; - } else { - dest.clone_from(&group.read()); - } - }, - } - }, - Channel::Guild(ref channel) => { - let (guild_id, channel_id) = channel.with(|channel| (channel.guild_id, channel.id)); - - cache.channels.insert(channel_id, Arc::clone(channel)); - cache.guilds.get_mut(&guild_id).map(|guild| { - guild - .with_mut(|g| g.channels.insert(channel_id, Arc::clone(channel))) - }); - }, - Channel::Private(ref channel) => { - cache - .private_channels - .get_mut(&channel.read().id) - .map(|private| private.clone_from(channel)); - }, - Channel::Category(ref category) => { - cache - .categories - .get_mut(&category.read().id) - .map(|c| c.clone_from(category)); - }, - } - - None - } -} - impl<'de> Deserialize<'de> for ChannelUpdateEvent { fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> { Ok(Self { @@ -347,31 +122,6 @@ pub struct GuildCreateEvent { pub guild: Guild, } -#[cfg(feature = "cache")] -impl CacheUpdate for GuildCreateEvent { - type Output = (); - - fn update(&mut self, cache: &mut Cache) -> Option<()> { - cache.unavailable_guilds.remove(&self.guild.id); - - let mut guild = self.guild.clone(); - - for (user_id, member) in &mut guild.members { - cache.update_user_entry(&member.user.read()); - let user = Arc::clone(&cache.users[user_id]); - - member.user = Arc::clone(&user); - } - - cache.channels.extend(guild.channels.clone()); - cache - .guilds - .insert(self.guild.id, Arc::new(RwLock::new(guild))); - - None - } -} - impl<'de> Deserialize<'de> for GuildCreateEvent { fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> { Ok(Self { @@ -392,22 +142,6 @@ pub struct GuildDeleteEvent { pub guild: PartialGuild, } -#[cfg(feature = "cache")] -impl CacheUpdate for GuildDeleteEvent { - type Output = Arc<RwLock<Guild>>; - - fn update(&mut self, cache: &mut Cache) -> Option<Self::Output> { - // Remove channel entries for the guild if the guild is found. - cache.guilds.remove(&self.guild.id).map(|guild| { - for channel_id in guild.write().channels.keys() { - cache.channels.remove(channel_id); - } - - guild - }) - } -} - impl<'de> Deserialize<'de> for GuildDeleteEvent { fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> { Ok(Self { @@ -429,21 +163,6 @@ pub struct GuildEmojisUpdateEvent { pub guild_id: GuildId, } -#[cfg(feature = "cache")] -impl CacheUpdate for GuildEmojisUpdateEvent { - type Output = (); - - fn update(&mut self, cache: &mut Cache) -> Option<()> { - cache.guilds.get_mut(&self.guild_id).map(|guild| { - guild.with_mut(|g| { - g.emojis.clone_from(&self.emojis) - }); - }); - - None - } -} - #[derive(Clone, Debug, Deserialize, Serialize)] pub struct GuildIntegrationsUpdateEvent { pub guild_id: GuildId, @@ -455,28 +174,6 @@ pub struct GuildMemberAddEvent { pub member: Member, } -#[cfg(feature = "cache")] -impl CacheUpdate for GuildMemberAddEvent { - type Output = (); - - fn update(&mut self, cache: &mut Cache) -> Option<()> { - let user_id = self.member.user.with(|u| u.id); - cache.update_user_entry(&self.member.user.read()); - - // Always safe due to being inserted above. - self.member.user = Arc::clone(&cache.users[&user_id]); - - cache.guilds.get_mut(&self.guild_id).map(|guild| { - guild.with_mut(|guild| { - guild.member_count += 1; - guild.members.insert(user_id, self.member.clone()); - }) - }); - - None - } -} - impl<'de> Deserialize<'de> for GuildMemberAddEvent { fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> { let map = JsonMap::deserialize(deserializer)?; @@ -500,20 +197,6 @@ pub struct GuildMemberRemoveEvent { pub user: User, } -#[cfg(feature = "cache")] -impl CacheUpdate for GuildMemberRemoveEvent { - type Output = Member; - - fn update(&mut self, cache: &mut Cache) -> Option<Self::Output> { - cache.guilds.get_mut(&self.guild_id).and_then(|guild| { - guild.with_mut(|guild| { - guild.member_count -= 1; - guild.members.remove(&self.user.id) - }) - }) - } -} - #[derive(Clone, Debug, Deserialize, Serialize)] pub struct GuildMemberUpdateEvent { pub guild_id: GuildId, @@ -522,77 +205,12 @@ pub struct GuildMemberUpdateEvent { pub user: User, } -#[cfg(feature = "cache")] -impl CacheUpdate for GuildMemberUpdateEvent { - type Output = Member; - - fn update(&mut self, cache: &mut Cache) -> Option<Self::Output> { - cache.update_user_entry(&self.user); - - if let Some(guild) = cache.guilds.get_mut(&self.guild_id) { - let mut guild = guild.write(); - - let mut found = false; - - let item = if let Some(member) = guild.members.get_mut(&self.user.id) { - let item = Some(member.clone()); - - member.nick.clone_from(&self.nick); - member.roles.clone_from(&self.roles); - member.user.write().clone_from(&self.user); - - found = true; - - item - } else { - None - }; - - if !found { - guild.members.insert( - self.user.id, - Member { - deaf: false, - guild_id: self.guild_id, - joined_at: None, - mute: false, - nick: self.nick.clone(), - roles: self.roles.clone(), - user: Arc::new(RwLock::new(self.user.clone())), - }, - ); - } - - item - } else { - None - } - } -} - #[derive(Clone, Debug, Serialize)] pub struct GuildMembersChunkEvent { pub guild_id: GuildId, pub members: HashMap<UserId, Member>, } -#[cfg(feature = "cache")] -impl CacheUpdate for GuildMembersChunkEvent { - type Output = (); - - fn update(&mut self, cache: &mut Cache) -> Option<()> { - for member in self.members.values() { - cache.update_user_entry(&member.user.read()); - } - - cache.guilds.get_mut(&self.guild_id).map(|guild| { - guild.with_mut(|g| g.members.extend(self.members.clone())) - }); - - None - } -} - impl<'de> Deserialize<'de> for GuildMembersChunkEvent { fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> { let mut map = JsonMap::deserialize(deserializer)?; @@ -619,9 +237,11 @@ impl<'de> Deserialize<'de> for GuildMembersChunkEvent { .map(|members| members .into_iter() .fold(HashMap::new(), |mut acc, member| { - let id = member.user.read().id; + let id = member.user.try_borrow().ok().map(|u| u.id); - acc.insert(id, member); + if let Some(id) = id { + acc.insert(id, member); + } acc })) @@ -640,105 +260,28 @@ pub struct GuildRoleCreateEvent { pub role: Role, } -#[cfg(feature = "cache")] -impl CacheUpdate for GuildRoleCreateEvent { - type Output = (); - - fn update(&mut self, cache: &mut Cache) -> Option<()> { - cache.guilds.get_mut(&self.guild_id).map(|guild| { - guild - .write() - .roles - .insert(self.role.id, self.role.clone()) - }); - - None - } -} - #[derive(Clone, Debug, Deserialize, Serialize)] pub struct GuildRoleDeleteEvent { pub guild_id: GuildId, pub role_id: RoleId, } -#[cfg(feature = "cache")] -impl CacheUpdate for GuildRoleDeleteEvent { - type Output = Role; - - fn update(&mut self, cache: &mut Cache) -> Option<Self::Output> { - cache - .guilds - .get_mut(&self.guild_id) - .and_then(|guild| guild.with_mut(|g| g.roles.remove(&self.role_id))) - } -} - #[derive(Clone, Debug, Deserialize, Serialize)] pub struct GuildRoleUpdateEvent { pub guild_id: GuildId, pub role: Role, } -#[cfg(feature = "cache")] -impl CacheUpdate for GuildRoleUpdateEvent { - type Output = Role; - - fn update(&mut self, cache: &mut Cache) -> Option<Self::Output> { - cache.guilds.get_mut(&self.guild_id).and_then(|guild| { - guild.with_mut(|g| { - g.roles - .get_mut(&self.role.id) - .map(|role| mem::replace(role, self.role.clone())) - }) - }) - } -} - #[derive(Clone, Debug, Deserialize, Serialize)] pub struct GuildUnavailableEvent { #[serde(rename = "id")] pub guild_id: GuildId, } -#[cfg(feature = "cache")] -impl CacheUpdate for GuildUnavailableEvent { - type Output = (); - - fn update(&mut self, cache: &mut Cache) -> Option<()> { - cache.unavailable_guilds.insert(self.guild_id); - cache.guilds.remove(&self.guild_id); - - None - } -} - #[derive(Clone, Debug)] pub struct GuildUpdateEvent { pub guild: PartialGuild, } -#[cfg(feature = "cache")] -impl CacheUpdate for GuildUpdateEvent { - type Output = (); - - fn update(&mut self, cache: &mut Cache) -> Option<()> { - cache.guilds.get_mut(&self.guild.id).map(|guild| { - let mut guild = guild.write(); - - guild.afk_timeout = self.guild.afk_timeout; - guild.afk_channel_id.clone_from(&self.guild.afk_channel_id); - guild.icon.clone_from(&self.guild.icon); - guild.name.clone_from(&self.guild.name); - guild.owner_id.clone_from(&self.guild.owner_id); - guild.region.clone_from(&self.guild.region); - guild.roles.clone_from(&self.guild.roles); - guild.verification_level = self.guild.verification_level; - }); - - None - } -} - impl<'de> Deserialize<'de> for GuildUpdateEvent { fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> { Ok(Self { @@ -802,7 +345,7 @@ pub struct MessageUpdateEvent { pub mentions: Option<Vec<User>>, pub mention_roles: Option<Vec<RoleId>>, pub attachments: Option<Vec<Attachment>>, - pub embeds: Option<Vec<Value>>, + pub embeds: Option<Vec<Embed>>, } #[derive(Clone, Debug, Serialize)] @@ -812,62 +355,6 @@ pub struct PresenceUpdateEvent { pub roles: Option<Vec<RoleId>>, } -#[cfg(feature = "cache")] -impl CacheUpdate for PresenceUpdateEvent { - type Output = (); - - fn update(&mut self, cache: &mut Cache) -> Option<()> { - let user_id = self.presence.user_id; - - if let Some(user) = self.presence.user.as_mut() { - cache.update_user_entry(&user.read()); - *user = Arc::clone(&cache.users[&user_id]); - } - - if let Some(guild_id) = self.guild_id { - if let Some(guild) = cache.guilds.get_mut(&guild_id) { - let mut guild = guild.write(); - - // If the member went offline, remove them from the presence list. - if self.presence.status == OnlineStatus::Offline { - guild.presences.remove(&self.presence.user_id); - } else { - guild - .presences - .insert(self.presence.user_id, self.presence.clone()); - } - - // Create a partial member instance out of the presence update - // data. This includes everything but `deaf`, `mute`, and - // `joined_at`. - if !guild.members.contains_key(&self.presence.user_id) { - if let Some(user) = self.presence.user.as_ref() { - let roles = self.roles.clone().unwrap_or_default(); - - guild.members.insert(self.presence.user_id, Member { - deaf: false, - guild_id, - joined_at: None, - mute: false, - nick: self.presence.nick.clone(), - user: Arc::clone(&user), - roles, - }); - } - } - } - } else if self.presence.status == OnlineStatus::Offline { - cache.presences.remove(&self.presence.user_id); - } else { - cache - .presences - .insert(self.presence.user_id, self.presence.clone()); - } - - None - } -} - impl<'de> Deserialize<'de> for PresenceUpdateEvent { fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> { let mut map = JsonMap::deserialize(deserializer)?; @@ -898,25 +385,6 @@ pub struct PresencesReplaceEvent { pub presences: Vec<Presence>, } -#[cfg(feature = "cache")] -impl CacheUpdate for PresencesReplaceEvent { - type Output = (); - - fn update(&mut self, cache: &mut Cache) -> Option<()> { - cache.presences.extend({ - let mut p: HashMap<UserId, Presence> = HashMap::default(); - - for presence in &self.presences { - p.insert(presence.user_id, presence.clone()); - } - - p - }); - - None - } -} - impl<'de> Deserialize<'de> for PresencesReplaceEvent { fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> { let presences: Vec<Presence> = Deserialize::deserialize(deserializer)?; @@ -992,46 +460,6 @@ pub struct ReadyEvent { pub ready: Ready, } -#[cfg(feature = "cache")] -impl CacheUpdate for ReadyEvent { - type Output = (); - - fn update(&mut self, cache: &mut Cache) -> Option<()> { - let mut ready = self.ready.clone(); - - for guild in ready.guilds { - match guild { - GuildStatus::Offline(unavailable) => { - cache.guilds.remove(&unavailable.id); - cache.unavailable_guilds.insert(unavailable.id); - }, - GuildStatus::OnlineGuild(guild) => { - cache.unavailable_guilds.remove(&guild.id); - cache.guilds.insert(guild.id, Arc::new(RwLock::new(guild))); - }, - GuildStatus::OnlinePartialGuild(_) => {}, - } - } - - // `ready.private_channels` will always be empty, and possibly be removed in the future. - // So don't handle it at all. - - for (user_id, presence) in &mut ready.presences { - if let Some(ref user) = presence.user { - cache.update_user_entry(&user.read()); - } - - presence.user = cache.users.get(user_id).cloned(); - } - - cache.presences.extend(ready.presences); - cache.shard_count = ready.shard.map_or(1, |s| s[1]); - cache.user = ready.user; - - None - } -} - impl<'de> Deserialize<'de> for ReadyEvent { fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> { Ok(Self { @@ -1070,15 +498,6 @@ pub struct UserUpdateEvent { pub current_user: CurrentUser, } -#[cfg(feature = "cache")] -impl CacheUpdate for UserUpdateEvent { - type Output = CurrentUser; - - fn update(&mut self, cache: &mut Cache) -> Option<Self::Output> { - Some(mem::replace(&mut cache.user, self.current_user.clone())) - } -} - impl<'de> Deserialize<'de> for UserUpdateEvent { fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> { Ok(Self { @@ -1108,43 +527,6 @@ pub struct VoiceStateUpdateEvent { pub voice_state: VoiceState, } -#[cfg(feature = "cache")] -impl CacheUpdate for VoiceStateUpdateEvent { - type Output = (); - - fn update(&mut self, cache: &mut Cache) -> Option<()> { - if let Some(guild_id) = self.guild_id { - if let Some(guild) = cache.guilds.get_mut(&guild_id) { - let mut guild = guild.write(); - - if self.voice_state.channel_id.is_some() { - // Update or add to the voice state list - { - let finding = guild.voice_states.get_mut(&self.voice_state.user_id); - - if let Some(srv_state) = finding { - srv_state.clone_from(&self.voice_state); - - return None; - } - } - - guild - .voice_states - .insert(self.voice_state.user_id, self.voice_state.clone()); - } else { - // Remove the user from the voice state list - guild.voice_states.remove(&self.voice_state.user_id); - } - } - - return None; - } - - None - } -} - impl<'de> Deserialize<'de> for VoiceStateUpdateEvent { fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> { let map = JsonMap::deserialize(deserializer)?; diff --git a/src/model/gateway.rs b/src/model/gateway.rs index 385902a..f1916f8 100644 --- a/src/model/gateway.rs +++ b/src/model/gateway.rs @@ -1,11 +1,11 @@ //! Models pertaining to the gateway. use chrono::{DateTime, Utc}; -use parking_lot::RwLock; use serde::de::Error as DeError; use serde::ser::{SerializeStruct, Serialize, Serializer}; use serde_json; -use std::sync::Arc; +use std::cell::RefCell; +use std::rc::Rc; use super::utils::*; use super::prelude::*; @@ -57,7 +57,6 @@ pub struct Activity { pub url: Option<String>, } -#[cfg(feature = "model")] impl Activity { /// Creates a `Game` struct that appears as a `Playing <name>` status. /// @@ -360,7 +359,7 @@ pub struct Presence { /// date. pub user_id: UserId, /// The associated user instance. - pub user: Option<Arc<RwLock<User>>>, + pub user: Option<Rc<RefCell<User>>>, } impl<'de> Deserialize<'de> for Presence { @@ -375,7 +374,7 @@ impl<'de> Deserialize<'de> for Presence { let user = User::deserialize(Value::Object(user_map)) .map_err(DeError::custom)?; - (user.id, Some(Arc::new(RwLock::new(user)))) + (user.id, Some(Rc::new(RefCell::new(user)))) } else { let user_id = user_map .remove("id") @@ -431,7 +430,7 @@ impl Serialize for Presence { state.serialize_field("status", &self.status)?; if let Some(ref user) = self.user { - state.serialize_field("user", &*user.read())?; + state.serialize_field("user", &*user.borrow())?; } else { state.serialize_field("user", &UserId { id: self.user_id.0, @@ -446,10 +445,14 @@ impl Serialize for Presence { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Ready { pub guilds: Vec<GuildStatus>, - #[serde(default, deserialize_with = "deserialize_presences")] - pub presences: HashMap<UserId, Presence>, - #[serde(default, deserialize_with = "deserialize_private_channels")] - pub private_channels: HashMap<ChannelId, Channel>, + #[serde(default, + deserialize_with = "deserialize_presences", + serialize_with = "serialize_gen_rc_map")] + pub presences: HashMap<UserId, Rc<RefCell<Presence>>>, + #[serde(default, + deserialize_with = "deserialize_private_channels", + serialize_with = "serialize_gen_rc_map")] + pub private_channels: HashMap<ChannelId, Rc<RefCell<Channel>>>, pub session_id: String, pub shard: Option<[u64; 2]>, #[serde(default, rename = "_trace")] diff --git a/src/model/guild/emoji.rs b/src/model/guild/emoji.rs index b2eb036..aca0153 100644 --- a/src/model/guild/emoji.rs +++ b/src/model/guild/emoji.rs @@ -1,22 +1,11 @@ use std::fmt::{ - Display, - Formatter, - Result as FmtResult, + Display, + Formatter, + Result as FmtResult, Write as FmtWrite }; use super::super::id::{EmojiId, RoleId}; -#[cfg(all(feature = "cache", feature = "model"))] -use internal::prelude::*; -#[cfg(all(feature = "cache", feature = "model"))] -use std::mem; -#[cfg(all(feature = "cache", feature = "model"))] -use super::super::ModelError; -#[cfg(all(feature = "cache", feature = "model"))] -use super::super::id::GuildId; -#[cfg(all(feature = "cache", feature = "model"))] -use {CACHE, http}; - /// Represents a custom guild emoji, which can either be created using the API, /// or via an integration. Emojis created using the API only work within the /// guild it was created in. @@ -44,132 +33,7 @@ pub struct Emoji { pub roles: Vec<RoleId>, } -#[cfg(feature = "model")] impl Emoji { - /// Deletes the emoji. - /// - /// **Note**: The [Manage Emojis] permission is required. - /// - /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html - /// - /// # Examples - /// - /// Delete a given emoji: - /// - /// ```rust,no_run - /// # use serenity::model::guild::Emoji; - /// # use serenity::model::id::EmojiId; - /// # - /// # let mut emoji = Emoji { - /// # animated: false, - /// # id: EmojiId(7), - /// # name: String::from("blobface"), - /// # managed: false, - /// # require_colons: false, - /// # roles: vec![], - /// # }; - /// # - /// // assuming emoji has been set already - /// match emoji.delete() { - /// Ok(()) => println!("Emoji deleted."), - /// Err(_) => println!("Could not delete emoji.") - /// } - /// ``` - #[cfg(feature = "cache")] - pub fn delete(&self) -> Result<()> { - match self.find_guild_id() { - Some(guild_id) => http::delete_emoji(guild_id.0, self.id.0), - None => Err(Error::Model(ModelError::ItemMissing)), - } - } - - /// Edits the emoji by updating it with a new name. - /// - /// **Note**: The [Manage Emojis] permission is required. - /// - /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html - /// - /// # Examples - /// - /// Change the name of an emoji: - /// - /// ```rust,no_run - /// # use serenity::model::guild::Emoji; - /// # use serenity::model::id::EmojiId; - /// # - /// # let mut emoji = Emoji { - /// # animated: false, - /// # id: EmojiId(7), - /// # name: String::from("blobface"), - /// # managed: false, - /// # require_colons: false, - /// # roles: vec![], - /// # }; - /// # - /// // assuming emoji has been set already - /// let _ = emoji.edit("blobuwu"); - /// assert_eq!(emoji.name, "blobuwu"); - /// ``` - #[cfg(feature = "cache")] - pub fn edit(&mut self, name: &str) -> Result<()> { - match self.find_guild_id() { - Some(guild_id) => { - let map = json!({ - "name": name, - }); - - match http::edit_emoji(guild_id.0, self.id.0, &map) { - Ok(emoji) => { - mem::replace(self, emoji); - - Ok(()) - }, - Err(why) => Err(why), - } - }, - None => Err(Error::Model(ModelError::ItemMissing)), - } - } - - /// Finds the [`Guild`] that owns the emoji by looking through the Cache. - /// - /// [`Guild`]: struct.Guild.html - /// - /// # Examples - /// - /// Print the guild id that owns this emoji: - /// - /// ```rust,no_run - /// # use serenity::model::guild::Emoji; - /// # use serenity::model::id::EmojiId; - /// # - /// # let mut emoji = Emoji { - /// # animated: false, - /// # id: EmojiId(7), - /// # name: String::from("blobface"), - /// # managed: false, - /// # require_colons: false, - /// # roles: vec![], - /// # }; - /// # - /// // assuming emoji has been set already - /// if let Some(guild_id) = emoji.find_guild_id() { - /// println!("{} is owned by {}", emoji.name, guild_id); - /// } - /// ``` - #[cfg(feature = "cache")] - pub fn find_guild_id(&self) -> Option<GuildId> { - for guild in CACHE.read().guilds.values() { - let guild = guild.read(); - - if guild.emojis.contains_key(&self.id) { - return Some(guild.id); - } - } - - None - } - /// Generates a URL to the emoji's image. /// /// # Examples diff --git a/src/model/guild/guild_id.rs b/src/model/guild/guild_id.rs index c07a40f..eacf07e 100644 --- a/src/model/guild/guild_id.rs +++ b/src/model/guild/guild_id.rs @@ -1,476 +1,15 @@ use model::prelude::*; -#[cfg(all(feature = "cache", feature = "model"))] -use CACHE; -#[cfg(feature = "model")] -use builder::{EditGuild, EditMember, EditRole}; -#[cfg(feature = "model")] -use internal::prelude::*; -#[cfg(feature = "model")] -use model::guild::BanOptions; -#[cfg(feature = "model")] -use {http, utils}; - -#[cfg(feature = "model")] impl GuildId { /// Converts the guild Id into the default channel's Id. #[inline] #[deprecated(note = "The concept of default channels is no more, use \ `Guild::default_channel{_guaranteed}` to simulate the concept.")] - 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 [`ModelError::DeleteMessageDaysAmount`] if the number of - /// days' worth of messages to delete is over the maximum. - /// - /// [`ModelError::DeleteMessageDaysAmount`]: - /// enum.ModelError.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, BO>(&self, user: U, ban_options: &BO) -> Result<()> - where U: Into<UserId>, BO: BanOptions { - let dmd = ban_options.dmd(); - if dmd > 7 { - return Err(Error::Model(ModelError::DeleteMessageDaysAmount(dmd))); - } - - let reason = ban_options.reason(); - - if reason.len() > 512 { - return Err(Error::ExceededLimit(reason.to_string(), 512)); - } - - http::ban_user(self.0, user.into().0, dmd, &*reason) - } - - /// Gets a list of the guild's bans. - /// - /// Requires the [Ban Members] permission. - /// - /// [Ban Members]: permissions/constant.BAN_MEMBERS.html - #[inline] - pub fn bans(&self) -> Result<Vec<Ban>> { http::get_bans(self.0) } - - /// Gets a list of the guild's audit log entries - #[inline] - pub fn audit_logs(&self, action_type: Option<u8>, - user_id: Option<UserId>, - before: Option<AuditLogEntryId>, - limit: Option<u8>) -> Result<AuditLogs> { - http::get_audit_logs(self.0, action_type, user_id.map(|u| u.0), before.map(|a| a.0), limit) - } - - /// Gets all of the guild's channels over the REST API. - /// - /// [`Guild`]: struct.Guild.html - pub fn channels(&self) -> Result<HashMap<ChannelId, GuildChannel>> { - let mut channels = HashMap::new(); - - for channel in http::get_channels(self.0)? { - channels.insert(channel.id, channel); - } - - Ok(channels) - } - - /// Creates a [`GuildChannel`] in the the guild. - /// - /// Refer to [`http::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, None); - /// ``` - /// - /// [`GuildChannel`]: struct.GuildChannel.html - /// [`http::create_channel`]: ../http/fn.create_channel.html - /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html - pub fn create_channel<C>(&self, name: &str, kind: ChannelType, category: C) -> Result<GuildChannel> - where C: Into<Option<ChannelId>> { - let map = json!({ - "name": name, - "type": kind as u8, - "parent_id": category.into().map(|c| c.0) - }); - - http::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`]: ../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 = json!({ - "name": name, - "image": image, - }); - - http::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 = json!({ - "id": integration_id.0, - "type": kind, - }); - - http::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> { - let map = utils::vecmap_to_json_map(f(EditRole::default()).0); - - let role = http::create_role(self.0, &map)?; - - if let Some(position) = map.get("position").and_then(Value::as_u64) { - self.edit_role_position(role.id, position)?; - } - - Ok(role) + pub fn as_channel_id(&self) -> ChannelId { + ChannelId(self.0) } - /// 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> { http::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<()> { - http::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<()> { - http::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<()> { - http::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> { - let map = utils::vecmap_to_json_map(f(EditGuild::default()).0); - - http::edit_guild(self.0, &map) - } - - /// 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 = json!({ - "name": name, - }); - - http::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> { - let map = utils::vecmap_to_json_map(f(EditMember::default()).0); - - http::edit_member(self.0, user_id.into().0, &map) - } - - /// 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<()> { - http::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> { - let map = utils::vecmap_to_json_map(f(EditRole::default()).0); - - http::edit_role(self.0, role_id.into().0, &map) - } - - /// Edits the order of [`Role`]s - /// Requires the [Manage Roles] permission. - /// - /// # Examples - /// - /// Change the order of a role: - /// - /// ```rust,ignore - /// use serenity::model::{GuildId, RoleId}; - /// GuildId(7).edit_role_position(RoleId(8), 2); - /// ``` - /// - /// [`Role`]: struct.Role.html - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html - #[inline] - pub fn edit_role_position<R>(&self, role_id: R, position: u64) -> Result<Vec<Role>> - where R: Into<RoleId> { - http::edit_role_position(self.0, role_id.into().0, position) - } - - - /// Search the cache for the guild. - #[cfg(feature = "cache")] - pub fn find(&self) -> Option<Arc<RwLock<Guild>>> { CACHE.read().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> { http::get_guild(self.0) } - - /// Gets all integration of the guild. - /// - /// This performs a request over the REST API. - #[inline] - pub fn integrations(&self) -> Result<Vec<Integration>> { http::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 invites(&self) -> Result<Vec<RichInvite>> { http::get_guild_invites(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<()> { - http::kick_member(self.0, user_id.into().0) - } - - /// Leaves the guild. - #[inline] - pub fn leave(&self) -> Result<()> { http::leave_guild(self.0) } - - /// Gets a user's [`Member`] for the guild by Id. - /// - /// [`Guild`]: struct.Guild.html - /// [`Member`]: struct.Member.html - #[inline] - pub fn member<U: Into<UserId>>(&self, user_id: U) -> Result<Member> { - http::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 members<U>(&self, limit: Option<u64>, after: Option<U>) -> Result<Vec<Member>> - where U: Into<UserId> { - http::get_guild_members(self.0, limit, after.map(|x| x.into().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 mut map = Map::new(); - map.insert( - "channel_id".to_string(), - Value::Number(Number::from(channel_id.into().0)), - ); - - http::edit_member(self.0, user_id.into().0, &map) - } - - /// 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 prune_count(&self, days: u16) -> Result<GuildPrune> { - let map = json!({ - "days": days, - }); - - http::get_guild_prune_count(self.0, &map) - } - - /// Re-orders the channels of the guild. - /// - /// Accepts an iterator of a tuple of the channel ID to modify and its new - /// position. - /// - /// Although not required, you should specify all channels' positions, - /// regardless of whether they were updated. Otherwise, positioning can - /// sometimes get weird. - pub fn reorder_channels<It>(&self, channels: It) -> Result<()> - where It: IntoIterator<Item = (ChannelId, u64)> { - let items = channels.into_iter().map(|(id, pos)| json!({ - "id": id, - "position": pos, - })).collect(); - - http::edit_guild_channel_positions(self.0, &Value::Array(items)) - } - - /// Returns the Id of the shard associated with the guild. - /// - /// When the cache is enabled this will automatically retrieve the total - /// number of shards. - /// - /// **Note**: When the cache is enabled, this function unlocks the cache to - /// retrieve the total number of shards in use. If you already have the - /// total, consider using [`utils::shard_id`]. - /// - /// [`utils::shard_id`]: ../utils/fn.shard_id.html - #[cfg(all(feature = "cache", feature = "utils"))] - #[inline] - pub fn shard_id(&self) -> u64 { ::utils::shard_id(self.0, CACHE.read().shard_count) } - /// Returns the Id of the shard associated with the guild. /// /// When the cache is enabled this will automatically retrieve the total @@ -492,66 +31,11 @@ impl GuildId { /// /// assert_eq!(guild_id.shard_id(17), 7); /// ``` - #[cfg(all(feature = "utils", not(feature = "cache")))] - #[inline] - pub fn shard_id(&self, shard_count: u64) -> u64 { ::utils::shard_id(self.0, shard_count) } - - /// 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<()> { - http::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> { - let map = json!({ - "days": days, - }); - - http::start_guild_prune(self.0, &map) - } - - /// Unbans a [`User`] from the guild. - /// - /// Requires the [Ban Members] permission. - /// - /// [`User`]: struct.User.html - /// [Ban Members]: permissions/constant.BAN_MEMBERS.html + #[cfg(feature = "utils")] #[inline] - pub fn unban<U: Into<UserId>>(&self, user_id: U) -> Result<()> { - http::remove_ban(self.0, user_id.into().0) + pub fn shard_id(&self, shard_count: u64) -> u64 { + ::utils::shard_id(self.0, shard_count) } - - /// Retrieve's the guild's vanity URL. - /// - /// **Note**: Requires the [Manage Guild] permission. - /// - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html - #[inline] - pub fn vanity_url(&self) -> Result<String> { - http::get_guild_vanity_url(self.0) - } - - /// Retrieves the guild's webhooks. - /// - /// **Note**: Requires the [Manage Webhooks] permission. - /// - /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html - #[inline] - pub fn webhooks(&self) -> Result<Vec<Webhook>> { http::get_guild_webhooks(self.0) } } impl From<PartialGuild> for GuildId { diff --git a/src/model/guild/member.rs b/src/model/guild/member.rs index ce3dd01..262cf84 100644 --- a/src/model/guild/member.rs +++ b/src/model/guild/member.rs @@ -1,22 +1,8 @@ -use model::prelude::*; use chrono::{DateTime, FixedOffset}; -use std::fmt::{ - Display, - Formatter, - Result as FmtResult -}; -use super::deserialize_sync_user; - -#[cfg(all(feature = "builder", feature = "cache", feature = "model"))] -use builder::EditMember; -#[cfg(all(feature = "cache", feature = "model"))] -use internal::prelude::*; -#[cfg(feature = "model")] +use model::prelude::*; use std::borrow::Cow; -#[cfg(all(feature = "cache", feature = "model", feature = "utils"))] -use utils::Colour; -#[cfg(all(feature = "cache", feature = "model"))] -use {CACHE, http, utils}; +use std::cell::RefCell; +use super::deserialize_user; /// A trait for allowing both u8 or &str or (u8, &str) to be passed into the `ban` methods in `Guild` and `Member`. pub trait BanOptions { @@ -70,138 +56,12 @@ pub struct Member { /// Vector of Ids of [`Role`]s given to the member. pub roles: Vec<RoleId>, /// Attached User struct. - #[serde(deserialize_with = "deserialize_sync_user", - serialize_with = "serialize_sync_user")] - pub user: Arc<RwLock<User>>, + #[serde(deserialize_with = "deserialize_user", + serialize_with = "serialize_user")] + pub user: Rc<RefCell<User>>, } -#[cfg(feature = "model")] 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(()); - } - - match http::add_member_role(self.guild_id.0, self.user.read().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<()> { - self.roles.extend_from_slice(role_ids); - - let mut builder = EditMember::default(); - builder.roles(&self.roles); - let map = utils::vecmap_to_json_map(builder.0); - - match http::edit_member(self.guild_id.0, self.user.read().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] permission. - /// - /// # Errors - /// - /// Returns a [`ModelError::GuildNotFound`] if the guild could not be - /// found. - /// - /// [`ModelError::GuildNotFound`]: enum.ModelError.html#variant.GuildNotFound - /// - /// [Ban Members]: permissions/constant.BAN_MEMBERS.html - #[cfg(feature = "cache")] - pub fn ban<BO: BanOptions>(&self, ban_options: &BO) -> Result<()> { - let dmd = ban_options.dmd(); - if dmd > 7 { - return Err(Error::Model(ModelError::DeleteMessageDaysAmount(dmd))); - } - - let reason = ban_options.reason(); - - if reason.len() > 512 { - return Err(Error::ExceededLimit(reason.to_string(), 512)); - } - - http::ban_user( - self.guild_id.0, - self.user.read().id.0, - dmd, - &*reason, - ) - } - - /// Determines the member's colour. - #[cfg(all(feature = "cache", feature = "utils"))] - pub fn colour(&self) -> Option<Colour> { - let cache = CACHE.read(); - let guild = cache.guilds.get(&self.guild_id)?.read(); - - 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) - } - - /// Returns the "default channel" of the guild for the member. - /// (This returns the first channel that can be read by the member, if there isn't - /// one returns `None`) - #[cfg(feature = "cache")] - pub fn default_channel(&self) -> Option<Arc<RwLock<GuildChannel>>> { - let guild = match self.guild_id.find() { - Some(guild) => guild, - None => return None, - }; - - let reader = guild.read(); - - for (cid, channel) in &reader.channels { - if reader.permissions_in(*cid, self.user.read().id).read_messages() { - return Some(Arc::clone(channel)); - } - } - - None - } - /// Calculates the member's display name. /// /// The nickname takes priority over the member's username if it exists. @@ -210,256 +70,23 @@ impl Member { self.nick .as_ref() .map(Cow::Borrowed) - .unwrap_or_else(|| Cow::Owned(self.user.read().name.clone())) + .unwrap_or_else(|| { + Cow::Owned(unsafe { (*self.user.as_ptr()).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().discriminator - ) - } - - /// Edits the member with the given data. See [`Guild::edit_member`] for - /// more information. - /// - /// See [`EditMember`] for the permission(s) required for separate builder - /// methods, as well as usage of this. - /// - /// [`Guild::edit_member`]: ../model/guild/struct.Guild.html#method.edit_member - /// [`EditMember`]: ../builder/struct.EditMember.html - #[cfg(feature = "cache")] - pub fn edit<F: FnOnce(EditMember) -> EditMember>(&self, f: F) -> Result<()> { - let map = utils::vecmap_to_json_map(f(EditMember::default()).0); - - http::edit_member(self.guild_id.0, self.user.read().id.0, &map) - } - - /// Retrieves the ID and position of the member's highest role in the - /// hierarchy, if they have one. - /// - /// This _may_ return `None` if: - /// - /// - the user has roles, but they are not present in the cache for cache - /// inconsistency reasons - /// - you already have a write lock to the member's guild - /// - /// The "highest role in hierarchy" is defined as the role with the highest - /// position. If two or more roles have the same highest position, then the - /// role with the lowest ID is the highest. - #[cfg(feature = "cache")] - pub fn highest_role_info(&self) -> Option<(RoleId, i64)> { - let guild = self.guild_id.find()?; - let reader = guild.try_read()?; - - let mut highest = None; - - for role_id in &self.roles { - if let Some(role) = reader.roles.get(&role_id) { - // Skip this role if this role in iteration has: - // - // - a position less than the recorded highest - // - a position equal to the recorded, but a higher ID - if let Some((id, pos)) = highest { - if role.position < pos || (role.position == pos && role.id > id) { - continue; - } - } - - highest = Some((role.id, role.position)); - } + unsafe { + let user = &*self.user.as_ptr(); + + format!( + "{}#{}", + self.display_name(), + user.discriminator, + ) } - - highest - } - - /// Kick the member from the guild. - /// - /// **Note**: Requires the [Kick Members] permission. - /// - /// # Examples - /// - /// Kick a member from its guild: - /// - /// ```rust,ignore - /// // assuming a `member` has already been bound - /// match member.kick() { - /// Ok(()) => println!("Successfully kicked member"), - /// Err(Error::Model(ModelError::GuildNotFound)) => { - /// println!("Couldn't determine guild of member"); - /// }, - /// Err(Error::Model(ModelError::InvalidPermissions(missing_perms))) => { - /// println!("Didn't have permissions; missing: {:?}", missing_perms); - /// }, - /// _ => {}, - /// } - /// ``` - /// - /// # Errors - /// - /// Returns a [`ModelError::GuildNotFound`] if the Id of the member's guild - /// could not be determined. - /// - /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] - /// if the current user does not have permission to perform the kick. - /// - /// [`ModelError::GuildNotFound`]: enum.ModelError.html#variant.GuildNotFound - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [Kick Members]: permissions/constant.KICK_MEMBERS.html - pub fn kick(&self) -> Result<()> { - #[cfg(feature = "cache")] - { - let cache = CACHE.read(); - - if let Some(guild) = cache.guilds.get(&self.guild_id) { - let req = Permissions::KICK_MEMBERS; - let reader = guild.read(); - - if !reader.has_perms(req) { - return Err(Error::Model(ModelError::InvalidPermissions(req))); - } - - reader.check_hierarchy(self.user.read().id)?; - } - } - - self.guild_id.kick(self.user.read().id) - } - - /// Returns the guild-level permissions for the member. - /// - /// # Examples - /// - /// ```rust,ignore - /// // assuming there's a `member` variable gotten from anything. - /// println!("The permission bits for the member are: {}", - /// member.permissions().expect("permissions").bits); - /// ``` - /// - /// # Errors - /// - /// Returns a [`ModelError::GuildNotFound`] if the guild the member's in could not be - /// found in the cache. - /// - /// And/or returns [`ModelError::ItemMissing`] if the "default channel" of the guild is not - /// found. - /// - /// [`ModelError::GuildNotFound`]: enum.ModelError.html#variant.GuildNotFound - /// [`ModelError::ItemMissing`]: enum.ModelError.html#variant.ItemMissing - #[cfg(feature = "cache")] - pub fn permissions(&self) -> Result<Permissions> { - let guild = match self.guild_id.find() { - Some(guild) => guild, - None => return Err(From::from(ModelError::GuildNotFound)), - }; - - let reader = guild.read(); - - Ok(reader.member_permissions(self.user.read().id)) - } - - /// 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(()); - } - - match http::remove_member_role(self.guild_id.0, self.user.read().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<()> { - self.roles.retain(|r| !role_ids.contains(r)); - - let mut builder = EditMember::default(); - builder.roles(&self.roles); - let map = utils::vecmap_to_json_map(builder.0); - - match http::edit_member(self.guild_id.0, self.user.read().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>> { - self - .guild_id - .find() - .map(|g| g - .read() - .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 [`ModelError::InvalidPermissions`] - /// if the current user does not have permission to perform bans. - /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [`User`]: struct.User.html - /// [Ban Members]: permissions/constant.BAN_MEMBERS.html - #[cfg(feature = "cache")] - pub fn unban(&self) -> Result<()> { - http::remove_ban(self.guild_id.0, self.user.read().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().mention(), f) } } diff --git a/src/model/guild/mod.rs b/src/model/guild/mod.rs index 9e4d8ef..5abf40f 100644 --- a/src/model/guild/mod.rs +++ b/src/model/guild/mod.rs @@ -17,23 +17,15 @@ pub use self::role::*; pub use self::audit_log::*; use chrono::{DateTime, FixedOffset}; +use constants::LARGE_THRESHOLD; use model::prelude::*; use serde::de::Error as DeError; use serde_json; -use super::utils::*; - -#[cfg(all(feature = "cache", feature = "model"))] -use CACHE; -#[cfg(feature = "model")] -use http; -#[cfg(feature = "model")] -use builder::{EditGuild, EditMember, EditRole}; -#[cfg(feature = "model")] -use constants::LARGE_THRESHOLD; -#[cfg(feature = "model")] -use std; -#[cfg(feature = "model")] use std::borrow::Cow; +use std::cell::RefCell; +use std::rc::Rc; +use std; +use super::utils::*; /// A representation of a banning of a user. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash, Serialize)] @@ -58,8 +50,8 @@ pub struct Guild { /// /// This contains all channels regardless of permissions (i.e. the ability /// of the bot to read from or connect to them). - #[serde(serialize_with = "serialize_gen_locked_map")] - pub channels: HashMap<ChannelId, Arc<RwLock<GuildChannel>>>, + #[serde(serialize_with = "serialize_gen_rc_map")] + pub channels: HashMap<ChannelId, Rc<RefCell<GuildChannel>>>, /// Indicator of whether notifications for all messages are enabled by /// default in the guild. pub default_message_notifications: DefaultMessageNotificationLevel, @@ -102,8 +94,8 @@ pub struct Guild { /// the library. /// /// [`ReadyEvent`]: events/struct.ReadyEvent.html - #[serde(serialize_with = "serialize_gen_map")] - pub members: HashMap<UserId, Member>, + #[serde(serialize_with = "serialize_gen_rc_map")] + pub members: HashMap<UserId, Rc<RefCell<Member>>>, /// Indicator of whether the guild requires multi-factor authentication for /// [`Role`]s or [`User`]s with moderation permissions. /// @@ -119,13 +111,13 @@ pub struct Guild { /// A mapping of [`User`]s' Ids to their current presences. /// /// [`User`]: struct.User.html - #[serde(serialize_with = "serialize_gen_map")] - pub presences: HashMap<UserId, Presence>, + #[serde(serialize_with = "serialize_gen_rc_map")] + pub presences: HashMap<UserId, Rc<RefCell<Presence>>>, /// The region that the voice servers that the guild uses are located in. pub region: String, /// A mapping of the guild's roles. - #[serde(serialize_with = "serialize_gen_map")] - pub roles: HashMap<RoleId, Role>, + #[serde(serialize_with = "serialize_gen_rc_map")] + pub roles: HashMap<RoleId, Rc<RefCell<Role>>>, /// An identifying hash of the guild's splash icon. /// /// If the [`InviteSplash`] feature is enabled, this can be used to generate @@ -144,28 +136,14 @@ pub struct Guild { pub voice_states: HashMap<UserId, VoiceState>, } -#[cfg(feature = "model")] impl Guild { - #[cfg(feature = "cache")] - fn check_hierarchy(&self, other_user: UserId) -> Result<()> { - let current_id = CACHE.read().user.id; - - if let Some(higher) = self.greater_member_hierarchy(other_user, current_id) { - if higher != current_id { - return Err(Error::Model(ModelError::Hierarchy)); - } - } - - Ok(()) - } - /// Returns the "default" channel of the guild for the passed user id. /// (This returns the first channel that can be read by the user, if there isn't one, /// returns `None`) - pub fn default_channel(&self, uid: UserId) -> Option<Arc<RwLock<GuildChannel>>> { + pub fn default_channel(&self, uid: UserId) -> Option<Rc<RefCell<GuildChannel>>> { for (cid, channel) in &self.channels { if self.permissions_in(*cid, uid).read_messages() { - return Some(Arc::clone(channel)); + return Some(Rc::clone(channel)); } } @@ -177,11 +155,11 @@ impl Guild { /// returns `None`) /// Note however that this is very costy if used in a server with lots of channels, /// members, or both. - pub fn default_channel_guaranteed(&self) -> Option<Arc<RwLock<GuildChannel>>> { + pub fn default_channel_guaranteed(&self) -> Option<Rc<RefCell<GuildChannel>>> { for (cid, channel) in &self.channels { for memid in self.members.keys() { if self.permissions_in(*cid, *memid).read_messages() { - return Some(Arc::clone(channel)); + return Some(Rc::clone(channel)); } } } @@ -189,537 +167,6 @@ impl Guild { None } - #[cfg(feature = "cache")] - fn has_perms(&self, mut permissions: Permissions) -> bool { - let user_id = CACHE.read().user.id; - - let perms = self.member_permissions(user_id); - permissions.remove(perms); - - 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 [`ModelError::InvalidPermissions`] if the current user does - /// not have permission to perform bans. - /// - /// Returns a [`ModelError::DeleteMessageDaysAmount`] if the number of - /// days' worth of messages to delete is over the maximum. - /// - /// [`ModelError::DeleteMessageDaysAmount`]: - /// enum.ModelError.html#variant.DeleteMessageDaysAmount - /// [`ModelError::InvalidPermissions`]: enum.ModelError.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>, BO: BanOptions>(&self, user: U, options: &BO) -> Result<()> { - let user = user.into(); - - #[cfg(feature = "cache")] - { - let req = Permissions::BAN_MEMBERS; - - if !self.has_perms(req) { - return Err(Error::Model(ModelError::InvalidPermissions(req))); - } - - self.check_hierarchy(user)?; - } - - self.id.ban(user, options) - } - - /// Retrieves a list of [`Ban`]s for the guild. - /// - /// **Note**: Requires the [Ban Members] permission. - /// - /// # Errors - /// - /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] - /// if the current user does not have permission to perform bans. - /// - /// [`Ban`]: struct.Ban.html - /// [`ModelError::InvalidPermissions`]: enum.ModelError.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::Model(ModelError::InvalidPermissions(req))); - } - } - - self.id.bans() - } - - /// Retrieves a list of [`AuditLogs`] for the guild. - /// - /// [`AuditLogs`]: audit_log/struct.AuditLogs.html - #[inline] - pub fn audit_logs(&self, action_type: Option<u8>, - user_id: Option<UserId>, - before: Option<AuditLogEntryId>, - limit: Option<u8>) -> Result<AuditLogs> { - self.id.audit_logs(action_type, user_id, before, limit) - } - - /// Gets all of the guild's channels over the REST API. - /// - /// [`Guild`]: struct.Guild.html - #[inline] - pub fn channels(&self) -> Result<HashMap<ChannelId, GuildChannel>> { self.id.channels() } - - /// Creates a guild with the data provided. - /// - /// Only a [`PartialGuild`] will be immediately returned, and a full - /// [`Guild`] will be received over a [`Shard`]. - /// - /// **Note**: This endpoint is usually only available for user accounts. - /// Refer to Discord's information for the endpoint [here][whitelist] for - /// more information. If you require this as a bot, re-think what you are - /// doing and if it _really_ needs to be doing this. - /// - /// # Examples - /// - /// Create a guild called `"test"` in the [US West region] with no icon: - /// - /// ```rust,ignore - /// use serenity::model::{Guild, Region}; - /// - /// let _guild = Guild::create_guild("test", Region::UsWest, None); - /// ``` - /// - /// [`Guild`]: struct.Guild.html - /// [`PartialGuild`]: struct.PartialGuild.html - /// [`Shard`]: ../gateway/struct.Shard.html - /// [US West region]: enum.Region.html#variant.UsWest - /// [whitelist]: https://discordapp.com/developers/docs/resources/guild#create-guild - pub fn create(name: &str, region: Region, icon: Option<&str>) -> Result<PartialGuild> { - let map = json!({ - "icon": icon, - "name": name, - "region": region.name(), - }); - - http::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, None); - /// ``` - /// - /// # Errors - /// - /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] - /// if the current user does not have permission to perform bans. - /// - /// [`Channel`]: struct.Channel.html - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html - pub fn create_channel<C>(&self, name: &str, kind: ChannelType, category: C) -> Result<GuildChannel> - where C: Into<Option<ChannelId>> { - #[cfg(feature = "cache")] - { - let req = Permissions::MANAGE_CHANNELS; - - if !self.has_perms(req) { - return Err(Error::Model(ModelError::InvalidPermissions(req))); - } - } - - self.id.create_channel(name, kind, category) - } - - /// 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`]: ../builder/struct.EditProfile.html#method.avatar - /// [`utils::read_image`]: ../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 [`ModelError::InvalidPermissions`] - /// if the current user does not have permission to perform bans. - /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [`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::Model(ModelError::InvalidPermissions(req))); - } - } - - 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. - /// - /// # Errors - /// - /// If the `cache` is enabled, then returns a [`ModelError::InvalidUser`] - /// if the current user is not the guild owner. - /// - /// [`ModelError::InvalidUser`]: enum.ModelError.html#variant.InvalidUser - pub fn delete(&self) -> Result<PartialGuild> { - #[cfg(feature = "cache")] - { - if self.owner_id != CACHE.read().user.id { - let req = Permissions::MANAGE_GUILD; - - return Err(Error::Model(ModelError::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 [`ModelError::InvalidPermissions`] - /// if the current user does not have permission to perform bans. - /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [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::Model(ModelError::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 [`ModelError::InvalidPermissions`] - /// if the current user does not have permission to change their own - /// nickname. - /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.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::Model(ModelError::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) - } - - /// Edits the order of [`Role`]s - /// Requires the [Manage Roles] permission. - /// - /// # Examples - /// - /// Change the order of a role: - /// - /// ```rust,ignore - /// use serenity::model::RoleId; - /// guild.edit_role_position(RoleId(8), 2); - /// ``` - /// - /// [`Role`]: struct.Role.html - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html - #[inline] - pub fn edit_role_position<R>(&self, role_id: R, position: u64) -> Result<Vec<Role>> - where R: Into<RoleId> { - self.id.edit_role_position(role_id, position) - } - - /// 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() } - - /// Returns which of two [`User`]s has a higher [`Member`] hierarchy. - /// - /// Hierarchy is essentially who has the [`Role`] with the highest - /// [`position`]. - /// - /// Returns [`None`] if at least one of the given users' member instances - /// is not present. Returns `None` if the users have the same hierarchy, as - /// neither are greater than the other. - /// - /// If both user IDs are the same, `None` is returned. If one of the users - /// is the guild owner, their ID is returned. - #[cfg(feature = "cache")] - pub fn greater_member_hierarchy<T, U>(&self, lhs_id: T, rhs_id: U) - -> Option<UserId> where T: Into<UserId>, U: Into<UserId> { - let lhs_id = lhs_id.into(); - let rhs_id = rhs_id.into(); - - // Check that the IDs are the same. If they are, neither is greater. - if lhs_id == rhs_id { - return None; - } - - // Check if either user is the guild owner. - if lhs_id == self.owner_id { - return Some(lhs_id); - } else if rhs_id == self.owner_id { - return Some(rhs_id); - } - - let lhs = self.members.get(&lhs_id)? - .highest_role_info() - .unwrap_or((RoleId(0), 0)); - let rhs = self.members.get(&rhs_id)? - .highest_role_info() - .unwrap_or((RoleId(0), 0)); - - // If LHS and RHS both have no top position or have the same role ID, - // then no one wins. - if (lhs.1 == 0 && rhs.1 == 0) || (lhs.0 == rhs.0) { - return None; - } - - // If LHS's top position is higher than RHS, then LHS wins. - if lhs.1 > rhs.1 { - return Some(lhs_id) - } - - // If RHS's top position is higher than LHS, then RHS wins. - if rhs.1 > lhs.1 { - return Some(rhs_id); - } - - // If LHS and RHS both have the same position, but LHS has the lower - // role ID, then LHS wins. - // - // If RHS has the higher role ID, then RHS wins. - if lhs.1 == rhs.1 && lhs.0 < rhs.0 { - Some(lhs_id) - } else { - Some(rhs_id) - } - } - /// Returns the formatted URL of the guild's icon, if one exists. pub fn icon_url(&self) -> Option<String> { self.icon @@ -727,81 +174,21 @@ impl Guild { .map(|icon| format!(cdn!("/icons/{}/{}.webp"), self.id, icon)) } - /// Gets all integration of the guild. - /// - /// This performs a request over the REST API. - #[inline] - pub fn integrations(&self) -> Result<Vec<Integration>> { self.id.integrations() } - - /// Retrieves the active invites for the guild. - /// - /// **Note**: Requires the [Manage Guild] permission. - /// - /// # Errors - /// - /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] - /// if the current user does not have permission to perform bans. - /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html - pub fn invites(&self) -> Result<Vec<RichInvite>> { - #[cfg(feature = "cache")] - { - let req = Permissions::MANAGE_GUILD; - - if !self.has_perms(req) { - return Err(Error::Model(ModelError::InvalidPermissions(req))); - } - } - - self.id.invites() - } - /// 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<()> { self.id.leave() } - - /// Gets a user's [`Member`] for the guild by Id. - /// - /// [`Guild`]: struct.Guild.html - /// [`Member`]: struct.Member.html - #[inline] - pub fn member<U: Into<UserId>>(&self, user_id: U) -> Result<Member> { self.id.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 members<U>(&self, limit: Option<u64>, after: Option<U>) -> Result<Vec<Member>> - where U: Into<UserId> { - self.id.members(limit, after) + pub fn is_large(&self) -> bool { + self.members.len() > LARGE_THRESHOLD as usize } /// Gets a list of all the members (satisfying the status provided to the function) in this /// guild. - pub fn members_with_status(&self, status: OnlineStatus) -> Vec<&Member> { + pub fn members_with_status(&self, status: OnlineStatus) + -> Vec<&Rc<RefCell<Member>>> { let mut members = vec![]; for (&id, member) in &self.members { - match self.presences.get(&id) { + match self.presences.get(&id).and_then(|x| x.try_borrow().ok()) { Some(presence) => if status == presence.status { members.push(member); }, @@ -828,7 +215,7 @@ impl Guild { /// - **username and discriminator**: "zey#5479" /// /// [`Member`]: struct.Member.html - pub fn member_named(&self, name: &str) -> Option<&Member> { + pub fn member_named(&self, name: &str) -> Option<&Rc<RefCell<Member>>> { let (name, discrim) = if let Some(pos) = name.rfind('#') { let split = name.split_at(pos + 1); @@ -851,9 +238,18 @@ impl Guild { self.members .values() .find(|member| { - let name_matches = member.user.read().name == name; + let member = match member.try_borrow().ok() { + Some(member) => member, + None => return false, + }; + let user = match member.user.try_borrow().ok() { + Some(user) => user, + None => return false, + }; + + let name_matches = user.name == name; let discrim_matches = match discrim { - Some(discrim) => member.user.read().discriminator == discrim, + Some(discrim) => user.discriminator == discrim, None => true, }; @@ -862,7 +258,7 @@ impl Guild { .or_else(|| { self.members .values() - .find(|member| member.nick.as_ref().map_or(false, |nick| nick == name)) + .find(|member| member.borrow().nick.as_ref().map_or(false, |nick| nick == name)) }) } @@ -876,18 +272,23 @@ impl Guild { /// - "zeya", "zeyaa", "zeyla", "zeyzey", "zeyzeyzey" /// /// [`Member`]: struct.Member.html - pub fn members_starting_with(&self, prefix: &str, case_sensitive: bool, sorted: bool) -> Vec<&Member> { - let mut members: Vec<&Member> = self.members + pub fn members_starting_with(&self, prefix: &str, case_sensitive: bool, sorted: bool) -> Vec<&Rc<RefCell<Member>>> { + let mut members: Vec<&Rc<RefCell<Member>>> = self.members .values() .filter(|member| - if case_sensitive { - member.user.read().name.starts_with(prefix) + let member = member.borrow(); + let user = member.user.borrow(); + + user.name.starts_with(prefix) } else { - starts_with_case_insensitive(&member.user.read().name, prefix) + let member = member.borrow(); + let user = member.user.borrow(); + + starts_with_case_insensitive(&user.name, prefix) } - || member.nick.as_ref() + || member.borrow().nick.as_ref() .map_or(false, |nick| if case_sensitive { @@ -899,26 +300,28 @@ impl Guild { if sorted { members .sort_by(|a, b| { + let (a, b) = (a.borrow(), b.borrow()); + let name_a = match a.nick { Some(ref nick) => { - if contains_case_insensitive(&a.user.read().name[..], prefix) { - Cow::Owned(a.user.read().name.clone()) + if contains_case_insensitive(&a.user.borrow().name[..], prefix) { + Cow::Owned(a.user.borrow().name.clone()) } else { Cow::Borrowed(nick) } }, - None => Cow::Owned(a.user.read().name.clone()), + None => Cow::Owned(a.user.borrow().name.clone()), }; let name_b = match b.nick { Some(ref nick) => { - if contains_case_insensitive(&b.user.read().name[..], prefix) { - Cow::Owned(b.user.read().name.clone()) + if contains_case_insensitive(&b.user.borrow().name[..], prefix) { + Cow::Owned(b.user.borrow().name.clone()) } else { Cow::Borrowed(nick) } }, - None => Cow::Owned(b.user.read().name.clone()), + None => Cow::Owned(b.user.borrow().name.clone()), }; closest_to_origin(prefix, &name_a[..], &name_b[..]) @@ -951,18 +354,24 @@ impl Guild { /// as both fields have to be considered again for sorting. /// /// [`Member`]: struct.Member.html - pub fn members_containing(&self, substring: &str, case_sensitive: bool, sorted: bool) -> Vec<&Member> { - let mut members: Vec<&Member> = self.members + pub fn members_containing(&self, substring: &str, case_sensitive: bool, sorted: bool) -> Vec<&Rc<RefCell<Member>>> { + let mut members: Vec<&Rc<RefCell<Member>>> = self.members .values() .filter(|member| if case_sensitive { - member.user.read().name.contains(substring) + let member = member.borrow(); + let user = member.user.borrow(); + + user.name.contains(substring) } else { - contains_case_insensitive(&member.user.read().name, substring) + let member = member.borrow(); + let user = member.user.borrow(); + + contains_case_insensitive(&user.name, substring) } - || member.nick.as_ref() + || member.borrow().nick.as_ref() .map_or(false, |nick| { if case_sensitive { @@ -975,26 +384,28 @@ impl Guild { if sorted { members .sort_by(|a, b| { + let (a, b) = (a.borrow(), b.borrow()); + let name_a = match a.nick { Some(ref nick) => { - if contains_case_insensitive(&a.user.read().name[..], substring) { - Cow::Owned(a.user.read().name.clone()) + if contains_case_insensitive(&a.user.borrow().name[..], substring) { + Cow::Owned(a.user.borrow().name.clone()) } else { Cow::Borrowed(nick) } }, - None => Cow::Owned(a.user.read().name.clone()), + None => Cow::Owned(a.user.borrow().name.clone()), }; let name_b = match b.nick { Some(ref nick) => { - if contains_case_insensitive(&b.user.read().name[..], substring) { - Cow::Owned(b.user.read().name.clone()) + if contains_case_insensitive(&b.user.borrow().name[..], substring) { + Cow::Owned(b.user.borrow().name.clone()) } else { Cow::Borrowed(nick) } }, - None => Cow::Owned(b.user.read().name.clone()), + None => Cow::Owned(b.user.borrow().name.clone()), }; closest_to_origin(substring, &name_a[..], &name_b[..]) @@ -1021,22 +432,34 @@ impl Guild { /// - "zey", "azey", "zeyla", "zeylaa", "zeyzeyzey" /// /// [`Member`]: struct.Member.html - pub fn members_username_containing(&self, substring: &str, case_sensitive: bool, sorted: bool) -> Vec<&Member> { - let mut members: Vec<&Member> = self.members + pub fn members_username_containing(&self, substring: &str, case_sensitive: bool, sorted: bool) -> Vec<&Rc<RefCell<Member>>> { + let mut members: Vec<&Rc<RefCell<Member>>> = self.members .values() .filter(|member| { + let member = match member.try_borrow().ok() { + Some(member) => member, + None => return false, + }; + let user = match member.user.try_borrow().ok() { + Some(user) => user, + None => return false, + }; + if case_sensitive { - member.user.read().name.contains(substring) + user.name.contains(substring) } else { - contains_case_insensitive(&member.user.read().name, substring) + contains_case_insensitive(&user.name, substring) } }).collect(); if sorted { members .sort_by(|a, b| { - let name_a = &a.user.read().name; - let name_b = &b.user.read().name; + let (a, b) = (a.borrow(), b.borrow()); + let (a_user, b_user) = (a.user.borrow(), b.user.borrow()); + + let name_a = &a_user.name; + let name_b = &b_user.name; closest_to_origin(substring, &name_a[..], &name_b[..]) }); members @@ -1064,35 +487,42 @@ impl Guild { /// a nick, the username will be used (this should never happen). /// /// [`Member`]: struct.Member.html - pub fn members_nick_containing(&self, substring: &str, case_sensitive: bool, sorted: bool) -> Vec<&Member> { - let mut members: Vec<&Member> = self.members + pub fn members_nick_containing(&self, substring: &str, case_sensitive: bool, sorted: bool) -> Vec<&Rc<RefCell<Member>>> { + let mut members = self.members .values() - .filter(|member| + .filter(|member| { + let member = match member.try_borrow() { + Ok(member) => member, + Err(_) => return false, + }; + member.nick.as_ref() .map_or(false, |nick| { - if case_sensitive { nick.contains(substring) } else { contains_case_insensitive(nick, substring) } - })).collect(); + }) + }).collect::<Vec<&Rc<RefCell<Member>>>>(); if sorted { members .sort_by(|a, b| { + let (a, b) = (a.borrow(), b.borrow()); + let name_a = match a.nick { Some(ref nick) => { Cow::Borrowed(nick) }, - None => Cow::Owned(a.user.read().name.clone()), + None => Cow::Owned(a.user.borrow().name.clone()), }; let name_b = match b.nick { Some(ref nick) => { Cow::Borrowed(nick) }, - None => Cow::Owned(b.user.read().name.clone()), + None => Cow::Owned(b.user.borrow().name.clone()), }; closest_to_origin(substring, &name_a[..], &name_b[..]) @@ -1114,7 +544,7 @@ impl Guild { return Permissions::all(); } - let everyone = match self.roles.get(&RoleId(self.id.0)) { + let everyone = match self.roles.get(&RoleId(self.id.0)).and_then(|x| x.try_borrow().ok()) { Some(everyone) => everyone, None => { error!( @@ -1127,7 +557,7 @@ impl Guild { }, }; - let member = match self.members.get(&user_id) { + let member = match self.members.get(&user_id).and_then(|x| x.try_borrow().ok()) { Some(member) => member, None => return everyone.permissions, }; @@ -1135,7 +565,7 @@ impl Guild { let mut permissions = everyone.permissions; for role in &member.roles { - if let Some(role) = self.roles.get(role) { + if let Some(role) = self.roles.get(role).and_then(|x| x.try_borrow().ok()) { if role.permissions.contains(Permissions::ADMINISTRATOR) { return Permissions::all(); } @@ -1144,7 +574,7 @@ impl Guild { } else { warn!( "(╯°□°)╯︵ ┻━┻ {} on {} has non-existent role {:?}", - member.user.read().id, + member.user.borrow().id, self.id, role, ); @@ -1154,17 +584,6 @@ impl Guild { permissions } - /// 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) - } - /// Alias for [`permissions_in`]. /// /// [`permissions_in`]: #method.permissions_in @@ -1191,7 +610,7 @@ impl Guild { let channel_id = channel_id.into(); // Start by retrieving the @everyone role's permissions. - let everyone = match self.roles.get(&RoleId(self.id.0)) { + let everyone = match self.roles.get(&RoleId(self.id.0)).and_then(|x| x.try_borrow().ok()) { Some(everyone) => everyone, None => { error!( @@ -1207,18 +626,18 @@ impl Guild { // Create a base set of permissions, starting with `@everyone`s. let mut permissions = everyone.permissions; - let member = match self.members.get(&user_id) { + let member = match self.members.get(&user_id).and_then(|x| x.try_borrow().ok()) { Some(member) => member, None => return everyone.permissions, }; for &role in &member.roles { - if let Some(role) = self.roles.get(&role) { + if let Some(role) = self.roles.get(&role).and_then(|x| x.try_borrow().ok()) { permissions |= role.permissions; } else { warn!( "(╯°□°)╯︵ ┻━┻ {} on {} has non-existent role {:?}", - member.user.read().id, + member.user.borrow().id, self.id, role ); @@ -1231,7 +650,7 @@ impl Guild { } if let Some(channel) = self.channels.get(&channel_id) { - let channel = channel.read(); + let channel = channel.borrow(); // If this is a text channel, then throw out voice permissions. if channel.kind == ChannelType::Text { @@ -1259,7 +678,7 @@ impl Guild { continue; } - if let Some(role) = self.roles.get(&role) { + if let Some(role) = self.roles.get(&role).and_then(|x| x.try_borrow().ok()) { data.push((role.position, overwrite.deny, overwrite.allow)); } } @@ -1316,59 +735,6 @@ impl Guild { permissions } - /// Retrieves the count of the number of [`Member`]s that would be pruned - /// with the number of given days. - /// - /// See the documentation on [`GuildPrune`] for more information. - /// - /// **Note**: Requires the [Kick Members] permission. - /// - /// # Errors - /// - /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] - /// if the current user does not have permission to perform bans. - /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [`GuildPrune`]: struct.GuildPrune.html - /// [`Member`]: struct.Member.html - /// [Kick Members]: permissions/constant.KICK_MEMBERS.html - pub fn prune_count(&self, days: u16) -> Result<GuildPrune> { - #[cfg(feature = "cache")] - { - let req = Permissions::KICK_MEMBERS; - - if !self.has_perms(req) { - return Err(Error::Model(ModelError::InvalidPermissions(req))); - } - } - - self.id.prune_count(days) - } - - /// Re-orders the channels of the guild. - /// - /// Although not required, you should specify all channels' positions, - /// regardless of whether they were updated. Otherwise, positioning can - /// sometimes get weird. - pub fn reorder_channels<It>(&self, channels: It) -> Result<()> - where It: IntoIterator<Item = (ChannelId, u64)> { - self.id.reorder_channels(channels) - } - - /// Returns the Id of the shard associated with the guild. - /// - /// When the cache is enabled this will automatically retrieve the total - /// number of shards. - /// - /// **Note**: When the cache is enabled, this function unlocks the cache to - /// retrieve the total number of shards in use. If you already have the - /// total, consider using [`utils::shard_id`]. - /// - /// [`utils::shard_id`]: ../utils/fn.shard_id.html - #[cfg(all(feature = "cache", feature = "utils"))] - #[inline] - pub fn shard_id(&self) -> u64 { self.id.shard_id() } - /// Returns the Id of the shard associated with the guild. /// /// When the cache is enabled this will automatically retrieve the total @@ -1389,7 +755,7 @@ impl Guild { /// /// assert_eq!(guild.shard_id(17), 7); /// ``` - #[cfg(all(feature = "utils", not(feature = "cache")))] + #[cfg(feature = "utils")] #[inline] pub fn shard_id(&self, shard_count: u64) -> u64 { self.id.shard_id(shard_count) } @@ -1400,87 +766,6 @@ impl Guild { .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 [`ModelError::InvalidPermissions`] - /// if the current user does not have permission to perform bans. - /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.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::Model(ModelError::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 [`ModelError::InvalidPermissions`] - /// if the current user does not have permission to perform bans. - /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.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::Model(ModelError::InvalidPermissions(req))); - } - } - - self.id.unban(user_id) - } - - /// Retrieve's the guild's vanity URL. - /// - /// **Note**: Requires the [Manage Guild] permission. - /// - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html - #[inline] - pub fn vanity_url(&self) -> Result<String> { - self.id.vanity_url() - } - - /// Retrieves the guild's webhooks. - /// - /// **Note**: Requires the [Manage Webhooks] permission. - /// - /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html - #[inline] - pub fn webhooks(&self) -> Result<Vec<Webhook>> { self.id.webhooks() } - /// Obtain a reference to a role by its name. /// /// **Note**: If two or more roles have the same name, obtained reference will be one of @@ -1512,8 +797,15 @@ impl Guild { /// /// client.start().unwrap(); /// ``` - pub fn role_by_name(&self, role_name: &str) -> Option<&Role> { - self.roles.values().find(|role| role_name == role.name) + pub fn role_by_name(&self, role_name: &str) -> Option<&Rc<RefCell<Role>>> { + self.roles.values().find(|role| { + let role = match role.try_borrow().ok() { + Some(role) => role, + None => return false, + }; + + role_name == role.name + }) } } @@ -1678,13 +970,11 @@ impl<'de> Deserialize<'de> for Guild { } /// Checks if a `&str` contains another `&str`. -#[cfg(feature = "model")] fn contains_case_insensitive(to_look_at: &str, to_find: &str) -> bool { to_look_at.to_lowercase().contains(to_find) } /// Checks if a `&str` starts with another `&str`. -#[cfg(feature = "model")] fn starts_with_case_insensitive(to_look_at: &str, to_find: &str) -> bool { to_look_at.to_lowercase().starts_with(to_find) } @@ -1696,7 +986,6 @@ fn starts_with_case_insensitive(to_look_at: &str, to_find: &str) -> bool { /// expected to contain `origin` as substring. /// If not, using `closest_to_origin` would sort these /// the end. -#[cfg(feature = "model")] fn closest_to_origin(origin: &str, word_a: &str, word_b: &str) -> std::cmp::Ordering { let value_a = match word_a.find(origin) { Some(value) => value + word_a.len(), @@ -1782,7 +1071,6 @@ impl From<u64> for GuildContainer { fn from(id: u64) -> GuildContainer { GuildContainer::Id(GuildId(id)) } } -#[cfg(feature = "model")] impl InviteGuild { /// Returns the formatted URL of the guild's splash image, if one exists. pub fn splash_url(&self) -> Option<String> { @@ -1814,7 +1102,6 @@ pub enum GuildStatus { Offline(GuildUnavailable), } -#[cfg(feature = "model")] impl GuildStatus { /// Retrieves the Id of the inner [`Guild`]. /// diff --git a/src/model/guild/partial_guild.rs b/src/model/guild/partial_guild.rs index d324517..39f158a 100644 --- a/src/model/guild/partial_guild.rs +++ b/src/model/guild/partial_guild.rs @@ -1,8 +1,8 @@ use model::prelude::*; +use std::cell::RefCell; +use std::rc::Rc; use super::super::utils::{deserialize_emojis, deserialize_roles}; -#[cfg(feature = "model")] -use builder::{EditGuild, EditMember, EditRole}; /// Partial information about a [`Guild`]. This does not include information /// like member data. @@ -28,281 +28,14 @@ pub struct PartialGuild { pub name: String, pub owner_id: UserId, pub region: String, - #[serde(deserialize_with = "deserialize_roles")] pub roles: HashMap<RoleId, Role>, + #[serde(deserialize_with = "deserialize_roles", + serialize_with = "serialize_gen_rc_map")] + pub roles: HashMap<RoleId, Rc<RefCell<Role>>>, pub splash: Option<String>, pub verification_level: VerificationLevel, } -#[cfg(feature = "model")] 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 [`ModelError::DeleteMessageDaysAmount`] if the number of - /// days' worth of messages to delete is over the maximum. - /// - /// [`ModelError::DeleteMessageDaysAmount`]: - /// enum.ModelError.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::Model( - ModelError::DeleteMessageDaysAmount(delete_message_days), - )); - } - - self.id.ban(user, &delete_message_days) - } - - /// Gets a list of the guild's bans. - /// - /// Requires the [Ban Members] permission. - /// - /// [Ban Members]: permissions/constant.BAN_MEMBERS.html - #[inline] - pub fn bans(&self) -> Result<Vec<Ban>> { self.id.bans() } - - /// Gets all of the guild's channels over the REST API. - /// - /// [`Guild`]: struct.Guild.html - #[inline] - pub fn channels(&self) -> Result<HashMap<ChannelId, GuildChannel>> { self.id.channels() } - - /// Creates a [`GuildChannel`] in the guild. - /// - /// Refer to [`http::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, None); - /// ``` - /// - /// [`GuildChannel`]: struct.GuildChannel.html - /// [`http::create_channel`]: ../http/fn.create_channel.html - /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html - #[inline] - pub fn create_channel<C>(&self, name: &str, kind: ChannelType, category: C) -> Result<GuildChannel> - where C: Into<Option<ChannelId>> { - self.id.create_channel(name, kind, category) - } - - /// 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`]: ../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 [`ModelError::InvalidPermissions`] - /// if the current user does not have permission to perform bans. - /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.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. - /// - /// [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 [`ModelError::InvalidPermissions`] - /// if the current user does not have permission to change their own - /// nickname. - /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.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() } - - /// 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 @@ -310,77 +43,6 @@ impl PartialGuild { .map(|icon| format!(cdn!("/icons/{}/{}.webp"), self.id, icon)) } - /// Gets all integration of the guild. - /// - /// This performs a request over the REST API. - #[inline] - pub fn integrations(&self) -> Result<Vec<Integration>> { self.id.integrations() } - - /// Gets all of the guild's invites. - /// - /// Requires the [Manage Guild] permission. - /// - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html - #[inline] - pub fn invites(&self) -> Result<Vec<RichInvite>> { self.id.invites() } - - /// Leaves the guild. - #[inline] - pub fn leave(&self) -> Result<()> { self.id.leave() } - - /// Gets a user's [`Member`] for the guild by Id. - /// - /// [`Guild`]: struct.Guild.html - /// [`Member`]: struct.Member.html - pub fn member<U: Into<UserId>>(&self, user_id: U) -> Result<Member> { self.id.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 members<U>(&self, limit: Option<u64>, after: Option<U>) -> Result<Vec<Member>> - where U: Into<UserId> { - self.id.members(limit, after) - } - - /// 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) - } - - /// 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 prune_count(&self, days: u16) -> Result<GuildPrune> { self.id.prune_count(days) } - - /// Returns the Id of the shard associated with the guild. - /// - /// When the cache is enabled this will automatically retrieve the total - /// number of shards. - /// - /// **Note**: When the cache is enabled, this function unlocks the cache to - /// retrieve the total number of shards in use. If you already have the - /// total, consider using [`utils::shard_id`]. - /// - /// [`utils::shard_id`]: ../utils/fn.shard_id.html - #[cfg(all(feature = "cache", feature = "utils"))] - #[inline] - pub fn shard_id(&self) -> u64 { self.id.shard_id() } - /// Returns the Id of the shard associated with the guild. /// /// When the cache is enabled this will automatically retrieve the total @@ -401,7 +63,7 @@ impl PartialGuild { /// /// assert_eq!(guild.shard_id(17), 7); /// ``` - #[cfg(all(feature = "utils", not(feature = "cache")))] + #[cfg(feature = "utils")] #[inline] pub fn shard_id(&self, shard_count: u64) -> u64 { self.id.shard_id(shard_count) } @@ -412,43 +74,6 @@ impl PartialGuild { .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) } - - /// Retrieve's the guild's vanity URL. - /// - /// **Note**: Requires the [Manage Guild] permission. - /// - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html - #[inline] - pub fn vanity_url(&self) -> Result<String> { - self.id.vanity_url() - } - - /// Retrieves the guild's webhooks. - /// - /// **Note**: Requires the [Manage Webhooks] permission. - /// - /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html - #[inline] - pub fn webhooks(&self) -> Result<Vec<Webhook>> { self.id.webhooks() } - /// Obtain a reference to a role by its name. /// /// **Note**: If two or more roles have the same name, obtained reference will be one of @@ -481,7 +106,7 @@ impl PartialGuild { /// /// client.start().unwrap(); /// ``` - pub fn role_by_name(&self, role_name: &str) -> Option<&Role> { - self.roles.values().find(|role| role_name == role.name) + pub fn role_by_name(&self, role_name: &str) -> Option<&Rc<RefCell<Role>>> { + self.roles.values().find(|role| role_name == role.borrow().name) } } diff --git a/src/model/guild/role.rs b/src/model/guild/role.rs index 800d178..bdc6e77 100644 --- a/src/model/guild/role.rs +++ b/src/model/guild/role.rs @@ -1,13 +1,6 @@ use model::prelude::*; use std::cmp::Ordering; -#[cfg(all(feature = "builder", feature = "cache", feature = "model"))] -use builder::EditRole; -#[cfg(all(feature = "cache", feature = "model"))] -use internal::prelude::*; -#[cfg(all(feature = "cache", feature = "model"))] -use {CACHE, http}; - /// Information about a role within a guild. A role represents a set of /// permissions, and can be attached to one or multiple users. A role has /// various miscellaneous configurations, such as being assigned a colour. Roles @@ -59,66 +52,7 @@ pub struct Role { pub position: i64, } -#[cfg(feature = "model")] 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<()> { http::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,no_run - /// # use serenity::model::id::RoleId; - /// # let role = RoleId(7).find().unwrap(); - /// // assuming a `role` has already been bound - // - /// role.edit(|mut r| { - /// r.hoist(true); - /// - /// r - /// }); - /// ``` - /// - /// [`Role`]: struct.Role.html - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html - #[cfg(all(feature = "builder", feature = "cache"))] - pub fn edit<F: FnOnce(EditRole) -> EditRole>(&self, f: F) -> Result<Role> { - self.find_guild() - .and_then(|guild_id| guild_id.edit_role(self.id, f)) - } - - /// Searches the cache for the guild that owns the role. - /// - /// # Errors - /// - /// Returns a [`ModelError::GuildNotFound`] if a guild is not in the cache - /// that contains the role. - /// - /// [`ModelError::GuildNotFound`]: enum.ModelError.html#variant.GuildNotFound - #[cfg(feature = "cache")] - pub fn find_guild(&self) -> Result<GuildId> { - for guild in CACHE.read().guilds.values() { - let guild = guild.read(); - - if guild.roles.contains_key(&RoleId(self.id.0)) { - return Ok(guild.id); - } - } - - Err(Error::Model(ModelError::GuildNotFound)) - } - /// Check that the role has the given permission. #[inline] pub fn has_permission(&self, permission: Permissions) -> bool { @@ -165,29 +99,6 @@ impl PartialOrd for Role { fn partial_cmp(&self, other: &Role) -> Option<Ordering> { Some(self.cmp(other)) } } -#[cfg(feature = "model")] -impl RoleId { - /// Search the cache for the role. - #[cfg(feature = "cache")] - pub fn find(&self) -> Option<Role> { - let cache = CACHE.read(); - - for guild in cache.guilds.values() { - let guild = guild.read(); - - if !guild.roles.contains_key(self) { - continue; - } - - if let Some(role) = guild.roles.get(self) { - return Some(role.clone()); - } - } - - None - } -} - impl From<Role> for RoleId { /// Gets the Id of a role. fn from(role: Role) -> RoleId { role.id } diff --git a/src/model/invite.rs b/src/model/invite.rs index 6573e35..63929e9 100644 --- a/src/model/invite.rs +++ b/src/model/invite.rs @@ -3,15 +3,6 @@ use chrono::{DateTime, FixedOffset}; use super::prelude::*; -#[cfg(feature = "model")] -use builder::CreateInvite; -#[cfg(feature = "model")] -use internal::prelude::*; -#[cfg(all(feature = "cache", feature = "model"))] -use super::{Permissions, utils as model_utils}; -#[cfg(feature = "model")] -use {http, utils}; - /// Information about an invite code. /// /// Information can not be accessed for guilds the current user is banned from. @@ -42,82 +33,7 @@ pub struct Invite { pub guild: InviteGuild, } -#[cfg(feature = "model")] impl Invite { - /// Creates an invite for a [`GuildChannel`], providing a builder so that - /// fields may optionally be set. - /// - /// See the documentation for the [`CreateInvite`] builder for information - /// on how to use this and the default values that it provides. - /// - /// Requires the [Create Invite] permission. - /// - /// # Errors - /// - /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] - /// if the current user does not have the required [permission]. - /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [`CreateInvite`]: ../builder/struct.CreateInvite.html - /// [`GuildChannel`]: struct.GuildChannel.html - /// [Create Invite]: permissions/constant.CREATE_INVITE.html - /// [permission]: permissions/index.html - pub fn create<C, F>(channel_id: C, f: F) -> Result<RichInvite> - where C: Into<ChannelId>, F: FnOnce(CreateInvite) -> CreateInvite { - let channel_id = channel_id.into(); - - #[cfg(feature = "cache")] - { - let req = Permissions::CREATE_INVITE; - - if !model_utils::user_has_perms(channel_id, req)? { - return Err(Error::Model(ModelError::InvalidPermissions(req))); - } - } - - let map = utils::vecmap_to_json_map(f(CreateInvite::default()).0); - - http::create_invite(channel_id.0, &map) - } - - /// Deletes the invite. - /// - /// **Note**: Requires the [Manage Guild] permission. - /// - /// # Errors - /// - /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] - /// if the current user does not have the required [permission]. - /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html - /// [permission]: permissions/index.html - pub fn delete(&self) -> Result<Invite> { - #[cfg(feature = "cache")] - { - let req = Permissions::MANAGE_GUILD; - - if !model_utils::user_has_perms(self.channel.id, req)? { - return Err(Error::Model(ModelError::InvalidPermissions(req))); - } - } - - http::delete_invite(&self.code) - } - - /// Gets the information about an invite. - #[allow(unused_mut)] - pub fn get(code: &str, stats: bool) -> Result<Invite> { - let mut invite = code; - - #[cfg(feature = "utils")] - { - invite = ::utils::parse_invite(invite); - } - - http::get_invite(invite, stats) - } - /// Returns a URL to use for the invite. /// /// # Examples @@ -170,27 +86,12 @@ pub struct InviteGuild { pub voice_channel_count: Option<u64>, } -#[cfg(feature = "model")] impl InviteGuild { /// Returns the Id of the shard associated with the guild. /// /// When the cache is enabled this will automatically retrieve the total /// number of shards. /// - /// **Note**: When the cache is enabled, this function unlocks the cache to - /// retrieve the total number of shards in use. If you already have the - /// total, consider using [`utils::shard_id`]. - /// - /// [`utils::shard_id`]: ../utils/fn.shard_id.html - #[cfg(all(feature = "cache", feature = "utils"))] - #[inline] - pub fn shard_id(&self) -> u64 { self.id.shard_id() } - - /// Returns the Id of the shard associated with the guild. - /// - /// When the cache is enabled this will automatically retrieve the total - /// number of shards. - /// /// When the cache is not enabled, the total number of shards being used /// will need to be passed. /// @@ -206,9 +107,11 @@ impl InviteGuild { /// /// assert_eq!(guild.shard_id(17), 7); /// ``` - #[cfg(all(feature = "utils", not(feature = "cache")))] + #[cfg(feature = "utils")] #[inline] - pub fn shard_id(&self, shard_count: u64) -> u64 { self.id.shard_id(shard_count) } + pub fn shard_id(&self, shard_count: u64) -> u64 { + self.id.shard_id(shard_count) + } } /// Detailed information about an invite. @@ -252,38 +155,7 @@ pub struct RichInvite { pub uses: u64, } -#[cfg(feature = "model")] impl RichInvite { - /// Deletes the invite. - /// - /// Refer to [`http::delete_invite`] for more information. - /// - /// **Note**: Requires the [Manage Guild] permission. - /// - /// # Errors - /// - /// If the `cache` feature is enabled, then this returns a - /// [`ModelError::InvalidPermissions`] if the current user does not have - /// the required [permission]. - /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [`Invite::delete`]: struct.Invite.html#method.delete - /// [`http::delete_invite`]: ../http/fn.delete_invite.html - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html - /// [permission]: permissions/index.html - pub fn delete(&self) -> Result<Invite> { - #[cfg(feature = "cache")] - { - let req = Permissions::MANAGE_GUILD; - - if !model_utils::user_has_perms(self.channel.id, req)? { - return Err(Error::Model(ModelError::InvalidPermissions(req))); - } - } - - http::delete_invite(&self.code) - } - /// Returns a URL to use for the invite. /// /// # Examples diff --git a/src/model/misc.rs b/src/model/misc.rs index 5278f89..4d4fc86 100644 --- a/src/model/misc.rs +++ b/src/model/misc.rs @@ -1,7 +1,6 @@ //! Miscellaneous helper traits, enums, and structs for models. use super::prelude::*; -use internal::RwLockExt; #[cfg(all(feature = "model", feature = "utils"))] use std::error::Error as StdError; @@ -28,9 +27,9 @@ impl Mentionable for ChannelId { impl Mentionable for Channel { fn mention(&self) -> String { match *self { - Channel::Guild(ref x) => format!("<#{}>", x.with(|x| x.id.0)), - Channel::Private(ref x) => format!("<#{}>", x.with(|x| x.id.0)), - Channel::Group(ref x) => format!("<#{}>", x.with(|x| x.channel_id.0)), + Channel::Guild(ref x) => format!("<#{}>", x.borrow().id.0), + Channel::Private(ref x) => format!("<#{}>", x.borrow().id.0), + Channel::Group(ref x) => format!("<#{}>", x.borrow().channel_id.0), Channel::Category(_) => panic!("Categories can't be mentioned"), } } @@ -46,10 +45,6 @@ impl Mentionable for Emoji { fn mention(&self) -> String { format!("<:{}:{}>", self.name, self.id.0) } } -impl Mentionable for Member { - fn mention(&self) -> String { format!("<@{}>", self.user.with(|u| u.id.0)) } -} - impl Mentionable for RoleId { fn mention(&self) -> String { format!("<@&{}>", self.0) } } @@ -90,20 +85,6 @@ impl StdError for UserParseError { } } -#[cfg(all(feature = "model", feature = "utils"))] -impl FromStr for User { - type Err = UserParseError; - - fn from_str(s: &str) -> StdResult<Self, Self::Err> { - match utils::parse_username(s) { - Some(x) => UserId(x as u64) - .get() - .map_err(|e| UserParseError::Rest(Box::new(e))), - _ => Err(UserParseError::InvalidUsername), - } - } -} - macro_rules! impl_from_str { (id: $($id:tt, $err:ident;)*) => { $( @@ -142,49 +123,6 @@ macro_rules! impl_from_str { } )* }; - - (struct: $($struct:ty, $id:tt, $err:ident, $invalid_variant:tt, $parse_fn:ident, $desc:expr;)*) => { - $( - #[cfg(all(feature = "cache", feature = "model", feature = "utils"))] - #[derive(Debug)] - pub enum $err { - NotPresentInCache, - $invalid_variant, - } - - #[cfg(all(feature = "cache", feature = "model", feature = "utils"))] - impl fmt::Display for $err { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.description()) } - } - - #[cfg(all(feature = "cache", feature = "model", feature = "utils"))] - impl StdError for $err { - fn description(&self) -> &str { - use self::$err::*; - - match *self { - NotPresentInCache => "not present in cache", - $invalid_variant => $desc, - } - } - } - - #[cfg(all(feature = "cache", feature = "model", feature = "utils"))] - impl FromStr for $struct { - type Err = $err; - - fn from_str(s: &str) -> StdResult<Self, Self::Err> { - match utils::$parse_fn(s) { - Some(x) => match $id(x).find() { - Some(user) => Ok(user), - _ => Err($err::NotPresentInCache), - }, - _ => Err($err::$invalid_variant), - } - } - } - )* - }; } impl_from_str! { id: @@ -193,11 +131,6 @@ impl_from_str! { id: ChannelId, ChannelIdParseError; } -impl_from_str! { struct: - Channel, ChannelId, ChannelParseError, InvalidChannel, parse_channel, "invalid channel"; - Role, RoleId, RoleParseError, InvalidRole, parse_role, "invalid role"; -} - /// A version of an emoji used only when solely the Id and name are known. #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct EmojiIdentifier { diff --git a/src/model/mod.rs b/src/model/mod.rs index 4416d2d..9ad7873 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -41,21 +41,15 @@ pub use self::error::Error as ModelError; pub use self::permissions::Permissions; use internal::prelude::*; -use parking_lot::RwLock; use self::utils::*; use serde::de::Visitor; +use serde::{Deserialize, Deserializer}; use std::{ collections::HashMap, - fmt::{ - Display, - Formatter, - Result as FmtResult - }, - sync::Arc, - result::Result as StdResult + fmt::{Display, Formatter, Result as FmtResult}, + rc::Rc, + result::Result as StdResult, }; #[cfg(feature = "utils")] use utils::Colour; - -use serde::{Deserialize, Deserializer}; diff --git a/src/model/permissions.rs b/src/model/permissions.rs index 0076b05..3f0577f 100644 --- a/src/model/permissions.rs +++ b/src/model/permissions.rs @@ -260,7 +260,6 @@ __impl_bitflags! { } } -#[cfg(feature = "model")] impl Permissions { /// Shorthand for checking that the set of permissions contains the /// [Add Reactions] permission. diff --git a/src/model/user.rs b/src/model/user.rs index 131e891..86907cd 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -7,24 +7,8 @@ use super::prelude::*; use internal::prelude::*; use model::misc::Mentionable; -#[cfg(all(feature = "cache", feature = "model"))] -use CACHE; -#[cfg(feature = "model")] -use builder::{CreateMessage, EditProfile}; -#[cfg(feature = "model")] use chrono::NaiveDateTime; -#[cfg(feature = "model")] -use http::{self, GuildPagination}; -#[cfg(all(feature = "cache", feature = "model"))] -use parking_lot::RwLock; -#[cfg(feature = "model")] use std::fmt::Write; -#[cfg(feature = "model")] -use std::mem; -#[cfg(all(feature = "cache", feature = "model"))] -use std::sync::Arc; -#[cfg(feature = "model")] -use utils::{self, VecMap}; /// Information about the current user. #[derive(Clone, Default, Debug, Deserialize, Serialize)] @@ -39,7 +23,6 @@ pub struct CurrentUser { pub verified: bool, } -#[cfg(feature = "model")] impl CurrentUser { /// Returns the formatted URL of the user's icon, if one exists. /// @@ -71,44 +54,6 @@ impl CurrentUser { #[inline] pub fn default_avatar_url(&self) -> String { default_avatar_url(self.discriminator) } - /// Edits the current user's profile settings. - /// - /// This mutates the current user in-place. - /// - /// Refer to `EditProfile`'s documentation for its methods. - /// - /// # Examples - /// - /// Change the avatar: - /// - /// ```rust,ignore - /// use serenity::CACHE; - /// - /// let avatar = serenity::utils::read_image("./avatar.png").unwrap(); - /// - /// CACHE.write().user.edit(|p| p.avatar(Some(&avatar))); - /// ``` - pub fn edit<F>(&mut self, f: F) -> Result<()> - where F: FnOnce(EditProfile) -> EditProfile { - let mut map = VecMap::new(); - map.insert("username", Value::String(self.name.clone())); - - if let Some(email) = self.email.as_ref() { - map.insert("email", Value::String(email.clone())); - } - - let map = utils::vecmap_to_json_map(f(EditProfile(map)).0); - - match http::edit_profile(&map) { - Ok(new) => { - let _ = mem::replace(self, new); - - Ok(()) - }, - Err(why) => Err(why), - } - } - /// Retrieves the URL to the current user's avatar, falling back to the /// default avatar if needed. /// @@ -122,111 +67,6 @@ impl CurrentUser { .unwrap_or_else(|| self.default_avatar_url()) } - /// Gets a list of guilds that the current user is in. - /// - /// # Examples - /// - /// Print out the names of all guilds the current user is in: - /// - /// ```rust,no_run - /// # use serenity::CACHE; - /// # - /// # let cache = CACHE.read(); - /// # - /// // assuming the cache has been unlocked - /// let user = &cache.user; - /// - /// if let Ok(guilds) = user.guilds() { - /// for (index, guild) in guilds.into_iter().enumerate() { - /// println!("{}: {}", index, guild.name); - /// } - /// } - /// ``` - pub fn guilds(&self) -> Result<Vec<GuildInfo>> { - http::get_guilds(&GuildPagination::After(GuildId(1)), 100) - } - - /// Returns the invite url for the bot with the given permissions. - /// - /// This queries the REST API for the client id. - /// - /// If the permissions passed are empty, the permissions part will be dropped. - /// - /// # Examples - /// - /// Get the invite url with no permissions set: - /// - /// ```rust,no_run - /// # use serenity::CACHE; - /// # - /// # let mut cache = CACHE.write(); - /// - /// use serenity::model::Permissions; - /// - /// // assuming the cache has been unlocked - /// let url = match cache.user.invite_url(Permissions::empty()) { - /// Ok(v) => v, - /// Err(why) => { - /// println!("Error getting invite url: {:?}", why); - /// - /// return; - /// }, - /// }; - /// - /// assert_eq!(url, "https://discordapp.com/api/oauth2/authorize? \ - /// client_id=249608697955745802&scope=bot"); - /// ``` - /// - /// Get the invite url with some basic permissions set: - /// - /// ```rust,no_run - /// # use serenity::CACHE; - /// # - /// # let mut cache = CACHE.write(); - /// - /// use serenity::model::Permissions; - /// - /// // assuming the cache has been unlocked - /// let url = match cache.user.invite_url(Permissions::READ_MESSAGES | Permissions::SEND_MESSAGES | Permissions::EMBED_LINKS) { - /// Ok(v) => v, - /// Err(why) => { - /// println!("Error getting invite url: {:?}", why); - /// - /// return; - /// }, - /// }; - /// - /// assert_eq!(url, - /// "https://discordapp. - /// com/api/oauth2/authorize?client_id=249608697955745802&scope=bot&permissions=19456"); - /// ``` - /// - /// # Errors - /// - /// Returns an - /// [`HttpError::InvalidRequest(Unauthorized)`][`HttpError::InvalidRequest`] - /// If the user is not authorized for this end point. - /// - /// May return [`Error::Format`] while writing url to the buffer. - /// - /// [`Error::Format`]: ../enum.Error.html#variant.Format - /// [`HttpError::InvalidRequest`]: ../http/enum.HttpError.html#variant.InvalidRequest - pub fn invite_url(&self, permissions: Permissions) -> Result<String> { - let bits = permissions.bits(); - let client_id = http::get_current_application_info().map(|v| v.id)?; - - let mut url = format!( - "https://discordapp.com/api/oauth2/authorize?client_id={}&scope=bot", - client_id - ); - - if bits != 0 { - write!(url, "&permissions={}", bits)?; - } - - Ok(url) - } - /// Returns a static formatted URL of the user's icon, if one exists. /// /// This will always produce a WEBP image URL. @@ -375,7 +215,6 @@ impl Hash for User { } } -#[cfg(feature = "model")] impl User { /// Returns the formatted URL of the user's icon, if one exists. /// @@ -383,13 +222,6 @@ impl User { #[inline] pub fn avatar_url(&self) -> Option<String> { avatar_url(self.id, self.avatar.as_ref()) } - /// Creates a direct message channel between the [current user] and the - /// user. This can also retrieve the channel if one already exists. - /// - /// [current user]: struct.CurrentUser.html - #[inline] - pub fn create_dm_channel(&self) -> Result<PrivateChannel> { self.id.create_dm_channel() } - /// Retrieves the time that this user was created at. #[inline] pub fn created_at(&self) -> NaiveDateTime { self.id.created_at() } @@ -400,155 +232,6 @@ impl User { #[inline] pub fn default_avatar_url(&self) -> String { default_avatar_url(self.discriminator) } - /// Sends a message to a user through a direct message channel. This is a - /// channel that can only be accessed by you and the recipient. - /// - /// # Examples - /// - /// When a user sends a message with a content of `"~help"`, DM the author a - /// help message, and then react with `'👌'` to verify message sending: - /// - /// ```rust,no_run - /// # use serenity::prelude::*; - /// # use serenity::model::prelude::*; - /// # - /// use serenity::model::Permissions; - /// use serenity::CACHE; - /// - /// struct Handler; - /// - /// impl EventHandler for Handler { - /// fn message(&self, _: Context, msg: Message) { - /// if msg.content == "~help" { - /// let cache = CACHE.read(); - /// - /// let url = match cache.user.invite_url(Permissions::empty()) { - /// Ok(v) => v, - /// Err(why) => { - /// println!("Error creating invite url: {:?}", why); - /// - /// return; - /// }, - /// }; - /// - /// let help = format!( - /// "Helpful info here. Invite me with this link: <{}>", - /// url, - /// ); - /// - /// let dm = msg.author.direct_message(|mut m| { - /// m.content(&help); - /// - /// m - /// }); - /// - /// match dm { - /// Ok(_) => { - /// let _ = msg.react('👌'); - /// }, - /// Err(why) => { - /// println!("Err sending help: {:?}", why); - /// - /// let _ = msg.reply("There was an error DMing you help."); - /// }, - /// }; - /// } - /// } - /// } - /// - /// let mut client = Client::new("token", Handler); - /// ``` - /// - /// # Examples - /// - /// Returns a [`ModelError::MessagingBot`] if the user being direct messaged - /// is a bot user. - /// - /// [`ModelError::MessagingBot`]: enum.ModelError.html#variant.MessagingBot - /// [`PrivateChannel`]: struct.PrivateChannel.html - /// [`User::dm`]: struct.User.html#method.dm - // A tale with Clippy: - // - // A person named Clippy once asked you to unlock a box and take something - // from it, but you never re-locked it, so you'll die and the universe will - // implode because the box must remain locked unless you're there, and you - // can't just borrow that item from it and take it with you forever. - // - // Instead what you do is unlock the box, take the item out of it, make a - // copy of said item, and then re-lock the box, and take your copy of the - // item with you. - // - // The universe is still fine, and nothing implodes. - // - // (AKA: Clippy is wrong and so we have to mark as allowing this lint.) - #[allow(let_and_return)] - #[cfg(feature = "builder")] - pub fn direct_message<F>(&self, f: F) -> Result<Message> - where F: FnOnce(CreateMessage) -> CreateMessage { - if self.bot { - return Err(Error::Model(ModelError::MessagingBot)); - } - - let private_channel_id = feature_cache! { - { - let finding = { - let cache = CACHE.read(); - - let finding = cache.private_channels - .values() - .map(|ch| ch.read()) - .find(|ch| ch.recipient.read().id == self.id) - .map(|ch| ch.id); - - finding - }; - - if let Some(finding) = finding { - finding - } else { - let map = json!({ - "recipient_id": self.id.0, - }); - - http::create_private_channel(&map)?.id - } - } else { - let map = json!({ - "recipient_id": self.id.0, - }); - - http::create_private_channel(&map)?.id - } - }; - - private_channel_id.send_message(f) - } - - /// This is an alias of [direct_message]. - /// - /// # Examples - /// - /// Sending a message: - /// - /// ```rust,ignore - /// // assuming you are in a context - /// - /// let _ = message.author.dm("Hello!"); - /// ``` - /// - /// # Examples - /// - /// Returns a [`ModelError::MessagingBot`] if the user being direct messaged - /// is a bot user. - /// - /// [`ModelError::MessagingBot`]: enum.ModelError.html#variant.MessagingBot - /// [direct_message]: #method.direct_message - #[cfg(feature = "builder")] - #[inline] - pub fn dm<F: FnOnce(CreateMessage) -> CreateMessage>(&self, f: F) -> Result<Message> { - self.direct_message(f) - } - /// Retrieves the URL to the user's avatar, falling back to the default /// avatar if needed. /// @@ -562,115 +245,6 @@ impl User { .unwrap_or_else(|| self.default_avatar_url()) } - /// Check if a user has a [`Role`]. This will retrieve the [`Guild`] from - /// the [`Cache`] if it is available, and then check if that guild has the - /// given [`Role`]. - /// - /// Three forms of data may be passed in to the guild parameter: either a - /// [`PartialGuild`], a [`GuildId`], or a `u64`. - /// - /// # Examples - /// - /// Check if a guild has a [`Role`] by Id: - /// - /// ```rust,ignore - /// // Assumes a 'guild_id' and `role_id` have already been bound - /// let _ = message.author.has_role(guild_id, role_id); - /// ``` - /// - /// [`Guild`]: struct.Guild.html - /// [`GuildId`]: struct.GuildId.html - /// [`PartialGuild`]: struct.PartialGuild.html - /// [`Role`]: struct.Role.html - /// [`Cache`]: ../cache/struct.Cache.html - // no-cache would warn on guild_id. - pub fn has_role<G, R>(&self, guild: G, role: R) -> bool - where G: Into<GuildContainer>, R: Into<RoleId> { - let role_id = role.into(); - - match guild.into() { - GuildContainer::Guild(guild) => guild.roles.contains_key(&role_id), - GuildContainer::Id(_guild_id) => { - feature_cache! {{ - CACHE.read() - .guilds - .get(&_guild_id) - .map(|g| { - g.read().members.get(&self.id) - .map(|m| m.roles.contains(&role_id)) - .unwrap_or(false) - }) - .unwrap_or(false) - } else { - true - }} - }, - } - } - - /// Refreshes the information about the user. - /// - /// Replaces the instance with the data retrieved over the REST API. - /// - /// # Examples - /// - /// If maintaing a very long-running bot, you may want to periodically - /// refresh information about certain users if the state becomes - /// out-of-sync: - /// - /// ```rust,no_run - /// # use serenity::prelude::*; - /// # use serenity::model::prelude::*; - /// # - /// struct Handler; - /// - /// impl EventHandler for Handler { - /// fn message(&self, _: Context, _: Message) { - /// // normal message handling here - /// } - /// } - /// - /// let mut client = Client::new("token", Handler).unwrap(); - /// # - /// use serenity::model::id::UserId; - /// use serenity::CACHE; - /// use std::thread; - /// use std::time::Duration; - /// - /// let special_users = vec![UserId(114941315417899012), UserId(87600987040120832)]; - /// - /// // start a new thread to periodically refresh the special users' data - /// // every 12 hours - /// let handle = thread::spawn(move || { - /// // 12 hours in seconds - /// let duration = Duration::from_secs(43200); - /// - /// loop { - /// thread::sleep(duration); - /// - /// let cache = CACHE.read(); - /// - /// for id in &special_users { - /// if let Some(user) = cache.user(*id) { - /// if let Err(why) = user.write().refresh() { - /// println!("Error refreshing {}: {:?}", id, why); - /// } - /// } - /// } - /// } - /// }); - /// - /// println!("{:?}", client.start()); - /// ``` - pub fn refresh(&mut self) -> Result<()> { - self.id.get().map(|replacement| { - mem::replace(self, replacement); - - () - }) - } - - /// Returns a static formatted URL of the user's icon, if one exists. /// /// This will always produce a WEBP image URL. @@ -724,64 +298,6 @@ impl fmt::Display for User { } } -#[cfg(feature = "model")] -impl UserId { - /// Creates a direct message channel between the [current user] and the - /// user. This can also retrieve the channel if one already exists. - /// - /// [current user]: struct.CurrentUser.html - pub fn create_dm_channel(&self) -> Result<PrivateChannel> { - let map = json!({ - "recipient_id": self.0, - }); - - http::create_private_channel(&map) - } - - /// Search the cache for the user with the Id. - #[cfg(feature = "cache")] - pub fn find(&self) -> Option<Arc<RwLock<User>>> { CACHE.read().user(*self) } - - /// Gets a user by its Id over the REST API. - /// - /// **Note**: The current user must be a bot user. - #[inline] - pub fn get(&self) -> Result<User> { - #[cfg(feature = "cache")] - { - if let Some(user) = CACHE.read().user(*self) { - return Ok(user.read().clone()); - } - } - - http::get_user(self.0) - } -} - -impl From<CurrentUser> for User { - fn from(user: CurrentUser) -> Self { - Self { - avatar: user.avatar, - bot: user.bot, - discriminator: user.discriminator, - id: user.id, - name: user.name, - } - } -} - -impl<'a> From<&'a CurrentUser> for User { - fn from(user: &'a CurrentUser) -> Self { - Self { - avatar: user.avatar.clone(), - bot: user.bot, - discriminator: user.discriminator, - id: user.id, - name: user.name.clone(), - } - } -} - impl From<CurrentUser> for UserId { /// Gets the Id of a `CurrentUser` struct. fn from(current_user: CurrentUser) -> UserId { current_user.id } @@ -792,16 +308,6 @@ impl<'a> From<&'a CurrentUser> for UserId { fn from(current_user: &CurrentUser) -> UserId { current_user.id } } -impl From<Member> for UserId { - /// Gets the Id of a `Member`. - fn from(member: Member) -> UserId { member.user.read().id } -} - -impl<'a> From<&'a Member> for UserId { - /// Gets the Id of a `Member`. - fn from(member: &Member) -> UserId { member.user.read().id } -} - impl From<User> for UserId { /// Gets the Id of a `User`. fn from(user: User) -> UserId { user.id } @@ -812,7 +318,6 @@ impl<'a> From<&'a User> for UserId { fn from(user: &User) -> UserId { user.id } } -#[cfg(feature = "model")] fn avatar_url(user_id: UserId, hash: Option<&String>) -> Option<String> { hash.map(|hash| { let ext = if hash.starts_with("a_") { @@ -825,17 +330,14 @@ fn avatar_url(user_id: UserId, hash: Option<&String>) -> Option<String> { }) } -#[cfg(feature = "model")] fn default_avatar_url(discriminator: u16) -> String { cdn!("/embed/avatars/{}.png", discriminator % 5u16) } -#[cfg(feature = "model")] fn static_avatar_url(user_id: UserId, hash: Option<&String>) -> Option<String> { hash.map(|hash| cdn!("/avatars/{}/{}.webp?size=1024", user_id, hash)) } -#[cfg(feature = "model")] fn tag(name: &str, discriminator: u16) -> String { // 32: max length of username // 1: `#` diff --git a/src/model/utils.rs b/src/model/utils.rs index f057047..e951edd 100644 --- a/src/model/utils.rs +++ b/src/model/utils.rs @@ -1,20 +1,21 @@ -use parking_lot::RwLock; use serde::de::Error as DeError; use serde::ser::{SerializeSeq, Serialize, Serializer}; use std::{ + cell::RefCell, collections::HashMap, hash::Hash, - sync::Arc + rc::Rc, }; use super::prelude::*; +#[cfg(all(feature = "cache", feature = "model"))] +use cache::Cache; + #[cfg(feature = "cache")] use internal::prelude::*; #[cfg(all(feature = "cache", feature = "model"))] use super::permissions::Permissions; -#[cfg(all(feature = "cache", feature = "model"))] -use CACHE; pub fn default_true() -> bool { true @@ -35,12 +36,12 @@ pub fn deserialize_emojis<'de, D: Deserializer<'de>>( pub fn deserialize_guild_channels<'de, D: Deserializer<'de>>( deserializer: D) - -> StdResult<HashMap<ChannelId, Arc<RwLock<GuildChannel>>>, D::Error> { + -> StdResult<HashMap<ChannelId, Rc<RefCell<GuildChannel>>>, D::Error> { let vec: Vec<GuildChannel> = Deserialize::deserialize(deserializer)?; let mut map = HashMap::new(); for channel in vec { - map.insert(channel.id, Arc::new(RwLock::new(channel))); + map.insert(channel.id, Rc::new(RefCell::new(channel))); } Ok(map) @@ -48,14 +49,14 @@ pub fn deserialize_guild_channels<'de, D: Deserializer<'de>>( pub fn deserialize_members<'de, D: Deserializer<'de>>( deserializer: D) - -> StdResult<HashMap<UserId, Member>, D::Error> { + -> StdResult<HashMap<UserId, Rc<RefCell<Member>>>, D::Error> { let vec: Vec<Member> = Deserialize::deserialize(deserializer)?; let mut members = HashMap::new(); for member in vec { - let user_id = member.user.read().id; + let user_id = member.user.borrow().id; - members.insert(user_id, member); + members.insert(user_id, Rc::new(RefCell::new(member))); } Ok(members) @@ -63,12 +64,12 @@ pub fn deserialize_members<'de, D: Deserializer<'de>>( pub fn deserialize_presences<'de, D: Deserializer<'de>>( deserializer: D) - -> StdResult<HashMap<UserId, Presence>, D::Error> { + -> StdResult<HashMap<UserId, Rc<RefCell<Presence>>>, D::Error> { let vec: Vec<Presence> = Deserialize::deserialize(deserializer)?; let mut presences = HashMap::new(); for presence in vec { - presences.insert(presence.user_id, presence); + presences.insert(presence.user_id, Rc::new(RefCell::new(presence))); } Ok(presences) @@ -76,19 +77,19 @@ pub fn deserialize_presences<'de, D: Deserializer<'de>>( pub fn deserialize_private_channels<'de, D: Deserializer<'de>>( deserializer: D) - -> StdResult<HashMap<ChannelId, Channel>, D::Error> { + -> StdResult<HashMap<ChannelId, Rc<RefCell<Channel>>>, D::Error> { let vec: Vec<Channel> = Deserialize::deserialize(deserializer)?; let mut private_channels = HashMap::new(); for private_channel in vec { let id = match private_channel { - Channel::Group(ref group) => group.read().channel_id, - Channel::Private(ref channel) => channel.read().id, + Channel::Group(ref group) => group.borrow().channel_id, + Channel::Private(ref channel) => channel.borrow().id, Channel::Guild(_) => unreachable!("Guild private channel decode"), Channel::Category(_) => unreachable!("Channel category private channel decode"), }; - private_channels.insert(id, private_channel); + private_channels.insert(id, Rc::new(RefCell::new(private_channel))); } Ok(private_channels) @@ -96,12 +97,12 @@ pub fn deserialize_private_channels<'de, D: Deserializer<'de>>( pub fn deserialize_roles<'de, D: Deserializer<'de>>( deserializer: D) - -> StdResult<HashMap<RoleId, Role>, D::Error> { + -> StdResult<HashMap<RoleId, Rc<RefCell<Role>>>, D::Error> { let vec: Vec<Role> = Deserialize::deserialize(deserializer)?; let mut roles = HashMap::new(); for role in vec { - roles.insert(role.id, role); + roles.insert(role.id, Rc::new(RefCell::new(role))); } Ok(roles) @@ -109,7 +110,7 @@ pub fn deserialize_roles<'de, D: Deserializer<'de>>( pub fn deserialize_single_recipient<'de, D: Deserializer<'de>>( deserializer: D) - -> StdResult<Arc<RwLock<User>>, D::Error> { + -> StdResult<Rc<RefCell<User>>, D::Error> { let mut users: Vec<User> = Deserialize::deserialize(deserializer)?; let user = if users.is_empty() { return Err(DeError::custom("Expected a single recipient")); @@ -117,42 +118,42 @@ pub fn deserialize_single_recipient<'de, D: Deserializer<'de>>( users.remove(0) }; - Ok(Arc::new(RwLock::new(user))) + Ok(Rc::new(RefCell::new(user))) } -pub fn deserialize_sync_user<'de, D>(deserializer: D) - -> StdResult<Arc<RwLock<User>>, D::Error> where D: Deserializer<'de> { - Ok(Arc::new(RwLock::new(User::deserialize(deserializer)?))) +pub fn deserialize_user<'de, D>(deserializer: D) + -> StdResult<Rc<RefCell<User>>, D::Error> where D: Deserializer<'de> { + Ok(Rc::new(RefCell::new(User::deserialize(deserializer)?))) } -pub fn serialize_sync_user<S: Serializer>( - user: &Arc<RwLock<User>>, +pub fn serialize_user<S: Serializer>( + user: &Rc<RefCell<User>>, serializer: S, ) -> StdResult<S::Ok, S::Error> { - User::serialize(&*user.read(), serializer) + User::serialize(&*user.borrow(), serializer) } pub fn deserialize_users<'de, D: Deserializer<'de>>( deserializer: D) - -> StdResult<HashMap<UserId, Arc<RwLock<User>>>, D::Error> { + -> StdResult<HashMap<UserId, Rc<RefCell<User>>>, D::Error> { let vec: Vec<User> = Deserialize::deserialize(deserializer)?; let mut users = HashMap::new(); for user in vec { - users.insert(user.id, Arc::new(RwLock::new(user))); + users.insert(user.id, Rc::new(RefCell::new(user))); } Ok(users) } pub fn serialize_users<S: Serializer>( - users: &HashMap<UserId, Arc<RwLock<User>>>, + users: &HashMap<UserId, Rc<RefCell<User>>>, serializer: S ) -> StdResult<S::Ok, S::Error> { let mut seq = serializer.serialize_seq(Some(users.len()))?; for user in users.values() { - seq.serialize_element(&*user.read())?; + seq.serialize_element(&*user.borrow())?; } seq.end() @@ -192,58 +193,71 @@ pub fn serialize_gen_map<K: Eq + Hash, S: Serializer, V: Serialize>( seq.end() } -pub fn serialize_gen_locked_map<K: Eq + Hash, S: Serializer, V: Serialize>( - map: &HashMap<K, Arc<RwLock<V>>>, +pub fn serialize_gen_rc_map<K: Eq + Hash, S: Serializer, V: Serialize>( + map: &HashMap<K, Rc<RefCell<V>>>, serializer: S, ) -> StdResult<S::Ok, S::Error> { let mut seq = serializer.serialize_seq(Some(map.len()))?; for value in map.values() { - seq.serialize_element(&*value.read())?; + if let Ok(item) = value.try_borrow() { + seq.serialize_element(&*item)?; + } } seq.end() } #[cfg(all(feature = "cache", feature = "model"))] -pub fn user_has_perms(channel_id: ChannelId, mut permissions: Permissions) -> Result<bool> { - let cache = CACHE.read(); - let current_user = &cache.user; +pub trait PermissionCheck { + fn user_has_perms(&self, channel_id: ChannelId, permissions: Permissions) + -> Result<bool>; +} - let channel = match cache.channel(channel_id) { - Some(channel) => channel, - None => return Err(Error::Model(ModelError::ItemMissing)), - }; +#[cfg(all(feature = "cache", feature = "model"))] +impl PermissionCheck for Cache { + fn user_has_perms( + &self, + channel_id: ChannelId, + mut permissions: Permissions, + ) -> Result<bool> { + let current_user = &self.user; + + let channel = match self.channel(channel_id) { + Some(channel) => channel, + None => return Err(Error::Model(ModelError::ItemMissing)), + }; - let guild_id = match channel { - Channel::Guild(channel) => channel.read().guild_id, - Channel::Group(_) | Channel::Private(_) | Channel::Category(_) => { - // Both users in DMs, and all users in groups and maybe all channels in categories will - // have the same - // permissions. - // - // The only exception to this is when the current user is blocked by - // the recipient in a DM channel, which results in the current user - // not being able to send messages. - // - // Since serenity can't _reasonably_ check and keep track of these, - // just assume that all permissions are granted and return `true`. - return Ok(true); - }, - }; + let guild_id = match channel { + Channel::Guild(channel) => channel.borrow().guild_id, + Channel::Group(_) | Channel::Private(_) | Channel::Category(_) => { + // Both users in DMs, and all users in groups and maybe all channels in categories will + // have the same + // permissions. + // + // The only exception to this is when the current user is blocked by + // the recipient in a DM channel, which results in the current user + // not being able to send messages. + // + // Since serenity can't _reasonably_ check and keep track of these, + // just assume that all permissions are granted and return `true`. + return Ok(true); + }, + }; - let guild = match cache.guild(guild_id) { - Some(guild) => guild, - None => return Err(Error::Model(ModelError::ItemMissing)), - }; + let guild = match self.guilds.get(&guild_id) { + Some(guild) => guild, + None => return Err(Error::Model(ModelError::ItemMissing)), + }; - let perms = guild - .read() - .permissions_in(channel_id, current_user.id); + let perms = guild + .borrow() + .permissions_in(channel_id, current_user.id); - permissions.remove(perms); + permissions.remove(perms); - Ok(permissions.is_empty()) + Ok(permissions.is_empty()) + } } macro_rules! num_visitors { diff --git a/src/model/webhook.rs b/src/model/webhook.rs index 8f2c6b2..511a283 100644 --- a/src/model/webhook.rs +++ b/src/model/webhook.rs @@ -2,24 +2,13 @@ use super::{ id::{ - ChannelId, - GuildId, + ChannelId, + GuildId, WebhookId }, user::User }; -#[cfg(feature = "model")] -use builder::ExecuteWebhook; -#[cfg(feature = "model")] -use internal::prelude::*; -#[cfg(feature = "model")] -use std::mem; -#[cfg(feature = "model")] -use super::channel::Message; -#[cfg(feature = "model")] -use {http, utils}; - /// A representation of a webhook, which is a low-effort way to post messages to /// channels. They do not necessarily require a bot user or authentication to /// use. @@ -52,188 +41,3 @@ pub struct Webhook { /// **Note**: This is not received when getting a webhook by its token. pub user: Option<User>, } - -#[cfg(feature = "model")] -impl Webhook { - /// Deletes the webhook. - /// - /// As this calls the [`http::delete_webhook_with_token`] function, - /// authentication is not required. - /// - /// [`http::delete_webhook_with_token`]: ../http/fn.delete_webhook_with_token.html - #[inline] - pub fn delete(&self) -> Result<()> { http::delete_webhook_with_token(self.id.0, &self.token) } - - /// - /// Edits the webhook in-place. All fields are optional. - /// - /// To nullify the avatar, pass `Some("")`. Otherwise, passing `None` will - /// not modify the avatar. - /// - /// Refer to [`http::edit_webhook`] for httprictions on editing webhooks. - /// - /// As this calls the [`http::edit_webhook_with_token`] function, - /// authentication is not required. - /// - /// # Examples - /// - /// Editing a webhook's name: - /// - /// ```rust,no_run - /// use serenity::http; - /// - /// let id = 245037420704169985; - /// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV"; - /// - /// let mut webhook = http::get_webhook_with_token(id, token) - /// .expect("valid webhook"); - /// - /// let _ = webhook.edit(Some("new name"), None).expect("Error editing"); - /// ``` - /// - /// Setting a webhook's avatar: - /// - /// ```rust,no_run - /// use serenity::http; - /// - /// let id = 245037420704169985; - /// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV"; - /// - /// let mut webhook = http::get_webhook_with_token(id, token) - /// .expect("valid webhook"); - /// - /// let image = serenity::utils::read_image("./webhook_img.png") - /// .expect("Error reading image"); - /// - /// let _ = webhook.edit(None, Some(&image)).expect("Error editing"); - /// ``` - /// - /// [`http::edit_webhook`]: ../http/fn.edit_webhook.html - /// [`http::edit_webhook_with_token`]: ../http/fn.edit_webhook_with_token.html - pub fn edit(&mut self, name: Option<&str>, avatar: Option<&str>) -> Result<()> { - if name.is_none() && avatar.is_none() { - return Ok(()); - } - - let mut map = Map::new(); - - if let Some(avatar) = avatar { - map.insert( - "avatar".to_string(), - if avatar.is_empty() { - Value::Null - } else { - Value::String(avatar.to_string()) - }, - ); - } - - if let Some(name) = name { - map.insert("name".to_string(), Value::String(name.to_string())); - } - - match http::edit_webhook_with_token(self.id.0, &self.token, &map) { - Ok(replacement) => { - mem::replace(self, replacement); - - Ok(()) - }, - Err(why) => Err(why), - } - } - - /// Executes a webhook with the fields set via the given builder. - /// - /// The builder provides a method of setting only the fields you need, - /// without needing to pass a long set of arguments. - /// - /// # Examples - /// - /// Execute a webhook with message content of `test`: - /// - /// ```rust,no_run - /// use serenity::http; - /// - /// let id = 245037420704169985; - /// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV"; - /// - /// let mut webhook = http::get_webhook_with_token(id, token) - /// .expect("valid webhook"); - /// - /// let _ = webhook.execute(false, |mut w| { - /// w.content("test"); - /// - /// w - /// }); - /// ``` - /// - /// Execute a webhook with message content of `test`, overriding the - /// username to `serenity`, and sending an embed: - /// - /// ```rust,no_run - /// use serenity::http; - /// use serenity::model::channel::Embed; - /// - /// let id = 245037420704169985; - /// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV"; - /// - /// let mut webhook = http::get_webhook_with_token(id, token) - /// .expect("valid webhook"); - /// - /// let embed = Embed::fake(|mut e| { - /// e.title("Rust's website"); - /// e.description("Rust is a systems programming language that runs - /// blazingly fast, prevents segfaults, and guarantees - /// thread safety."); - /// e.url("https://rust-lang.org"); - /// - /// e - /// }); - /// - /// let _ = webhook.execute(false, |mut w| { - /// w.content("test"); - /// w.username("serenity"); - /// w.embeds(vec![embed]); - /// - /// w - /// }); - /// ``` - #[inline] - pub fn execute<F: FnOnce(ExecuteWebhook) -> ExecuteWebhook>(&self, - wait: bool, - f: F) - -> Result<Option<Message>> { - let map = utils::vecmap_to_json_map(f(ExecuteWebhook::default()).0); - - http::execute_webhook(self.id.0, &self.token, wait, &map) - } - - /// Retrieves the latest information about the webhook, editing the - /// webhook in-place. - /// - /// As this calls the [`http::get_webhook_with_token`] function, - /// authentication is not required. - /// - /// [`http::get_webhook_with_token`]: ../http/fn.get_webhook_with_token.html - pub fn refresh(&mut self) -> Result<()> { - match http::get_webhook_with_token(self.id.0, &self.token) { - Ok(replacement) => { - let _ = mem::replace(self, replacement); - - Ok(()) - }, - Err(why) => Err(why), - } - } -} - -#[cfg(feature = "model")] -impl WebhookId { - /// Retrieves the webhook by the Id. - /// - /// **Note**: Requires the [Manage Webhooks] permission. - /// - /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html - #[inline] - pub fn get(&self) -> Result<Webhook> { http::get_webhook(self.0) } -} |