aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md95
-rw-r--r--Cargo.toml2
-rw-r--r--README.md17
-rw-r--r--examples/08_env_logging/Cargo.toml12
-rw-r--r--examples/08_env_logging/src/main.rs45
-rw-r--r--logo.pngbin4366 -> 32038 bytes
-rw-r--r--src/builder/create_embed.rs2
-rw-r--r--src/cache/mod.rs6
-rw-r--r--src/client/bridge/gateway/shard_manager.rs13
-rw-r--r--src/client/bridge/gateway/shard_queuer.rs14
-rw-r--r--src/client/bridge/gateway/shard_runner.rs8
-rw-r--r--src/client/dispatch.rs2
-rw-r--r--src/client/mod.rs12
-rw-r--r--src/framework/standard/args.rs49
-rw-r--r--src/framework/standard/help_commands.rs143
-rw-r--r--src/framework/standard/mod.rs16
-rw-r--r--src/http/ratelimiting.rs5
-rw-r--r--src/internal/macros.rs7
-rw-r--r--src/internal/timer.rs4
-rw-r--r--src/lib.rs24
-rw-r--r--src/model/channel/message.rs4
-rw-r--r--src/model/event.rs34
-rw-r--r--src/model/guild/member.rs34
-rw-r--r--src/model/guild/mod.rs239
-rw-r--r--src/model/permissions.rs2
-rw-r--r--src/utils/colour.rs6
-rw-r--r--src/voice/audio.rs2
-rw-r--r--src/voice/connection.rs2
-rw-r--r--tests/test_colour.rs2
-rw-r--r--tests/test_create_embed.rs2
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
diff --git a/Cargo.toml b/Cargo.toml
index ca5c732..fefa679 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/README.md b/README.md
index 281fb43..973b0e9 100644
--- a/README.md
+++ b/README.md
@@ -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);
+ }
+}
diff --git a/logo.png b/logo.png
index 4946260..b5ddceb 100644
--- a/logo.png
+++ b/logo.png
Binary files differ
diff --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 {
diff --git a/src/lib.rs b/src/lib.rs
index a3710eb..121740e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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]