diff options
| author | acdenisSK <[email protected]> | 2017-11-04 20:06:40 +0100 |
|---|---|---|
| committer | acdenisSK <[email protected]> | 2017-11-04 20:06:40 +0100 |
| commit | 0bd519f4ef9784d0fb5663d74db0d567f0bb1ae5 (patch) | |
| tree | cc2a6f44e97e42420507964dab4662fcccd9beb3 /src | |
| parent | Fix Help-Commands to list all eligible commands in DMs. (#212) (diff) | |
| parent | Bump to v0.4.3 (diff) | |
| download | serenity-0bd519f4ef9784d0fb5663d74db0d567f0bb1ae5.tar.xz serenity-0bd519f4ef9784d0fb5663d74db0d567f0bb1ae5.zip | |
Merge v0.4.3
Diffstat (limited to 'src')
| -rw-r--r-- | src/builder/create_embed.rs | 6 | ||||
| -rw-r--r-- | src/builder/create_invite.rs | 12 | ||||
| -rw-r--r-- | src/cache/mod.rs | 62 | ||||
| -rw-r--r-- | src/client/bridge/gateway/shard_runner.rs | 7 | ||||
| -rw-r--r-- | src/client/context.rs | 6 | ||||
| -rw-r--r-- | src/client/mod.rs | 2 | ||||
| -rw-r--r-- | src/framework/mod.rs | 2 | ||||
| -rw-r--r-- | src/framework/standard/command.rs | 37 | ||||
| -rw-r--r-- | src/framework/standard/mod.rs | 4 | ||||
| -rw-r--r-- | src/gateway/mod.rs | 2 | ||||
| -rw-r--r-- | src/gateway/shard.rs | 130 | ||||
| -rw-r--r-- | src/lib.rs | 7 | ||||
| -rw-r--r-- | src/model/channel/guild_channel.rs | 2 | ||||
| -rw-r--r-- | src/model/error.rs | 8 | ||||
| -rw-r--r-- | src/model/guild/member.rs | 19 | ||||
| -rw-r--r-- | src/model/guild/mod.rs | 108 | ||||
| -rw-r--r-- | src/model/user.rs | 12 | ||||
| -rw-r--r-- | src/model/utils.rs | 5 | ||||
| -rw-r--r-- | src/prelude.rs | 3 |
19 files changed, 340 insertions, 94 deletions
diff --git a/src/builder/create_embed.rs b/src/builder/create_embed.rs index e22fc82..7c4d3d6 100644 --- a/src/builder/create_embed.rs +++ b/src/builder/create_embed.rs @@ -235,9 +235,15 @@ impl CreateEmbed { /// struct Handler; /// /// impl EventHandler for Handler { +<<<<<<< HEAD /// fn guild_member_addition(&self, _: Context, guild_id: GuildId, member: Member) { /// use serenity::client::CACHE; /// let cache = CACHE.read(); +======= + /// fn on_guild_member_addition(&self, _: Context, guild_id: GuildId, member: Member) { + /// use serenity::CACHE; + /// let cache = CACHE.read().unwrap(); +>>>>>>> v0.4.3 /// /// if let Some(guild) = cache.guild(guild_id) { /// let guild = guild.read(); diff --git a/src/builder/create_invite.rs b/src/builder/create_invite.rs index 138e5c7..0b05b66 100644 --- a/src/builder/create_invite.rs +++ b/src/builder/create_invite.rs @@ -19,8 +19,8 @@ use internal::prelude::*; /// struct Handler; /// /// impl EventHandler for Handler { -/// fn message(&self, _: Context, msg: Message) { -/// use serenity::client::CACHE; +/// fn on_message(&self, _: Context, msg: Message) { +/// use serenity::CACHE; /// if msg.content == "!createinvite" { /// let channel = match CACHE.read().guild_channel(msg.channel_id) { /// Some(channel) => channel, @@ -76,7 +76,7 @@ impl CreateInvite { /// Create an invite with a max age of `3600` seconds, or 1 hour: /// /// ```rust,no_run - /// # use serenity::client::CACHE; + /// # use serenity::CACHE; /// # use serenity::model::ChannelId; /// # use std::error::Error; /// # @@ -109,7 +109,7 @@ impl CreateInvite { /// Create an invite with a max use limit of `5`: /// /// ```rust,no_run - /// # use serenity::client::CACHE; + /// # use serenity::CACHE; /// # use serenity::model::ChannelId; /// # use std::error::Error; /// # @@ -140,7 +140,7 @@ impl CreateInvite { /// Create an invite which is temporary: /// /// ```rust,no_run - /// # use serenity::client::CACHE; + /// # use serenity::CACHE; /// # use serenity::model::ChannelId; /// # use std::error::Error; /// # @@ -171,7 +171,7 @@ impl CreateInvite { /// Create an invite which is unique: /// /// ```rust,no_run - /// # use serenity::client::CACHE; + /// # use serenity::CACHE; /// # use serenity::model::ChannelId; /// # use std::error::Error; /// # diff --git a/src/cache/mod.rs b/src/cache/mod.rs index 730d169..e23e17e 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -171,7 +171,9 @@ impl Cache { /// # use serenity::prelude::*; /// # use serenity::model::*; /// # - /// use serenity::client::CACHE; + /// # #[cfg(feature = "client")] + /// # fn main() { + /// use serenity::CACHE; /// use std::thread; /// use std::time::Duration; /// @@ -194,9 +196,13 @@ impl Cache { /// } /// } /// - /// let mut client = Client::new("token", Handler).unwrap(); - /// + /// let mut client = Client::new("token", Handler).unwrap(); + /// /// client.start().unwrap(); + /// # } + /// # + /// # #[cfg(not(feature = "client"))] + /// # fn main() { } /// ``` /// /// [`Member`]: ../model/struct.Member.html @@ -229,7 +235,7 @@ impl Cache { /// Printing the count of all private channels and groups: /// /// ```rust,no_run - /// use serenity::client::CACHE; + /// use serenity::CACHE; /// /// let amount = CACHE.read().all_private_channels().len(); /// @@ -256,18 +262,26 @@ impl Cache { /// Print all of the Ids of guilds in the Cache: /// /// ```rust,no_run + /// # #[cfg(feature = "client")] + /// # fn main() { /// # use serenity::prelude::*; /// # use serenity::model::*; /// # - /// use serenity::client::CACHE; + /// use serenity::CACHE; /// /// struct Handler; + /// /// impl EventHandler for Handler { - /// fn ready(&self, _: Context, _: Ready) { - /// println!("Guilds in the Cache: {:?}", CACHE.read().all_guilds()); + /// fn on_ready(&self, _: Context, _: Ready) { + /// let guilds = CACHE.read().unwrap().guilds.len(); + /// + /// println!("Guilds in the Cache: {}", guilds); /// } /// } - /// let mut client = Client::new("token", Handler); + /// # } + /// # + /// # #[cfg(not(feature = "client"))] + /// # fn main() { } /// ``` /// /// [`Context`]: ../client/struct.Context.html @@ -334,7 +348,7 @@ impl Cache { /// # use std::error::Error; /// # /// # fn try_main() -> Result<(), Box<Error>> { - /// use serenity::client::CACHE; + /// use serenity::CACHE; /// /// if let Some(guild) = CACHE.read().guild(7) { /// println!("Guild name: {}", guild.read().name); @@ -363,10 +377,12 @@ impl Cache { /// [`Client::on_message`] event dispatch: /// /// ```rust,no_run + /// # #[cfg(feature = "client")] + /// # fn main() { /// # use serenity::prelude::*; /// # use serenity::model::*; /// # - /// use serenity::client::CACHE; + /// use serenity::CACHE; /// /// struct Handler; /// @@ -389,8 +405,12 @@ impl Cache { /// } /// /// let mut client = Client::new("token", Handler).unwrap(); - /// + /// /// client.start().unwrap(); + /// # } + /// # + /// # #[cfg(not(feature = "client"))] + /// # fn main() { } /// ``` /// /// [`ChannelId`]: ../model/struct.ChannelId.html @@ -419,7 +439,7 @@ impl Cache { /// # use std::error::Error; /// # /// # fn try_main() -> Result<(), Box<Error>> { - /// use serenity::client::CACHE; + /// use serenity::CACHE; /// /// if let Some(group) = CACHE.read().group(7) { /// println!("Owner Id: {}", group.read().owner_id); @@ -496,16 +516,22 @@ impl Cache { /// /// # Examples /// - /// Retrieve a private channel from the cache and send a message: + /// Retrieve a private channel from the cache and print its recipient's + /// name: /// /// ```rust,no_run /// # use std::error::Error; /// # /// # fn try_main() -> Result<(), Box<Error>> { - /// use serenity::client::CACHE; + /// use serenity::CACHE; + /// + /// let cache = CACHE.read()?; /// - /// if let Some(channel) = CACHE.read().private_channel(7) { - /// channel.read().say("Hello there!"); + /// if let Some(channel) = cache.private_channel(7) { + /// let channel_reader = channel.read().unwrap(); + /// let user_reader = channel_reader.recipient.read().unwrap(); + /// + /// println!("The recipient is {}", user_reader.name); /// } /// # Ok(()) /// # } @@ -537,7 +563,7 @@ impl Cache { /// # use std::error::Error; /// # /// # fn try_main() -> Result<(), Box<Error>> { - /// use serenity::client::CACHE; + /// use serenity::CACHE; /// /// if let Some(role) = CACHE.read().role(7, 77) { /// println!("Role with Id 77 is called {}", role.name); @@ -572,7 +598,7 @@ impl Cache { /// # use std::error::Error; /// # /// # fn try_main() -> Result<(), Box<Error>> { - /// use serenity::client::CACHE; + /// use serenity::CACHE; /// /// if let Some(user) = CACHE.read().user(7) { /// println!("User with Id 7 is currently named {}", user.read().name); diff --git a/src/client/bridge/gateway/shard_runner.rs b/src/client/bridge/gateway/shard_runner.rs index a5fde3d..0ba83ba 100644 --- a/src/client/bridge/gateway/shard_runner.rs +++ b/src/client/bridge/gateway/shard_runner.rs @@ -422,4 +422,11 @@ impl<H: EventHandler + Send + Sync + 'static> ShardRunner<H> { Ok(()) } + + fn request_shutdown(&self) -> Result<()> { + debug!("[ShardRunner {:?}] Requesting shutdown", self.shard_info); + let _ = self.manager_tx.send(ShardManagerMessage::ShutdownAll); + + Ok(()) + } } diff --git a/src/client/context.rs b/src/client/context.rs index 9b89cde..cc7ee63 100644 --- a/src/client/context.rs +++ b/src/client/context.rs @@ -281,6 +281,8 @@ impl Context { /// playing: /// /// ```rust,no_run + /// # #[cfg(feature = "model")] + /// # fn main() { /// # use serenity::prelude::*; /// # use serenity::model::*; /// # @@ -303,6 +305,10 @@ impl Context { /// let mut client = Client::new("token", Handler).unwrap(); /// /// client.start().unwrap(); + /// # } + /// + /// # #[cfg(not(feature = "model"))] + /// # fn main() {} /// ``` /// /// [`Online`]: ../model/enum.OnlineStatus.html#variant.Online diff --git a/src/client/mod.rs b/src/client/mod.rs index 16e61fa..a227920 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -720,7 +720,7 @@ impl Client { /// use serenity::Client; /// use std::env; /// - /// let token = env::var("DISCORD_BOT_TOKEN").unwrap(); + /// let token = env::var("DISCORD_TOKEN").unwrap(); /// let mut client = Client::new(&token, Handler); /// /// let _ = client.start_shard_range([4, 7], 10); diff --git a/src/framework/mod.rs b/src/framework/mod.rs index a5f458d..85ae6f4 100644 --- a/src/framework/mod.rs +++ b/src/framework/mod.rs @@ -36,7 +36,7 @@ //! use serenity::model::Message; //! use std::env; //! -//! let mut client = Client::new(&env::var("DISCORD_BOT_TOKEN").unwrap()); +//! let mut client = Client::new(&env::var("DISCORD_TOKEN").unwrap()); //! //! client.with_framework(|f| f //! .configure(|c| c.prefix("~")) diff --git a/src/framework/standard/command.rs b/src/framework/standard/command.rs index 797bed5..be7ec2a 100644 --- a/src/framework/standard/command.rs +++ b/src/framework/standard/command.rs @@ -128,6 +128,7 @@ pub fn positions(ctx: &mut Context, msg: &Message, conf: &Configuration) -> Opti if let Some(mention_end) = find_mention_end(&msg.content, conf) { positions.push(mention_end); + return Some(positions); } else if let Some(ref func) = conf.dynamic_prefix { if let Some(x) = func(ctx, msg) { if msg.content.starts_with(&x) { @@ -152,22 +153,18 @@ pub fn positions(ctx: &mut Context, msg: &Message, conf: &Configuration) -> Opti return None; } - if conf.allow_whitespace { - let pos = *unsafe { positions.get_unchecked(0) }; + let pos = *unsafe { positions.get_unchecked(0) }; - positions.insert(0, pos + 1); + if conf.allow_whitespace { + positions.insert(0, find_end_of_prefix_with_whitespace(&msg.content, pos).unwrap_or(pos)); + } else if find_end_of_prefix_with_whitespace(&msg.content, pos).is_some() { + return None; } Some(positions) } else if conf.on_mention.is_some() { find_mention_end(&msg.content, conf).map(|mention_end| { - let mut positions = vec![mention_end]; - - if conf.allow_whitespace { - positions.insert(0, mention_end + 1); - } - - positions + vec![mention_end] // This can simply be returned without trying to find the end whitespaces as trim will remove it later }) } else { None @@ -182,3 +179,23 @@ fn find_mention_end(content: &str, conf: &Configuration) -> Option<usize> { .map(|m| m.len()) }) } + +// Finds the end of the first continuous block of whitespace after the prefix +fn find_end_of_prefix_with_whitespace(content: &str, position: usize) -> Option<usize> { + let mut ws_split = content.split_whitespace(); + if let Some(cmd) = ws_split.nth(1) { + if let Some(index_of_cmd) = content.find(cmd) { + if index_of_cmd > position && index_of_cmd <= content.len() { + let slice = unsafe { content.slice_unchecked(position, index_of_cmd) }.as_bytes(); + for byte in slice.iter() { + // 0x20 is ASCII for space + if *byte != 0x20u8 { + return None; + } + } + return Some(index_of_cmd); + } + } + } + None +} diff --git a/src/framework/standard/mod.rs b/src/framework/standard/mod.rs index 1681bd7..fdab3a8 100644 --- a/src/framework/standard/mod.rs +++ b/src/framework/standard/mod.rs @@ -859,7 +859,7 @@ impl Framework for StandardFramework { 'outer: for position in positions { let mut built = String::new(); let round = message.content.chars().skip(position).collect::<String>(); - let round = round.trim().split_whitespace().collect::<Vec<&str>>(); + let round = round.trim().split_whitespace().collect::<Vec<&str>>(); // Call to `trim` causes the related bug under the main bug #206 - where the whitespace settings are ignored. The fix is implemented as an additional check inside command::positions for i in 0..self.configuration.depth { if i != 0 { @@ -965,7 +965,7 @@ pub fn has_correct_permissions(command: &Command, message: &Message) -> bool { if !command.required_permissions.is_empty() { if let Some(guild) = message.guild() { let perms = guild - .with(|g| g.permissions_for(message.channel_id, message.author.id)); + .with(|g| g.permissions_in(message.channel_id, message.author.id)); return perms.contains(command.required_permissions); } diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 147808e..c2acaac 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -141,7 +141,7 @@ impl ConnectionStage { match *self { Connecting | Handshake | Identifying | Resuming => true, - _ => false, + Connected | Disconnected => false, } } } diff --git a/src/gateway/shard.rs b/src/gateway/shard.rs index ef05cf4..340e2e8 100644 --- a/src/gateway/shard.rs +++ b/src/gateway/shard.rs @@ -82,6 +82,8 @@ pub struct Shard { seq: u64, session_id: Option<String>, shard_info: [u64; 2], + /// Whether the shard has permanently shutdown. + shutdown: bool, stage: ConnectionStage, pub token: Arc<Mutex<String>>, ws_url: Arc<Mutex<String>>, @@ -154,8 +156,12 @@ impl Shard { let user = http::get_current_user()?; Shard { +<<<<<<< HEAD manager: VoiceManager::new(tx, user.id), manager_rx: rx, +======= + shutdown: false, +>>>>>>> v0.4.3 client, current_presence, heartbeat_instants, @@ -170,6 +176,7 @@ impl Shard { } } else { Shard { + shutdown: false, client, current_presence, heartbeat_instants, @@ -186,11 +193,49 @@ impl Shard { }) } +<<<<<<< HEAD /// Retrieves the current presence of the shard. #[inline] pub fn current_presence(&self) -> &CurrentPresence { &self.current_presence } +======= + /// Whether the shard has permanently shutdown. + /// + /// This should normally happen due to manual calling of [`shutdown`] or + /// [`shutdown_clean`]. + /// + /// [`shutdown`]: #method.shutdown + /// [`shutdown_clean`]: #method.shutdown_clean + #[inline] + pub fn is_shutdown(&self) -> bool { + self.shutdown + } + + /// Retrieves a copy of the current shard information. + /// + /// The first element is the _current_ shard - 0-indexed - while the second + /// element is the _total number_ of shards -- 1-indexed. + /// + /// For example, if using 3 shards in total, and if this is shard 1, then it + /// can be read as "the second of three shards". + /// + /// # Examples + /// + /// Retrieving the shard info for the second shard, out of two shards total: + /// + /// ```rust,no_run + /// # use serenity::client::gateway::Shard; + /// # use std::sync::{Arc, Mutex}; + /// # + /// # let mutex = Arc::new(Mutex::new("".to_string())); + /// # + /// # let shard = Shard::new(mutex.clone(), mutex, [1, 2]).unwrap(); + /// # + /// assert_eq!(shard.shard_info(), [1, 2]); + /// ``` + pub fn shard_info(&self) -> [u64; 2] { self.shard_info } +>>>>>>> v0.4.3 /// Retrieves the heartbeat instants of the shard. /// @@ -220,6 +265,7 @@ impl Shard { /// /// # Errors /// +<<<<<<< HEAD /// Returns [`GatewayError::HeartbeatFailed`] if there was an error sending /// a heartbeat. /// @@ -272,6 +318,26 @@ impl Shard { } #[inline] +======= + /// ```rust,no_run + /// # #[cfg(feature = "model")] + /// # fn main() { + /// # use serenity::client::gateway::Shard; + /// # use std::sync::{Arc, Mutex}; + /// # + /// # let mutex = Arc::new(Mutex::new("".to_string())); + /// # + /// # let mut shard = Shard::new(mutex.clone(), mutex, [0, 1]).unwrap(); + /// # + /// use serenity::model::Game; + /// + /// shard.set_game(Some(Game::playing("Heroes of the Storm"))); + /// # } + /// # + /// # #[cfg(not(feature = "model"))] + /// # fn main() { } + /// ``` +>>>>>>> v0.4.3 pub fn set_game(&mut self, game: Option<Game>) { self.current_presence.0 = game; } @@ -304,10 +370,15 @@ impl Shard { /// Retrieving the shard info for the second shard, out of two shards total: /// /// ```rust,no_run +<<<<<<< HEAD /// # extern crate parking_lot; /// # extern crate serenity; /// # /// # use parking_lot::Mutex; +======= + /// # #[cfg(feature = "model")] + /// # fn main() { +>>>>>>> v0.4.3 /// # use serenity::client::gateway::Shard; /// # use std::error::Error; /// # use std::sync::Arc; @@ -317,6 +388,7 @@ impl Shard { /// # /// # let shard = Shard::new(mutex.clone(), mutex, [1, 2]).unwrap(); /// # +<<<<<<< HEAD /// assert_eq!(shard.shard_info(), [1, 2]); /// # Ok(()) /// # } @@ -324,6 +396,15 @@ impl Shard { /// # fn main() { /// # try_main().unwrap(); /// # } +======= + /// use serenity::model::{Game, OnlineStatus}; + /// + /// shard.set_presence(Some(Game::playing("Heroes of the Storm")), OnlineStatus::Online, false); + /// # } + /// # + /// # #[cfg(not(feature = "model"))] + /// # fn main() { } +>>>>>>> v0.4.3 /// ``` pub fn shard_info(&self) -> [u64; 2] { self.shard_info } @@ -614,6 +695,44 @@ impl Shard { } /// Calculates the heartbeat latency between the shard and the gateway. +<<<<<<< HEAD +======= + /// + /// # Examples + /// + /// When using the [`Client`], output the latency in response to a `"~ping"` + /// message handled through [`Client::on_message`]. + /// + /// ```rust,no_run + /// # #[cfg(feature = "model")] + /// # fn main() { + /// # use serenity::prelude::*; + /// # use serenity::model::*; + /// struct Handler; + /// + /// impl EventHandler for Handler { + /// fn on_message(&self, ctx: Context, msg: Message) { + /// if msg.content == "~ping" { + /// if let Some(latency) = ctx.shard.lock().latency() { + /// let s = format!("{}.{}s", latency.as_secs(), latency.subsec_nanos()); + /// + /// let _ = msg.channel_id.say(&s); + /// } else { + /// let _ = msg.channel_id.say("N/A"); + /// } + /// } + /// } + /// } + /// let mut client = Client::new("token", Handler); client.start().unwrap(); + /// # } + /// # + /// # #[cfg(not(feature = "model"))] + /// # fn main() { } + /// ``` + /// + /// [`Client`]: ../struct.Client.html + /// [`EventHandler::on_message`]: ../event_handler/trait.EventHandler.html#method.on_message +>>>>>>> v0.4.3 // Shamelessly stolen from brayzure's commit in eris: // <https://github.com/abalabahaha/eris/commit/0ce296ae9a542bcec0edf1c999ee2d9986bed5a6> pub fn latency(&self) -> Option<StdDuration> { @@ -647,9 +766,14 @@ impl Shard { pub(crate) fn cycle_voice_recv(&mut self) -> Vec<Value> { let mut messages = vec![]; +<<<<<<< HEAD while let Ok(v) = self.manager_rx.try_recv() { messages.push(v); } +======= + self.shutdown = true; + debug!("[Shard {:?}] Cleanly shutdown shard", self.shard_info); +>>>>>>> v0.4.3 messages } @@ -684,8 +808,14 @@ impl Shard { self.shard_info, ); +<<<<<<< HEAD self.reconnect() } +======= + self.shutdown = true; + + Ok(()) +>>>>>>> v0.4.3 } /// Requests that one or multiple [`Guild`]s be chunked. @@ -33,12 +33,17 @@ //! #[macro_use] extern crate serenity; //! //! use serenity::client::Client; +//! use serenity::prelude::EventHandler; //! use serenity::framework::standard::StandardFramework; //! use std::env; //! +//! struct Handler; +//! +//! impl EventHandler for Handler {} +//! //! fn main() { //! // Login with a bot token from the environment -//! let mut client = Client::new(&env::var("DISCORD_TOKEN").expect("token")); +//! let mut client = Client::new(&env::var("DISCORD_TOKEN").expect("token"), Handler); //! client.with_framework(StandardFramework::new() //! .configure(|c| c.prefix("~")) // set the bot's prefix to "~" //! .on("ping", ping)); diff --git a/src/model/channel/guild_channel.rs b/src/model/channel/guild_channel.rs index 8fc9abb..76cea47 100644 --- a/src/model/channel/guild_channel.rs +++ b/src/model/channel/guild_channel.rs @@ -512,7 +512,7 @@ impl GuildChannel { 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_for(self.id, user_id)) + .map(|g| g.read().permissions_in(self.id, user_id)) } /// Pins a [`Message`] to the channel. diff --git a/src/model/error.rs b/src/model/error.rs index 835678a..c2f568b 100644 --- a/src/model/error.rs +++ b/src/model/error.rs @@ -13,10 +13,10 @@ use super::Permissions; /// re-ban all members with an odd discriminator: /// /// ```rust,no_run -/// # #[cfg(feature="client")] +/// # #[cfg(all(feature = "client", feature = "model"))] /// # use std::error::Error; /// # -/// # #[cfg(feature="client")] +/// # #[cfg(all(feature = "client", feature = "model"))] /// # fn try_main() -> Result<(), Box<Error>> { /// use serenity::prelude::*; /// use serenity::model::*; @@ -53,12 +53,12 @@ use super::Permissions; /// # Ok(()) /// # } /// # -/// # #[cfg(feature="client")] +/// # #[cfg(all(feature = "client", feature = "model"))] /// # fn main() { /// # try_main().unwrap(); /// # } /// # -/// # #[cfg(not(feature="client"))] +/// # #[cfg(not(all(feature="client", feature = "model")))] /// # fn main() { } /// ``` /// diff --git a/src/model/guild/member.rs b/src/model/guild/member.rs index 7420faa..e5feee7 100644 --- a/src/model/guild/member.rs +++ b/src/model/guild/member.rs @@ -14,6 +14,7 @@ use utils::Colour; #[cfg(all(feature = "cache", feature = "model"))] use {CACHE, http, utils}; +/// 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 { fn dmd(&self) -> u8 { 0 } fn reason(&self) -> &str { "" } @@ -275,7 +276,7 @@ impl Member { .get(&self.guild_id) .map(|guild| guild.read().has_perms(req)); - if let Some(Ok(false)) = has_perms { + if let Some(false) = has_perms { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -283,7 +284,7 @@ impl Member { self.guild_id.kick(self.user.read().id) } - /// Returns the permissions for the member. + /// Returns the guild-level permissions for the member. /// /// # Examples /// @@ -310,19 +311,9 @@ impl Member { None => return Err(From::from(ModelError::GuildNotFound)), }; - let guild = guild.read(); + let reader = guild.read().unwrap(); - let default_channel = match guild.default_channel(self.user.read().id) { - Some(dc) => dc, - None => return Err(From::from(ModelError::ItemMissing)), - }; - - let default_channel_reader = default_channel.read(); - - Ok( - guild - .permissions_for(default_channel_reader.id, self.user.read().id), - ) + Ok(reader.member_permissions(self.user.read().unwrap().id)) } /// Removes a [`Role`] from the member, editing its roles in-place if the diff --git a/src/model/guild/mod.rs b/src/model/guild/mod.rs index 8483e75..ab74109 100644 --- a/src/model/guild/mod.rs +++ b/src/model/guild/mod.rs @@ -135,8 +135,8 @@ impl Guild { /// returns `None`) pub fn default_channel(&self, uid: UserId) -> Option<Arc<RwLock<GuildChannel>>> { for (cid, channel) in &self.channels { - if self.permissions_for(*cid, uid).read_messages() { - return Some(channel.clone()); + if self.permissions_in(*cid, uid).read_messages() { + return Some(channel.read().unwrap().clone()); } } @@ -151,8 +151,8 @@ impl Guild { pub fn default_channel_guaranteed(&self) -> Option<Arc<RwLock<GuildChannel>>> { for (cid, channel) in &self.channels { for memid in self.members.keys() { - if self.permissions_for(*cid, *memid).read_messages() { - return Some(channel.clone()); + if self.permissions_in(*cid, *memid).read_messages() { + return Some(channel.read().unwrap().clone()); } } } @@ -161,21 +161,13 @@ impl Guild { } #[cfg(feature = "cache")] - fn has_perms(&self, mut permissions: Permissions) -> Result<bool> { - let member = match self.members.get(&CACHE.read().user.id) { - Some(member) => member, - None => return Err(Error::Model(ModelError::ItemMissing)), - }; - - let default_channel = match self.default_channel(member.user.read().id) { - Some(dc) => dc, - None => return Err(Error::Model(ModelError::ItemMissing)), - }; + fn has_perms(&self, mut permissions: Permissions) -> bool { + let user_id = CACHE.read().unwrap().user.id; - let perms = self.permissions_for(default_channel.read().id, member.user.read().id); + let perms = self.member_permissions(user_id); permissions.remove(perms); - Ok(permissions.is_empty()) + permissions.is_empty() } /// Ban a [`User`] from the guild. All messages by the @@ -213,7 +205,7 @@ impl Guild { { let req = Permissions::BAN_MEMBERS; - if !self.has_perms(req)? { + if !self.has_perms(req) { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -238,7 +230,7 @@ impl Guild { { let req = Permissions::BAN_MEMBERS; - if !self.has_perms(req)? { + if !self.has_perms(req) { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -325,7 +317,7 @@ impl Guild { { let req = Permissions::MANAGE_CHANNELS; - if !self.has_perms(req)? { + if !self.has_perms(req) { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -396,7 +388,7 @@ impl Guild { { let req = Permissions::MANAGE_ROLES; - if !self.has_perms(req)? { + if !self.has_perms(req) { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -498,7 +490,7 @@ impl Guild { { let req = Permissions::MANAGE_GUILD; - if !self.has_perms(req)? { + if !self.has_perms(req) { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -578,7 +570,7 @@ impl Guild { { let req = Permissions::CHANGE_NICKNAME; - if !self.has_perms(req)? { + if !self.has_perms(req) { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -660,7 +652,7 @@ impl Guild { { let req = Permissions::MANAGE_GUILD; - if !self.has_perms(req)? { + if !self.has_perms(req) { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -1007,6 +999,57 @@ impl Guild { } } + /// Calculate a [`Member`]'s permissions in the guild. + /// + /// [`Member`]: struct.Member.html + pub fn member_permissions<U>(&self, user_id: U) -> Permissions + where U: Into<UserId> { + let user_id = user_id.into(); + + if user_id == self.owner_id { + return Permissions::all(); + } + + let everyone = match self.roles.get(&RoleId(self.id.0)) { + Some(everyone) => everyone, + None => { + error!( + "(╯°□°)╯︵ ┻━┻ @everyone role ({}) missing in '{}'", + self.id, + self.name, + ); + + return Permissions::empty(); + }, + }; + + let member = match self.members.get(&user_id) { + Some(member) => member, + None => return everyone.permissions, + }; + + let mut permissions = everyone.permissions; + + for role in &member.roles { + if let Some(role) = self.roles.get(&role) { + if role.permissions.contains(Permissions::ADMINISTRATOR) { + return Permissions::all(); + } + + permissions |= role.permissions; + } else { + warn!( + "(╯°□°)╯︵ ┻━┻ {} on {} has non-existent role {:?}", + member.user.read().unwrap().id, + self.id, + role, + ); + } + } + + permissions + } + /// Moves a member to a specific voice channel. /// /// Requires the [Move Members] permission. @@ -1018,10 +1061,21 @@ impl Guild { self.id.move_member(user_id, channel_id) } + /// Alias for [`permissions_in`]. + /// + /// [`permissions_in`]: #method.permissions_in + #[deprecated(since = "0.4.3", + note = "This will serve a different purpose in 0.5")] + #[inline] + pub fn permissions_for<C, U>(&self, channel_id: C, user_id: U) + -> Permissions where C: Into<ChannelId>, U: Into<UserId> { + self.permissions_in(channel_id, user_id) + } + /// Calculate a [`User`]'s permissions in a given channel in the guild. /// /// [`User`]: struct.User.html - pub fn permissions_for<C, U>(&self, channel_id: C, user_id: U) -> Permissions + pub fn permissions_in<C, U>(&self, channel_id: C, user_id: U) -> Permissions where C: Into<ChannelId>, U: Into<UserId> { let user_id = user_id.into(); @@ -1169,7 +1223,7 @@ impl Guild { { let req = Permissions::KICK_MEMBERS; - if !self.has_perms(req)? { + if !self.has_perms(req) { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -1252,7 +1306,7 @@ impl Guild { { let req = Permissions::KICK_MEMBERS; - if !self.has_perms(req)? { + if !self.has_perms(req) { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -1277,7 +1331,7 @@ impl Guild { { let req = Permissions::BAN_MEMBERS; - if !self.has_perms(req)? { + if !self.has_perms(req) { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } diff --git a/src/model/user.rs b/src/model/user.rs index 7eea83e..9f36d0f 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -48,7 +48,7 @@ impl CurrentUser { /// Print out the current user's avatar url if one is set: /// /// ```rust,no_run - /// # use serenity::client::CACHE; + /// # use serenity::CACHE; /// # /// # let cache = CACHE.read(); /// # @@ -127,7 +127,7 @@ impl CurrentUser { /// Print out the names of all guilds the current user is in: /// /// ```rust,no_run - /// # use serenity::client::CACHE; + /// # use serenity::CACHE; /// # /// # let cache = CACHE.read(); /// # @@ -155,7 +155,7 @@ impl CurrentUser { /// Get the invite url with no permissions set: /// /// ```rust,no_run - /// # use serenity::client::CACHE; + /// # use serenity::CACHE; /// # /// # let mut cache = CACHE.write(); /// @@ -178,7 +178,7 @@ impl CurrentUser { /// Get the invite url with some basic permissions set: /// /// ```rust,no_run - /// # use serenity::client::CACHE; + /// # use serenity::CACHE; /// # /// # let mut cache = CACHE.write(); /// @@ -234,7 +234,7 @@ impl CurrentUser { /// Print out the current user's static avatar url if one is set: /// /// ```rust,no_run - /// # use serenity::client::CACHE; + /// # use serenity::CACHE; /// # /// # let cache = CACHE.read(); /// # @@ -258,7 +258,7 @@ impl CurrentUser { /// Print out the current user's distinct identifier (e.g., Username#1234): /// /// ```rust,no_run - /// # use serenity::client::CACHE; + /// # use serenity::CACHE; /// # /// # let cache = CACHE.read(); /// # diff --git a/src/model/utils.rs b/src/model/utils.rs index 62ecc4e..d366e5f 100644 --- a/src/model/utils.rs +++ b/src/model/utils.rs @@ -178,7 +178,10 @@ pub fn user_has_perms(channel_id: ChannelId, mut permissions: Permissions) -> Re None => return Err(Error::Model(ModelError::ItemMissing)), }; - let perms = guild.read().permissions_for(channel_id, current_user.id); + let perms = guild + .read() + .unwrap() + .permissions_in(channel_id, current_user.id); permissions.remove(perms); diff --git a/src/prelude.rs b/src/prelude.rs index 435fb8a..8a361e9 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -16,7 +16,6 @@ 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}; @@ -26,5 +25,7 @@ pub use gateway::GatewayError; pub use http::HttpError; #[cfg(feature = "model")] pub use model::ModelError; +#[cfg(feature = "parking_lot")] +pub use parking_lot::{Mutex, RwLock}; #[cfg(feature = "voice")] pub use voice::VoiceError; |