diff options
30 files changed, 606 insertions, 197 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index b91eb6f..d934490 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,66 @@ All notable changes to this project will be documented in this file. This project mostly adheres to [Semantic Versioning][semver]. +## [0.4.1] - 2017-10-14 + +This release contains bugfixes and some newly added or newly exposed +functionality. + +Thanks to the following for their contributions this release: + +- [@acdenisSK] +- [@ftriquet] +- [@hsiW] +- [@Lakelezz] +- [@lolzballs] +- [@Roughsketch] +- [@zeyla] + +### Added + +- [general] Replace Vec parameters by `IntoIterator` ([@ftriquet])[c:55167c3] +- [general] Replace slice parameters by `IntoIterator` ([@ftriquet]) [c:022e35d] +- [model] Add `Guild::members_starting_with` ([@Lakelezz]) [c:b3aa441] +- [model] Add `Guild::members_containing` ([@Lakelezz]) [c:1b167b5] +- [model] `impl<'a> BanOptions for &'a str` ([@acdenisSK]) [c:cf40386] +- [model] Derive `Default` on `CurrentUser` and IDs ([@acdenisSK]) [c:09a8a44] +- [client] Add a configurable, shard-shared threadpool ([@zeyla]) [c:d7621aa], + [c:8109619] +- [model] Add `Guild::members_username_containing, members_nick_containing` + ([@Lakelezz]) [c:002ce3a] +- [framework] Add an iterator for `Args` ([@acdenisSK]) [c:0ed1972] +- [framework] Make `has_all_requirements` public ([@Lakelezz]) [c:08d390c] +- [framework] Make default help messages list help for aliases ([@Lakelezz]) + [c:0d1c0f1] + +### Fixed + +- [model] Use `request_client!` for attachment downloading ([@lolzballs]) + [c:71f709d] +- [client] Fix client no-framework compilation ([@zeyla]) [c:1d4ecb2] +- [client] Fix client shards not filling ([@zeyla]) [c:86d8bdd] +- [model] Fix `User::tag` and `CurrentUser::tag` discrim output ([@zeyla]) + [c:6b9dcf5] +- [framework] Modify `initialized` method purpose ([@acdenisSK]) [c:05f158f] +- [framework] Make command Error string public ([@acdenisSK]) [c:917dd30] +- [client, gateway] Improve shard logic ([@acdenisSK], [@zeyla]) [c:683691f], + [c:7befcd5] +- [gateway] Reset shard heartbeat state on resume ([@zeyla]) [c:c98cae4] +- [general] Fix font-height and soften the logo ([@Lakelezz]) [c:3b2c246] + +### Misc. + +- [client, gateway] Improve shard and shard runner logging ([@zeyla]) + [c:21e194b] +- `to_owned` -> `to_string` ([@acdenisSK]) [c:1bf4d9c] +- [general] Fix most clippy warnings ([@Roughsketch]) [c:7945094] +- [framework] Add some docs to `Args` ([@acdenisSK]) [c:8572943] +- [examples] Add `env_logger` bot example [c:0df77b9] +- [general] Fix clippy lints ([@zeyla]) [c:483b069] +- [model] Optimize `Member::roles` ([@hsiW]) [c:8565fa2] +- [general] Internally use a `try_opt!` macro ([@hsiW]) [c:9b0c053] +- [general] Feature-flag extern crates behind their name ([@zeyla]) [c:11b85ca] + ## [0.4.0] - 2017-09-25 This release contains a lot of added functionality, minor-scale rewrites, @@ -1367,6 +1427,7 @@ rest::get_guilds(GuildPagination::After(GuildId(777)), 50); Initial commit. +[0.4.1]: https://github.com/zeyla/serenity/compare/v0.4.0...v0.4.1 [0.4.0]: https://github.com/zeyla/serenity/compare/v0.3.0...v0.4.0 [0.3.0]: https://github.com/zeyla/serenity/compare/v0.2.0...v0.3.0 [0.2.0]: https://github.com/zeyla/serenity/compare/v0.1.5...v0.2.0 @@ -1394,6 +1455,7 @@ Initial commit. [@emoticon]: https://github.com/emoticon [@Flat]: https://github.com/Flat [@foxbot]: https://github.com/foxbot +[@ftriquet]: https://github.com/ftriquet [@fwrs]: https://github.com/fwrs [@GetRektByMe]: https://github.com/GetRektByMe [@hsiW]: https://github.com/hsiW @@ -1402,6 +1464,7 @@ Initial commit. [@indiv0]: https://github.com/indiv0 [@joek13]: https://github.com/joek13 [@Lakelezz]: https://github.com/Lakelezz +[@lolzballs]: https://github.com/lolzballs [@khazhyk]: https://github.com/khazhyk [@Roughsketch]: https://github.com/Roughsketch [@sschroe]: https://github.com/sschroe @@ -1409,6 +1472,38 @@ Initial commit. [@xentec]: https://github.com/xentec [@zeyla]: https://github.com/zeyla +[c:002ce3a]: https://github.com/zeyla/serenity/commit/002ce3aa272fa51b84e820f12db39cb87a461a83 +[c:022e35d]: https://github.com/zeyla/serenity/commit/022e35d5b12322bd77bbe74a1a3b2ad319977390 +[c:05f158f]: https://github.com/zeyla/serenity/commit/05f158fc89f2adc82e31cf4b93706dc7d25e11d8 +[c:08d390c]: https://github.com/zeyla/serenity/commit/08d390c19f187986fd2856fe5cbb9035a0877e0f +[c:09a8a44]: https://github.com/zeyla/serenity/commit/09a8a444f5bcefaee8b83dc129a3cea2de8792f9 +[c:0d1c0f1]: https://github.com/zeyla/serenity/commit/0d1c0f1356fd3a891232498c2230d0bb4d2ed4ff +[c:0df77b9]: https://github.com/zeyla/serenity/commit/0df77b933ff5e98725252116069afad2dec9f89b +[c:0ed1972]: https://github.com/zeyla/serenity/commit/0ed19727debf28a8aa0818b44713090e97dd6eee +[c:11b85ca]: https://github.com/zeyla/serenity/commit/11b85ca6799b9984481119851f983d8e3c84cdc0 +[c:1b167b5]: https://github.com/zeyla/serenity/commit/1b167b5496ce816cbcacb0e4f6e63399dffaa25c +[c:1bf4d9c]: https://github.com/zeyla/serenity/commit/1bf4d9cb9823dca8c4bb77147c66eac2d53f609f +[c:1d4ecb2]: https://github.com/zeyla/serenity/commit/1d4ecb2f13258d286378c44d59c2ee4b1c68349d +[c:21e194b]: https://github.com/zeyla/serenity/commit/21e194bffc37f396f007d390170f5b60e22f5d02 +[c:3b2c246]: https://github.com/zeyla/serenity/commit/3b2c2462cb34b5ae5190ebc4a9e04968dc8d5335 +[c:483b069]: https://github.com/zeyla/serenity/commit/483b069cc0c821ec673ac475b168809e3a41525a +[c:55167c3]: https://github.com/zeyla/serenity/commit/55167c300598536a852b3596fcf1c420aeb96c3a +[c:683691f]: https://github.com/zeyla/serenity/commit/683691f762bbf58e3abf3bc67381e18112f5c8ad +[c:6b9dcf5]: https://github.com/zeyla/serenity/commit/6b9dcf5272458499c1caef544cb82d5a8624258b +[c:71f709d]: https://github.com/zeyla/serenity/commit/71f709d0aceedb6d3091d0c28c9535e281270f71 +[c:7945094]: https://github.com/zeyla/serenity/commit/794509421f21bee528e582a7b109d6a99284510a +[c:7befcd5]: https://github.com/zeyla/serenity/commit/7befcd5caa9ccdf44d90ecc12014c335b1bd2be7 +[c:8109619]: https://github.com/zeyla/serenity/commit/8109619184867fc843a1e73d18d37726a34f7fbf +[c:8565fa2]: https://github.com/zeyla/serenity/commit/8565fa2cb356cf8cbccfeb09828c9d136ad3d614 +[c:8572943]: https://github.com/zeyla/serenity/commit/857294358d5f3029850dc79c174b831c0b0c161c +[c:86d8bdd]: https://github.com/zeyla/serenity/commit/86d8bddff3e3242186d0c2607b34771e5422ba5b +[c:917dd30]: https://github.com/zeyla/serenity/commit/917dd3071dc8a145b9c379cb3a8a84731c690340 +[c:9b0c053]: https://github.com/zeyla/serenity/commit/9b0c053725e04c60eb7ddcfeb847be4189b3dbf6 +[c:b3aa441]: https://github.com/zeyla/serenity/commit/b3aa441c2d61ba324396deaf70f2c5818fd3f528 +[c:c98cae4]: https://github.com/zeyla/serenity/commit/c98cae4e838147eaa077bbc68ffebf8834ff7b6b +[c:cf40386]: https://github.com/zeyla/serenity/commit/cf403867400110f446720fc20fad6781cf8c6b13 +[c:d7621aa]: https://github.com/zeyla/serenity/commit/d7621aa4dfb2a3dea22e7848eb97e2b4cc1ade14 + [c:005437f]: https://github.com/zeyla/serenity/commit/005437f56869e846ff677b6516605def0c4de7bc [c:0186754]: https://github.com/zeyla/serenity/commit/01867549709ef73ee09ed442e1d5ea938fd7f74d [c:0240717]: https://github.com/zeyla/serenity/commit/02407175e463b2b75295364d6b0e182fe34966ed @@ -1,5 +1,5 @@ [package] -authors = ["alex <[email protected]", "Zeyla Hellyer <[email protected]>"] +authors = ["alex <[email protected]>", "Zeyla Hellyer <[email protected]>"] description = "A Rust library for the Discord API." documentation = "https://docs.rs/serenity" homepage = "https://github.com/zeyla/serenity" @@ -138,6 +138,16 @@ Voice+youtube-dl: - youtube-dl (Arch: `community/youtube-dl`) +# Related Projects + +- [Discord.net][library:Discord.net] +- [JDA][library:JDA] +- [disco][library:disco] +- [discordrb][library:discordrb] +- [discord.js][library:discord.js] +- [discord.py][library:discord.py] +- [discord-rs][library:discord-rs] + [`Cache`]: https://docs.rs/serenity/*/serenity/cache/struct.Cache.html [`Client::new`]: https://docs.rs/serenity/*/serenity/client/struct.Client.html#method.new [`EventHandler::on_message`]: https://docs.rs/serenity/*/serenity/client/struct.EventHandler.html#method.on_message @@ -157,4 +167,11 @@ Voice+youtube-dl: [docs-badge]: https://img.shields.io/badge/docs-online-5023dd.svg [examples]: https://github.com/zeyla/serenity/tree/master/examples [gateway docs]: https://docs.rs/serenity/*/serenity/gateway/index.html +[library:Discord.net]: https://github.com/RogueException/Discord.Net +[library:JDA]: https://github.com/DV8FromTheWorld/JDA +[library:disco]: https://github.com/b1naryth1ef/disco +[library:discordrb]: https://github.com/meew0/discordrb +[library:discord.js]: https://github.com/hydrabolt/discord.js +[library:discord.py]: https://github.com/Rapptz/discord.py +[library:discord-rs]: https://github.com/SpaceManiac/discord-rs [logo]: https://raw.githubusercontent.com/zeyla/serenity/master/logo.png diff --git a/examples/08_env_logging/Cargo.toml b/examples/08_env_logging/Cargo.toml new file mode 100644 index 0000000..a17d55e --- /dev/null +++ b/examples/08_env_logging/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "08_env_logging" +version = "0.1.0" +authors = ["my name <[email protected]>"] + +[dependencies] +env_logger = "~0.4" +log = "~0.3" + +[dependencies.serenity] +features = ["client"] +path = "../../" diff --git a/examples/08_env_logging/src/main.rs b/examples/08_env_logging/src/main.rs new file mode 100644 index 0000000..db34037 --- /dev/null +++ b/examples/08_env_logging/src/main.rs @@ -0,0 +1,45 @@ +#[macro_use] extern crate log; + +extern crate env_logger; +extern crate serenity; + +use serenity::prelude::*; +use serenity::model::event::ResumedEvent; +use serenity::model::Ready; +use std::env; + +struct Handler; + +impl EventHandler for Handler { + fn on_ready(&self, _: Context, ready: Ready) { + // Log at the INFO level. This is a macro from the `log` crate. + info!("{} is connected!", ready.user.name); + } + + fn on_resume(&self, _: Context, resume: ResumedEvent) { + // Log at the DEBUG level. + // + // In this example, this will not show up in the logs because DEBUG is + // below INFO, which is the set debug level. + debug!("Resumed; trace: {:?}", resume.trace); + } +} + +fn main() { + // Call env_logger's initialize function, which configures `log` via + // environment variables. + // + // For example, you can say to log all levels INFO and up via setting the + // environment variable `RUST_LOG` to `INFO`. + env_logger::init().expect("Unable to init env_logger"); + + // Configure the client with your Discord bot token in the environment. + let token = env::var("DISCORD_TOKEN") + .expect("Expected a token in the environment"); + + let mut client = Client::new(&token, Handler); + + if let Err(why) = client.start() { + error!("Client error: {:?}", why); + } +} Binary files differdiff --git a/src/builder/create_embed.rs b/src/builder/create_embed.rs index f7dc0b1..f197507 100644 --- a/src/builder/create_embed.rs +++ b/src/builder/create_embed.rs @@ -69,7 +69,7 @@ impl CreateEmbed { pub fn colour<C: Into<Colour>>(mut self, colour: C) -> Self { self.0.insert( "color".to_string(), - Value::Number(Number::from(colour.into().0 as u64)), + Value::Number(Number::from(u64::from(colour.into().0))), ); CreateEmbed(self.0) diff --git a/src/cache/mod.rs b/src/cache/mod.rs index 6b93d41..238ca97 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -303,15 +303,15 @@ impl Cache { let id = id.into(); if let Some(channel) = self.channels.get(&id) { - return Some(Channel::Guild(channel.clone())); + return Some(Channel::Guild(Arc::clone(channel))); } if let Some(private_channel) = self.private_channels.get(&id) { - return Some(Channel::Private(private_channel.clone())); + return Some(Channel::Private(Arc::clone(private_channel))); } if let Some(group) = self.groups.get(&id) { - return Some(Channel::Group(group.clone())); + return Some(Channel::Group(Arc::clone(group))); } None diff --git a/src/client/bridge/gateway/shard_manager.rs b/src/client/bridge/gateway/shard_manager.rs index 4a50b56..6e3b285 100644 --- a/src/client/bridge/gateway/shard_manager.rs +++ b/src/client/bridge/gateway/shard_manager.rs @@ -32,6 +32,7 @@ pub struct ShardManager { impl ShardManager { #[cfg(feature = "framework")] + #[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] pub fn new<H>( shard_index: u64, shard_init: u64, @@ -49,15 +50,15 @@ impl ShardManager { let runners = Arc::new(Mutex::new(HashMap::new())); let mut shard_queuer = ShardQueuer { - data: data.clone(), - event_handler: event_handler.clone(), - framework: framework.clone(), + data: Arc::clone(&data), + event_handler: Arc::clone(&event_handler), + framework: Arc::clone(&framework), last_start: None, manager_tx: thread_tx.clone(), - runners: runners.clone(), + runners: Arc::clone(&runners), rx: shard_queue_rx, - token: token.clone(), - ws_url: ws_url.clone(), + token: Arc::clone(&token), + ws_url: Arc::clone(&ws_url), threadpool, }; diff --git a/src/client/bridge/gateway/shard_queuer.rs b/src/client/bridge/gateway/shard_queuer.rs index f75e35d..cb3f749 100644 --- a/src/client/bridge/gateway/shard_queuer.rs +++ b/src/client/bridge/gateway/shard_queuer.rs @@ -80,16 +80,20 @@ impl<H: EventHandler + Send + Sync + 'static> ShardQueuer<H> { fn start(&mut self, shard_id: ShardId, shard_total: ShardId) -> Result<()> { let shard_info = [shard_id.0, shard_total.0]; - let shard = Shard::new(self.ws_url.clone(), self.token.clone(), shard_info)?; + let shard = Shard::new( + Arc::clone(&self.ws_url), + Arc::clone(&self.token), + shard_info, + )?; let locked = Arc::new(Mutex::new(shard)); let mut runner = feature_framework! {{ ShardRunner::new( - locked.clone(), + Arc::clone(&locked), self.manager_tx.clone(), - self.framework.clone(), - self.data.clone(), - self.event_handler.clone(), + Arc::clone(&self.framework), + Arc::clone(&self.data), + Arc::clone(&self.event_handler), self.threadpool.clone(), ) } else { diff --git a/src/client/bridge/gateway/shard_runner.rs b/src/client/bridge/gateway/shard_runner.rs index 53d4b80..b14e48b 100644 --- a/src/client/bridge/gateway/shard_runner.rs +++ b/src/client/bridge/gateway/shard_runner.rs @@ -110,12 +110,12 @@ impl<H: EventHandler + Send + Sync + 'static> ShardRunner<H> { let (event, successful) = self.recv_event(); if let Some(event) = event { - let data = self.data.clone(); - let event_handler = self.event_handler.clone(); - let shard = self.shard.clone(); + let data = Arc::clone(&self.data); + let event_handler = Arc::clone(&self.event_handler); + let shard = Arc::clone(&self.shard); feature_framework! {{ - let framework = self.framework.clone(); + let framework = Arc::clone(&self.framework); self.threadpool.execute(|| { dispatch( diff --git a/src/client/dispatch.rs b/src/client/dispatch.rs index aec6283..c296b70 100644 --- a/src/client/dispatch.rs +++ b/src/client/dispatch.rs @@ -204,7 +204,7 @@ fn handle_event<H: EventHandler + 'static>(event: Event, let cache = CACHE.read(); if cache.unavailable_guilds.is_empty() { - let context = context(conn.clone(), data.clone()); + let context = context(Arc::clone(&conn), Arc::clone(&data)); let guild_amount = cache .guilds diff --git a/src/client/mod.rs b/src/client/mod.rs index c17e284..6f6b5ba 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -766,16 +766,16 @@ impl<H: EventHandler + Send + Sync + 'static> Client<H> { shard_data[0], shard_data[1] - shard_data[0] + 1, shard_data[2], - gateway_url.clone(), - self.token.clone(), - self.data.clone(), - self.event_handler.clone(), + Arc::clone(&gateway_url), + Arc::clone(&self.token), + Arc::clone(&self.data), + Arc::clone(&self.event_handler), #[cfg(feature = "framework")] - self.framework.clone(), + Arc::clone(&self.framework), self.threadpool.clone(), ); - self.shard_runners = manager.runners.clone(); + self.shard_runners = Arc::clone(&manager.runners); if let Err(why) = manager.initialize() { error!("Failed to boot a shard: {:?}", why); diff --git a/src/framework/standard/args.rs b/src/framework/standard/args.rs index 30264b3..f59908a 100644 --- a/src/framework/standard/args.rs +++ b/src/framework/standard/args.rs @@ -53,6 +53,10 @@ impl<E: StdError> fmt::Display for Error<E> { type Result<T, E> = ::std::result::Result<T, Error<E>>; +/// A utility struct for handling arguments of a command. +/// +/// General functionality is done via removing an item, parsing it, then returning it; this however +/// can be mitigated with the `*_n` methods, which just parse and return. #[derive(Clone, Debug)] pub struct Args { delimiter: String, @@ -116,10 +120,7 @@ impl Args { let mut vec = Vec::with_capacity(i as usize); for _ in 0..i { - vec.push(match self.delimiter_split.shift() { - Some(x) => x, - None => return None, - }); + vec.push(try_opt!(self.delimiter_split.shift())); } Some(vec) @@ -161,16 +162,14 @@ impl Args { } /// Empty outs the internal vector while parsing (if necessary) and returning them - pub fn list<T: FromStr>(self) -> Result<Vec<T>, T::Err> + pub fn list<T: FromStr>(mut self) -> Result<Vec<T>, T::Err> where T::Err: StdError { - if self.delimiter_split.is_empty() { - return Err(Error::Eos); - } + Iter::<T>::new(&mut self).collect() + } - self.delimiter_split - .into_iter() - .map(|s| s.parse::<T>().map_err(Error::Parse)) - .collect() + /// Provides an iterator of items: (`T: FromStr`) `Result<T, T::Err>`. + pub fn iter<T: FromStr>(&mut self) -> Iter<T> where T::Err: StdError { + Iter::new(self) } /// This method is just `internal_vector.join(delimiter)` @@ -221,3 +220,29 @@ impl ::std::ops::Deref for Args { fn deref(&self) -> &Self::Target { &self.delimiter_split } } + +use std::marker::PhantomData; + +/// Provides `list`'s functionality, but as an iterator. +pub struct Iter<'a, T: FromStr> where T::Err: StdError { + args: &'a mut Args, + _marker: PhantomData<T>, +} + +impl<'a, T: FromStr> Iter<'a, T> where T::Err: StdError { + fn new(args: &'a mut Args) -> Self { + Iter { args, _marker: PhantomData } + } +} + +impl<'a, T: FromStr> Iterator for Iter<'a, T> where T::Err: StdError { + type Item = Result<T, T::Err>; + + fn next(&mut self) -> Option<Self::Item> { + if self.args.is_empty() { + None + } else { + Some(self.args.single::<T>()) + } + } +} diff --git a/src/framework/standard/help_commands.rs b/src/framework/standard/help_commands.rs index dbf25a9..ce90e74 100644 --- a/src/framework/standard/help_commands.rs +++ b/src/framework/standard/help_commands.rs @@ -29,7 +29,7 @@ use std::fmt::Write; use super::command::InternalCommand; use super::{Args, Command, CommandGroup, CommandOrAlias, CommandError}; use client::Context; -use model::{ChannelId, Guild, Member, Message}; +use model::{ChannelId, Message}; use utils::Colour; use framework::standard::{has_correct_roles, has_correct_permissions}; @@ -51,14 +51,25 @@ fn remove_aliases(cmds: &HashMap<String, CommandOrAlias>) -> 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. -fn has_all_requirements(cmd: &Command, guild: &Guild, member: &Member, msg: &Message) -> bool { - if cmd.allowed_roles.is_empty() { - has_correct_permissions(cmd, msg) - } else { - has_correct_roles(cmd, guild, member) && has_correct_permissions(cmd, msg) +pub fn has_all_requirements(cmd: &Command, msg: &Message) -> bool { + if let Some(guild) = msg.guild() { + let guild = guild.read(); + + if let Some(member) = guild.members.get(&msg.author.id) { + + if let Ok(permissions) = member.permissions() { + + if cmd.allowed_roles.is_empty() { + return permissions.administrator() || has_correct_permissions(&cmd, &msg); + } else { + return permissions.administrator() || (has_correct_roles(&cmd, &guild, &member) && has_correct_permissions(cmd, msg)); + } + } + } } + false } /// Posts an embed showing each individual command group and its commands. @@ -100,26 +111,31 @@ pub fn with_embeds(_: &mut Context, if name == with_prefix || name == *command_name { match *command { CommandOrAlias::Command(ref cmd) => { - if !cmd.allowed_roles.is_empty() { - if let Some(guild) = msg.guild() { - let guild = guild.read(); - - if let Some(member) = guild.members.get(&msg.author.id) { - if let Ok(permissions) = member.permissions() { - if !permissions.administrator() && - !has_all_requirements(cmd, &guild, member, &msg) { - break; - } - } - } - } + if has_all_requirements(&cmd, &msg) { + found = Some((command_name, cmd)); } - found = Some((command_name, cmd)); + else { + break; + } }, CommandOrAlias::Alias(ref name) => { - error_embed(&msg.channel_id, &format!("Did you mean \"{}\"?", name)); + let actual_command = group.commands.get(name).unwrap(); - return Ok(()); + match *actual_command { + CommandOrAlias::Command(ref cmd) => { + if has_all_requirements(&cmd, &msg) { + found = Some((name, cmd)); + } + else { + break; + } + }, + + CommandOrAlias::Alias(ref name) => { + let _ = msg.channel_id.say(&format!("Did you mean {:?}?", name)); + return Ok(()); + }, + } }, } } @@ -135,6 +151,7 @@ pub fn with_embeds(_: &mut Context, let _ = msg.channel_id.send_message(|m| { m.embed(|e| { let mut embed = e.colour(Colour::rosewater()).title(command_name); + if let Some(ref desc) = command.desc { embed = embed.description(desc); } @@ -157,6 +174,11 @@ pub fn with_embeds(_: &mut Context, embed = embed.field(|f| f.name("Group").value(&group_name)); } + if !command.aliases.is_empty() { + let aliases = command.aliases.join(", "); + embed = embed.field(|f| f.name("Aliases").value(&aliases)); + } + let available = if command.dm_only { "Only in DM" } else if command.guild_only { @@ -209,22 +231,14 @@ pub fn with_embeds(_: &mut Context, let cmd = &commands[name]; if cmd.help_available { - if let Some(guild) = msg.guild() { - let guild = guild.read(); - - if let Some(member) = guild.members.get(&msg.author.id) { - if let Ok(permissions) = member.permissions() { - if cmd.help_available && - (has_all_requirements(cmd, &guild, member, &msg) || - permissions.administrator()) { - 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; } } } + if has_commands { e = e.field(|f| f.name(group_name).value(&desc)); } @@ -275,25 +289,31 @@ pub fn plain(_: &mut Context, if name == with_prefix || name == *command_name { match *command { CommandOrAlias::Command(ref cmd) => { - if !cmd.allowed_roles.is_empty() { - if let Some(guild) = msg.guild() { - let guild = guild.read(); - - if let Some(member) = guild.members.get(&msg.author.id) { - if let Ok(permissions) = member.permissions() { - if !permissions.administrator() && - !has_all_requirements(cmd, &guild, member, &msg) { - break; - } - } - } - } + if has_all_requirements(&cmd, &msg) { + found = Some((command_name, cmd)); + } + else { + break; } - found = Some((command_name, cmd)); }, CommandOrAlias::Alias(ref name) => { - let _ = msg.channel_id.say(&format!("Did you mean {:?}?", name)); - return Ok(()); + let actual_command = group.commands.get(name).unwrap(); + + match *actual_command { + CommandOrAlias::Command(ref cmd) => { + if has_all_requirements(&cmd, &msg) { + found = Some((name, cmd)); + } + else { + break; + } + }, + + CommandOrAlias::Alias(ref name) => { + let _ = msg.channel_id.say(&format!("Did you mean {:?}?", name)); + return Ok(()); + }, + } }, } } @@ -307,6 +327,11 @@ pub fn plain(_: &mut Context, let mut result = format!("**{}**\n", command_name); + if !command.aliases.is_empty() { + let aliases = command.aliases.join("`, `"); + let _ = write!(result, "**Aliases:** `{}`\n", aliases); + } + if let Some(ref desc) = command.desc { let _ = write!(result, "**Description:** {}\n", desc); } @@ -364,17 +389,9 @@ pub fn plain(_: &mut Context, for name in command_names { let cmd = &commands[name]; - if let Some(guild) = msg.guild() { - let guild = guild.read(); - - if let Some(member) = guild.members.get(&msg.author.id) { - if let Ok(permissions) = member.permissions() { - if cmd.help_available && - (permissions.administrator() || has_all_requirements(cmd, &guild, member, &msg)) { - let _ = write!(group_help, "`{}` ", name); - } - } - } + + if cmd.help_available && has_all_requirements(&cmd, &msg) { + let _ = write!(group_help, "`{}` ", name); } } diff --git a/src/framework/standard/mod.rs b/src/framework/standard/mod.rs index c3f0d70..6504c6a 100644 --- a/src/framework/standard/mod.rs +++ b/src/framework/standard/mod.rs @@ -417,6 +417,7 @@ impl StandardFramework { } #[allow(too_many_arguments)] + #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] fn should_fail(&mut self, mut context: &mut Context, message: &Message, @@ -514,10 +515,9 @@ impl StandardFramework { if let Some(member) = guild.members.get(&message.author.id) { if let Ok(permissions) = member.permissions() { - if !permissions.administrator() { - if !has_correct_roles(&command, &guild, &member) { - return Some(DispatchError::LackingRole); - } + if !permissions.administrator() + && !has_correct_roles(command, &guild, member) { + return Some(DispatchError::LackingRole); } } } @@ -532,7 +532,7 @@ impl StandardFramework { if all_passed { None } else { - Some(DispatchError::CheckFailed(command.clone())) + Some(DispatchError::CheckFailed(Arc::clone(command))) } } } @@ -875,7 +875,7 @@ impl Framework for StandardFramework { if let Some(&CommandOrAlias::Command(ref command)) = group.commands.get(&to_check) { let before = self.before.clone(); - let command = command.clone(); + let command = Arc::clone(command); let after = self.after.clone(); let groups = self.groups.clone(); @@ -935,7 +935,7 @@ impl Framework for StandardFramework { } #[cfg(feature = "cache")] -pub(crate) fn has_correct_permissions(command: &Command, message: &Message) -> bool { +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 @@ -949,7 +949,7 @@ pub(crate) fn has_correct_permissions(command: &Command, message: &Message) -> b } #[cfg(feature = "cache")] -pub(crate) fn has_correct_roles(cmd: &Command, guild: &Guild, member: &Member) -> bool { +pub fn has_correct_roles(cmd: &Command, guild: &Guild, member: &Member) -> bool { cmd.allowed_roles .iter() .flat_map(|r| guild.role_by_name(r)) diff --git a/src/http/ratelimiting.rs b/src/http/ratelimiting.rs index 09c2527..08dc9ee 100644 --- a/src/http/ratelimiting.rs +++ b/src/http/ratelimiting.rs @@ -361,7 +361,7 @@ pub(crate) fn perform<'a, F>(route: Route, f: F) -> Result<Response> // - get the global rate; // - sleep if there is 0 remaining // - then, perform the request - let bucket = ROUTES + let bucket = Arc::clone(ROUTES .lock() .entry(route) .or_insert_with(|| { @@ -370,8 +370,7 @@ pub(crate) fn perform<'a, F>(route: Route, f: F) -> Result<Response> remaining: i64::MAX, reset: i64::MAX, })) - }) - .clone(); + })); let mut lock = bucket.lock(); lock.pre_hook(&route); diff --git a/src/internal/macros.rs b/src/internal/macros.rs index 4ee43d3..92a21c1 100644 --- a/src/internal/macros.rs +++ b/src/internal/macros.rs @@ -181,3 +181,10 @@ macro_rules! enum_number { } } } + +macro_rules! try_opt { + ($x:expr) => (match $x { + Some(v) => v, + None => return None, + }); +} diff --git a/src/internal/timer.rs b/src/internal/timer.rs index 0bd0d13..6a8bf74 100644 --- a/src/internal/timer.rs +++ b/src/internal/timer.rs @@ -18,11 +18,11 @@ impl Timer { } pub fn await(&mut self) { - let due_time = (self.due.timestamp() * 1000) + self.due.timestamp_subsec_millis() as i64; + let due_time = (self.due.timestamp() * 1000) + i64::from(self.due.timestamp_subsec_millis()); let now_time = { let now = Utc::now(); - (now.timestamp() * 1000) + now.timestamp_subsec_millis() as i64 + (now.timestamp() * 1000) + i64::from(now.timestamp_subsec_millis()) }; if due_time > now_time { @@ -90,15 +90,12 @@ #![allow(doc_markdown, inline_always)] #![warn(enum_glob_use, if_not_else)] -#[allow(unused_imports)] #[macro_use] extern crate bitflags; -#[allow(unused_imports)] #[macro_use] extern crate log; #[macro_use] extern crate serde_derive; -#[allow(unused_imports)] #[macro_use] extern crate serde_json; @@ -107,34 +104,35 @@ extern crate serde_json; extern crate lazy_static; extern crate chrono; -extern crate parking_lot; extern crate serde; -#[cfg(feature = "utils")] +#[cfg(feature = "base64")] extern crate base64; -#[cfg(feature = "voice")] +#[cfg(feature = "byteorder")] extern crate byteorder; -#[cfg(feature = "gateway")] +#[cfg(feature = "flate2")] extern crate flate2; #[cfg(feature = "hyper")] extern crate hyper; #[cfg(feature = "hyper-native-tls")] extern crate hyper_native_tls; -#[cfg(feature = "http")] +#[cfg(feature = "multipart")] extern crate multipart; #[cfg(feature = "native-tls")] extern crate native_tls; -#[cfg(feature = "voice")] +#[cfg(feature = "opus")] extern crate opus; -#[cfg(feature = "voice")] +#[cfg(feature = "parking_lot")] +extern crate parking_lot; +#[cfg(feature = "sodiumoxide")] extern crate sodiumoxide; #[cfg(feature = "threadpool")] extern crate threadpool; -#[cfg(feature = "client")] +#[cfg(feature = "typemap")] extern crate typemap; -#[cfg(feature = "standard_framework")] +#[cfg(feature = "vec_shift")] extern crate vec_shift; -#[cfg(feature = "gateway")] +#[cfg(feature = "evzht9h3nznqzwl")] extern crate evzht9h3nznqzwl as websocket; #[macro_use] diff --git a/src/model/channel/message.rs b/src/model/channel/message.rs index f6efd74..0e939b2 100644 --- a/src/model/channel/message.rs +++ b/src/model/channel/message.rs @@ -370,7 +370,7 @@ impl Message { // Check if the content is over the maximum number of unicode code // points. let count = content.chars().count() as i64; - let diff = count - (constants::MESSAGE_CODE_LIMIT as i64); + let diff = count - i64::from(constants::MESSAGE_CODE_LIMIT); if diff > 0 { Some(diff as u64) @@ -561,7 +561,7 @@ impl Message { if total <= constants::EMBED_MAX_LENGTH as usize { Ok(()) } else { - let overflow = total as u64 - constants::EMBED_MAX_LENGTH as u64; + let overflow = total as u64 - u64::from(constants::EMBED_MAX_LENGTH); Err(Error::Model(ModelError::EmbedTooLarge(overflow))) } diff --git a/src/model/event.rs b/src/model/event.rs index 8d949c7..90a6828 100644 --- a/src/model/event.rs +++ b/src/model/event.rs @@ -57,13 +57,13 @@ impl CacheUpdate for ChannelCreateEvent { fn update(&mut self, cache: &mut Cache) -> Option<Self::Output> { match self.channel { Channel::Group(ref group) => { - let group = group.clone(); + 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 = cache.users[recipient_id].clone(); + *recipient = Arc::clone(&cache.users[recipient_id]); } writer.channel_id @@ -76,23 +76,23 @@ impl CacheUpdate for ChannelCreateEvent { Channel::Guild(ref channel) => { let (guild_id, channel_id) = channel.with(|channel| (channel.guild_id, channel.id)); - cache.channels.insert(channel_id, channel.clone()); + 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, channel.clone())) + .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((*channel).clone())); + return Some(Channel::Private(Arc::clone(&(*channel)))); } - let channel = channel.clone(); + let channel = Arc::clone(channel); let id = channel.with_mut(|writer| { let user_id = writer.recipient.with_mut(|user| { @@ -101,16 +101,16 @@ impl CacheUpdate for ChannelCreateEvent { user.id }); - writer.recipient = cache.users[&user_id].clone(); + writer.recipient = Arc::clone(&cache.users[&user_id]); writer.id }); - let ch = cache.private_channels.insert(id, channel.clone()); + let ch = cache.private_channels.insert(id, Arc::clone(&channel)); ch.map(Channel::Private) }, Channel::Category(ref category) => cache .categories - .insert(category.read().id, category.clone()) + .insert(category.read().id, Arc::clone(category)) .map(Channel::Category), } } @@ -211,7 +211,7 @@ impl CacheUpdate for ChannelRecipientAddEvent { fn update(&mut self, cache: &mut Cache) -> Option<()> { cache.update_user_entry(&self.user); - let user = cache.users[&self.user.id].clone(); + 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); @@ -260,7 +260,7 @@ impl CacheUpdate for ChannelUpdateEvent { match cache.groups.entry(ch_id) { Entry::Vacant(e) => { - e.insert(group.clone()); + e.insert(Arc::clone(group)); }, Entry::Occupied(mut e) => { let mut dest = e.get_mut().write(); @@ -280,10 +280,10 @@ impl CacheUpdate for ChannelUpdateEvent { Channel::Guild(ref channel) => { let (guild_id, channel_id) = channel.with(|channel| (channel.guild_id, channel.id)); - cache.channels.insert(channel_id, channel.clone()); + 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, channel.clone())) + .with_mut(|g| g.channels.insert(channel_id, Arc::clone(channel))) }); }, Channel::Private(ref channel) => { @@ -341,9 +341,9 @@ impl CacheUpdate for GuildCreateEvent { for (user_id, member) in &mut guild.members { cache.update_user_entry(&member.user.read()); - let user = cache.users[user_id].clone(); + let user = Arc::clone(&cache.users[user_id]); - member.user = user.clone(); + member.user = Arc::clone(&user); } cache.channels.extend(guild.channels.clone()); @@ -431,7 +431,7 @@ impl CacheUpdate for GuildMemberAddEvent { cache.update_user_entry(&self.member.user.read()); // Always safe due to being inserted above. - self.member.user = cache.users[&user_id].clone(); + self.member.user = Arc::clone(&cache.users[&user_id]); cache.guilds.get_mut(&self.guild_id).map(|guild| { guild.with_mut(|guild| { @@ -766,7 +766,7 @@ impl CacheUpdate for PresenceUpdateEvent { if let Some(user) = self.presence.user.as_mut() { cache.update_user_entry(&user.read()); - *user = cache.users[&user_id].clone(); + *user = Arc::clone(&cache.users[&user_id]); } if let Some(guild_id) = self.guild_id { diff --git a/src/model/guild/member.rs b/src/model/guild/member.rs index bca3ecb..02e5afb 100644 --- a/src/model/guild/member.rs +++ b/src/model/guild/member.rs @@ -159,10 +159,7 @@ impl Member { #[cfg(all(feature = "cache", feature = "utils"))] pub fn colour(&self) -> Option<Colour> { let cache = CACHE.read(); - let guild = match cache.guilds.get(&self.guild_id) { - Some(guild) => guild.read(), - None => return None, - }; + let guild = try_opt!(cache.guilds.get(&self.guild_id)).read(); let mut roles = self.roles .iter() @@ -382,25 +379,16 @@ impl Member { /// If role data can not be found for the member, then `None` is returned. #[cfg(feature = "cache")] pub fn roles(&self) -> Option<Vec<Role>> { - CACHE - .read() - .guilds - .values() - .find(|guild| { - guild.read().members.values().any(|m| { - m.user.read().id == self.user.read().id && - m.joined_at == self.joined_at - }) - }) - .map(|guild| { - guild - .read() - .roles - .values() - .filter(|role| self.roles.contains(&role.id)) - .cloned() - .collect() - }) + 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. diff --git a/src/model/guild/mod.rs b/src/model/guild/mod.rs index ea6cb17..110ef4e 100644 --- a/src/model/guild/mod.rs +++ b/src/model/guild/mod.rs @@ -21,6 +21,7 @@ use serde::de::Error as DeError; use serde_json; use super::utils::*; use model::*; +use std; #[cfg(all(feature = "cache", feature = "model"))] use CACHE; @@ -740,21 +741,23 @@ impl Guild { /// Retrieves all [`Member`] that start with a given `String`. /// - /// If the prefix is "zey", following results are possible: - /// - "zey", "zeyla", "zey mei" - /// If 'case_sensitive' is false, the following are not found: - /// - "Zey", "ZEYla", "zeY mei" + /// `sorted` decides whether the best early match of the `prefix` + /// should be the criteria to sort the result. + /// For the `prefix` "zey" and the unsorted result: + /// - "zeya", "zeyaa", "zeyla", "zeyzey", "zeyzeyzey" + /// It would be sorted: + /// - "zeya", "zeyaa", "zeyla", "zeyzey", "zeyzeyzey" /// /// [`Member`]: struct.Member.html - pub fn members_starting_with(&self, prefix: &str, case_sensitive: bool) -> Vec<&Member> { - self.members + pub fn members_starting_with(&self, prefix: &str, case_sensitive: bool, sorted: bool) -> Vec<&Member> { + let mut members: Vec<&Member> = self.members .values() .filter(|member| if case_sensitive { member.user.read().name.starts_with(prefix) } else { - starts_with_case_insensitive(&member.user.read().name, &prefix) + starts_with_case_insensitive(&member.user.read().name, prefix) } || member.nick.as_ref() @@ -763,37 +766,214 @@ impl Guild { if case_sensitive { nick.starts_with(prefix) } else { - starts_with_case_insensitive(&nick, &prefix) - })).collect() + starts_with_case_insensitive(nick, prefix) + })).collect(); + + if sorted { + members + .sort_by(|a, b| { + let name_a = match a.nick { + Some(ref nick) => { + if contains_case_insensitive(&a.user.read().name[..], prefix) { + a.user.read().name.clone() + } else { + nick.clone() + } + }, + None => a.user.read().name.clone(), + }; + + let name_b = match b.nick { + Some(ref nick) => { + if contains_case_insensitive(&b.user.read().name[..], prefix) { + b.user.read().name.clone() + } else { + nick.clone() + } + }, + None => b.user.read().name.clone(), + }; + + closest_to_origin(prefix, &name_a[..], &name_b[..]) + }); + members + } else { + members + } } - /// Retrieves all [`Member`] containing a given `String`. + /// Retrieves all [`Member`] containing a given `String` as + /// either username or nick, with a priority on username. /// /// If the substring is "yla", following results are possible: /// - "zeyla", "meiyla", "yladenisyla" /// If 'case_sensitive' is false, the following are not found: /// - "zeYLa", "meiyLa", "LYAdenislyA" /// + /// `sorted` decides whether the best early match of the search-term + /// should be the criteria to sort the result. + /// It will look at the account name first, if that does not fit the + /// search-criteria `substring`, the display-name will be considered. + /// For the `substring` "zey" and the unsorted result: + /// - "azey", "zey", "zeyla", "zeylaa", "zeyzeyzey" + /// It would be sorted: + /// - "zey", "azey", "zeyla", "zeylaa", "zeyzeyzey" + /// + /// **Note**: Due to two fields of a `Member` being candidates for + /// the searched field, setting `sorted` to `true` will result in an overhead, + /// as both fields have to be considered again for sorting. + /// /// [`Member`]: struct.Member.html - pub fn members_containing(&self, substring: &str, case_sensitive: bool) -> Vec<&Member> { - self.members + pub fn members_containing(&self, substring: &str, case_sensitive: bool, sorted: bool) -> Vec<&Member> { + let mut members: Vec<&Member> = self.members .values() .filter(|member| if case_sensitive { member.user.read().name.contains(substring) } else { - contains_case_insensitive(&member.user.read().name, &substring) + contains_case_insensitive(&member.user.read().name, substring) } || member.nick.as_ref() - .map_or(false, |nick| + .map_or(false, |nick| { + + if case_sensitive { + nick.contains(substring) + } else { + contains_case_insensitive(nick, substring) + } + })).collect(); + + if sorted { + members + .sort_by(|a, b| { + let name_a = match a.nick { + Some(ref nick) => { + if contains_case_insensitive(&a.user.read().name[..], substring) { + a.user.read().name.clone() + } else { + nick.clone() + } + }, + None => a.user.read().name.clone(), + }; + + let name_b = match b.nick { + Some(ref nick) => { + if contains_case_insensitive(&b.user.read().name[..], substring) { + b.user.read().name.clone() + } else { + nick.clone() + } + }, + None => b.user.read().name.clone(), + }; + + closest_to_origin(substring, &name_a[..], &name_b[..]) + }); + members + } else { + members + } + } - if case_sensitive { - nick.starts_with(substring) - } else { - contains_case_insensitive(&nick, &substring) - })).collect() + /// Retrieves all [`Member`] containing a given `String` in + /// their username. + /// + /// If the substring is "yla", following results are possible: + /// - "zeyla", "meiyla", "yladenisyla" + /// If 'case_sensitive' is false, the following are not found: + /// - "zeYLa", "meiyLa", "LYAdenislyA" + /// + /// `sort` decides whether the best early match of the search-term + /// should be the criteria to sort the result. + /// For the `substring` "zey" and the unsorted result: + /// - "azey", "zey", "zeyla", "zeylaa", "zeyzeyzey" + /// It would be sorted: + /// - "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 + .values() + .filter(|member| { + if case_sensitive { + member.user.read().name.contains(substring) + } else { + contains_case_insensitive(&member.user.read().name, substring) + } + }).collect(); + + if sorted { + members + .sort_by(|a, b| { + let name_a = &a.user.read().name; + let name_b = &b.user.read().name; + closest_to_origin(substring, &name_a[..], &name_b[..]) + }); + members + } else { + members + } + } + + /// Retrieves all [`Member`] containing a given `String` in + /// their nick. + /// + /// If the substring is "yla", following results are possible: + /// - "zeyla", "meiyla", "yladenisyla" + /// If 'case_sensitive' is false, the following are not found: + /// - "zeYLa", "meiyLa", "LYAdenislyA" + /// + /// `sort` decides whether the best early match of the search-term + /// should be the criteria to sort the result. + /// For the `substring` "zey" and the unsorted result: + /// - "azey", "zey", "zeyla", "zeylaa", "zeyzeyzey" + /// It would be sorted: + /// - "zey", "azey", "zeyla", "zeylaa", "zeyzeyzey" + /// + /// **Note**: Instead of panicing, when sorting does not find + /// 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 + .values() + .filter(|member| + member.nick.as_ref() + .map_or(false, |nick| { + + if case_sensitive { + nick.contains(substring) + } else { + contains_case_insensitive(nick, substring) + } + })).collect(); + + if sorted { + members + .sort_by(|a, b| { + let name_a = match a.nick { + Some(ref nick) => { + nick.clone() + }, + None => a.user.read().name.clone(), + }; + + let name_b = match b.nick { + Some(ref nick) => { + nick.clone() + }, + None => b.user.read().name.clone(), + }; + + closest_to_origin(substring, &name_a[..], &name_b[..]) + }); + members + } else { + members + } } /// Moves a member to a specific voice channel. @@ -1270,6 +1450,27 @@ fn starts_with_case_insensitive(to_look_at: &str, to_find: &str) -> bool { to_look_at.to_lowercase().starts_with(to_find) } +/// Takes a `&str` as `origin` and tests if either +/// `word_a` or `word_b` is closer. +/// +/// **Note**: Normally `word_a` and `word_b` are +/// expected to contain `origin` as substring. +/// If not, using `closest_to_origin` would sort these +/// the end. +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(), + None => return std::cmp::Ordering::Greater, + }; + + let value_b = match word_b.find(origin) { + Some(value) => value + word_b.len(), + None => return std::cmp::Ordering::Less, + }; + + value_a.cmp(&value_b) +} + /// Information relating to a guild's widget embed. #[derive(Clone, Copy, Debug, Deserialize)] pub struct GuildEmbed { diff --git a/src/model/permissions.rs b/src/model/permissions.rs index 22599ea..51aafe5 100644 --- a/src/model/permissions.rs +++ b/src/model/permissions.rs @@ -445,7 +445,7 @@ impl<'de> Visitor<'de> for U64Visitor { fn visit_i64<E: DeError>(self, value: i64) -> StdResult<u64, E> { Ok(value as u64) } - fn visit_u32<E: DeError>(self, value: u32) -> StdResult<u64, E> { Ok(value as u64) } + fn visit_u32<E: DeError>(self, value: u32) -> StdResult<u64, E> { Ok(u64::from(value)) } fn visit_u64<E: DeError>(self, value: u64) -> StdResult<u64, E> { Ok(value) } } diff --git a/src/utils/colour.rs b/src/utils/colour.rs index 72df2e9..4e612cc 100644 --- a/src/utils/colour.rs +++ b/src/utils/colour.rs @@ -125,9 +125,9 @@ impl Colour { /// assert_eq!(colour.tuple(), (217, 45, 215)); /// ``` pub fn from_rgb(r: u8, g: u8, b: u8) -> Colour { - let mut uint = r as u32; - uint = (uint << 8) | (g as u32); - uint = (uint << 8) | (b as u32); + let mut uint = u32::from(r); + uint = (uint << 8) | (u32::from(g)); + uint = (uint << 8) | (u32::from(b)); Colour(uint) } diff --git a/src/voice/audio.rs b/src/voice/audio.rs index be3ae60..24a4fcd 100644 --- a/src/voice/audio.rs +++ b/src/voice/audio.rs @@ -1,5 +1,5 @@ pub const HEADER_LEN: usize = 12; -pub const SAMPLE_RATE: u32 = 48000; +pub const SAMPLE_RATE: u32 = 48_000; /// A readable audio source. pub trait AudioSource: Send { diff --git a/src/voice/connection.rs b/src/voice/connection.rs index 47b448c..e507f41 100644 --- a/src/voice/connection.rs +++ b/src/voice/connection.rs @@ -139,7 +139,7 @@ impl Connection { .set_read_timeout(Some(Duration::from_millis(25))); let mutexed_client = Arc::new(Mutex::new(client)); - let thread_items = start_threads(mutexed_client.clone(), &udp)?; + let thread_items = start_threads(Arc::clone(&mutexed_client), &udp)?; info!("[Voice] Connected to: {}", info.endpoint); diff --git a/tests/test_colour.rs b/tests/test_colour.rs index c26bf23..2b36562 100644 --- a/tests/test_colour.rs +++ b/tests/test_colour.rs @@ -1,4 +1,4 @@ -#![allow(unreadable_literal)] +#![cfg_attr(feature = "cargo-clippy", allow(unreadable_literal))] #![cfg(feature = "utils")] extern crate serenity; diff --git a/tests/test_create_embed.rs b/tests/test_create_embed.rs index e20308e..8cc0de7 100644 --- a/tests/test_create_embed.rs +++ b/tests/test_create_embed.rs @@ -1,4 +1,4 @@ -#![allow(unreadable_literal)] +#![cfg_attr(feature = "cargo-clippy", allow(unreadable_literal))] #![cfg(feature = "utils")] #[macro_use] |