From b7cdf1542cb9199c61c0b17bdd381d4f117f635e Mon Sep 17 00:00:00 2001 From: acdenisSK Date: Sun, 15 Oct 2017 17:09:33 +0200 Subject: Hash and do equality on just the id for `User` --- src/model/user.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/model/user.rs b/src/model/user.rs index 39efa10..c1c3135 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -343,7 +343,7 @@ impl Default for OnlineStatus { } /// Information about a user. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] +#[derive(Clone, Debug, Deserialize)] pub struct User { /// The unique Id of the user. Can be used to calculate the account's /// cration date. @@ -365,6 +365,22 @@ pub struct User { pub name: String, } +use std::hash::{Hash, Hasher}; + +impl PartialEq for User { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Eq for User {} + +impl Hash for User { + fn hash(&self, hasher: &mut H) { + self.id.hash(hasher); + } +} + #[cfg(feature = "model")] impl User { /// Returns the formatted URL of the user's icon, if one exists. -- cgit v1.2.3 From fcc4e2ce2e523248ed33c9f4853d3485cbc9b6e6 Mon Sep 17 00:00:00 2001 From: Edward Yang Date: Mon, 16 Oct 2017 07:29:39 -0500 Subject: Use update syntax for Shard (#195) --- src/gateway/shard.rs | 51 +++++++++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/gateway/shard.rs b/src/gateway/shard.rs index fbe1572..f3c072c 100644 --- a/src/gateway/shard.rs +++ b/src/gateway/shard.rs @@ -128,14 +128,6 @@ impl Shard { -> Result { let client = connect(&*ws_url.lock().unwrap())?; - let current_presence = (None, OnlineStatus::Online, false); - let heartbeat_instants = (None, None); - let heartbeat_interval = None; - let last_heartbeat_acknowledged = true; - let seq = 0; - let stage = ConnectionStage::Handshake; - let session_id = None; - Ok(feature_voice! { { let (tx, rx) = mpsc::channel(); @@ -144,32 +136,20 @@ impl Shard { Shard { client, - current_presence, - heartbeat_instants, - heartbeat_interval, - last_heartbeat_acknowledged, - seq, - stage, token, - session_id, shard_info, ws_url, manager: VoiceManager::new(tx, user.id), manager_rx: rx, + .. Default::default() } } else { Shard { client, - current_presence, - heartbeat_instants, - heartbeat_interval, - last_heartbeat_acknowledged, - seq, - stage, token, - session_id, shard_info, ws_url, + .. Default::default() } } }) @@ -1043,3 +1023,30 @@ fn build_gateway_url(base: &str) -> Result { Url::parse(&format!("{}?v={}", base, constants::GATEWAY_VERSION)) .map_err(|_| Error::Gateway(GatewayError::BuildingUrl)) } + +impl Default for Shard { + fn default() -> Self { + #[cfg(feature = "voice")] + let (tx, rx) = mpsc::channel(); + #[cfg(feature = "voice")] + let user = http::get_current_user().unwrap(); + + Shard { + client: connect(&String::new()).unwrap(), + current_presence: (None, OnlineStatus::Online, false), + heartbeat_instants: (None, None), + heartbeat_interval: None, + last_heartbeat_acknowledged: true, + seq: 0, + stage: ConnectionStage::Handshake, + token: Arc::new(Mutex::new(String::new())), + session_id: None, + shard_info: [0; 2], + ws_url: Arc::new(Mutex::new(String::new())), + #[cfg(feature = "voice")] + manager: VoiceManager::new(tx, user.id), + #[cfg(feature = "voice")] + manager_rx: rx, + } + } +} -- cgit v1.2.3 From c7aa27dbb64e64d70c7f13725c79017c4bba1c95 Mon Sep 17 00:00:00 2001 From: acdenisSK Date: Mon, 16 Oct 2017 14:39:57 +0200 Subject: defer to `delete_message` if there's just one message to delete --- src/model/channel/channel_id.rs | 10 +++++++--- src/model/channel/guild_channel.rs | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/model/channel/channel_id.rs b/src/model/channel/channel_id.rs index f728191..2e415dc 100644 --- a/src/model/channel/channel_id.rs +++ b/src/model/channel/channel_id.rs @@ -122,10 +122,14 @@ impl ChannelId { .into_iter() .map(|message_id| message_id.as_ref().0) .collect::>(); + + if ids.len() == 1 { + self.delete_message(ids[0]) + } else { + let map = json!({ "messages": ids }); - let map = json!({ "messages": ids }); - - http::delete_messages(self.0, &map) + http::delete_messages(self.0, &map) + } } /// Deletes all permission overrides in the channel from a member or role. diff --git a/src/model/channel/guild_channel.rs b/src/model/channel/guild_channel.rs index e2e5f09..d6649b4 100644 --- a/src/model/channel/guild_channel.rs +++ b/src/model/channel/guild_channel.rs @@ -258,7 +258,7 @@ impl GuildChannel { /// [`Channel::delete_messages`]: enum.Channel.html#method.delete_messages /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html #[inline] - pub fn delete_messages<'a, It: IntoIterator>(&self, message_ids: It) -> Result<()> { + pub fn delete_messages, It: IntoIterator>(&self, message_ids: It) -> Result<()> { self.id.delete_messages(message_ids) } -- cgit v1.2.3 From e57b510edd640abb243664337a1c163924313612 Mon Sep 17 00:00:00 2001 From: acdenisSK Date: Tue, 17 Oct 2017 22:24:12 +0200 Subject: Use the underlaying integer value of a `ChannelType` variant --- src/model/guild/guild_id.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/model/guild/guild_id.rs b/src/model/guild/guild_id.rs index 4b9a713..64e1181 100644 --- a/src/model/guild/guild_id.rs +++ b/src/model/guild/guild_id.rs @@ -110,7 +110,7 @@ impl GuildId { pub fn create_channel(&self, name: &str, kind: ChannelType) -> Result { let map = json!({ "name": name, - "type": kind.name(), + "type": kind as u8, }); http::create_channel(self.0, &map) -- cgit v1.2.3 From f9e5e76585a1f6317dadb67e440765b0070ca131 Mon Sep 17 00:00:00 2001 From: acdenisSK Date: Wed, 18 Oct 2017 20:56:07 +0200 Subject: Add `PartialEq` impls and doc tests to `Args` --- src/framework/standard/args.rs | 155 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 154 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/framework/standard/args.rs b/src/framework/standard/args.rs index 7e3d579..8190dd5 100644 --- a/src/framework/standard/args.rs +++ b/src/framework/standard/args.rs @@ -78,6 +78,17 @@ impl Args { } /// Removes the first element, parses it to a specific type if necessary, returns. + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new("42 69", " "); + /// + /// assert_eq!(args.single::().unwrap(), 42); + /// assert_eq!(args, ["69"]); + /// ``` pub fn single(&mut self) -> Result where T::Err: StdError { if self.delimiter_split.is_empty() { @@ -92,6 +103,17 @@ impl Args { /// Like [`single`], but doesn't remove the element. /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let args = Args::new("42 69", " "); + /// + /// assert_eq!(args.single_n::().unwrap(), 42); + /// assert!(args == ["42", "69"]); + /// ``` + /// /// [`single`]: #method.single pub fn single_n(&self) -> Result where T::Err: StdError { @@ -106,10 +128,31 @@ impl Args { } /// Skips if there's a first element, but also returns it. + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new("42 69", " "); + /// + /// assert_eq!(args.skip().unwrap(), "42"); + /// assert_eq!(args, ["69"]); + /// ``` pub fn skip(&mut self) -> Option { self.delimiter_split.shift() } /// Like [`skip`], but allows for multiple at once. /// + /// # Examples + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new("42 69 88 99", " "); + /// + /// assert_eq!(*args.skip_for(3).unwrap(), ["42".to_string(), "69".to_string(), "88".to_string()]); + /// assert_eq!(args, ["99"]); + /// ``` + /// /// [`skip`]: #method.skip pub fn skip_for(&mut self, i: u32) -> Option> { let mut vec = Vec::with_capacity(i as usize); @@ -122,7 +165,18 @@ impl Args { } /// Like [`single`], but takes quotes into account. - /// + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new(r#""42"#, " "); + /// + /// assert_eq!(args.single_quoted::().unwrap(), 42); + /// assert!(args.is_empty()); + /// ``` + /// /// [`single`]: #method.single pub fn single_quoted(&mut self) -> Result where T::Err: StdError { @@ -157,20 +211,62 @@ impl Args { } /// Empty outs the internal vector while parsing (if necessary) and returning them + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new("42 69", " "); + /// + /// assert_eq!(*args.list::().unwrap(), [42, 69]); + /// ``` pub fn list(mut self) -> Result, T::Err> where T::Err: StdError { Iter::::new(&mut self).collect() } /// Provides an iterator of items: (`T: FromStr`) `Result`. + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new("3 4", " "); + /// + /// assert_eq!(*args.iter::().map(|num| num.unwrap().pow(2)).collect::>(), [9, 16]); + /// assert!(args.is_empty()); + /// ``` pub fn iter(&mut self) -> Iter where T::Err: StdError { Iter::new(self) } /// This method is just `internal_vector.join(delimiter)` + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new("42 69", " "); + /// + /// assert_eq!(args.full(), "42 69"); + /// ``` pub fn full(&self) -> String { self.delimiter_split.join(&self.delimiter) } /// Returns the first argument that can be converted and removes it from the list. + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new("c47 69", " "); + /// + /// assert_eq!(args.find::().unwrap(), 69); + /// assert_eq!(args, ["c47"]); + /// ``` pub fn find(&mut self) -> Result where T::Err: StdError { if self.delimiter_split.is_empty() { @@ -196,6 +292,17 @@ impl Args { } /// Returns the first argument that can be converted and does not remove it from the list. + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let args = Args::new("c47 69", " "); + /// + /// assert_eq!(args.find_n::().unwrap(), 69); + /// assert!(args == ["c47", "69"]); + /// ``` pub fn find_n(&self) -> Result where T::Err: StdError { if self.delimiter_split.is_empty() { @@ -216,6 +323,52 @@ impl ::std::ops::Deref for Args { fn deref(&self) -> &Self::Target { &self.delimiter_split } } +impl<'a> PartialEq<[&'a str]> for Args { + fn eq(&self, other: &[&str]) -> bool { + let mut b = true; + + for (s, o) in self.delimiter_split.iter().zip(other.iter()) { + if s != o { + b = false; + } + } + + b + } +} + +macro_rules! impl_slices { + ($($num:expr),*) => { + impl<'a> PartialEq<[&'a str; 0]> for Args { + fn eq(&self, _: &[&str; 0]) -> bool { + self.delimiter_split.is_empty() + } + } + + $( + impl<'a> PartialEq<[&'a str; $num]> for Args { + fn eq(&self, other: &[&str; $num]) -> bool { + >::eq(self, &other[..]) + } + } + )* + } +} + +impl_slices! { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31, 32 +} + +impl PartialEq for Args { + fn eq(&self, other: &Self) -> bool { + self.delimiter_split == other.delimiter_split + } +} + +impl Eq for Args {} + use std::marker::PhantomData; /// Provides `list`'s functionality, but as an iterator. -- cgit v1.2.3 From f5b19305f07fd5e5a63f9c919d1cf9160d49f21e Mon Sep 17 00:00:00 2001 From: acdenisSK Date: Wed, 18 Oct 2017 20:58:52 +0200 Subject: `assert` -> `assert_eq` --- src/framework/standard/args.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/framework/standard/args.rs b/src/framework/standard/args.rs index 8190dd5..630143d 100644 --- a/src/framework/standard/args.rs +++ b/src/framework/standard/args.rs @@ -111,7 +111,7 @@ impl Args { /// let args = Args::new("42 69", " "); /// /// assert_eq!(args.single_n::().unwrap(), 42); - /// assert!(args == ["42", "69"]); + /// assert_eq!(args, ["42", "69"]); /// ``` /// /// [`single`]: #method.single @@ -301,7 +301,7 @@ impl Args { /// let args = Args::new("c47 69", " "); /// /// assert_eq!(args.find_n::().unwrap(), 69); - /// assert!(args == ["c47", "69"]); + /// assert_eq!(args, ["c47", "69"]); /// ``` pub fn find_n(&self) -> Result where T::Err: StdError { -- cgit v1.2.3 From bae0b4a3efde572543a4bf61e0a2484d8d451256 Mon Sep 17 00:00:00 2001 From: acdenisSK Date: Wed, 18 Oct 2017 21:02:00 +0200 Subject: Add a missing break --- src/framework/standard/args.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/framework/standard/args.rs b/src/framework/standard/args.rs index 630143d..d299b64 100644 --- a/src/framework/standard/args.rs +++ b/src/framework/standard/args.rs @@ -330,6 +330,7 @@ impl<'a> PartialEq<[&'a str]> for Args { for (s, o) in self.delimiter_split.iter().zip(other.iter()) { if s != o { b = false; + break; } } -- cgit v1.2.3 From f47a0c831efe5842ca38cb1067de361ae42f6edc Mon Sep 17 00:00:00 2001 From: Ken Swenson Date: Thu, 19 Oct 2017 08:27:40 -0400 Subject: Implement changing a role's position (#201) --- src/http/mod.rs | 18 ++++++++++++++++++ src/model/guild/guild_id.rs | 21 +++++++++++++++++++++ src/model/guild/mod.rs | 20 ++++++++++++++++++++ 3 files changed, 59 insertions(+) (limited to 'src') diff --git a/src/http/mod.rs b/src/http/mod.rs index 0abed02..099a33c 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -814,6 +814,24 @@ pub fn edit_role(guild_id: u64, role_id: u64, map: &JsonMap) -> Result { .map_err(From::from) } +/// Changes the position of a role in a guild. +pub fn edit_role_position(guild_id: u64, role_id: u64, position: u64) -> Result> { + let body = serde_json::to_string(&json!({ + "id": role_id, + "position": position, + }))?; + let response = request!( + Route::GuildsIdRolesId(guild_id), + patch(body), + "/guilds/{}/roles/{}", + guild_id, + role_id + ); + + serde_json::from_reader::>(response) + .map_err(From::from) +} + /// Edits a the webhook with the given data. /// /// The Value is a map with optional values of: diff --git a/src/model/guild/guild_id.rs b/src/model/guild/guild_id.rs index 64e1181..622d059 100644 --- a/src/model/guild/guild_id.rs +++ b/src/model/guild/guild_id.rs @@ -303,6 +303,27 @@ impl GuildId { http::edit_role(self.0, role_id.into().0, &f(EditRole::default()).0) } + /// 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(&self, role_id: R, position: u64) -> Result> + where R: Into { + 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>> { CACHE.read().unwrap().guild(*self) } diff --git a/src/model/guild/mod.rs b/src/model/guild/mod.rs index 5a5ab63..aa9fd1d 100644 --- a/src/model/guild/mod.rs +++ b/src/model/guild/mod.rs @@ -597,6 +597,26 @@ impl Guild { 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(&self, role_id: R, position: u64) -> Result> + where R: Into { + 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. -- cgit v1.2.3 From 74ec713825b2b4c55382fb76fa57bd967e66b3aa Mon Sep 17 00:00:00 2001 From: Zeyla Hellyer Date: Mon, 16 Oct 2017 06:38:15 -0700 Subject: Re-export parking_lot::{Arc, Mutex} from prelude These two types are used in public APIs, and so by re-exporting them it makes it easier for the user to use them without needing to depend on `parking_lot` themselves. --- src/prelude.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/prelude.rs b/src/prelude.rs index ff27bb6..435fb8a 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -16,6 +16,7 @@ pub use error::Error as SerenityError; pub use model::Mentionable; +pub use parking_lot::{Mutex, RwLock}; #[cfg(feature = "client")] pub use client::{Client, ClientError as ClientError, Context, EventHandler}; -- cgit v1.2.3 From 23ff6f21019bc94f8dc32355fa34691b881bfb69 Mon Sep 17 00:00:00 2001 From: Zeyla Hellyer Date: Mon, 16 Oct 2017 20:19:27 -0700 Subject: Deprecate some text-only Channel methods Some methods on `model::Channel` are only for text channels. Methods on Channel should be applicable to all channel types. Deprecates the following methods on `model::Channel`: - `create_reaction` - `delete_message` - `delete_reaction` - `edit_message` - `message` - `messages` - `reaction_users` - `say` - `send_files` - `send_message` - `unpin` --- src/model/channel/mod.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'src') diff --git a/src/model/channel/mod.rs b/src/model/channel/mod.rs index 4ba0322..07a73d7 100644 --- a/src/model/channel/mod.rs +++ b/src/model/channel/mod.rs @@ -67,6 +67,7 @@ impl Channel { /// [`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(&self, message_id: M, reaction_type: R) -> Result<()> where M: Into, R: Into { @@ -110,6 +111,7 @@ impl Channel { /// [`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>(&self, message_id: M) -> Result<()> { self.id().delete_message(message_id) @@ -123,6 +125,7 @@ impl Channel { /// [`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(&self, message_id: M, @@ -154,6 +157,7 @@ impl Channel { /// [`Message`]: struct.Message.html /// [`the limit`]: ../builder/struct.CreateMessage.html#method.content #[cfg(feature = "model")] + #[deprecated(since = "0.4.2", note = "Use the inner channel's method")] #[inline] pub fn edit_message(&self, message_id: M, f: F) -> Result where F: FnOnce(CreateMessage) -> CreateMessage, M: Into { @@ -181,6 +185,7 @@ impl Channel { /// /// [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>(&self, message_id: M) -> Result { self.id().message(message_id) @@ -203,6 +208,7 @@ impl Channel { /// /// [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(&self, f: F) -> Result> where F: FnOnce(GetMessages) -> GetMessages { @@ -226,6 +232,7 @@ impl Channel { /// [`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(&self, message_id: M, @@ -264,6 +271,7 @@ impl Channel { /// [`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 { self.id().say(content) } @@ -286,6 +294,7 @@ impl Channel { /// [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>(&self, files: It, f: F) -> Result where F: FnOnce(CreateMessage) -> CreateMessage, T: Into> { @@ -312,6 +321,7 @@ impl Channel { /// [`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(&self, f: F) -> Result where F: FnOnce(CreateMessage) -> CreateMessage { @@ -325,6 +335,7 @@ impl Channel { /// [`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>(&self, message_id: M) -> Result<()> { self.id().unpin(message_id) -- cgit v1.2.3 From 585ac6e6ca792facf29063776c83262fa849161b Mon Sep 17 00:00:00 2001 From: Zeyla Hellyer Date: Thu, 19 Oct 2017 15:49:31 -0700 Subject: Fix shard connection Fixes shard connections by removing the Default implementation for Shards. This was because when instantiating a Shard, `..Default::default()` syntax was used, which calls the `Default` implementation of the struct and moves the necessary values over to the overlying instantiation. When using `..Default::default()` syntax, all values in the Default implementation are instantiated, even ones that could be deemed unnecessary. For example, in the following sample code: ```rust struct Foo { x: bool, y: bool, } impl Default for Foo { fn default() -> Self { Self { x: true, y: true, } } } // ... let foo = Foo { x: false, ..Default::default() }; ``` `Foo::x` will still be instantiated in the Default implementation despite a value being set in the user's instantiation of Foo. This causes WebSocket client instantiation to be invalid in the Default instantiation, because the WebSocket instantiated has no base URL, causing an error to be returned. This commit resolves this issue by simply not having a Default implementation - due to the fact that a default implementation will never be valid and successful - and instead set all values in `Shard::new`. Notes: This issue surfaced with commit fcc4e2ce2e523248ed33c9f4853d3485cbc9b6e6 (PR #195). Closes #203. --- src/gateway/shard.rs | 55 +++++++++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/gateway/shard.rs b/src/gateway/shard.rs index f3c072c..87940fd 100644 --- a/src/gateway/shard.rs +++ b/src/gateway/shard.rs @@ -128,6 +128,14 @@ impl Shard { -> Result { let client = connect(&*ws_url.lock().unwrap())?; + let current_presence = (None, OnlineStatus::Online, false); + let heartbeat_instants = (None, None); + let heartbeat_interval = None; + let last_heartbeat_acknowledged = true; + let seq = 0; + let stage = ConnectionStage::Handshake; + let session_id = None; + Ok(feature_voice! { { let (tx, rx) = mpsc::channel(); @@ -136,20 +144,32 @@ impl Shard { Shard { client, + current_presence, + heartbeat_instants, + heartbeat_interval, + last_heartbeat_acknowledged, + seq, + stage, token, shard_info, + session_id, ws_url, manager: VoiceManager::new(tx, user.id), manager_rx: rx, - .. Default::default() } } else { Shard { client, + current_presence, + heartbeat_instants, + heartbeat_interval, + last_heartbeat_acknowledged, + seq, + stage, token, + session_id, shard_info, ws_url, - .. Default::default() } } }) @@ -1021,32 +1041,9 @@ fn set_client_timeout(client: &mut WsClient) -> Result<()> { fn build_gateway_url(base: &str) -> Result { Url::parse(&format!("{}?v={}", base, constants::GATEWAY_VERSION)) - .map_err(|_| Error::Gateway(GatewayError::BuildingUrl)) -} + .map_err(|why| { + warn!("Error building gateway URL with base `{}`: {:?}", base, why); -impl Default for Shard { - fn default() -> Self { - #[cfg(feature = "voice")] - let (tx, rx) = mpsc::channel(); - #[cfg(feature = "voice")] - let user = http::get_current_user().unwrap(); - - Shard { - client: connect(&String::new()).unwrap(), - current_presence: (None, OnlineStatus::Online, false), - heartbeat_instants: (None, None), - heartbeat_interval: None, - last_heartbeat_acknowledged: true, - seq: 0, - stage: ConnectionStage::Handshake, - token: Arc::new(Mutex::new(String::new())), - session_id: None, - shard_info: [0; 2], - ws_url: Arc::new(Mutex::new(String::new())), - #[cfg(feature = "voice")] - manager: VoiceManager::new(tx, user.id), - #[cfg(feature = "voice")] - manager_rx: rx, - } - } + Error::Gateway(GatewayError::BuildingUrl) + }) } -- cgit v1.2.3 From fbd625839e6a2e01b16e6c3814cb9b9f31dc7caa Mon Sep 17 00:00:00 2001 From: Mei Boudreau Date: Thu, 19 Oct 2017 22:50:38 -0400 Subject: Fix clippy warnings --- src/framework/standard/help_commands.rs | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/framework/standard/help_commands.rs b/src/framework/standard/help_commands.rs index 0c018eb..5c1c58f 100644 --- a/src/framework/standard/help_commands.rs +++ b/src/framework/standard/help_commands.rs @@ -51,7 +51,7 @@ fn remove_aliases(cmds: &HashMap) -> HashMap<&String, &I result } -/// Checks whether a user is member of required roles +/// Checks whether a user is member of required roles /// and given the required permissions. pub fn has_all_requirements(cmd: &Command, msg: &Message) -> bool { if let Some(guild) = msg.guild() { @@ -62,9 +62,9 @@ pub fn has_all_requirements(cmd: &Command, msg: &Message) -> bool { if let Ok(permissions) = member.permissions() { if cmd.allowed_roles.is_empty() { - return permissions.administrator() || has_correct_permissions(&cmd, &msg); + return permissions.administrator() || has_correct_permissions(cmd, msg); } else { - return permissions.administrator() || (has_correct_roles(&cmd, &guild, &member) && has_correct_permissions(cmd, msg)); + return permissions.administrator() || (has_correct_roles(cmd, &guild, member) && has_correct_permissions(cmd, msg)); } } } @@ -111,19 +111,19 @@ pub fn with_embeds(_: &mut Context, if name == with_prefix || name == *command_name { match *command { CommandOrAlias::Command(ref cmd) => { - if has_all_requirements(&cmd, &msg) { + if has_all_requirements(cmd, msg) { found = Some((command_name, cmd)); } else { break; - } + } }, CommandOrAlias::Alias(ref name) => { - let actual_command = group.commands.get(name).unwrap(); + let actual_command = &group.commands[name]; match *actual_command { CommandOrAlias::Command(ref cmd) => { - if has_all_requirements(&cmd, &msg) { + if has_all_requirements(cmd, msg) { found = Some((name, cmd)); } else { @@ -230,12 +230,9 @@ pub fn with_embeds(_: &mut Context, for name in command_names { let cmd = &commands[name]; - if cmd.help_available { - - if cmd.help_available && has_all_requirements(&cmd, &msg) { - let _ = write!(desc, "`{}`\n", name); - has_commands = true; - } + if cmd.help_available && has_all_requirements(cmd, msg) { + let _ = write!(desc, "`{}`\n", name); + has_commands = true; } } @@ -289,7 +286,7 @@ pub fn plain(_: &mut Context, if name == with_prefix || name == *command_name { match *command { CommandOrAlias::Command(ref cmd) => { - if has_all_requirements(&cmd, &msg) { + if has_all_requirements(cmd, msg) { found = Some((command_name, cmd)); } else { @@ -297,11 +294,11 @@ pub fn plain(_: &mut Context, } }, CommandOrAlias::Alias(ref name) => { - let actual_command = group.commands.get(name).unwrap(); + let actual_command = &group.commands[name]; match *actual_command { CommandOrAlias::Command(ref cmd) => { - if has_all_requirements(&cmd, &msg) { + if has_all_requirements(cmd, msg) { found = Some((name, cmd)); } else { @@ -390,7 +387,7 @@ pub fn plain(_: &mut Context, for name in command_names { let cmd = &commands[name]; - if cmd.help_available && has_all_requirements(&cmd, &msg) { + if cmd.help_available && has_all_requirements(cmd, msg) { let _ = write!(group_help, "`{}` ", name); } } -- cgit v1.2.3 From 9428787abb6126ba05bfef96cd2b8d2a217fdf5d Mon Sep 17 00:00:00 2001 From: acdenisSK Date: Sat, 21 Oct 2017 12:27:23 +0200 Subject: Add "zero-copy" parsing --- src/framework/standard/args.rs | 82 ++++++++++++++++++++++++++++++++++++++++++ src/framework/standard/mod.rs | 2 +- 2 files changed, 83 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/framework/standard/args.rs b/src/framework/standard/args.rs index d299b64..7fb82e4 100644 --- a/src/framework/standard/args.rs +++ b/src/framework/standard/args.rs @@ -101,6 +101,36 @@ impl Args { .parse::()?) } + /// Like [`single`], but does "zero-copy" parsing. + /// + /// Refer to [`FromStrZc`]'s example on how to use this method. + /// + /// [`single`]: #method.single + /// [`FromStrZc`]: trait.FromStrZc.html + pub fn single_zc<'a, T: FromStrZc<'a> + 'a>(&'a mut self) -> Result + where T::Err: StdError { + + // This is a hack as to mitigate some nasty lifetime errors. + // + // (Culprit `Vec::remove`s return type) + fn get_and_remove(b: &mut Vec) -> Option<&str> { + struct GetThenRemove<'a>(&'a mut Vec); + + impl<'a> Drop for GetThenRemove<'a> { + fn drop(&mut self) { + if !self.0.is_empty() { + self.0.remove(0); + } + } + } + + GetThenRemove(b).0.get(0).map(|s| s.as_str()) + } + + let a = get_and_remove(&mut self.delimiter_split).ok_or(Error::Eos)?; + Ok(FromStrZc::from_str(a)?) + } + /// Like [`single`], but doesn't remove the element. /// /// # Examples @@ -317,6 +347,58 @@ impl Args { } } +/// A version of `FromStr` that allows for "zero-copy" parsing. +/// +/// # Examples +/// +/// ```rust,ignore +/// use serenity::framework::standard::{Args, FromStrZc}; +/// use std::fmt; +/// +/// struct NameDiscrim<'a>(&'a str, Option<&'a str>); +/// +/// #[derive(Debug)] +/// struct Error(&'static str); +/// +/// impl std::error::Error for Error { +/// fn description(&self) -> &str { self.0 } +/// } +/// +/// impl fmt::Display for Error { +/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) } +/// } +/// +/// impl<'a> FromStrZc<'a> for NameDiscrim<'a> { +/// type Err = Error; +/// +/// fn from_str(s: &'a str) -> Result, Error> { +/// let mut it = s.split("#"); +/// let name = it.next().ok_or(Error("name must be specified"))?; +/// let discrim = it.next(); +/// Ok(NameDiscrim(name, discrim)) +/// } +/// } +/// +/// let mut args = Args::new("abc#1234", " "); +/// let NameDiscrim(name, discrim) = args.single_zc::().unwrap(); +/// +/// assert_eq!(name, "abc"); +/// assert_eq!(discrim, Some("1234")); +/// ``` +pub trait FromStrZc<'a>: Sized { + type Err; + + fn from_str(s: &'a str) -> ::std::result::Result; +} + +impl<'a, T: FromStr> FromStrZc<'a> for T { + type Err = T::Err; + + fn from_str(s: &'a str) -> ::std::result::Result { + ::from_str(s) + } +} + impl ::std::ops::Deref for Args { type Target = [String]; diff --git a/src/framework/standard/mod.rs b/src/framework/standard/mod.rs index 38c1d2d..947a885 100644 --- a/src/framework/standard/mod.rs +++ b/src/framework/standard/mod.rs @@ -14,7 +14,7 @@ pub use self::command::CommandOrAlias; pub use self::configuration::Configuration; pub use self::create_command::CreateCommand; pub use self::create_group::CreateGroup; -pub use self::args::{Args, Error as ArgError}; +pub use self::args::{Args, Iter, FromStrZc, Error as ArgError}; use self::command::{AfterHook, BeforeHook}; use std::collections::HashMap; -- cgit v1.2.3 From e02d5fb8171b11214e1502c6754fef1972bbf1b9 Mon Sep 17 00:00:00 2001 From: Lakelezz <12222135+Lakelezz@users.noreply.github.com> Date: Mon, 23 Oct 2017 21:10:47 +0200 Subject: Properly update emojis, fix shard retries, fix cs * If a guild's emojis are being altered, Serenity will straight up use the new `HashMap` instead of just extending. If `connect()` returns an `Err`, it will retry connecting. Cleaned up `help_command.rs`. --- src/framework/standard/help_commands.rs | 6 ++---- src/gateway/shard.rs | 18 +++++++++++++++++- src/model/event.rs | 4 +++- 3 files changed, 22 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/framework/standard/help_commands.rs b/src/framework/standard/help_commands.rs index 5c1c58f..6b85239 100644 --- a/src/framework/standard/help_commands.rs +++ b/src/framework/standard/help_commands.rs @@ -113,8 +113,7 @@ pub fn with_embeds(_: &mut Context, CommandOrAlias::Command(ref cmd) => { if has_all_requirements(cmd, msg) { found = Some((command_name, cmd)); - } - else { + } else { break; } }, @@ -125,8 +124,7 @@ pub fn with_embeds(_: &mut Context, CommandOrAlias::Command(ref cmd) => { if has_all_requirements(cmd, msg) { found = Some((name, cmd)); - } - else { + } else { break; } }, diff --git a/src/gateway/shard.rs b/src/gateway/shard.rs index 87940fd..3a8209d 100644 --- a/src/gateway/shard.rs +++ b/src/gateway/shard.rs @@ -5,6 +5,7 @@ use std::io::Write; use std::net::Shutdown; use std::sync::{Arc, Mutex}; use std::time::{Duration as StdDuration, Instant}; +use std::thread; use super::{ConnectionStage, GatewayError}; use websocket::client::Url; use websocket::message::{CloseData, OwnedMessage}; @@ -126,7 +127,7 @@ impl Shard { token: Arc>, shard_info: [u64; 2]) -> Result { - let client = connect(&*ws_url.lock().unwrap())?; + let client = connecting(&*ws_url.lock().unwrap()); let current_presence = (None, OnlineStatus::Online, false); let heartbeat_instants = (None, None); @@ -1047,3 +1048,18 @@ fn build_gateway_url(base: &str) -> Result { Error::Gateway(GatewayError::BuildingUrl) }) } + +/// Tries to connect and upon failure, retries. +fn connecting(uri: &str) -> WsClient { + let waiting_time = 30; + + loop { + match connect(&uri) { + Ok(client) => return client, + Err(why) => { + warn!("Connecting failed: {:?}\n Will retry in {} seconds.", why, waiting_time); + thread::sleep(StdDuration::from_secs(waiting_time)); + }, + }; + } +} diff --git a/src/model/event.rs b/src/model/event.rs index 086f422..d4148b9 100644 --- a/src/model/event.rs +++ b/src/model/event.rs @@ -404,7 +404,9 @@ impl CacheUpdate for GuildEmojisUpdateEvent { fn update(&mut self, cache: &mut Cache) -> Option<()> { cache.guilds.get_mut(&self.guild_id).map(|guild| { - guild.with_mut(|g| g.emojis.extend(self.emojis.clone())) + guild.with_mut(|g| { + g.emojis.clone_from(&self.emojis) + }); }); None -- cgit v1.2.3 From a58de97e6089aa98f04d2cdc7312ed38a9f72b22 Mon Sep 17 00:00:00 2001 From: acdenisSK Date: Mon, 23 Oct 2017 23:50:09 +0200 Subject: Add a debug impl for `DispatchError` Why this was hand-made instead of derived is because of `CheckFailed`'s content, which is mostly `Command` not also deriving `Debug`; except that even `Command` has a trouble maker that would force us to do this hand-made anyway, `checks`. Fixes #204 --- src/framework/standard/mod.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'src') diff --git a/src/framework/standard/mod.rs b/src/framework/standard/mod.rs index 947a885..1a7a5ee 100644 --- a/src/framework/standard/mod.rs +++ b/src/framework/standard/mod.rs @@ -138,6 +138,31 @@ pub enum DispatchError { WebhookAuthor, } +use std::fmt; + +impl fmt::Debug for DispatchError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::DispatchError::*; + + match *self { + CheckFailed(..) => write!(f, "DispatchError::CheckFailed"), + CommandDisabled(ref s) => f.debug_tuple("DispatchError::CommandDisabled").field(&s).finish(), + BlockedUser => write!(f, "DispatchError::BlockedUser"), + BlockedGuild => write!(f, "DispatchError::BlockedGuild"), + LackOfPermissions(ref perms) => f.debug_tuple("DispatchError::LackOfPermissions").field(&perms).finish(), + RateLimited(ref num) => f.debug_tuple("DispatchError::RateLimited").field(&num).finish(), + OnlyForDM => write!(f, "DispatchError::OnlyForDM"), + OnlyForOwners => write!(f, "DispatchError::OnlyForOwners"), + OnlyForGuilds => write!(f, "DispatchError::OnlyForGuilds"), + LackingRole => write!(f, "DispatchError::LackingRole"), + NotEnoughArguments { ref min, ref given } => f.debug_struct("DispatchError::NotEnoughArguments").field("min", &min).field("given", &given).finish(), + TooManyArguments { ref max, ref given } => f.debug_struct("DispatchError::TooManyArguments").field("max", &max).field("given", &given).finish(), + IgnoredBot => write!(f, "DispatchError::IgnoredBot"), + WebhookAuthor => write!(f, "DispatchError::WebhookAuthor"), + } + } +} + type DispatchErrorHook = Fn(Context, Message, DispatchError) + Send + Sync + 'static; /// A utility for easily managing dispatches to commands. -- cgit v1.2.3 From d3015a0ff0c0c87888437f991945453b92296875 Mon Sep 17 00:00:00 2001 From: Zeyla Hellyer Date: Tue, 24 Oct 2017 07:52:24 -0700 Subject: Fix User::has_role Fix the return value of the function by properly checking whether the user has the role in the given guild. The function made the erraneous mistake of simply checking if the given guild had the role by Id, not whether the _member in the given guild_ had the role by Id. --- src/model/user.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/model/user.rs b/src/model/user.rs index c1c3135..3d39759 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -599,7 +599,11 @@ impl User { .unwrap() .guilds .get(&_guild_id) - .map(|g| g.read().unwrap().roles.contains_key(&role_id)) + .map(|g| { + g.read().unwrap().members.get(&self.id) + .map(|m| m.roles.contains(&role_id)) + .unwrap_or(false) + }) .unwrap_or(false) } else { true -- cgit v1.2.3 From 8c85664a94f7439ab4bc3a132f313a9e26d94fe7 Mon Sep 17 00:00:00 2001 From: acdenisSK Date: Tue, 24 Oct 2017 18:05:25 +0200 Subject: Fall back to `str::parse` if `parse_username` fails --- src/model/misc.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/model/misc.rs b/src/model/misc.rs index af49a76..3398a14 100644 --- a/src/model/misc.rs +++ b/src/model/misc.rs @@ -125,9 +125,10 @@ impl FromStr for UserId { type Err = UserIdParseError; fn from_str(s: &str) -> StdResult { - utils::parse_username(s) - .ok_or_else(|| UserIdParseError::InvalidFormat) - .map(UserId) + Ok(match utils::parse_username(s) + Some(id) => UserId(id), + None => s.parse::().map(UserId).map_err(|_| UserIdParseError::InvalidFormat) + }) } } -- cgit v1.2.3