aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml10
-rw-r--r--examples/01_basic_ping_bot/src/main.rs6
-rw-r--r--examples/02_transparent_guild_sharding/src/main.rs17
-rw-r--r--examples/03_struct_utilities/src/main.rs6
-rw-r--r--examples/04_message_builder/src/main.rs6
-rw-r--r--examples/05_command_framework/src/main.rs6
-rw-r--r--examples/06_voice/src/main.rs32
-rw-r--r--examples/07_sample_bot_structure/src/main.rs5
-rw-r--r--examples/08_env_logging/src/main.rs6
-rw-r--r--src/builder/create_embed.rs188
-rw-r--r--src/builder/create_invite.rs43
-rw-r--r--src/builder/create_message.rs18
-rw-r--r--src/builder/edit_channel.rs20
-rw-r--r--src/builder/edit_guild.rs26
-rw-r--r--src/builder/edit_member.rs18
-rw-r--r--src/builder/edit_profile.rs29
-rw-r--r--src/builder/edit_role.rs62
-rw-r--r--src/builder/execute_webhook.rs33
-rw-r--r--src/builder/get_messages.rs12
-rw-r--r--src/builder/mod.rs2
-rw-r--r--src/cache/mod.rs54
-rw-r--r--src/client/bridge/gateway/mod.rs114
-rw-r--r--src/client/bridge/gateway/shard_manager.rs299
-rw-r--r--src/client/bridge/gateway/shard_manager_monitor.rs54
-rw-r--r--src/client/bridge/gateway/shard_messenger.rs276
-rw-r--r--src/client/bridge/gateway/shard_queuer.rs64
-rw-r--r--src/client/bridge/gateway/shard_runner.rs403
-rw-r--r--src/client/bridge/gateway/shard_runner_message.rs43
-rw-r--r--src/client/bridge/mod.rs9
-rw-r--r--src/client/context.rs153
-rw-r--r--src/client/dispatch.rs252
-rw-r--r--src/client/event_handler.rs105
-rw-r--r--src/client/mod.rs184
-rw-r--r--src/framework/standard/args.rs149
-rw-r--r--src/framework/standard/command.rs23
-rw-r--r--src/framework/standard/configuration.rs24
-rw-r--r--src/framework/standard/create_command.rs21
-rw-r--r--src/framework/standard/create_group.rs8
-rw-r--r--src/framework/standard/help_commands.rs31
-rw-r--r--src/framework/standard/mod.rs43
-rw-r--r--src/gateway/mod.rs17
-rw-r--r--src/gateway/shard.rs887
-rw-r--r--src/gateway/ws_client_ext.rs133
-rw-r--r--src/http/mod.rs48
-rw-r--r--src/http/ratelimiting.rs22
-rw-r--r--src/internal/macros.rs1
-rw-r--r--src/internal/rwlock_ext.rs13
-rw-r--r--src/lib.rs14
-rw-r--r--src/model/channel/attachment.rs8
-rw-r--r--src/model/channel/channel_category.rs18
-rw-r--r--src/model/channel/channel_id.rs36
-rw-r--r--src/model/channel/embed.rs29
-rw-r--r--src/model/channel/guild_channel.rs54
-rw-r--r--src/model/channel/message.rs32
-rw-r--r--src/model/channel/mod.rs18
-rw-r--r--src/model/channel/private_channel.rs2
-rw-r--r--src/model/channel/reaction.rs2
-rw-r--r--src/model/error.rs8
-rw-r--r--src/model/event.rs52
-rw-r--r--src/model/gateway.rs3
-rw-r--r--src/model/guild/audit_log.rs109
-rw-r--r--src/model/guild/emoji.rs4
-rw-r--r--src/model/guild/feature.rs25
-rw-r--r--src/model/guild/guild_id.rs31
-rw-r--r--src/model/guild/member.rs65
-rw-r--r--src/model/guild/mod.rs102
-rw-r--r--src/model/guild/partial_guild.rs11
-rw-r--r--src/model/guild/role.rs8
-rw-r--r--src/model/invite.rs8
-rw-r--r--src/model/misc.rs11
-rw-r--r--src/model/mod.rs5
-rw-r--r--src/model/user.rs92
-rw-r--r--src/model/utils.rs13
-rw-r--r--src/model/webhook.rs11
-rw-r--r--src/utils/mod.rs22
-rw-r--r--src/voice/connection.rs15
-rw-r--r--tests/resources/guild_create_features.json32
-rw-r--r--tests/test_channels.rs4
-rw-r--r--tests/test_create_embed.rs4
-rw-r--r--tests/test_deser.rs8
-rw-r--r--tests/test_formatters.rs4
81 files changed, 2964 insertions, 1871 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 2094590..b19621b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,11 +8,12 @@ license = "ISC"
name = "serenity"
readme = "README.md"
repository = "https://github.com/zeyla/serenity.git"
-version = "0.4.3"
+version = "0.5.0"
[dependencies]
bitflags = "^1.0"
log = "~0.3"
+parking_lot = "^0.4"
serde = "^1.0"
serde_derive = "^1.0"
serde_json = "^1.0"
@@ -23,7 +24,7 @@ version = "~0.7"
[dependencies.byteorder]
optional = true
-version = "1.1"
+version = "^1.0"
[dependencies.chrono]
features = ["serde"]
@@ -59,10 +60,6 @@ version = "0.1"
optional = true
version = "0.2"
-[dependencies.parking_lot]
-optional = true
-version = "0.4"
-
[dependencies.sodiumoxide]
default-features = false
optional = true
@@ -104,7 +101,6 @@ client = [
"gateway",
"http",
"lazy_static",
- "parking_lot",
"threadpool",
"typemap",
]
diff --git a/examples/01_basic_ping_bot/src/main.rs b/examples/01_basic_ping_bot/src/main.rs
index 4c2f35c..a900cc2 100644
--- a/examples/01_basic_ping_bot/src/main.rs
+++ b/examples/01_basic_ping_bot/src/main.rs
@@ -12,7 +12,7 @@ impl EventHandler for Handler {
//
// Event handlers are dispatched through multi-threading, and so multiple
// of a single event can be dispatched simultaneously.
- fn on_message(&self, _: Context, msg: Message) {
+ fn message(&self, _: Context, msg: Message) {
if msg.content == "!ping" {
// Sending a message can fail, due to a network error, an
// authentication error, or lack of permissions to post in the
@@ -30,7 +30,7 @@ impl EventHandler for Handler {
// private channels, and more.
//
// In this case, just print what the current user's username is.
- fn on_ready(&self, _: Context, ready: Ready) {
+ fn ready(&self, _: Context, ready: Ready) {
println!("{} is connected!", ready.user.name);
}
}
@@ -43,7 +43,7 @@ fn main() {
// Create a new instance of the Client, logging in as a bot. This will
// automatically prepend your bot token with "Bot ", which is a requirement
// by Discord for bot users.
- let mut client = Client::new(&token, Handler);
+ let mut client = Client::new(&token, Handler).expect("Err creating client");
// Finally, start a single shard, and start listening to events.
//
diff --git a/examples/02_transparent_guild_sharding/src/main.rs b/examples/02_transparent_guild_sharding/src/main.rs
index 55b20c1..c1a5bbb 100644
--- a/examples/02_transparent_guild_sharding/src/main.rs
+++ b/examples/02_transparent_guild_sharding/src/main.rs
@@ -25,24 +25,17 @@ use std::env;
struct Handler;
impl EventHandler for Handler {
- fn on_message(&self, ctx: Context, msg: Message) {
+ fn message(&self, ctx: Context, msg: Message) {
if msg.content == "!ping" {
- // The current shard needs to be unlocked so it can be read from, as
- // multiple threads may otherwise attempt to read from or mutate it
- // concurrently.
- {
- let shard = ctx.shard.lock();
- let shard_info = shard.shard_info();
- println!("Shard {}", shard_info[0]);
- }
+ println!("Shard {}", ctx.shard_id);
if let Err(why) = msg.channel_id.say("Pong!") {
println!("Error sending message: {:?}", why);
}
- }
+ }
}
- fn on_ready(&self, _: Context, ready: Ready) {
+ fn ready(&self, _: Context, ready: Ready) {
println!("{} is connected!", ready.user.name);
}
}
@@ -52,7 +45,7 @@ fn main() {
// 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);
+ let mut client = Client::new(&token, Handler).expect("Err creating client");
// The total number of shards to use. The "current shard number" of a
// shard - that is, the shard it is assigned to - is indexed at 0,
diff --git a/examples/03_struct_utilities/src/main.rs b/examples/03_struct_utilities/src/main.rs
index e08cf00..4aafb07 100644
--- a/examples/03_struct_utilities/src/main.rs
+++ b/examples/03_struct_utilities/src/main.rs
@@ -7,7 +7,7 @@ use std::env;
struct Handler;
impl EventHandler for Handler {
- fn on_message(&self, _: Context, msg: Message) {
+ fn message(&self, _: Context, msg: Message) {
if msg.content == "!messageme" {
// If the `methods` feature is enabled, then model structs will
// have a lot of useful methods implemented, to avoid using an
@@ -23,7 +23,7 @@ impl EventHandler for Handler {
}
}
- fn on_ready(&self, _: Context, ready: Ready) {
+ fn ready(&self, _: Context, ready: Ready) {
println!("{} is connected!", ready.user.name);
}
}
@@ -32,7 +32,7 @@ fn main() {
// 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);
+ let mut client = Client::new(&token, Handler).expect("Err creating client");
if let Err(why) = client.start() {
println!("Client error: {:?}", why);
diff --git a/examples/04_message_builder/src/main.rs b/examples/04_message_builder/src/main.rs
index 713224b..1ecd302 100644
--- a/examples/04_message_builder/src/main.rs
+++ b/examples/04_message_builder/src/main.rs
@@ -8,7 +8,7 @@ use std::env;
struct Handler;
impl EventHandler for Handler {
- fn on_message(&self, _: Context, msg: Message) {
+ fn message(&self, _: Context, msg: Message) {
if msg.content == "!ping" {
let channel = match msg.channel_id.get() {
Ok(channel) => channel,
@@ -37,7 +37,7 @@ impl EventHandler for Handler {
}
}
- fn on_ready(&self, _: Context, ready: Ready) {
+ fn ready(&self, _: Context, ready: Ready) {
println!("{} is connected!", ready.user.name);
}
}
@@ -45,7 +45,7 @@ fn main() {
// 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);
+ let mut client = Client::new(&token, Handler).expect("Err creating client");
if let Err(why) = client.start() {
println!("Client error: {:?}", why);
diff --git a/examples/05_command_framework/src/main.rs b/examples/05_command_framework/src/main.rs
index 437c44e..02a4f41 100644
--- a/examples/05_command_framework/src/main.rs
+++ b/examples/05_command_framework/src/main.rs
@@ -31,7 +31,7 @@ impl Key for CommandCounter {
struct Handler;
impl EventHandler for Handler {
- fn on_ready(&self, _: Context, ready: Ready) {
+ fn ready(&self, _: Context, ready: Ready) {
println!("{} is connected!", ready.user.name);
}
}
@@ -41,7 +41,7 @@ fn main() {
let token = env::var("DISCORD_TOKEN").expect(
"Expected a token in the environment",
);
- let mut client = Client::new(&token, Handler);
+ let mut client = Client::new(&token, Handler).expect("Err creating client");
{
let mut data = client.data.lock();
@@ -199,7 +199,7 @@ command!(about_role(_ctx, msg, args) {
if let Some(guild) = msg.guild() {
// `role_by_name()` allows us to attempt attaining a reference to a role
// via its name.
- if let Some(role) = guild.read().unwrap().role_by_name(&potential_role_name) {
+ if let Some(role) = guild.read().role_by_name(&potential_role_name) {
if let Err(why) = msg.channel_id.say(&format!("Role-ID: {}", role.id)) {
println!("Error sending message: {:?}", why);
}
diff --git a/examples/06_voice/src/main.rs b/examples/06_voice/src/main.rs
index 8de2c3b..64738a3 100644
--- a/examples/06_voice/src/main.rs
+++ b/examples/06_voice/src/main.rs
@@ -21,7 +21,7 @@ use std::env;
struct Handler;
impl EventHandler for Handler {
- fn on_ready(&self, _: Context, ready: Ready) {
+ fn ready(&self, _: Context, ready: Ready) {
println!("{} is connected!", ready.user.name);
}
}
@@ -30,7 +30,7 @@ fn main() {
// 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);
+ let mut client = Client::new(&token, Handler).expect("Err creating client");
client.with_framework(StandardFramework::new()
.configure(|c| c
@@ -49,8 +49,8 @@ fn main() {
}
command!(deafen(ctx, msg) {
- let guild_id = match CACHE.read().unwrap().guild_channel(msg.channel_id) {
- Some(channel) => channel.read().unwrap().guild_id,
+ let guild_id = match CACHE.read().guild_channel(msg.channel_id) {
+ Some(channel) => channel.read().guild_id,
None => {
check_msg(msg.channel_id.say("Groups and DMs not supported"));
@@ -95,8 +95,8 @@ command!(join(ctx, msg, args) {
},
};
- let guild_id = match CACHE.read().unwrap().guild_channel(msg.channel_id) {
- Some(channel) => channel.read().unwrap().guild_id,
+ let guild_id = match CACHE.read().guild_channel(msg.channel_id) {
+ Some(channel) => channel.read().guild_id,
None => {
check_msg(msg.channel_id.say("Groups and DMs not supported"));
@@ -111,8 +111,8 @@ command!(join(ctx, msg, args) {
});
command!(leave(ctx, msg) {
- let guild_id = match CACHE.read().unwrap().guild_channel(msg.channel_id) {
- Some(channel) => channel.read().unwrap().guild_id,
+ let guild_id = match CACHE.read().guild_channel(msg.channel_id) {
+ Some(channel) => channel.read().guild_id,
None => {
check_msg(msg.channel_id.say("Groups and DMs not supported"));
@@ -133,8 +133,8 @@ command!(leave(ctx, msg) {
});
command!(mute(ctx, msg) {
- let guild_id = match CACHE.read().unwrap().guild_channel(msg.channel_id) {
- Some(channel) => channel.read().unwrap().guild_id,
+ let guild_id = match CACHE.read().guild_channel(msg.channel_id) {
+ Some(channel) => channel.read().guild_id,
None => {
check_msg(msg.channel_id.say("Groups and DMs not supported"));
@@ -182,8 +182,8 @@ command!(play(ctx, msg, args) {
return Ok(());
}
- let guild_id = match CACHE.read().unwrap().guild_channel(msg.channel_id) {
- Some(channel) => channel.read().unwrap().guild_id,
+ let guild_id = match CACHE.read().guild_channel(msg.channel_id) {
+ Some(channel) => channel.read().guild_id,
None => {
check_msg(msg.channel_id.say("Error finding channel info"));
@@ -212,8 +212,8 @@ command!(play(ctx, msg, args) {
});
command!(undeafen(ctx, msg) {
- let guild_id = match CACHE.read().unwrap().guild_channel(msg.channel_id) {
- Some(channel) => channel.read().unwrap().guild_id,
+ let guild_id = match CACHE.read().guild_channel(msg.channel_id) {
+ Some(channel) => channel.read().guild_id,
None => {
check_msg(msg.channel_id.say("Error finding channel info"));
@@ -231,8 +231,8 @@ command!(undeafen(ctx, msg) {
});
command!(unmute(ctx, msg) {
- let guild_id = match CACHE.read().unwrap().guild_channel(msg.channel_id) {
- Some(channel) => channel.read().unwrap().guild_id,
+ let guild_id = match CACHE.read().guild_channel(msg.channel_id) {
+ Some(channel) => channel.read().guild_id,
None => {
check_msg(msg.channel_id.say("Error finding channel info"));
diff --git a/examples/07_sample_bot_structure/src/main.rs b/examples/07_sample_bot_structure/src/main.rs
index 37f6c98..4c0ff8b 100644
--- a/examples/07_sample_bot_structure/src/main.rs
+++ b/examples/07_sample_bot_structure/src/main.rs
@@ -48,7 +48,10 @@ fn main() {
// `RUST_LOG` to debug`.
env_logger::init().expect("Failed to initialize env_logger");
- let mut client = Client::new(&env::var("DISCORD_TOKEN").unwrap(), Handler);
+ let token = env::var("DISCORD_TOKEN")
+ .expect("Expected a token in the environment");
+
+ let mut client = Client::new(&token, Handler).expect("Err creating client");
let owners = match http::get_current_application_info() {
Ok(info) => {
diff --git a/examples/08_env_logging/src/main.rs b/examples/08_env_logging/src/main.rs
index db34037..ca6af8f 100644
--- a/examples/08_env_logging/src/main.rs
+++ b/examples/08_env_logging/src/main.rs
@@ -11,12 +11,12 @@ use std::env;
struct Handler;
impl EventHandler for Handler {
- fn on_ready(&self, _: Context, ready: Ready) {
+ fn 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) {
+ fn resume(&self, _: Context, resume: ResumedEvent) {
// Log at the DEBUG level.
//
// In this example, this will not show up in the logs because DEBUG is
@@ -37,7 +37,7 @@ fn main() {
let token = env::var("DISCORD_TOKEN")
.expect("Expected a token in the environment");
- let mut client = Client::new(&token, Handler);
+ let mut client = Client::new(&token, Handler).expect("Err creating client");
if let Err(why) = client.start() {
error!("Client error: {:?}", why);
diff --git a/src/builder/create_embed.rs b/src/builder/create_embed.rs
index b38984c..7c4d3d6 100644
--- a/src/builder/create_embed.rs
+++ b/src/builder/create_embed.rs
@@ -17,10 +17,12 @@
use chrono::{DateTime, TimeZone};
use serde_json::Value;
+use std::collections::HashMap;
use std::default::Default;
use std::fmt::Display;
use internal::prelude::*;
use model::Embed;
+use utils;
#[cfg(feature = "utils")]
use utils::Colour;
@@ -37,7 +39,7 @@ use utils::Colour;
/// [`Embed`]: ../model/struct.Embed.html
/// [`ExecuteWebhook::embeds`]: struct.ExecuteWebhook.html#method.embeds
#[derive(Clone, Debug)]
-pub struct CreateEmbed(pub Map<String, Value>);
+pub struct CreateEmbed(pub HashMap<&'static str, Value>);
impl CreateEmbed {
/// Set the author of the embed.
@@ -48,9 +50,9 @@ impl CreateEmbed {
/// [`CreateEmbedAuthor`]: struct.CreateEmbedAuthor.html
pub fn author<F>(mut self, f: F) -> Self
where F: FnOnce(CreateEmbedAuthor) -> CreateEmbedAuthor {
- let author = f(CreateEmbedAuthor::default()).0;
+ let map = utils::hashmap_to_json_map(f(CreateEmbedAuthor::default()).0);
- self.0.insert("author".to_string(), Value::Object(author));
+ self.0.insert("author", Value::Object(map));
CreateEmbed(self.0)
}
@@ -68,7 +70,7 @@ impl CreateEmbed {
#[cfg(feature = "utils")]
pub fn colour<C: Into<Colour>>(mut self, colour: C) -> Self {
self.0.insert(
- "color".to_string(),
+ "color",
Value::Number(Number::from(u64::from(colour.into().0))),
);
@@ -88,7 +90,7 @@ impl CreateEmbed {
#[cfg(not(feature = "utils"))]
pub fn colour(mut self, colour: u32) -> Self {
self.0
- .insert("color".to_string(), Value::Number(Number::from(colour)));
+ .insert("color", Value::Number(Number::from(colour)));
CreateEmbed(self.0)
}
@@ -98,7 +100,7 @@ impl CreateEmbed {
/// **Note**: This can't be longer than 2048 characters.
pub fn description<D: Display>(mut self, description: D) -> Self {
self.0.insert(
- "description".to_string(),
+ "description",
Value::String(format!("{}", description)),
);
@@ -115,54 +117,35 @@ impl CreateEmbed {
/// name and 1024 in a field value and a field is inline by default.
///
/// [`CreateEmbedField`]: struct.CreateEmbedField.html
- pub fn field<F>(mut self, f: F) -> Self
- where F: FnOnce(CreateEmbedField) -> CreateEmbedField {
- let field = f(CreateEmbedField::default()).0;
-
+ pub fn field<T, U>(mut self, name: T, value: U, inline: bool) -> Self
+ where T: Into<String>, U: Into<String> {
{
- let key = "fields".to_string();
-
- let entry = self.0.remove(&key).unwrap_or_else(|| Value::Array(vec![]));
- let mut arr = match entry {
- Value::Array(inner) => inner,
- _ => {
- // The type of `entry` should always be a `Value::Array`.
- //
- // Theoretically this never happens, but you never know.
- //
- // In the event that it does, just return the current value.
- return CreateEmbed(self.0);
- },
- };
- arr.push(Value::Object(field));
-
- self.0.insert("fields".to_string(), Value::Array(arr));
+ let entry = self.0
+ .entry("fields")
+ .or_insert_with(|| Value::Array(vec![]));
+
+ if let Value::Array(ref mut inner) = *entry {
+ inner.push(json!({
+ "inline": inline,
+ "name": name.into(),
+ "value": value.into(),
+ }));
+ }
}
- CreateEmbed(self.0)
+ self
}
/// Adds multiple fields at once.
- pub fn fields<It: IntoIterator<Item=CreateEmbedField>>(mut self, fields: It) -> Self {
- let fields = fields
- .into_iter()
- .map(|m| Value::Object(m.0))
- .collect::<Vec<Value>>();
-
- {
- let key = "fields".to_string();
-
- let entry = self.0.remove(&key).unwrap_or_else(|| Value::Array(vec![]));
- let mut arr = match entry {
- Value::Array(inner) => inner,
- _ => return CreateEmbed(self.0),
- };
- arr.extend(fields);
-
- self.0.insert("fields".to_string(), Value::Array(arr));
+ pub fn fields<T, U, It>(mut self, fields: It) -> Self
+ where It: IntoIterator<Item=(T, U, bool)>,
+ T: Into<String>,
+ U: Into<String> {
+ for field in fields {
+ self = self.field(field.0.into(), field.1.into(), field.2);
}
- CreateEmbed(self.0)
+ self
}
/// Set the footer of the embed.
@@ -174,8 +157,9 @@ impl CreateEmbed {
pub fn footer<F>(mut self, f: F) -> Self
where F: FnOnce(CreateEmbedFooter) -> CreateEmbedFooter {
let footer = f(CreateEmbedFooter::default()).0;
+ let map = utils::hashmap_to_json_map(footer);
- self.0.insert("footer".to_string(), Value::Object(footer));
+ self.0.insert("footer", Value::Object(map));
CreateEmbed(self.0)
}
@@ -186,7 +170,7 @@ impl CreateEmbed {
"url": url.to_string()
});
- self.0.insert("image".to_string(), image);
+ self.0.insert("image", image);
CreateEmbed(self.0)
}
@@ -197,7 +181,7 @@ impl CreateEmbed {
"url": url.to_string(),
});
- self.0.insert("thumbnail".to_string(), thumbnail);
+ self.0.insert("thumbnail", thumbnail);
CreateEmbed(self.0)
}
@@ -223,8 +207,9 @@ impl CreateEmbed {
/// # use serenity::model::*;
/// #
/// struct Handler;
+ ///
/// impl EventHandler for Handler {
- /// fn on_message(&self, _: Context, msg: Message) {
+ /// fn message(&self, _: Context, msg: Message) {
/// if msg.content == "~embed" {
/// let _ = msg.channel_id.send_message(|m| m
/// .embed(|e| e
@@ -234,7 +219,9 @@ impl CreateEmbed {
/// }
/// }
///
- /// let mut client = Client::new("token", Handler); client.start().unwrap();
+ /// let mut client = Client::new("token", Handler).unwrap();
+ ///
+ /// client.start().unwrap();
/// ```
///
/// Creating a join-log:
@@ -246,23 +233,30 @@ impl CreateEmbed {
/// # use serenity::model::*;
/// #
/// struct Handler;
+ ///
/// impl EventHandler for Handler {
+<<<<<<< HEAD
+ /// fn guild_member_addition(&self, _: Context, guild_id: GuildId, member: Member) {
+ /// use serenity::client::CACHE;
+ /// let cache = CACHE.read();
+=======
/// fn on_guild_member_addition(&self, _: Context, guild_id: GuildId, member: Member) {
/// use serenity::CACHE;
/// let cache = CACHE.read().unwrap();
+>>>>>>> v0.4.3
///
/// if let Some(guild) = cache.guild(guild_id) {
- /// let guild = guild.read().unwrap();
+ /// let guild = guild.read();
///
/// let channel_search = guild
/// .channels
/// .values()
- /// .find(|c| c.read().unwrap().name == "join-log");
+ /// .find(|c| c.read().name == "join-log");
///
/// if let Some(channel) = channel_search {
- /// let user = member.user.read().unwrap();
+ /// let user = member.user.read();
///
- /// let _ = channel.read().unwrap().send_message(|m| m
+ /// let _ = channel.read().send_message(|m| m
/// .embed(|e| {
/// let mut e = e
/// .author(|a| a.icon_url(&user.face()).name(&user.name))
@@ -279,11 +273,13 @@ impl CreateEmbed {
/// }
/// }
///
- /// let mut client = Client::new("token", Handler); client.start().unwrap();
+ /// let mut client = Client::new("token", Handler).unwrap();
+ ///
+ /// client.start().unwrap();
/// ```
pub fn timestamp<T: Into<Timestamp>>(mut self, timestamp: T) -> Self {
self.0
- .insert("timestamp".to_string(), Value::String(timestamp.into().ts));
+ .insert("timestamp", Value::String(timestamp.into().ts));
CreateEmbed(self.0)
}
@@ -291,7 +287,7 @@ impl CreateEmbed {
/// Set the title of the embed.
pub fn title<D: Display>(mut self, title: D) -> Self {
self.0
- .insert("title".to_string(), Value::String(format!("{}", title)));
+ .insert("title", Value::String(format!("{}", title)));
CreateEmbed(self.0)
}
@@ -299,7 +295,7 @@ impl CreateEmbed {
/// Set the URL to direct to when clicking on the title.
pub fn url(mut self, url: &str) -> Self {
self.0
- .insert("url".to_string(), Value::String(url.to_string()));
+ .insert("url", Value::String(url.to_string()));
CreateEmbed(self.0)
}
@@ -318,8 +314,8 @@ impl CreateEmbed {
impl Default for CreateEmbed {
/// Creates a builder with default values, setting the `type` to `rich`.
fn default() -> CreateEmbed {
- let mut map = Map::new();
- map.insert("type".to_string(), Value::String("rich".to_string()));
+ let mut map = HashMap::new();
+ map.insert("type", Value::String("rich".to_string()));
CreateEmbed(map)
}
@@ -353,9 +349,7 @@ impl From<Embed> for CreateEmbed {
}
for field in embed.fields {
- b = b.field(move |f| {
- f.inline(field.inline).name(&field.name).value(&field.value)
- });
+ b = b.field(field.name, field.value, field.inline);
}
if let Some(image) = embed.image {
@@ -391,81 +385,31 @@ impl From<Embed> for CreateEmbed {
/// [`CreateEmbed::author`]: struct.CreateEmbed.html#method.author
/// [`name`]: #method.name
#[derive(Clone, Debug, Default)]
-pub struct CreateEmbedAuthor(pub Map<String, Value>);
+pub struct CreateEmbedAuthor(pub HashMap<&'static str, Value>);
impl CreateEmbedAuthor {
/// Set the URL of the author's icon.
pub fn icon_url(mut self, icon_url: &str) -> Self {
- self.0
- .insert("icon_url".to_string(), Value::String(icon_url.to_string()));
+ self.0.insert("icon_url", Value::String(icon_url.to_string()));
self
}
/// Set the author's name.
pub fn name(mut self, name: &str) -> Self {
- self.0
- .insert("name".to_string(), Value::String(name.to_string()));
+ self.0.insert("name", Value::String(name.to_string()));
self
}
/// Set the author's URL.
pub fn url(mut self, url: &str) -> Self {
- self.0
- .insert("url".to_string(), Value::String(url.to_string()));
+ self.0.insert("url", Value::String(url.to_string()));
self
}
}
-/// A builder to create a fake [`Embed`] object's field, for use with the
-/// [`CreateEmbed::field`] method.
-///
-/// This does not require any field be set. `inline` is set to `true` by
-/// default.
-///
-/// [`Embed`]: ../model/struct.Embed.html
-/// [`CreateEmbed::field`]: struct.CreateEmbed.html#method.field
-#[derive(Clone, Debug)]
-pub struct CreateEmbedField(pub Map<String, Value>);
-
-impl CreateEmbedField {
- /// Set whether the field is inlined. Set to true by default.
- pub fn inline(mut self, inline: bool) -> Self {
- self.0.insert("inline".to_string(), Value::Bool(inline));
-
- self
- }
-
- /// Set the field's name. It can't be longer than 256 characters.
- pub fn name<D: Display>(mut self, name: D) -> Self {
- self.0
- .insert("name".to_string(), Value::String(format!("{}", name)));
-
- self
- }
-
- /// Set the field's value. It can't be longer than 1024 characters.
- pub fn value<D: Display>(mut self, value: D) -> Self {
- self.0
- .insert("value".to_string(), Value::String(format!("{}", value)));
-
- self
- }
-}
-
-impl Default for CreateEmbedField {
- /// Creates a builder with default values, setting the value of `inline` to
- /// `true`.
- fn default() -> CreateEmbedField {
- let mut map = Map::new();
- map.insert("inline".to_string(), Value::Bool(true));
-
- CreateEmbedField(map)
- }
-}
-
/// A builder to create a fake [`Embed`] object's footer, for use with the
/// [`CreateEmbed::footer`] method.
///
@@ -474,21 +418,19 @@ impl Default for CreateEmbedField {
/// [`Embed`]: ../model/struct.Embed.html
/// [`CreateEmbed::footer`]: struct.CreateEmbed.html#method.footer
#[derive(Clone, Debug, Default)]
-pub struct CreateEmbedFooter(pub Map<String, Value>);
+pub struct CreateEmbedFooter(pub HashMap<&'static str, Value>);
impl CreateEmbedFooter {
/// Set the icon URL's value. This only supports HTTP(S).
pub fn icon_url(mut self, icon_url: &str) -> Self {
- self.0
- .insert("icon_url".to_string(), Value::String(icon_url.to_string()));
+ self.0.insert("icon_url", Value::String(icon_url.to_string()));
self
}
/// Set the footer's text.
pub fn text<D: Display>(mut self, text: D) -> Self {
- self.0
- .insert("text".to_string(), Value::String(format!("{}", text)));
+ self.0.insert("text", Value::String(format!("{}", text)));
self
}
diff --git a/src/builder/create_invite.rs b/src/builder/create_invite.rs
index 645d401..0b05b66 100644
--- a/src/builder/create_invite.rs
+++ b/src/builder/create_invite.rs
@@ -1,4 +1,5 @@
use serde_json::Value;
+use std::collections::HashMap;
use std::default::Default;
use internal::prelude::*;
@@ -21,7 +22,7 @@ use internal::prelude::*;
/// fn on_message(&self, _: Context, msg: Message) {
/// use serenity::CACHE;
/// if msg.content == "!createinvite" {
-/// let channel = match CACHE.read().unwrap().guild_channel(msg.channel_id) {
+/// let channel = match CACHE.read().guild_channel(msg.channel_id) {
/// Some(channel) => channel,
/// None => {
/// let _ = msg.channel_id.say("Error creating invite");
@@ -30,7 +31,7 @@ use internal::prelude::*;
/// },
/// };
///
-/// let reader = channel.read().unwrap();
+/// let reader = channel.read();
///
/// let invite = match reader.create_invite(|i| i.max_age(3600).max_uses(10)) {
/// Ok(invite) => invite,
@@ -52,13 +53,16 @@ use internal::prelude::*;
/// }
/// }
/// }
-/// let mut client = Client::new("token", Handler); client.start().unwrap();
+///
+/// let mut client = Client::new("token", Handler).unwrap();
+///
+/// client.start().unwrap();
/// ```
///
/// [`GuildChannel::create_invite`]: ../model/struct.GuildChannel.html#method.create_invite
/// [`RichInvite`]: ../model/struct.Invite.html
#[derive(Clone, Debug)]
-pub struct CreateInvite(pub JsonMap);
+pub struct CreateInvite(pub HashMap<&'static str, Value>);
impl CreateInvite {
/// The duration that the invite will be valid for.
@@ -77,8 +81,8 @@ impl CreateInvite {
/// # use std::error::Error;
/// #
/// # fn try_main() -> Result<(), Box<Error>> {
- /// # let channel = CACHE.read().unwrap().guild_channel(81384788765712384).unwrap();
- /// # let channel = channel.read().unwrap();
+ /// # let channel = CACHE.read().guild_channel(81384788765712384).unwrap();
+ /// # let channel = channel.read();
/// #
/// let invite = channel.create_invite(|i| i.max_age(3600))?;
/// # Ok(())
@@ -89,8 +93,7 @@ impl CreateInvite {
/// # }
/// ```
pub fn max_age(mut self, max_age: u64) -> Self {
- self.0
- .insert("max_age".to_string(), Value::Number(Number::from(max_age)));
+ self.0.insert("max_age", Value::Number(Number::from(max_age)));
self
}
@@ -111,8 +114,8 @@ impl CreateInvite {
/// # use std::error::Error;
/// #
/// # fn try_main() -> Result<(), Box<Error>> {
- /// # let channel = CACHE.read().unwrap().guild_channel(81384788765712384).unwrap();
- /// # let channel = channel.read().unwrap();
+ /// # let channel = CACHE.read().guild_channel(81384788765712384).unwrap();
+ /// # let channel = channel.read();
/// #
/// let invite = channel.create_invite(|i| i.max_uses(5))?;
/// # Ok(())
@@ -123,8 +126,7 @@ impl CreateInvite {
/// # }
/// ```
pub fn max_uses(mut self, max_uses: u64) -> Self {
- self.0
- .insert("max_uses".to_string(), Value::Number(Number::from(max_uses)));
+ self.0.insert("max_uses", Value::Number(Number::from(max_uses)));
self
}
@@ -143,8 +145,8 @@ impl CreateInvite {
/// # use std::error::Error;
/// #
/// # fn try_main() -> Result<(), Box<Error>> {
- /// # let channel = CACHE.read().unwrap().guild_channel(81384788765712384).unwrap();
- /// # let channel = channel.read().unwrap();
+ /// # let channel = CACHE.read().guild_channel(81384788765712384).unwrap();
+ /// # let channel = channel.read();
/// #
/// let invite = channel.create_invite(|i| i.temporary(true))?;
/// # Ok(())
@@ -155,8 +157,7 @@ impl CreateInvite {
/// # }
/// ```
pub fn temporary(mut self, temporary: bool) -> Self {
- self.0
- .insert("temporary".to_string(), Value::Bool(temporary));
+ self.0.insert("temporary", Value::Bool(temporary));
self
}
@@ -175,8 +176,8 @@ impl CreateInvite {
/// # use std::error::Error;
/// #
/// # fn try_main() -> Result<(), Box<Error>> {
- /// # let channel = CACHE.read().unwrap().guild_channel(81384788765712384).unwrap();
- /// # let channel = channel.read().unwrap();
+ /// # let channel = CACHE.read().guild_channel(81384788765712384).unwrap();
+ /// # let channel = channel.read();
/// #
/// let invite = channel.create_invite(|i| i.unique(true))?;
/// # Ok(())
@@ -187,7 +188,7 @@ impl CreateInvite {
/// # }
/// ```
pub fn unique(mut self, unique: bool) -> Self {
- self.0.insert("unique".to_string(), Value::Bool(unique));
+ self.0.insert("unique", Value::Bool(unique));
self
}
@@ -206,8 +207,8 @@ impl Default for CreateInvite {
/// let invite_builder = CreateInvite::default();
/// ```
fn default() -> CreateInvite {
- let mut map = Map::new();
- map.insert("validate".to_string(), Value::Null);
+ let mut map = HashMap::new();
+ map.insert("validate", Value::Null);
CreateInvite(map)
}
diff --git a/src/builder/create_message.rs b/src/builder/create_message.rs
index 821048b..5b3abdb 100644
--- a/src/builder/create_message.rs
+++ b/src/builder/create_message.rs
@@ -1,6 +1,8 @@
use super::CreateEmbed;
use model::ReactionType;
use internal::prelude::*;
+use utils;
+use std::collections::HashMap;
use std::fmt::Display;
/// A builder to specify the contents of an [`http::send_message`] request,
@@ -39,15 +41,14 @@ use std::fmt::Display;
/// [`embed`]: #method.embed
/// [`http::send_message`]: ../http/fn.send_message.html
#[derive(Clone, Debug)]
-pub struct CreateMessage(pub Map<String, Value>, pub Option<Vec<ReactionType>>);
+pub struct CreateMessage(pub HashMap<&'static str, Value>, pub Option<Vec<ReactionType>>);
impl CreateMessage {
/// Set the content of the message.
///
/// **Note**: Message contents must be under 2000 unicode code points.
pub fn content<D: Display>(mut self, content: D) -> Self {
- self.0
- .insert("content".to_string(), Value::String(format!("{}", content)));
+ self.0.insert("content", Value::String(format!("{}", content)));
CreateMessage(self.0, self.1)
}
@@ -55,9 +56,10 @@ impl CreateMessage {
/// Set an embed for the message.
pub fn embed<F>(mut self, f: F) -> Self
where F: FnOnce(CreateEmbed) -> CreateEmbed {
- let embed = Value::Object(f(CreateEmbed::default()).0);
+ let map = utils::hashmap_to_json_map(f(CreateEmbed::default()).0);
+ let embed = Value::Object(map);
- self.0.insert("embed".to_string(), embed);
+ self.0.insert("embed", embed);
CreateMessage(self.0, self.1)
}
@@ -68,7 +70,7 @@ impl CreateMessage {
///
/// Defaults to `false`.
pub fn tts(mut self, tts: bool) -> Self {
- self.0.insert("tts".to_string(), Value::Bool(tts));
+ self.0.insert("tts", Value::Bool(tts));
CreateMessage(self.0, self.1)
}
@@ -88,8 +90,8 @@ impl Default for CreateMessage {
/// [`Message`]: ../model/struct.Message.html
/// [`tts`]: #method.tts
fn default() -> CreateMessage {
- let mut map = Map::default();
- map.insert("tts".to_string(), Value::Bool(false));
+ let mut map = HashMap::default();
+ map.insert("tts", Value::Bool(false));
CreateMessage(map, None)
}
diff --git a/src/builder/edit_channel.rs b/src/builder/edit_channel.rs
index 1ed1551..96455e2 100644
--- a/src/builder/edit_channel.rs
+++ b/src/builder/edit_channel.rs
@@ -1,4 +1,5 @@
use internal::prelude::*;
+use std::collections::HashMap;
/// A builder to edit a [`GuildChannel`] for use via [`GuildChannel::edit`]
///
@@ -18,7 +19,7 @@ use internal::prelude::*;
/// [`GuildChannel`]: ../model/struct.GuildChannel.html
/// [`GuildChannel::edit`]: ../model/struct.GuildChannel.html#method.edit
#[derive(Clone, Debug, Default)]
-pub struct EditChannel(pub JsonMap);
+pub struct EditChannel(pub HashMap<&'static str, Value>);
impl EditChannel {
/// The bitrate of the channel in bits.
@@ -27,8 +28,7 @@ impl EditChannel {
///
/// [voice]: ../model/enum.ChannelType.html#variant.Voice
pub fn bitrate(mut self, bitrate: u64) -> Self {
- self.0
- .insert("bitrate".to_string(), Value::Number(Number::from(bitrate)));
+ self.0.insert("bitrate", Value::Number(Number::from(bitrate)));
self
}
@@ -37,16 +37,14 @@ impl EditChannel {
///
/// Must be between 2 and 100 characters long.
pub fn name(mut self, name: &str) -> Self {
- self.0
- .insert("name".to_string(), Value::String(name.to_string()));
+ self.0.insert("name", Value::String(name.to_string()));
self
}
/// The position of the channel in the channel list.
pub fn position(mut self, position: u64) -> Self {
- self.0
- .insert("position".to_string(), Value::Number(Number::from(position)));
+ self.0.insert("position", Value::Number(Number::from(position)));
self
}
@@ -59,8 +57,7 @@ impl EditChannel {
///
/// [text]: ../model/enum.ChannelType.html#variant.Text
pub fn topic(mut self, topic: &str) -> Self {
- self.0
- .insert("topic".to_string(), Value::String(topic.to_string()));
+ self.0.insert("topic", Value::String(topic.to_string()));
self
}
@@ -71,10 +68,7 @@ impl EditChannel {
///
/// [voice]: ../model/enum.ChannelType.html#variant.Voice
pub fn user_limit(mut self, user_limit: u64) -> Self {
- self.0.insert(
- "user_limit".to_string(),
- Value::Number(Number::from(user_limit)),
- );
+ self.0.insert("user_limit", Value::Number(Number::from(user_limit)));
self
}
diff --git a/src/builder/edit_guild.rs b/src/builder/edit_guild.rs
index 0719305..45969fe 100644
--- a/src/builder/edit_guild.rs
+++ b/src/builder/edit_guild.rs
@@ -1,5 +1,6 @@
use internal::prelude::*;
use model::{ChannelId, Region, UserId, VerificationLevel};
+use std::collections::HashMap;
/// A builder to optionally edit certain fields of a [`Guild`]. This is meant
/// for usage with [`Guild::edit`].
@@ -11,7 +12,7 @@ use model::{ChannelId, Region, UserId, VerificationLevel};
/// [`Guild`]: ../model/struct.Guild.html
/// [Manage Guild]: ../model/permissions/constant.MANAGE_GUILD.html
#[derive(Clone, Debug, Default)]
-pub struct EditGuild(pub Map<String, Value>);
+pub struct EditGuild(pub HashMap<&'static str, Value>);
impl EditGuild {
/// Set the "AFK voice channel" that users are to move to if they have been
@@ -24,7 +25,7 @@ impl EditGuild {
/// [`afk_timeout`]: #method.afk_timeout
pub fn afk_channel<C: Into<ChannelId>>(mut self, channel: Option<C>) -> Self {
self.0.insert(
- "afk_channel_id".to_string(),
+ "afk_channel_id",
match channel {
Some(channel) => Value::Number(Number::from(channel.into().0)),
None => Value::Null,
@@ -40,7 +41,7 @@ impl EditGuild {
/// [`afk_channel`]: #method.afk_channel
pub fn afk_timeout(mut self, timeout: u64) -> Self {
self.0.insert(
- "afk_timeout".to_string(),
+ "afk_timeout",
Value::Number(Number::from(timeout)),
);
@@ -78,7 +79,7 @@ impl EditGuild {
/// [`utils::read_image`]: ../utils/fn.read_image.html
pub fn icon(mut self, icon: Option<&str>) -> Self {
self.0.insert(
- "icon".to_string(),
+ "icon",
icon.map_or_else(|| Value::Null, |x| Value::String(x.to_string())),
);
@@ -89,8 +90,7 @@ impl EditGuild {
///
/// **Note**: Must be between (and including) 2-100 chracters.
pub fn name(mut self, name: &str) -> Self {
- self.0
- .insert("name".to_string(), Value::String(name.to_string()));
+ self.0.insert("name", Value::String(name.to_string()));
self
}
@@ -99,10 +99,8 @@ impl EditGuild {
///
/// **Note**: The current user must be the owner of the guild.
pub fn owner<U: Into<UserId>>(mut self, user_id: U) -> Self {
- self.0.insert(
- "owner_id".to_string(),
- Value::Number(Number::from(user_id.into().0)),
- );
+ let id = Value::Number(Number::from(user_id.into().0));
+ self.0.insert("owner_id", id);
self
}
@@ -134,8 +132,7 @@ impl EditGuild {
///
/// [`Region::UsWest`]: ../model/enum.Region.html#variant.UsWest
pub fn region(mut self, region: Region) -> Self {
- self.0
- .insert("region".to_string(), Value::String(region.name().to_string()));
+ self.0.insert("region", Value::String(region.name().to_string()));
self
}
@@ -149,8 +146,7 @@ impl EditGuild {
/// [`features`]: ../model/struct.LiveGuild.html#structfield.features
pub fn splash(mut self, splash: Option<&str>) -> Self {
let splash = splash.map_or(Value::Null, |x| Value::String(x.to_string()));
-
- self.0.insert("splash".to_string(), splash);
+ self.0.insert("splash", splash);
self
}
@@ -189,7 +185,7 @@ impl EditGuild {
where V: Into<VerificationLevel> {
let num = Value::Number(Number::from(verification_level.into().num()));
- self.0.insert("verification_level".to_string(), num);
+ self.0.insert("verification_level", num);
self
}
diff --git a/src/builder/edit_member.rs b/src/builder/edit_member.rs
index 560d241..5a399f5 100644
--- a/src/builder/edit_member.rs
+++ b/src/builder/edit_member.rs
@@ -1,5 +1,6 @@
use model::{ChannelId, RoleId};
use internal::prelude::*;
+use std::collections::HashMap;
/// A builder which edits the properties of a [`Member`], to be used in
/// conjunction with [`Member::edit`].
@@ -7,7 +8,7 @@ use internal::prelude::*;
/// [`Member`]: ../model/struct.Member.html
/// [`Member::edit`]: ../model/struct.Member.html#method.edit
#[derive(Clone, Debug, Default)]
-pub struct EditMember(pub JsonMap);
+pub struct EditMember(pub HashMap<&'static str, Value>);
impl EditMember {
/// Whether to deafen the member.
@@ -16,7 +17,7 @@ impl EditMember {
///
/// [Deafen Members]: ../model/permissions/constant.DEAFEN_MEMBERS.html
pub fn deafen(mut self, deafen: bool) -> Self {
- self.0.insert("deaf".to_string(), Value::Bool(deafen));
+ self.0.insert("deaf", Value::Bool(deafen));
self
}
@@ -27,7 +28,7 @@ impl EditMember {
///
/// [Mute Members]: ../model/permissions/constant.MUTE_MEMBERS.html
pub fn mute(mut self, mute: bool) -> Self {
- self.0.insert("mute".to_string(), Value::Bool(mute));
+ self.0.insert("mute", Value::Bool(mute));
self
}
@@ -39,8 +40,7 @@ impl EditMember {
///
/// [Manage Nicknames]: ../model/permissions/constant.MANAGE_NICKNAMES.html
pub fn nickname(mut self, nickname: &str) -> Self {
- self.0
- .insert("nick".to_string(), Value::String(nickname.to_string()));
+ self.0.insert("nick", Value::String(nickname.to_string()));
self
}
@@ -56,7 +56,7 @@ impl EditMember {
.map(|x| Value::Number(Number::from(x.as_ref().0)))
.collect();
- self.0.insert("roles".to_string(), Value::Array(role_ids));
+ self.0.insert("roles", Value::Array(role_ids));
self
}
@@ -67,10 +67,8 @@ impl EditMember {
///
/// [Move Members]: ../model/permissions/constant.MOVE_MEMBERS.html
pub fn voice_channel<C: Into<ChannelId>>(mut self, channel_id: C) -> Self {
- self.0.insert(
- "channel_id".to_string(),
- Value::Number(Number::from(channel_id.into().0)),
- );
+ let num = Value::Number(Number::from(channel_id.into().0));
+ self.0.insert("channel_id", num);
self
}
diff --git a/src/builder/edit_profile.rs b/src/builder/edit_profile.rs
index ae0bbd5..09d3337 100644
--- a/src/builder/edit_profile.rs
+++ b/src/builder/edit_profile.rs
@@ -1,11 +1,12 @@
use internal::prelude::*;
+use std::collections::HashMap;
/// A builder to edit the current user's settings, to be used in conjunction
/// with [`CurrentUser::edit`].
///
/// [`CurrentUser::edit`]: ../model/struct.CurrentUser.html#method.edit
#[derive(Clone, Debug, Default)]
-pub struct EditProfile(pub JsonMap);
+pub struct EditProfile(pub HashMap<&'static str, Value>);
impl EditProfile {
/// Sets the avatar of the current user. `None` can be passed to remove an
@@ -23,8 +24,9 @@ impl EditProfile {
/// # use serenity::model::*;
/// #
/// # struct Handler;
+ ///
/// # impl EventHandler for Handler {
- /// # fn on_message(&self, context: Context, _: Message) {
+ /// # fn message(&self, context: Context, _: Message) {
/// use serenity::utils;
///
/// // assuming a `context` has been bound
@@ -37,15 +39,16 @@ impl EditProfile {
/// });
/// # }
/// }
-
- /// # let mut client = Client::new("token", Handler); client.start().unwrap();
+ /// #
+ /// # let mut client = Client::new("token", Handler).unwrap();
+ /// #
+ /// # client.start().unwrap();
/// ```
///
/// [`utils::read_image`]: ../fn.read_image.html
pub fn avatar(mut self, avatar: Option<&str>) -> Self {
let avatar = avatar.map_or(Value::Null, |x| Value::String(x.to_string()));
-
- self.0.insert("avatar".to_string(), avatar);
+ self.0.insert("avatar", avatar);
self
}
@@ -61,8 +64,7 @@ impl EditProfile {
///
/// [provided]: #method.password
pub fn email(mut self, email: &str) -> Self {
- self.0
- .insert("email".to_string(), Value::String(email.to_string()));
+ self.0.insert("email", Value::String(email.to_string()));
self
}
@@ -74,10 +76,7 @@ impl EditProfile {
///
/// [provided]: #method.password
pub fn new_password(mut self, new_password: &str) -> Self {
- self.0.insert(
- "new_password".to_string(),
- Value::String(new_password.to_string()),
- );
+ self.0.insert("new_password", Value::String(new_password.to_string()));
self
}
@@ -88,8 +87,7 @@ impl EditProfile {
/// [modifying the password]: #method.new_password
/// [modifying the associated email address]: #method.email
pub fn password(mut self, password: &str) -> Self {
- self.0
- .insert("password".to_string(), Value::String(password.to_string()));
+ self.0.insert("password", Value::String(password.to_string()));
self
}
@@ -101,8 +99,7 @@ impl EditProfile {
/// If there are no available discriminators with the requested username,
/// an error will occur.
pub fn username(mut self, username: &str) -> Self {
- self.0
- .insert("username".to_string(), Value::String(username.to_string()));
+ self.0.insert("username", Value::String(username.to_string()));
self
}
diff --git a/src/builder/edit_role.rs b/src/builder/edit_role.rs
index 0ef1b35..0906329 100644
--- a/src/builder/edit_role.rs
+++ b/src/builder/edit_role.rs
@@ -1,6 +1,7 @@
use std::default::Default;
use internal::prelude::*;
use model::{permissions, Permissions, Role};
+use std::collections::HashMap;
/// A builer to create or edit a [`Role`] for use via a number of model methods.
///
@@ -39,48 +40,38 @@ use model::{permissions, Permissions, Role};
/// [`Role`]: ../model/struct.Role.html
/// [`Role::edit`]: ../model/struct.Role.html#method.edit
#[derive(Clone, Debug)]
-pub struct EditRole(pub JsonMap);
+pub struct EditRole(pub HashMap<&'static str, Value>);
impl EditRole {
/// Creates a new builder with the values of the given [`Role`].
///
/// [`Role`]: ../model/struct.Role.html
pub fn new(role: &Role) -> Self {
- let mut map = Map::new();
+ let mut map = HashMap::new();
#[cfg(feature = "utils")]
{
- map.insert(
- "color".to_string(),
- Value::Number(Number::from(role.colour.0)),
- );
+ map.insert("color", Value::Number(Number::from(role.colour.0)));
}
#[cfg(not(feature = "utils"))]
{
- map.insert("color".to_string(), Value::Number(Number::from(role.colour)));
+ map.insert("color", Value::Number(Number::from(role.colour)));
}
- map.insert("hoist".to_string(), Value::Bool(role.hoist));
- map.insert("managed".to_string(), Value::Bool(role.managed));
- map.insert("mentionable".to_string(), Value::Bool(role.mentionable));
- map.insert("name".to_string(), Value::String(role.name.clone()));
- map.insert(
- "permissions".to_string(),
- Value::Number(Number::from(role.permissions.bits())),
- );
- map.insert(
- "position".to_string(),
- Value::Number(Number::from(role.position)),
- );
+ map.insert("hoist", Value::Bool(role.hoist));
+ map.insert("managed", Value::Bool(role.managed));
+ map.insert("mentionable", Value::Bool(role.mentionable));
+ map.insert("name", Value::String(role.name.clone()));
+ map.insert("permissions",Value::Number(Number::from(role.permissions.bits())));
+ map.insert("position", Value::Number(Number::from(role.position)));
EditRole(map)
}
/// Sets the colour of the role.
pub fn colour(mut self, colour: u64) -> Self {
- self.0
- .insert("color".to_string(), Value::Number(Number::from(colour)));
+ self.0.insert("color", Value::Number(Number::from(colour)));
self
}
@@ -88,15 +79,14 @@ impl EditRole {
/// Whether or not to hoist the role above lower-positioned role in the user
/// list.
pub fn hoist(mut self, hoist: bool) -> Self {
- self.0.insert("hoist".to_string(), Value::Bool(hoist));
+ self.0.insert("hoist", Value::Bool(hoist));
self
}
/// Whether or not to make the role mentionable, notifying its users.
pub fn mentionable(mut self, mentionable: bool) -> Self {
- self.0
- .insert("mentionable".to_string(), Value::Bool(mentionable));
+ self.0.insert("mentionable", Value::Bool(mentionable));
self
}
@@ -104,17 +94,14 @@ impl EditRole {
/// The name of the role to set.
pub fn name(mut self, name: &str) -> Self {
self.0
- .insert("name".to_string(), Value::String(name.to_string()));
+ .insert("name", Value::String(name.to_string()));
self
}
/// The set of permissions to assign the role.
pub fn permissions(mut self, permissions: Permissions) -> Self {
- self.0.insert(
- "permissions".to_string(),
- Value::Number(Number::from(permissions.bits())),
- );
+ self.0.insert("permissions", Value::Number(Number::from(permissions.bits())));
self
}
@@ -122,8 +109,7 @@ impl EditRole {
/// The position to assign the role in the role list. This correlates to the
/// role's position in the user list.
pub fn position(mut self, position: u8) -> Self {
- self.0
- .insert("position".to_string(), Value::Number(Number::from(position)));
+ self.0.insert("position", Value::Number(Number::from(position)));
self
}
@@ -143,15 +129,15 @@ impl Default for EditRole {
///
/// [general permissions set]: ../model/permissions/constant.PRESET_GENERAL.html
fn default() -> EditRole {
- let mut map = Map::new();
+ let mut map = HashMap::new();
let permissions = Number::from(permissions::PRESET_GENERAL.bits());
- map.insert("color".to_string(), Value::Number(Number::from(10_070_709)));
- map.insert("hoist".to_string(), Value::Bool(false));
- map.insert("mentionable".to_string(), Value::Bool(false));
- map.insert("name".to_string(), Value::String("new role".to_string()));
- map.insert("permissions".to_string(), Value::Number(permissions));
- map.insert("position".to_string(), Value::Number(Number::from(1)));
+ map.insert("color", Value::Number(Number::from(10_070_709)));
+ map.insert("hoist", Value::Bool(false));
+ map.insert("mentionable", Value::Bool(false));
+ map.insert("name", Value::String("new role".to_string()));
+ map.insert("permissions", Value::Number(permissions));
+ map.insert("position", Value::Number(Number::from(1)));
EditRole(map)
}
diff --git a/src/builder/execute_webhook.rs b/src/builder/execute_webhook.rs
index ba1668e..6276da1 100644
--- a/src/builder/execute_webhook.rs
+++ b/src/builder/execute_webhook.rs
@@ -1,6 +1,6 @@
use serde_json::Value;
+use std::collections::HashMap;
use std::default::Default;
-use internal::prelude::*;
/// A builder to create the inner content of a [`Webhook`]'s execution.
///
@@ -35,14 +35,8 @@ use internal::prelude::*;
/// .title("Rust Resources")
/// .description("A few resources to help with learning Rust")
/// .colour(0xDEA584)
-/// .field(|f| f
-/// .inline(false)
-/// .name("The Rust Book")
-/// .value("A comprehensive resource for all topics related to Rust"))
-/// .field(|f| f
-/// .inline(false)
-/// .name("Rust by Example")
-/// .value("A collection of Rust examples on topics, useable in-browser")));
+/// .field("The Rust Book", "A comprehensive resource for Rust.", false)
+/// .field("Rust by Example", "A collection of Rust examples", false));
///
/// let _ = webhook.execute(false, |w| w
/// .content("Here's some information on Rust:")
@@ -53,7 +47,7 @@ use internal::prelude::*;
/// [`Webhook::execute`]: ../model/struct.Webhook.html#method.execute
/// [`execute_webhook`]: ../http/fn.execute_webhook.html
#[derive(Clone, Debug)]
-pub struct ExecuteWebhook(pub JsonMap);
+pub struct ExecuteWebhook(pub HashMap<&'static str, Value>);
impl ExecuteWebhook {
/// Override the default avatar of the webhook with an image URL.
@@ -74,10 +68,7 @@ impl ExecuteWebhook {
/// .content("Here's a webhook"));
/// ```
pub fn avatar_url(mut self, avatar_url: &str) -> Self {
- self.0.insert(
- "avatar_url".to_string(),
- Value::String(avatar_url.to_string()),
- );
+ self.0.insert("avatar_url", Value::String(avatar_url.to_string()));
self
}
@@ -103,8 +94,7 @@ impl ExecuteWebhook {
///
/// [`embeds`]: #method.embeds
pub fn content(mut self, content: &str) -> Self {
- self.0
- .insert("content".to_string(), Value::String(content.to_string()));
+ self.0.insert("content", Value::String(content.to_string()));
self
}
@@ -123,7 +113,7 @@ impl ExecuteWebhook {
/// [`Webhook::execute`]: ../model/struct.Webhook.html#method.execute
/// [struct-level documentation]: #examples
pub fn embeds(mut self, embeds: Vec<Value>) -> Self {
- self.0.insert("embeds".to_string(), Value::Array(embeds));
+ self.0.insert("embeds", Value::Array(embeds));
self
}
@@ -144,7 +134,7 @@ impl ExecuteWebhook {
/// }
/// ```
pub fn tts(mut self, tts: bool) -> Self {
- self.0.insert("tts".to_string(), Value::Bool(tts));
+ self.0.insert("tts", Value::Bool(tts));
self
}
@@ -165,8 +155,7 @@ impl ExecuteWebhook {
/// }
/// ```
pub fn username(mut self, username: &str) -> Self {
- self.0
- .insert("username".to_string(), Value::String(username.to_string()));
+ self.0.insert("username", Value::String(username.to_string()));
self
}
@@ -190,8 +179,8 @@ impl Default for ExecuteWebhook {
/// [`Webhook`]: ../model/struct.Webhook.html
/// [`tts`]: #method.tts
fn default() -> ExecuteWebhook {
- let mut map = Map::new();
- map.insert("tts".to_string(), Value::Bool(false));
+ let mut map = HashMap::new();
+ map.insert("tts", Value::Bool(false));
ExecuteWebhook(map)
}
diff --git a/src/builder/get_messages.rs b/src/builder/get_messages.rs
index 71af9e5..e59584f 100644
--- a/src/builder/get_messages.rs
+++ b/src/builder/get_messages.rs
@@ -1,5 +1,5 @@
-use std::collections::BTreeMap;
use model::MessageId;
+use std::collections::HashMap;
/// Builds a request for a request to the API to retrieve messages.
///
@@ -50,13 +50,13 @@ use model::MessageId;
///
/// [`GuildChannel::messages`]: ../model/struct.GuildChannel.html#method.messages
#[derive(Clone, Debug, Default)]
-pub struct GetMessages(pub BTreeMap<String, u64>);
+pub struct GetMessages(pub HashMap<&'static str, u64>);
impl GetMessages {
/// Indicates to retrieve the messages after a specific message, given by
/// its Id.
pub fn after<M: Into<MessageId>>(mut self, message_id: M) -> Self {
- self.0.insert("after".to_string(), message_id.into().0);
+ self.0.insert("after", message_id.into().0);
self
}
@@ -64,7 +64,7 @@ impl GetMessages {
/// Indicates to retrieve the messages _around_ a specific message in either
/// direction (before+after) the given message.
pub fn around<M: Into<MessageId>>(mut self, message_id: M) -> Self {
- self.0.insert("around".to_string(), message_id.into().0);
+ self.0.insert("around", message_id.into().0);
self
}
@@ -72,7 +72,7 @@ impl GetMessages {
/// Indicates to retrieve the messages before a specific message, given by
/// its Id.
pub fn before<M: Into<MessageId>>(mut self, message_id: M) -> Self {
- self.0.insert("before".to_string(), message_id.into().0);
+ self.0.insert("before", message_id.into().0);
self
}
@@ -86,7 +86,7 @@ impl GetMessages {
/// reduced.
pub fn limit(mut self, limit: u64) -> Self {
self.0
- .insert("limit".to_string(), if limit > 100 { 100 } else { limit });
+ .insert("limit", if limit > 100 { 100 } else { limit });
self
}
diff --git a/src/builder/mod.rs b/src/builder/mod.rs
index 496d7e1..3c59c0d 100644
--- a/src/builder/mod.rs
+++ b/src/builder/mod.rs
@@ -16,7 +16,7 @@ mod edit_role;
mod execute_webhook;
mod get_messages;
-pub use self::create_embed::{CreateEmbed, CreateEmbedAuthor, CreateEmbedField, CreateEmbedFooter};
+pub use self::create_embed::{CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter};
pub use self::create_invite::CreateInvite;
pub use self::create_message::CreateMessage;
pub use self::edit_channel::EditChannel;
diff --git a/src/cache/mod.rs b/src/cache/mod.rs
index a663a79..e23e17e 100644
--- a/src/cache/mod.rs
+++ b/src/cache/mod.rs
@@ -41,10 +41,12 @@
//! [`Role`]: ../model/struct.Role.html
//! [`CACHE`]: ../struct.CACHE.html
//! [`http`]: ../http/index.html
+
+use parking_lot::RwLock;
use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
use std::default::Default;
-use std::sync::{Arc, RwLock};
+use std::sync::Arc;
use model::*;
mod cache_update;
@@ -178,7 +180,7 @@ impl Cache {
/// struct Handler;
///
/// impl EventHandler for Handler {
- /// fn on_ready(&self, ctx: Context, _: Ready) {
+ /// fn ready(&self, ctx: Context, _: Ready) {
/// // Wait some time for guilds to be received.
/// //
/// // You should keep track of this in a better fashion by tracking how
@@ -190,11 +192,13 @@ impl Cache {
/// // seconds.
/// thread::sleep(Duration::from_secs(5));
///
- /// println!("{} unknown members", CACHE.read().unwrap().unknown_members());
+ /// println!("{} unknown members", CACHE.read().unknown_members());
/// }
/// }
///
- /// let mut client = Client::new("token", Handler); client.start().unwrap();
+ /// let mut client = Client::new("token", Handler).unwrap();
+ ///
+ /// client.start().unwrap();
/// # }
/// #
/// # #[cfg(not(feature = "client"))]
@@ -208,7 +212,7 @@ impl Cache {
let mut total = 0;
for guild in self.guilds.values() {
- let guild = guild.read().unwrap();
+ let guild = guild.read();
let members = guild.members.len() as u64;
@@ -233,7 +237,7 @@ impl Cache {
/// ```rust,no_run
/// use serenity::CACHE;
///
- /// let amount = CACHE.read().unwrap().all_private_channels().len();
+ /// let amount = CACHE.read().all_private_channels().len();
///
/// println!("There are {} private channels", amount);
/// ```
@@ -346,10 +350,8 @@ impl Cache {
/// # fn try_main() -> Result<(), Box<Error>> {
/// use serenity::CACHE;
///
- /// let cache = CACHE.read()?;
- ///
- /// if let Some(guild) = cache.guild(7) {
- /// println!("Guild name: {}", guild.read().unwrap().name);
+ /// if let Some(guild) = CACHE.read().guild(7) {
+ /// println!("Guild name: {}", guild.read().name);
/// }
/// # Ok(())
/// # }
@@ -385,8 +387,8 @@ impl Cache {
/// struct Handler;
///
/// impl EventHandler for Handler {
- /// fn on_message(&self, ctx: Context, message: Message) {
- /// let cache = CACHE.read().unwrap();
+ /// fn message(&self, ctx: Context, message: Message) {
+ /// let cache = CACHE.read();
///
/// let channel = match cache.guild_channel(message.channel_id) {
/// Some(channel) => channel,
@@ -402,7 +404,9 @@ impl Cache {
/// }
/// }
///
- /// let mut client = Client::new("token", Handler); client.start().unwrap();
+ /// let mut client = Client::new("token", Handler).unwrap();
+ ///
+ /// client.start().unwrap();
/// # }
/// #
/// # #[cfg(not(feature = "client"))]
@@ -437,10 +441,8 @@ impl Cache {
/// # fn try_main() -> Result<(), Box<Error>> {
/// use serenity::CACHE;
///
- /// let cache = CACHE.read()?;
- ///
- /// if let Some(group) = cache.group(7) {
- /// println!("Owner Id: {}", group.read().unwrap().owner_id);
+ /// if let Some(group) = CACHE.read().group(7) {
+ /// println!("Owner Id: {}", group.read().owner_id);
/// }
/// # Ok(())
/// # }
@@ -468,7 +470,7 @@ impl Cache {
/// ```rust,ignore
/// use serenity::CACHE;
///
- /// let cache = CACHE.read().unwrap();
+ /// let cache = CACHE.read();
/// let member = {
/// let channel = match cache.guild_channel(message.channel_id) {
/// Some(channel) => channel,
@@ -502,7 +504,7 @@ impl Cache {
pub fn member<G, U>(&self, guild_id: G, user_id: U) -> Option<Member>
where G: Into<GuildId>, U: Into<UserId> {
self.guilds.get(&guild_id.into()).and_then(|guild| {
- guild.read().unwrap().members.get(&user_id.into()).cloned()
+ guild.read().members.get(&user_id.into()).cloned()
})
}
@@ -563,9 +565,7 @@ impl Cache {
/// # fn try_main() -> Result<(), Box<Error>> {
/// use serenity::CACHE;
///
- /// let cache = CACHE.read()?;
- ///
- /// if let Some(role) = cache.role(7, 77) {
+ /// if let Some(role) = CACHE.read().role(7, 77) {
/// println!("Role with Id 77 is called {}", role.name);
/// }
/// # Ok(())
@@ -579,7 +579,7 @@ impl Cache {
where G: Into<GuildId>, R: Into<RoleId> {
self.guilds
.get(&guild_id.into())
- .and_then(|g| g.read().unwrap().roles.get(&role_id.into()).cloned())
+ .and_then(|g| g.read().roles.get(&role_id.into()).cloned())
}
/// Retrieves a `User` from the cache's [`users`] map, if it exists.
@@ -600,10 +600,8 @@ impl Cache {
/// # fn try_main() -> Result<(), Box<Error>> {
/// use serenity::CACHE;
///
- /// let cache = CACHE.read()?;
- ///
- /// if let Some(user) = cache.user(7) {
- /// println!("User with Id 7 is currently named {}", user.read().unwrap().name);
+ /// if let Some(user) = CACHE.read().user(7) {
+ /// println!("User with Id 7 is currently named {}", user.read().name);
/// }
/// # Ok(())
/// # }
@@ -635,7 +633,7 @@ impl Cache {
e.insert(Arc::new(RwLock::new(user.clone())));
},
Entry::Occupied(mut e) => {
- e.get_mut().write().unwrap().clone_from(user);
+ e.get_mut().write().clone_from(user);
},
}
}
diff --git a/src/client/bridge/gateway/mod.rs b/src/client/bridge/gateway/mod.rs
index 0b873aa..752b015 100644
--- a/src/client/bridge/gateway/mod.rs
+++ b/src/client/bridge/gateway/mod.rs
@@ -1,28 +1,113 @@
+//! The client gateway bridge is support essential for the [`client`] module.
+//!
+//! This is made available for user use if one wishes to be lower-level or avoid
+//! the higher functionality of the [`Client`].
+//!
+//! Of interest are three pieces:
+//!
+//! ### [`ShardManager`]
+//!
+//! The shard manager is responsible for being a clean interface between the
+//! user and the shard runners, providing essential functions such as
+//! [`ShardManager::shutdown`] to shutdown a shard and [`ShardManager::restart`]
+//! to restart a shard.
+//!
+//! If you are using the `Client`, this is likely the only piece of interest to
+//! you. Refer to [its documentation][`ShardManager`] for more information.
+//!
+//! ### [`ShardQueuer`]
+//!
+//! The shard queuer is a light wrapper around an mpsc receiver that receives
+//! [`ShardManagerMessage`]s. It should be run in its own thread so it can
+//! receive messages to start shards in a queue.
+//!
+//! Refer to [its documentation][`ShardQueuer`] for more information.
+//!
+//! ### [`ShardRunner`]
+//!
+//! The shard runner is responsible for actually running a shard and
+//! communicating with its respective WebSocket client.
+//!
+//! It is performs all actions such as sending a presence update over the client
+//! and, with the help of the [`Shard`], will be able to determine what to do.
+//! This is, for example, whether to reconnect, resume, or identify with the
+//! gateway.
+//!
+//! ### In Conclusion
+//!
+//! For almost every - if not every - use case, you only need to _possibly_ be
+//! concerned about the [`ShardManager`] in this module.
+//!
+//! [`Client`]: ../../struct.Client.html
+//! [`client`]: ../..
+//! [`Shard`]: ../../../gateway/struct.Shard.html
+//! [`ShardManager`]: struct.ShardManager.html
+//! [`ShardManager::restart`]: struct.ShardManager.html#method.restart
+//! [`ShardManager::shutdown`]: struct.ShardManager.html#method.shutdown
+//! [`ShardQueuer`]: struct.ShardQueuer.html
+//! [`ShardRunner`]: struct.ShardRunner.html
+
mod shard_manager;
+mod shard_manager_monitor;
+mod shard_messenger;
mod shard_queuer;
mod shard_runner;
+mod shard_runner_message;
pub use self::shard_manager::ShardManager;
+pub use self::shard_manager_monitor::ShardManagerMonitor;
+pub use self::shard_messenger::ShardMessenger;
pub use self::shard_queuer::ShardQueuer;
pub use self::shard_runner::ShardRunner;
+pub use self::shard_runner_message::ShardRunnerMessage;
-use gateway::Shard;
-use parking_lot::Mutex;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::sync::mpsc::Sender;
-use std::sync::Arc;
-type Parked<T> = Arc<Mutex<T>>;
-type LockedShard = Parked<Shard>;
+/// A message either for a [`ShardManager`] or a [`ShardRunner`].
+///
+/// [`ShardManager`]: struct.ShardManager.html
+/// [`ShardRunner`]: struct.ShardRunner.html
+pub enum ShardClientMessage {
+ /// A message intended to be worked with by a [`ShardManager`].
+ ///
+ /// [`ShardManager`]: struct.ShardManager.html
+ Manager(ShardManagerMessage),
+ /// A message intended to be worked with by a [`ShardRunner`].
+ ///
+ /// [`ShardRunner`]: struct.ShardRunner.html
+ Runner(ShardRunnerMessage),
+}
+/// A message for a [`ShardManager`] relating to an operation with a shard.
+///
+/// [`ShardManager`]: struct.ShardManager.html
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum ShardManagerMessage {
+ /// Indicator that a [`ShardManagerMonitor`] should restart a shard.
+ ///
+ /// [`ShardManagerMonitor`]: struct.ShardManagerMonitor.html
Restart(ShardId),
+ /// Indicator that a [`ShardManagerMonitor`] should fully shutdown a shard
+ /// without bringing it back up.
+ ///
+ /// [`ShardManagerMonitor`]: struct.ShardManagerMonitor.html
Shutdown(ShardId),
- #[allow(dead_code)]
+ /// Indicator that a [`ShardManagerMonitor`] should fully shutdown all shards
+ /// and end its monitoring process for the [`ShardManager`].
+ ///
+ /// [`ShardManager`]: struct.ShardManager.html
+ /// [`ShardManagerMonitor`]: struct.ShardManagerMonitor.html
ShutdownAll,
}
+/// A message to be sent to the [`ShardQueuer`].
+///
+/// This should usually be wrapped in a [`ShardClientMessage`].
+///
+/// [`ShardClientMessage`]: enum.ShardClientMessage.html
+/// [`ShardQueuer`]: enum.ShardQueuer.html
+#[derive(Clone, Debug)]
pub enum ShardQueuerMessage {
/// Message to start a shard, where the 0-index element is the ID of the
/// Shard to start and the 1-index element is the total shards in use.
@@ -31,8 +116,8 @@ pub enum ShardQueuerMessage {
Shutdown,
}
-// A light tuplestruct wrapper around a u64 to verify type correctness when
-// working with the IDs of shards.
+/// A light tuplestruct wrapper around a u64 to verify type correctness when
+/// working with the IDs of shards.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct ShardId(pub u64);
@@ -42,7 +127,16 @@ impl Display for ShardId {
}
}
+/// Information about a [`ShardRunner`].
+///
+/// The [`ShardId`] is not included because, as it stands, you probably already
+/// know the Id if you obtained this.
+///
+/// [`ShardId`]: struct.ShardId.html
+/// [`ShardRunner`]: struct.ShardRunner.html
+#[derive(Debug)]
pub struct ShardRunnerInfo {
- pub runner_tx: Sender<ShardManagerMessage>,
- pub shard: Arc<Mutex<Shard>>,
+ /// The channel used to communicate with the shard runner, telling it
+ /// what to do with regards to its status.
+ pub runner_tx: Sender<ShardClientMessage>,
}
diff --git a/src/client/bridge/gateway/shard_manager.rs b/src/client/bridge/gateway/shard_manager.rs
index dfd4a16..2cf45fd 100644
--- a/src/client/bridge/gateway/shard_manager.rs
+++ b/src/client/bridge/gateway/shard_manager.rs
@@ -1,13 +1,15 @@
use internal::prelude::*;
-use parking_lot::Mutex as ParkingLotMutex;
+use parking_lot::Mutex;
use std::collections::HashMap;
-use std::sync::mpsc::{self, Receiver, Sender};
-use std::sync::{Arc, Mutex};
+use std::sync::mpsc::{self, Sender};
+use std::sync::Arc;
use std::thread;
use super::super::super::EventHandler;
use super::{
+ ShardClientMessage,
ShardId,
ShardManagerMessage,
+ ShardManagerMonitor,
ShardQueuer,
ShardQueuerMessage,
ShardRunnerInfo,
@@ -18,8 +20,80 @@ use typemap::ShareMap;
#[cfg(feature = "framework")]
use framework::Framework;
+/// A manager for handling the status of shards by starting them, restarting
+/// them, and stopping them when required.
+///
+/// **Note**: The [`Client`] internally uses a shard manager. If you are using a
+/// Client, then you do not need to make one of these.
+///
+/// # Examples
+///
+/// Initialize a shard manager with a framework responsible for shards 0 through
+/// 2, of 5 total shards:
+///
+/// ```rust,no_run
+/// extern crate parking_lot;
+/// extern crate serenity;
+/// extern crate threadpool;
+/// extern crate typemap;
+///
+/// # use std::error::Error;
+/// #
+/// # #[cfg(feature = "framework")]
+/// # fn try_main() -> Result<(), Box<Error>> {
+/// #
+/// use parking_lot::Mutex;
+/// use serenity::client::bridge::gateway::ShardManager;
+/// use serenity::client::EventHandler;
+/// use serenity::http;
+/// use std::sync::Arc;
+/// use std::env;
+/// use threadpool::ThreadPool;
+/// use typemap::ShareMap;
+///
+/// struct Handler;
+///
+/// impl EventHandler for Handler { }
+///
+/// let token = env::var("DISCORD_TOKEN")?;
+/// http::set_token(&token);
+/// let token = Arc::new(Mutex::new(token));
+///
+/// let gateway_url = Arc::new(Mutex::new(http::get_gateway()?.url));
+/// let data = Arc::new(Mutex::new(ShareMap::custom()));
+/// let event_handler = Arc::new(Handler);
+/// let framework = Arc::new(Mutex::new(None));
+/// let threadpool = ThreadPool::with_name("my threadpool".to_owned(), 5);
+///
+/// ShardManager::new(
+/// 0, // the shard index to start initiating from
+/// 3, // the number of shards to initiate (this initiates 0, 1, and 2)
+/// 5, // the total number of shards in use
+/// gateway_url,
+/// token,
+/// data,
+/// event_handler,
+/// framework,
+/// threadpool,
+/// );
+/// # Ok(())
+/// # }
+/// #
+/// # #[cfg(not(feature = "framework"))]
+/// # fn try_main() -> Result<(), Box<Error>> {
+/// # Ok(())
+/// # }
+/// #
+/// # fn main() {
+/// # try_main().unwrap();
+/// # }
+/// ```
+///
+/// [`Client`]: ../../struct.Client.html
+#[derive(Debug)]
pub struct ShardManager {
- pub runners: Arc<ParkingLotMutex<HashMap<ShardId, ShardRunnerInfo>>>,
+ /// The shard runners currently managed.
+ runners: Arc<Mutex<HashMap<ShardId, ShardRunnerInfo>>>,
/// The index of the first shard to initialize, 0-indexed.
shard_index: u64,
/// The number of shards to initialize.
@@ -27,10 +101,11 @@ pub struct ShardManager {
/// The total shards in use, 1-indexed.
shard_total: u64,
shard_queuer: Sender<ShardQueuerMessage>,
- thread_rx: Receiver<ShardManagerMessage>,
}
impl ShardManager {
+ /// Creates a new shard manager, returning both the manager and a monitor
+ /// for usage in a separate thread.
#[cfg(feature = "framework")]
#[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))]
pub fn new<H>(
@@ -39,15 +114,15 @@ impl ShardManager {
shard_total: u64,
ws_url: Arc<Mutex<String>>,
token: Arc<Mutex<String>>,
- data: Arc<ParkingLotMutex<ShareMap>>,
+ data: Arc<Mutex<ShareMap>>,
event_handler: Arc<H>,
framework: Arc<Mutex<Option<Box<Framework + Send>>>>,
threadpool: ThreadPool,
- ) -> Self where H: EventHandler + Send + Sync + 'static {
+ ) -> (Arc<Mutex<Self>>, ShardManagerMonitor) where H: EventHandler + Send + Sync + 'static {
let (thread_tx, thread_rx) = mpsc::channel();
let (shard_queue_tx, shard_queue_rx) = mpsc::channel();
- let runners = Arc::new(ParkingLotMutex::new(HashMap::new()));
+ let runners = Arc::new(Mutex::new(HashMap::new()));
let mut shard_queuer = ShardQueuer {
data: Arc::clone(&data),
@@ -66,16 +141,22 @@ impl ShardManager {
shard_queuer.run();
});
- Self {
+ let manager = Arc::new(Mutex::new(Self {
shard_queuer: shard_queue_tx,
- thread_rx: thread_rx,
runners,
shard_index,
shard_init,
shard_total,
- }
+ }));
+
+ (Arc::clone(&manager), ShardManagerMonitor {
+ rx: thread_rx,
+ manager,
+ })
}
+ /// Creates a new shard manager, returning both the manager and a monitor
+ /// for usage in a separate thread.
#[cfg(not(feature = "framework"))]
pub fn new<H>(
shard_index: u64,
@@ -83,24 +164,24 @@ impl ShardManager {
shard_total: u64,
ws_url: Arc<Mutex<String>>,
token: Arc<Mutex<String>>,
- data: Arc<ParkingLotMutex<ShareMap>>,
+ data: Arc<Mutex<ShareMap>>,
event_handler: Arc<H>,
threadpool: ThreadPool,
- ) -> Self where H: EventHandler + Send + Sync + 'static {
+ ) -> (Arc<Mutex<Self>>, ShardManagerMonitor) where H: EventHandler + Send + Sync + 'static {
let (thread_tx, thread_rx) = mpsc::channel();
let (shard_queue_tx, shard_queue_rx) = mpsc::channel();
- let runners = Arc::new(ParkingLotMutex::new(HashMap::new()));
+ let runners = Arc::new(Mutex::new(HashMap::new()));
let mut shard_queuer = ShardQueuer {
- data: data.clone(),
- event_handler: event_handler.clone(),
+ data: Arc::clone(&data),
+ event_handler: Arc::clone(&event_handler),
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,
};
@@ -108,21 +189,41 @@ impl ShardManager {
shard_queuer.run();
});
- Self {
+ let manager = Arc::new(Mutex::new(Self {
shard_queuer: shard_queue_tx,
- thread_rx: thread_rx,
runners,
shard_index,
shard_init,
shard_total,
- }
+ }));
+
+ (Arc::clone(&manager), ShardManagerMonitor {
+ rx: thread_rx,
+ manager,
+ })
+ }
+
+ /// Returns whether the shard manager contains either an active instance of
+ /// a shard runner responsible for the given ID.
+ ///
+ /// If a shard has been queued but has not yet been initiated, then this
+ /// will return `false`. Consider double-checking [`is_responsible_for`] to
+ /// determine whether this shard manager is responsible for the given shard.
+ ///
+ /// [`is_responsible_for`]: #method.is_responsible_for
+ pub fn has(&self, shard_id: ShardId) -> bool {
+ self.runners.lock().contains_key(&shard_id)
}
+ /// Initializes all shards that the manager is responsible for.
+ ///
+ /// This will communicate shard boots with the [`ShardQueuer`] so that they
+ /// are properly queued.
+ ///
+ /// [`ShardQueuer`]: struct.ShardQueuer.html
pub fn initialize(&mut self) -> Result<()> {
let shard_to = self.shard_index + self.shard_init;
- debug!("{}, {}", self.shard_index, self.shard_init);
-
for shard_id in self.shard_index..shard_to {
let shard_total = self.shard_total;
@@ -132,26 +233,118 @@ impl ShardManager {
Ok(())
}
- pub fn run(&mut self) {
- while let Ok(value) = self.thread_rx.recv() {
- match value {
- ShardManagerMessage::Restart(shard_id) => self.restart(shard_id),
- ShardManagerMessage::Shutdown(shard_id) => self.shutdown(shard_id),
- ShardManagerMessage::ShutdownAll => {
- self.shutdown_all();
+ /// Sets the new sharding information for the manager.
+ ///
+ /// This will shutdown all existing shards.
+ ///
+ /// This will _not_ instantiate the new shards.
+ pub fn set_shards(&mut self, index: u64, init: u64, total: u64) {
+ self.shutdown_all();
+
+ self.shard_index = index;
+ self.shard_init = init;
+ self.shard_total = total;
+ }
- break;
- },
+ /// Restarts a shard runner.
+ ///
+ /// This sends a shutdown signal to a shard's associated [`ShardRunner`],
+ /// and then queues a initialization of a shard runner for the same shard
+ /// via the [`ShardQueuer`].
+ ///
+ /// # Examples
+ ///
+ /// Creating a client and then restarting a shard by ID:
+ ///
+ /// _(note: in reality this precise code doesn't have an effect since the
+ /// shard would not yet have been initialized via [`initialize`], but the
+ /// concept is the same)_
+ ///
+ /// ```rust,no_run
+ /// use serenity::client::bridge::gateway::ShardId;
+ /// use serenity::client::{Client, EventHandler};
+ /// use std::env;
+ ///
+ /// struct Handler;
+ ///
+ /// impl EventHandler for Handler { }
+ ///
+ /// let token = env::var("DISCORD_TOKEN").unwrap();
+ /// let mut client = Client::new(&token, Handler).unwrap();
+ ///
+ /// // restart shard ID 7
+ /// client.shard_manager.lock().restart(ShardId(7));
+ /// ```
+ ///
+ /// [`ShardQueuer`]: struct.ShardQueuer.html
+ /// [`ShardRunner`]: struct.ShardRunner.html
+ pub fn restart(&mut self, shard_id: ShardId) {
+ info!("Restarting shard {}", shard_id);
+ self.shutdown(shard_id);
+
+ let shard_total = self.shard_total;
+
+ self.boot([shard_id, ShardId(shard_total)]);
+ }
+
+ /// Returns the [`ShardId`]s of the shards that have been instantiated and
+ /// currently have a valid [`ShardRunner`].
+ ///
+ /// [`ShardId`]: struct.ShardId.html
+ /// [`ShardRunner`]: struct.ShardRunner.html
+ pub fn shards_instantiated(&self) -> Vec<ShardId> {
+ self.runners.lock().keys().cloned().collect()
+ }
+
+ /// Attempts to shut down the shard runner by Id.
+ ///
+ /// Returns a boolean indicating whether a shard runner was present. This is
+ /// _not_ necessary an indicator of whether the shard runner was
+ /// successfully shut down.
+ ///
+ /// **Note**: If the receiving end of an mpsc channel - theoretically owned
+ /// by the shard runner - no longer exists, then the shard runner will not
+ /// know it should shut down. This _should never happen_. It may already be
+ /// stopped.
+ pub fn shutdown(&mut self, shard_id: ShardId) -> bool {
+ info!("Shutting down shard {}", shard_id);
+
+ if let Some(runner) = self.runners.lock().get(&shard_id) {
+ let shutdown = ShardManagerMessage::Shutdown(shard_id);
+ let msg = ShardClientMessage::Manager(shutdown);
+
+ if let Err(why) = runner.runner_tx.send(msg) {
+ warn!(
+ "Failed to cleanly shutdown shard {}: {:?}",
+ shard_id,
+ why,
+ );
}
}
+
+ self.runners.lock().remove(&shard_id).is_some()
}
+ /// Sends a shutdown message for all shards that the manager is responsible
+ /// for that are still known to be running.
+ ///
+ /// If you only need to shutdown a select number of shards, prefer looping
+ /// over the [`shutdown`] method.
+ ///
+ /// [`shutdown`]: #method.shutdown
pub fn shutdown_all(&mut self) {
- info!("Shutting down all shards");
let keys = {
- self.runners.lock().keys().cloned().collect::<Vec<ShardId>>()
+ let runners = self.runners.lock();
+
+ if runners.is_empty() {
+ return;
+ }
+
+ runners.keys().cloned().collect::<Vec<_>>()
};
+ info!("Shutting down all shards");
+
for shard_id in keys {
self.shutdown(shard_id);
}
@@ -163,37 +356,19 @@ impl ShardManager {
let msg = ShardQueuerMessage::Start(shard_info[0], shard_info[1]);
let _ = self.shard_queuer.send(msg);
}
-
- fn restart(&mut self, shard_id: ShardId) {
- info!("Restarting shard {}", shard_id);
- self.shutdown(shard_id);
-
- let shard_total = self.shard_total;
-
- self.boot([shard_id, ShardId(shard_total)]);
- }
-
- fn shutdown(&mut self, shard_id: ShardId) {
- if let Some(runner) = self.runners.lock().get(&shard_id) {
- let is_shutdown = runner.shard.lock().is_shutdown();
-
- if !is_shutdown {
- info!("Shutting down shard {}", shard_id);
-
- let msg = ShardManagerMessage::Shutdown(shard_id);
-
- if let Err(why) = runner.runner_tx.send(msg) {
- warn!("Failed to cleanly shutdown shard {}: {:?}", shard_id, why);
- }
- }
- }
-
- self.runners.lock().remove(&shard_id);
- }
}
impl Drop for ShardManager {
+ /// A custom drop implementation to clean up after the manager.
+ ///
+ /// This shuts down all active [`ShardRunner`]s and attempts to tell the
+ /// [`ShardQueuer`] to shutdown.
+ ///
+ /// [`ShardQueuer`]: struct.ShardQueuer.html
+ /// [`ShardRunner`]: struct.ShardRunner.html
fn drop(&mut self) {
+ self.shutdown_all();
+
if let Err(why) = self.shard_queuer.send(ShardQueuerMessage::Shutdown) {
warn!("Failed to send shutdown to shard queuer: {:?}", why);
}
diff --git a/src/client/bridge/gateway/shard_manager_monitor.rs b/src/client/bridge/gateway/shard_manager_monitor.rs
new file mode 100644
index 0000000..4a64473
--- /dev/null
+++ b/src/client/bridge/gateway/shard_manager_monitor.rs
@@ -0,0 +1,54 @@
+use parking_lot::Mutex;
+use std::sync::mpsc::Receiver;
+use std::sync::Arc;
+use super::{ShardManager, ShardManagerMessage};
+
+/// The shard manager monitor does what it says on the tin -- it monitors the
+/// shard manager and performs actions on it as received.
+///
+/// The monitor is essentially responsible for running in its own thread and
+/// receiving [`ShardManagerMessage`]s, such as whether to shutdown a shard or
+/// shutdown everything entirely.
+///
+/// [`ShardManagerMessage`]: struct.ShardManagerMessage.html
+#[derive(Debug)]
+pub struct ShardManagerMonitor {
+ /// An clone of the Arc to the manager itself.
+ pub manager: Arc<Mutex<ShardManager>>,
+ /// The mpsc Receiver channel to receive shard manager messages over.
+ pub rx: Receiver<ShardManagerMessage>,
+}
+
+impl ShardManagerMonitor {
+ /// "Runs" the monitor, waiting for messages over the Receiver.
+ ///
+ /// This should be called in its own thread due to its blocking, looped
+ /// nature.
+ ///
+ /// This will continue running until either:
+ ///
+ /// - a [`ShardManagerMessage::ShutdownAll`] has been received
+ /// - an error is returned while receiving a message from the
+ /// channel (probably indicating that the shard manager should stop anyway)
+ ///
+ /// [`ShardManagerMessage::ShutdownAll`]: enum.ShardManagerMessage.html#variant.ShutdownAll
+ pub fn run(&mut self) {
+ debug!("Starting shard manager worker");
+
+ while let Ok(value) = self.rx.recv() {
+ match value {
+ ShardManagerMessage::Restart(shard_id) => {
+ self.manager.lock().restart(shard_id);
+ },
+ ShardManagerMessage::Shutdown(shard_id) => {
+ self.manager.lock().shutdown(shard_id);
+ },
+ ShardManagerMessage::ShutdownAll => {
+ self.manager.lock().shutdown_all();
+
+ break;
+ },
+ }
+ }
+ }
+}
diff --git a/src/client/bridge/gateway/shard_messenger.rs b/src/client/bridge/gateway/shard_messenger.rs
new file mode 100644
index 0000000..069c838
--- /dev/null
+++ b/src/client/bridge/gateway/shard_messenger.rs
@@ -0,0 +1,276 @@
+use model::{Game, GuildId, OnlineStatus};
+use super::{ShardClientMessage, ShardRunnerMessage};
+use std::sync::mpsc::{SendError, Sender};
+use websocket::message::OwnedMessage;
+
+/// A lightweight wrapper around an mpsc sender.
+///
+/// This is used to cleanly communicate with a shard's respective
+/// [`ShardRunner`]. This can be used for actions such as setting the game via
+/// [`set_game`] or shutting down via [`shutdown`].
+///
+/// [`ShardRunner`]: struct.ShardRunner.html
+/// [`set_game`]: #method.set_game
+/// [`shutdown`]: #method.shutdown
+#[derive(Clone, Debug)]
+pub struct ShardMessenger {
+ tx: Sender<ShardClientMessage>,
+}
+
+impl ShardMessenger {
+ /// Creates a new shard messenger.
+ ///
+ /// If you are using the [`Client`], you do not need to do this.
+ ///
+ /// [`Client`]: ../../struct.Client.html
+ #[inline]
+ pub fn new(tx: Sender<ShardClientMessage>) -> Self {
+ Self {
+ tx,
+ }
+ }
+
+ /// Requests that one or multiple [`Guild`]s be chunked.
+ ///
+ /// This will ask the gateway to start sending member chunks for large
+ /// guilds (250 members+). If a guild is over 250 members, then a full
+ /// member list will not be downloaded, and must instead be requested to be
+ /// sent in "chunks" containing members.
+ ///
+ /// Member chunks are sent as the [`Event::GuildMembersChunk`] event. Each
+ /// chunk only contains a partial amount of the total members.
+ ///
+ /// If the `cache` feature is enabled, the cache will automatically be
+ /// updated with member chunks.
+ ///
+ /// # Examples
+ ///
+ /// Chunk a single guild by Id, limiting to 2000 [`Member`]s, and not
+ /// specifying a query parameter:
+ ///
+ /// ```rust,no_run
+ /// # extern crate parking_lot;
+ /// # extern crate serenity;
+ /// #
+ /// # use parking_lot::Mutex;
+ /// # use serenity::client::gateway::Shard;
+ /// # use std::error::Error;
+ /// # use std::sync::Arc;
+ /// #
+ /// # fn try_main() -> Result<(), Box<Error>> {
+ /// # let mutex = Arc::new(Mutex::new("".to_string()));
+ /// #
+ /// # let mut shard = Shard::new(mutex.clone(), mutex, [0, 1])?;
+ /// #
+ /// use serenity::model::GuildId;
+ ///
+ /// let guild_ids = vec![GuildId(81384788765712384)];
+ ///
+ /// shard.chunk_guilds(guild_ids, Some(2000), None);
+ /// # Ok(())
+ /// # }
+ /// #
+ /// # fn main() {
+ /// # try_main().unwrap();
+ /// # }
+ /// ```
+ ///
+ /// Chunk a single guild by Id, limiting to 20 members, and specifying a
+ /// query parameter of `"do"`:
+ ///
+ /// ```rust,no_run
+ /// # extern crate parking_lot;
+ /// # extern crate serenity;
+ /// #
+ /// # use parking_lot::Mutex;
+ /// # use serenity::client::gateway::Shard;
+ /// # use std::error::Error;
+ /// # use std::sync::Arc;
+ /// #
+ /// # fn try_main() -> Result<(), Box<Error>> {
+ /// # let mutex = Arc::new(Mutex::new("".to_string()));
+ /// #
+ /// # let mut shard = Shard::new(mutex.clone(), mutex, [0, 1])?;
+ /// #
+ /// use serenity::model::GuildId;
+ ///
+ /// let guild_ids = vec![GuildId(81384788765712384)];
+ ///
+ /// shard.chunk_guilds(guild_ids, Some(20), Some("do"));
+ /// # Ok(())
+ /// # }
+ /// #
+ /// # fn main() {
+ /// # try_main().unwrap();
+ /// # }
+ /// ```
+ ///
+ /// [`Event::GuildMembersChunk`]:
+ /// ../../model/event/enum.Event.html#variant.GuildMembersChunk
+ /// [`Guild`]: ../../model/struct.Guild.html
+ /// [`Member`]: ../../model/struct.Member.html
+ pub fn chunk_guilds<It>(
+ &self,
+ guild_ids: It,
+ limit: Option<u16>,
+ query: Option<String>,
+ ) where It: IntoIterator<Item=GuildId> {
+ let guilds = guild_ids.into_iter().collect::<Vec<GuildId>>();
+
+ let _ = self.send(ShardRunnerMessage::ChunkGuilds {
+ guild_ids: guilds,
+ limit,
+ query,
+ });
+ }
+
+ /// Sets the user's current game, if any.
+ ///
+ /// Other presence settings are maintained.
+ ///
+ /// # Examples
+ ///
+ /// Setting the current game to playing `"Heroes of the Storm"`:
+ ///
+ /// ```rust,no_run
+ /// # extern crate parking_lot;
+ /// # extern crate serenity;
+ /// #
+ /// # use parking_lot::Mutex;
+ /// # use serenity::client::gateway::Shard;
+ /// # use std::error::Error;
+ /// # use std::sync::Arc;
+ /// #
+ /// # fn try_main() -> Result<(), Box<Error>> {
+ /// # let mutex = Arc::new(Mutex::new("".to_string()));
+ /// #
+ /// # let mut shard = Shard::new(mutex.clone(), mutex, [0, 1]).unwrap();
+ /// #
+ /// use serenity::model::Game;
+ ///
+ /// shard.set_game(Some(Game::playing("Heroes of the Storm")));
+ /// # Ok(())
+ /// # }
+ /// #
+ /// # fn main() {
+ /// # try_main().unwrap();
+ /// # }
+ /// ```
+ pub fn set_game(&self, game: Option<Game>) {
+ let _ = self.send(ShardRunnerMessage::SetGame(game));
+ }
+
+ /// Sets the user's full presence information.
+ ///
+ /// Consider using the individual setters if you only need to modify one of
+ /// these.
+ ///
+ /// # Examples
+ ///
+ /// Set the current user as playing `"Heroes of the Storm"` and being
+ /// online:
+ ///
+ /// ```rust,ignore
+ /// # extern crate parking_lot;
+ /// # extern crate serenity;
+ /// #
+ /// # use parking_lot::Mutex;
+ /// # use serenity::client::gateway::Shard;
+ /// # use std::error::Error;
+ /// # use std::sync::Arc;
+ /// #
+ /// # fn try_main() -> Result<(), Box<Error>> {
+ /// # let mutex = Arc::new(Mutex::new("".to_string()));
+ /// #
+ /// # let mut shard = Shard::new(mutex.clone(), mutex, [0, 1]).unwrap();
+ /// #
+ /// use serenity::model::{Game, OnlineStatus};
+ ///
+ /// shard.set_presence(Some(Game::playing("Heroes of the Storm")), OnlineStatus::Online);
+ /// # Ok(())
+ /// # }
+ /// #
+ /// # fn main() {
+ /// # try_main().unwrap();
+ /// # }
+ /// ```
+ pub fn set_presence(&self, game: Option<Game>, mut status: OnlineStatus) {
+ if status == OnlineStatus::Offline {
+ status = OnlineStatus::Invisible;
+ }
+
+ let _ = self.send(ShardRunnerMessage::SetPresence(status, game));
+ }
+
+ /// Sets the user's current online status.
+ ///
+ /// Note that [`Offline`] is not a valid online status, so it is
+ /// automatically converted to [`Invisible`].
+ ///
+ /// Other presence settings are maintained.
+ ///
+ /// # Examples
+ ///
+ /// Setting the current online status for the shard to [`DoNotDisturb`].
+ ///
+ /// ```rust,no_run
+ /// # extern crate parking_lot;
+ /// # extern crate serenity;
+ /// #
+ /// # use parking_lot::Mutex;
+ /// # use serenity::client::gateway::Shard;
+ /// # use std::error::Error;
+ /// # use std::sync::Arc;
+ /// #
+ /// # fn try_main() -> Result<(), Box<Error>> {
+ /// # let mutex = Arc::new(Mutex::new("".to_string()));
+ /// #
+ /// # let mut shard = Shard::new(mutex.clone(), mutex, [0, 1]).unwrap();
+ /// #
+ /// use serenity::model::OnlineStatus;
+ ///
+ /// shard.set_status(OnlineStatus::DoNotDisturb);
+ /// # Ok(())
+ /// # }
+ /// #
+ /// # fn main() {
+ /// # try_main().unwrap();
+ /// # }
+ /// ```
+ ///
+ /// [`DoNotDisturb`]: ../../model/enum.OnlineStatus.html#variant.DoNotDisturb
+ /// [`Invisible`]: ../../model/enum.OnlineStatus.html#variant.Invisible
+ /// [`Offline`]: ../../model/enum.OnlineStatus.html#variant.Offline
+ pub fn set_status(&self, mut online_status: OnlineStatus) {
+ if online_status == OnlineStatus::Offline {
+ online_status = OnlineStatus::Invisible;
+ }
+
+ let _ = self.send(ShardRunnerMessage::SetStatus(online_status));
+ }
+
+ /// Shuts down the websocket by attempting to cleanly close the
+ /// connection.
+ pub fn shutdown_clean(&self) {
+ let _ = self.send(ShardRunnerMessage::Close(1000, None));
+ }
+
+ /// Sends a raw message over the WebSocket.
+ ///
+ /// The given message is not mutated in any way, and is sent as-is.
+ ///
+ /// You should only use this if you know what you're doing. If you're
+ /// wanting to, for example, send a presence update, prefer the usage of
+ /// the [`set_presence`] method.
+ ///
+ /// [`set_presence`]: #method.set_presence
+ pub fn websocket_message(&self, message: OwnedMessage) {
+ let _ = self.send(ShardRunnerMessage::Message(message));
+ }
+
+ #[inline]
+ fn send(&self, msg: ShardRunnerMessage)
+ -> Result<(), SendError<ShardClientMessage>> {
+ self.tx.send(ShardClientMessage::Runner(msg))
+ }
+}
diff --git a/src/client/bridge/gateway/shard_queuer.rs b/src/client/bridge/gateway/shard_queuer.rs
index 78118c7..bd02532 100644
--- a/src/client/bridge/gateway/shard_queuer.rs
+++ b/src/client/bridge/gateway/shard_queuer.rs
@@ -1,9 +1,9 @@
use gateway::Shard;
use internal::prelude::*;
-use parking_lot::Mutex as ParkingLotMutex;
+use parking_lot::Mutex;
use std::collections::HashMap;
use std::sync::mpsc::{Receiver, Sender};
-use std::sync::{Arc, Mutex};
+use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant};
use super::super::super::EventHandler;
@@ -27,20 +27,69 @@ use framework::Framework;
/// blocking nature of the loop itself as well as a 5 second thread sleep
/// between shard starts.
pub struct ShardQueuer<H: EventHandler + Send + Sync + 'static> {
- pub data: Arc<ParkingLotMutex<ShareMap>>,
+ /// A copy of [`Client::data`] to be given to runners for contextual
+ /// dispatching.
+ ///
+ /// [`Client::data`]: ../../struct.Client.html#structfield.data
+ pub data: Arc<Mutex<ShareMap>>,
+ /// A reference to an `EventHandler`, such as the one given to the
+ /// [`Client`].
+ ///
+ /// [`Client`]: ../../struct.Client.html
pub event_handler: Arc<H>,
+ /// A copy of the framework
#[cfg(feature = "framework")]
pub framework: Arc<Mutex<Option<Box<Framework + Send>>>>,
+ /// The instant that a shard was last started.
+ ///
+ /// This is used to determine how long to wait between shard IDENTIFYs.
pub last_start: Option<Instant>,
+ /// A copy of the sender channel to communicate with the
+ /// [`ShardManagerMonitor`].
+ ///
+ /// [`ShardManagerMonitor`]: struct.ShardManagerMonitor.html
pub manager_tx: Sender<ShardManagerMessage>,
- pub runners: Arc<ParkingLotMutex<HashMap<ShardId, ShardRunnerInfo>>>,
+ /// A copy of the map of shard runners.
+ pub runners: Arc<Mutex<HashMap<ShardId, ShardRunnerInfo>>>,
+ /// A receiver channel for the shard queuer to be told to start shards.
pub rx: Receiver<ShardQueuerMessage>,
+ /// A copy of a threadpool to give shard runners.
+ ///
+ /// For example, when using the [`Client`], this will be a copy of
+ /// [`Client::threadpool`].
+ ///
+ /// [`Client`]: ../../struct.Client.html
+ /// [`Client::threadpool`]: ../../struct.Client.html#structfield.threadpool
pub threadpool: ThreadPool,
+ /// A copy of the token to connect with.
pub token: Arc<Mutex<String>>,
+ /// A copy of the URI to use to connect to the gateway.
pub ws_url: Arc<Mutex<String>>,
}
impl<H: EventHandler + Send + Sync + 'static> ShardQueuer<H> {
+ /// Begins the shard queuer loop.
+ ///
+ /// This will loop over the internal [`rx`] for [`ShardQueuerMessage`]s,
+ /// blocking for messages on what to do.
+ ///
+ /// If a [`ShardQueuerMessage::Start`] is received, this will:
+ ///
+ /// 1. Check how much time has passed since the last shard was started
+ /// 2. If the amount of time is less than the ratelimit, it will sleep until
+ /// that time has passed
+ /// 3. Start the shard by ID
+ ///
+ /// If a [`ShardQueuerMessage::Shutdown`] is received, this will return and
+ /// the loop will be over.
+ ///
+ /// **Note**: This should be run in its own thread due to the blocking
+ /// nature of the loop.
+ ///
+ /// [`ShardQueuerMessage`]: enum.ShardQueuerMessage.html
+ /// [`ShardQueuerMessage::Shutdown`]: enum.ShardQueuerMessage.html#variant.Shutdown
+ /// [`ShardQueuerMessage::Start`]: enum.ShardQueuerMessage.html#variant.Start
+ /// [`rx`]: #structfield.rx
pub fn run(&mut self) {
while let Ok(msg) = self.rx.recv() {
match msg {
@@ -80,16 +129,16 @@ 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(
Arc::clone(&self.ws_url),
Arc::clone(&self.token),
shard_info,
)?;
- let locked = Arc::new(ParkingLotMutex::new(shard));
let mut runner = feature_framework! {{
ShardRunner::new(
- Arc::clone(&locked),
+ shard,
self.manager_tx.clone(),
Arc::clone(&self.framework),
Arc::clone(&self.data),
@@ -98,7 +147,7 @@ impl<H: EventHandler + Send + Sync + 'static> ShardQueuer<H> {
)
} else {
ShardRunner::new(
- locked.clone(),
+ shard,
self.manager_tx.clone(),
self.data.clone(),
self.event_handler.clone(),
@@ -108,7 +157,6 @@ impl<H: EventHandler + Send + Sync + 'static> ShardQueuer<H> {
let runner_info = ShardRunnerInfo {
runner_tx: runner.runner_tx(),
- shard: locked,
};
thread::spawn(move || {
diff --git a/src/client/bridge/gateway/shard_runner.rs b/src/client/bridge/gateway/shard_runner.rs
index aa0e064..0ba83ba 100644
--- a/src/client/bridge/gateway/shard_runner.rs
+++ b/src/client/bridge/gateway/shard_runner.rs
@@ -1,45 +1,52 @@
+use gateway::{Shard, ShardAction};
use internal::prelude::*;
use internal::ws_impl::ReceiverExt;
use model::event::{Event, GatewayEvent};
-use parking_lot::Mutex as ParkingLotMutex;
-use std::sync::mpsc::{self, Receiver, Sender};
+use parking_lot::Mutex;
+use std::sync::mpsc::{self, Receiver, Sender, TryRecvError};
use std::sync::Arc;
use super::super::super::{EventHandler, dispatch};
-use super::{LockedShard, ShardId, ShardManagerMessage};
+use super::{ShardClientMessage, ShardId, ShardManagerMessage, ShardRunnerMessage};
use threadpool::ThreadPool;
use typemap::ShareMap;
+use websocket::message::{CloseData, OwnedMessage};
use websocket::WebSocketError;
#[cfg(feature = "framework")]
use framework::Framework;
-#[cfg(feature = "framework")]
-use std::sync::Mutex;
+#[cfg(feature = "voice")]
+use internal::ws_impl::SenderExt;
+/// A runner for managing a [`Shard`] and its respective WebSocket client.
+///
+/// [`Shard`]: ../../../gateway/struct.Shard.html
pub struct ShardRunner<H: EventHandler + Send + Sync + 'static> {
- data: Arc<ParkingLotMutex<ShareMap>>,
+ data: Arc<Mutex<ShareMap>>,
event_handler: Arc<H>,
#[cfg(feature = "framework")]
framework: Arc<Mutex<Option<Box<Framework + Send>>>>,
manager_tx: Sender<ShardManagerMessage>,
- runner_rx: Receiver<ShardManagerMessage>,
- runner_tx: Sender<ShardManagerMessage>,
- shard: LockedShard,
- shard_info: [u64; 2],
+ // channel to receive messages from the shard manager and dispatches
+ runner_rx: Receiver<ShardClientMessage>,
+ // channel to send messages to the shard runner from the shard manager
+ runner_tx: Sender<ShardClientMessage>,
+ shard: Shard,
threadpool: ThreadPool,
}
impl<H: EventHandler + Send + Sync + 'static> ShardRunner<H> {
+ /// Creates a new runner for a Shard.
+ #[allow(too_many_arguments)]
#[cfg(feature = "framework")]
pub fn new(
- shard: LockedShard,
+ shard: Shard,
manager_tx: Sender<ShardManagerMessage>,
framework: Arc<Mutex<Option<Box<Framework + Send>>>>,
- data: Arc<ParkingLotMutex<ShareMap>>,
+ data: Arc<Mutex<ShareMap>>,
event_handler: Arc<H>,
threadpool: ThreadPool,
) -> Self {
let (tx, rx) = mpsc::channel();
- let shard_info = shard.lock().shard_info();
Self {
runner_rx: rx,
@@ -49,21 +56,20 @@ impl<H: EventHandler + Send + Sync + 'static> ShardRunner<H> {
framework,
manager_tx,
shard,
- shard_info,
threadpool,
}
}
+ /// Creates a new runner for a Shard.
#[cfg(not(feature = "framework"))]
pub fn new(
- shard: LockedShard,
+ shard: Shard,
manager_tx: Sender<ShardManagerMessage>,
- data: Arc<ParkingLotMutex<ShareMap>>,
+ data: Arc<Mutex<ShareMap>>,
event_handler: Arc<H>,
threadpool: ThreadPool,
) -> Self {
let (tx, rx) = mpsc::channel();
- let shard_info = shard.lock().shard_info();
Self {
runner_rx: rx,
@@ -72,98 +78,276 @@ impl<H: EventHandler + Send + Sync + 'static> ShardRunner<H> {
event_handler,
manager_tx,
shard,
- shard_info,
threadpool,
}
}
+ /// Starts the runner's loop to receive events.
+ ///
+ /// This runs a loop that performs the following in each iteration:
+ ///
+ /// 1. checks the receiver for [`ShardRunnerMessage`]s, possibly from the
+ /// [`ShardManager`], and if there is one, acts on it.
+ ///
+ /// 2. checks if a heartbeat should be sent to the discord Gateway, and if
+ /// so, sends one.
+ ///
+ /// 3. attempts to retrieve a message from the WebSocket, processing it into
+ /// a [`GatewayEvent`]. This will block for 100ms before assuming there is
+ /// no message available.
+ ///
+ /// 4. Checks with the [`Shard`] to determine if the gateway event is
+ /// specifying an action to take (e.g. resuming, reconnecting, heartbeating)
+ /// and then performs that action, if any.
+ ///
+ /// 5. Dispatches the event via the Client.
+ ///
+ /// 6. Go back to 1.
+ ///
+ /// [`GatewayEvent`]: ../../../model/event/enum.GatewayEvent.html
+ /// [`Shard`]: ../../../gateway/struct.Shard.html
+ /// [`ShardManager`]: struct.ShardManager.html
+ /// [`ShardRunnerMessage`]: enum.ShardRunnerMessage.html
pub fn run(&mut self) -> Result<()> {
- debug!("[ShardRunner {:?}] Running", self.shard_info);
+ debug!("[ShardRunner {:?}] Running", self.shard.shard_info());
loop {
- {
- let mut shard = self.shard.lock();
- let incoming = self.runner_rx.try_recv();
+ if !self.recv()? {
+ return Ok(());
+ }
- // Check for an incoming message over the runner channel.
- //
- // If the message is to shutdown, first verify the ID so we know
- // for certain this runner is to shutdown.
- if let Ok(ShardManagerMessage::Shutdown(id)) = incoming {
- if id.0 == self.shard_info[0] {
- let _ = shard.shutdown_clean();
+ // check heartbeat
+ if let Err(why) = self.shard.check_heartbeat() {
+ warn!(
+ "[ShardRunner {:?}] Error heartbeating: {:?}",
+ self.shard.shard_info(),
+ why,
+ );
+ debug!(
+ "[ShardRunner {:?}] Requesting restart",
+ self.shard.shard_info(),
+ );
+
+ return self.request_restart();
+ }
- return Ok(());
+ #[cfg(feature = "voice")]
+ {
+ for message in self.shard.cycle_voice_recv() {
+ if let Err(why) = self.shard.client.send_json(&message) {
+ println!("Err sending from voice over WS: {:?}", why);
}
}
+ }
- if let Err(why) = shard.check_heartbeat() {
- error!("Failed to heartbeat and reconnect: {:?}", why);
-
- return self.request_restart();
- }
+ let (event, action, successful) = self.recv_event();
- #[cfg(feature = "voice")]
- {
- shard.cycle_voice_recv();
- }
+ if let Some(action) = action {
+ let _ = self.action(action);
}
- let (event, successful) = self.recv_event();
-
if let Some(event) = event {
- let data = Arc::clone(&self.data);
- let event_handler = Arc::clone(&self.event_handler);
- let shard = Arc::clone(&self.shard);
-
- feature_framework! {{
- let framework = Arc::clone(&self.framework);
-
- self.threadpool.execute(|| {
- dispatch(
- event,
- shard,
- framework,
- data,
- event_handler,
- );
- });
- } else {
- self.threadpool.execute(|| {
- dispatch(
- event,
- shard,
- data,
- event_handler,
- );
- });
- }}
+ self.dispatch(event);
}
- {
- let shard = self.shard.lock();
+ if !successful && !self.shard.stage().is_connecting() {
+ return self.request_restart();
+ }
+ }
+ }
- if !successful && !shard.stage().is_connecting() {
- return self.request_restart();
- }
+ /// Clones the internal copy of the Sender to the shard runner.
+ pub(super) fn runner_tx(&self) -> Sender<ShardClientMessage> {
+ self.runner_tx.clone()
+ }
- if shard.is_shutdown() {
- return self.request_shutdown();
- }
+ /// Takes an action that a [`Shard`] has determined should happen and then
+ /// does it.
+ ///
+ /// For example, if the shard says that an Identify message needs to be
+ /// sent, this will do that.
+ ///
+ /// # Errors
+ ///
+ /// Returns
+ fn action(&mut self, action: ShardAction) -> Result<()> {
+ match action {
+ ShardAction::Autoreconnect => self.shard.autoreconnect(),
+ ShardAction::Heartbeat => self.shard.heartbeat(),
+ ShardAction::Identify => self.shard.identify(),
+ ShardAction::Reconnect => self.shard.reconnect(),
+ ShardAction::Resume => self.shard.resume(),
+ }
+ }
+
+ // Checks if the ID received to shutdown is equivalent to the ID of the
+ // shard this runner is responsible. If so, it shuts down the WebSocket
+ // client.
+ //
+ // Returns whether the WebSocket client is still active.
+ //
+ // If true, the WebSocket client was _not_ shutdown. If false, it was.
+ fn checked_shutdown(&mut self, id: ShardId) -> bool {
+ // First verify the ID so we know for certain this runner is
+ // to shutdown.
+ if id.0 != self.shard.shard_info()[0] {
+ // Not meant for this runner for some reason, don't
+ // shutdown.
+ return true;
+ }
+
+ let close_data = CloseData::new(1000, String::new());
+ let msg = OwnedMessage::Close(Some(close_data));
+ let _ = self.shard.client.send_message(&msg);
+
+ false
+ }
+
+ fn dispatch(&self, event: Event) {
+ let data = Arc::clone(&self.data);
+ let event_handler = Arc::clone(&self.event_handler);
+ let runner_tx = self.runner_tx.clone();
+ let shard_id = self.shard.shard_info()[0];
+
+ feature_framework! {{
+ let framework = Arc::clone(&self.framework);
+
+ self.threadpool.execute(move || {
+ dispatch(
+ event,
+ framework,
+ data,
+ event_handler,
+ runner_tx,
+ shard_id,
+ );
+ });
+ } else {
+ self.threadpool.execute(move || {
+ dispatch(
+ event,
+ data,
+ event_handler,
+ runner_tx,
+ shard_id,
+ );
+ });
+ }}
+ }
+
+ // Handles a received value over the shard runner rx channel.
+ //
+ // Returns a boolean on whether the shard runner can continue.
+ //
+ // This always returns true, except in the case that the shard manager asked
+ // the runner to shutdown.
+ fn handle_rx_value(&mut self, value: ShardClientMessage) -> bool {
+ match value {
+ ShardClientMessage::Manager(x) => match x {
+ ShardManagerMessage::Restart(id) |
+ ShardManagerMessage::Shutdown(id) => {
+ self.checked_shutdown(id)
+ },
+ ShardManagerMessage::ShutdownAll => {
+ // This variant should never be received.
+ warn!(
+ "[ShardRunner {:?}] Received a ShutdownAll?",
+ self.shard.shard_info(),
+ );
+
+ true
+ },
}
+ ShardClientMessage::Runner(x) => match x {
+ ShardRunnerMessage::ChunkGuilds { guild_ids, limit, query } => {
+ self.shard.chunk_guilds(
+ guild_ids,
+ limit,
+ query.as_ref().map(String::as_str),
+ ).is_ok()
+ },
+ ShardRunnerMessage::Close(code, reason) => {
+ let reason = reason.unwrap_or_else(String::new);
+ let data = CloseData::new(code, reason);
+ let msg = OwnedMessage::Close(Some(data));
+
+ self.shard.client.send_message(&msg).is_ok()
+ },
+ ShardRunnerMessage::Message(msg) => {
+ self.shard.client.send_message(&msg).is_ok()
+ },
+ ShardRunnerMessage::SetGame(game) => {
+ // To avoid a clone of `game`, we do a little bit of
+ // trickery here:
+ //
+ // First, we obtain a reference to the current presence of
+ // the shard, and create a new presence tuple of the new
+ // game we received over the channel as well as the online
+ // status that the shard already had.
+ //
+ // We then (attempt to) send the websocket message with the
+ // status update, expressively returning:
+ //
+ // - whether the message successfully sent
+ // - the original game we received over the channel
+ self.shard.set_game(game);
+
+ self.shard.update_presence().is_ok()
+ },
+ ShardRunnerMessage::SetPresence(status, game) => {
+ self.shard.set_presence(status, game);
+
+ self.shard.update_presence().is_ok()
+ },
+ ShardRunnerMessage::SetStatus(status) => {
+ self.shard.set_status(status);
+
+ self.shard.update_presence().is_ok()
+ },
+ },
}
}
- pub(super) fn runner_tx(&self) -> Sender<ShardManagerMessage> {
- self.runner_tx.clone()
+ // Receives values over the internal shard runner rx channel and handles
+ // them.
+ //
+ // This will loop over values until there is no longer one.
+ //
+ // Requests a restart if the sending half of the channel disconnects. This
+ // should _never_ happen, as the sending half is kept on the runner.
+
+ // Returns whether the shard runner is in a state that can continue.
+ fn recv(&mut self) -> Result<bool> {
+ loop {
+ match self.runner_rx.try_recv() {
+ Ok(value) => {
+ if !self.handle_rx_value(value) {
+ return Ok(false);
+ }
+ },
+ Err(TryRecvError::Disconnected) => {
+ warn!(
+ "[ShardRunner {:?}] Sending half DC; restarting",
+ self.shard.shard_info(),
+ );
+
+ let _ = self.request_restart();
+
+ return Ok(false);
+ },
+ Err(TryRecvError::Empty) => break,
+ }
+ }
+
+ // There are no longer any values available.
+
+ Ok(true)
}
/// Returns a received event, as well as whether reading the potentially
/// present event was successful.
- fn recv_event(&mut self) -> (Option<Event>, bool) {
- let mut shard = self.shard.lock();
-
- let gw_event = match shard.client.recv_json(GatewayEvent::decode) {
+ fn recv_event(&mut self) -> (Option<Event>, Option<ShardAction>, bool) {
+ let gw_event = match self.shard.client.recv_json(GatewayEvent::decode) {
Err(Error::WebSocket(WebSocketError::IoError(_))) => {
// Check that an amount of time at least double the
// heartbeat_interval has passed.
@@ -171,58 +355,69 @@ impl<H: EventHandler + Send + Sync + 'static> ShardRunner<H> {
// If not, continue on trying to receive messages.
//
// If it has, attempt to auto-reconnect.
- let last = shard.last_heartbeat_ack();
- let interval = shard.heartbeat_interval();
-
- if let (Some(last_heartbeat_ack), Some(interval)) = (last, interval) {
- let seconds_passed = last_heartbeat_ack.elapsed().as_secs();
- let interval_in_secs = interval / 1000;
-
- if seconds_passed <= interval_in_secs * 2 {
- return (None, true);
+ {
+ let last = self.shard.last_heartbeat_ack();
+ let interval = self.shard.heartbeat_interval();
+
+ if let (Some(last_heartbeat_ack), Some(interval)) = (last, interval) {
+ let seconds_passed = last_heartbeat_ack.elapsed().as_secs();
+ let interval_in_secs = interval / 1000;
+
+ if seconds_passed <= interval_in_secs * 2 {
+ return (None, None, true);
+ }
+ } else {
+ return (None, None, true);
}
- } else {
- return (None, true);
}
debug!("Attempting to auto-reconnect");
- if let Err(why) = shard.autoreconnect() {
+ if let Err(why) = self.shard.autoreconnect() {
error!("Failed to auto-reconnect: {:?}", why);
}
- return (None, true);
+ return (None, None, true);
},
Err(Error::WebSocket(WebSocketError::NoDataAvailable)) => {
// This is hit when the websocket client dies this will be
// hit every iteration.
- return (None, false);
+ return (None, None, false);
},
other => other,
};
let event = match gw_event {
Ok(Some(event)) => Ok(event),
- Ok(None) => return (None, true),
+ Ok(None) => return (None, None, true),
Err(why) => Err(why),
};
- let event = match shard.handle_event(event) {
- Ok(Some(event)) => event,
- Ok(None) => return (None, true),
+ let action = match self.shard.handle_event(&event) {
+ Ok(Some(action)) => Some(action),
+ Ok(None) => None,
Err(why) => {
error!("Shard handler received err: {:?}", why);
- return (None, true);
+ return (None, None, true);
},
- };
+ };
+
+ let event = match event {
+ Ok(GatewayEvent::Dispatch(_, event)) => Some(event),
+ _ => None,
+ };
- (Some(event), true)
+ (event, action, true)
}
fn request_restart(&self) -> Result<()> {
- debug!("[ShardRunner {:?}] Requesting restart", self.shard_info);
- let msg = ShardManagerMessage::Restart(ShardId(self.shard_info[0]));
+ debug!(
+ "[ShardRunner {:?}] Requesting restart",
+ self.shard.shard_info(),
+ );
+ let shard_id = ShardId(self.shard.shard_info()[0]);
+ let msg = ShardManagerMessage::Restart(shard_id);
let _ = self.manager_tx.send(msg);
Ok(())
diff --git a/src/client/bridge/gateway/shard_runner_message.rs b/src/client/bridge/gateway/shard_runner_message.rs
new file mode 100644
index 0000000..e6458eb
--- /dev/null
+++ b/src/client/bridge/gateway/shard_runner_message.rs
@@ -0,0 +1,43 @@
+use model::{Game, GuildId, OnlineStatus};
+use websocket::message::OwnedMessage;
+
+/// A message to send from a shard over a WebSocket.
+pub enum ShardRunnerMessage {
+ /// Indicates that the client is to send a member chunk message.
+ ChunkGuilds {
+ /// The IDs of the [`Guild`]s to chunk.
+ ///
+ /// [`Guild`]: ../../../model/struct.Guild.html
+ guild_ids: Vec<GuildId>,
+ /// The maximum number of members to receive [`GuildMembersChunkEvent`]s
+ /// for.
+ ///
+ /// [`GuildMembersChunkEvent`]: ../../../model/event/GuildMembersChunkEvent.html
+ limit: Option<u16>,
+ /// Text to filter members by.
+ ///
+ /// For example, a query of `"s"` will cause only [`Member`]s whose
+ /// usernames start with `"s"` to be chunked.
+ ///
+ /// [`Member`]: ../../../model/struct.Member.html
+ query: Option<String>,
+ },
+ /// Indicates that the client is to close with the given status code and
+ /// reason.
+ ///
+ /// You should rarely - if _ever_ - need this, but the option is available.
+ /// Prefer to use the [`ShardManager`] to shutdown WebSocket clients if you
+ /// are intending to send a 1000 close code.
+ ///
+ /// [`ShardManager`]: struct.ShardManager.html
+ Close(u16, Option<String>),
+ /// Indicates that the client is to send a custom WebSocket message.
+ Message(OwnedMessage),
+ /// Indicates that the client is to update the shard's presence's game.
+ SetGame(Option<Game>),
+ /// Indicates that the client is to update the shard's presence in its
+ /// entirity.
+ SetPresence(OnlineStatus, Option<Game>),
+ /// Indicates that the client is to update the shard's presence's status.
+ SetStatus(OnlineStatus),
+}
diff --git a/src/client/bridge/mod.rs b/src/client/bridge/mod.rs
index 4f27526..ea18d73 100644
--- a/src/client/bridge/mod.rs
+++ b/src/client/bridge/mod.rs
@@ -1 +1,10 @@
+//! A collection of bridged support between the [`client`] module and other
+//! modules.
+//!
+//! **Warning**: You likely _do not_ need to mess with anything in here. Beware.
+//! This is lower-level functionality abstracted by the [`Client`].
+//!
+//! [`Client`]: ../struct.Client.html
+//! [`client`]: ../
+
pub mod gateway;
diff --git a/src/client/context.rs b/src/client/context.rs
index d0a6fc7..cc7ee63 100644
--- a/src/client/context.rs
+++ b/src/client/context.rs
@@ -1,7 +1,7 @@
-use Result;
+use client::bridge::gateway::{ShardClientMessage, ShardMessenger};
+use std::sync::mpsc::Sender;
use std::sync::Arc;
use typemap::ShareMap;
-use gateway::Shard;
use model::*;
use parking_lot::Mutex;
@@ -12,7 +12,9 @@ use internal::prelude::*;
#[cfg(feature = "builder")]
use builder::EditProfile;
#[cfg(feature = "builder")]
-use http;
+use {Result, http, utils};
+#[cfg(feature = "builder")]
+use std::collections::HashMap;
/// The context is a general utility struct provided on event dispatches, which
/// helps with dealing with the current "context" of the event dispatch.
@@ -36,21 +38,23 @@ pub struct Context {
///
/// [`Client::data`]: struct.Client.html#structfield.data
pub data: Arc<Mutex<ShareMap>>,
- /// The associated shard which dispatched the event handler.
- ///
- /// Note that if you are sharding, in relevant terms, this is the shard
- /// which received the event being dispatched.
- pub shard: Arc<Mutex<Shard>>,
+ /// The messenger to communicate with the shard runner.
+ pub shard: ShardMessenger,
+ /// The ID of the shard this context is related to.
+ pub shard_id: u64,
}
impl Context {
/// Create a new Context to be passed to an event handler.
- pub(crate) fn new(shard: Arc<Mutex<Shard>>,
- data: Arc<Mutex<ShareMap>>)
- -> Context {
+ pub(crate) fn new(
+ data: Arc<Mutex<ShareMap>>,
+ runner_tx: Sender<ShardClientMessage>,
+ shard_id: u64,
+ ) -> Context {
Context {
+ shard: ShardMessenger::new(runner_tx),
+ shard_id,
data,
- shard,
}
}
@@ -69,43 +73,46 @@ impl Context {
/// struct Handler;
///
/// impl EventHandler for Handler {
- /// fn on_message(&self, ctx: Context, msg: Message) {
+ /// fn message(&self, ctx: Context, msg: Message) {
/// if msg.content == "!changename" {
/// ctx.edit_profile(|e| e.username("Edward Elric"));
/// }
/// }
/// }
- /// let mut client = Client::new("token", Handler); client.start().unwrap();
+ /// let mut client = Client::new("token", Handler).unwrap();
+ ///
+ /// client.start().unwrap();
/// ```
#[cfg(feature = "builder")]
pub fn edit_profile<F: FnOnce(EditProfile) -> EditProfile>(&self, f: F) -> Result<CurrentUser> {
- let mut map = Map::new();
+ let mut map = HashMap::new();
feature_cache! {
{
- let cache = CACHE.read().unwrap();
+ let cache = CACHE.read();
- map.insert("username".to_string(), Value::String(cache.user.name.clone()));
+ map.insert("username", Value::String(cache.user.name.clone()));
if let Some(email) = cache.user.email.as_ref() {
- map.insert("email".to_string(), Value::String(email.clone()));
+ map.insert("email", Value::String(email.clone()));
}
} else {
let user = http::get_current_user()?;
- map.insert("username".to_string(), Value::String(user.name.clone()));
+ map.insert("username", Value::String(user.name.clone()));
if let Some(email) = user.email.as_ref() {
- map.insert("email".to_string(), Value::String(email.clone()));
+ map.insert("email", Value::String(email.clone()));
}
}
}
- let edited = f(EditProfile(map)).0;
+ let edited = utils::hashmap_to_json_map(f(EditProfile(map)).0);
http::edit_profile(&edited)
}
+
/// Sets the current user as being [`Online`]. This maintains the current
/// game.
///
@@ -120,19 +127,22 @@ impl Context {
/// struct Handler;
///
/// impl EventHandler for Handler {
- /// fn on_message(&self, ctx: Context, msg: Message) {
+ /// fn message(&self, ctx: Context, msg: Message) {
/// if msg.content == "!online" {
/// ctx.online();
/// }
/// }
/// }
- /// let mut client = Client::new("token", Handler); client.start().unwrap();
+ ///
+ /// let mut client = Client::new("token", Handler).unwrap();
+ ///
+ /// client.start().unwrap();
/// ```
///
/// [`Online`]: ../model/enum.OnlineStatus.html#variant.Online
+ #[inline]
pub fn online(&self) {
- let mut shard = self.shard.lock();
- shard.set_status(OnlineStatus::Online);
+ self.shard.set_status(OnlineStatus::Online);
}
/// Sets the current user as being [`Idle`]. This maintains the current
@@ -149,19 +159,21 @@ impl Context {
/// struct Handler;
///
/// impl EventHandler for Handler {
- /// fn on_message(&self, ctx: Context, msg: Message) {
+ /// fn message(&self, ctx: Context, msg: Message) {
/// if msg.content == "!idle" {
/// ctx.idle();
/// }
/// }
/// }
- /// let mut client = Client::new("token", Handler); client.start().unwrap();
+ /// let mut client = Client::new("token", Handler).unwrap();
+ ///
+ /// client.start().unwrap();
/// ```
///
/// [`Idle`]: ../model/enum.OnlineStatus.html#variant.Idle
+ #[inline]
pub fn idle(&self) {
- let mut shard = self.shard.lock();
- shard.set_status(OnlineStatus::Idle);
+ self.shard.set_status(OnlineStatus::Idle);
}
/// Sets the current user as being [`DoNotDisturb`]. This maintains the
@@ -178,19 +190,21 @@ impl Context {
/// struct Handler;
///
/// impl EventHandler for Handler {
- /// fn on_message(&self, ctx: Context, msg: Message) {
+ /// fn message(&self, ctx: Context, msg: Message) {
/// if msg.content == "!dnd" {
/// ctx.dnd();
/// }
/// }
/// }
- /// let mut client = Client::new("token", Handler); client.start().unwrap();
+ /// let mut client = Client::new("token", Handler).unwrap();
+ ///
+ /// client.start().unwrap();
/// ```
///
/// [`DoNotDisturb`]: ../model/enum.OnlineStatus.html#variant.DoNotDisturb
+ #[inline]
pub fn dnd(&self) {
- let mut shard = self.shard.lock();
- shard.set_status(OnlineStatus::DoNotDisturb);
+ self.shard.set_status(OnlineStatus::DoNotDisturb);
}
/// Sets the current user as being [`Invisible`]. This maintains the current
@@ -208,19 +222,21 @@ impl Context {
/// struct Handler;
///
/// impl EventHandler for Handler {
- /// fn on_ready(&self, ctx: Context, _: Ready) {
+ /// fn ready(&self, ctx: Context, _: Ready) {
/// ctx.invisible();
/// }
/// }
///
- /// let mut client = Client::new("token", Handler); client.start().unwrap();
+ /// let mut client = Client::new("token", Handler).unwrap();
+ ///
+ /// client.start().unwrap();
/// ```
///
/// [`Event::Ready`]: ../model/event/enum.Event.html#variant.Ready
/// [`Invisible`]: ../model/enum.OnlineStatus.html#variant.Invisible
+ #[inline]
pub fn invisible(&self) {
- let mut shard = self.shard.lock();
- shard.set_status(OnlineStatus::Invisible);
+ self.shard.set_status(OnlineStatus::Invisible);
}
/// "Resets" the current user's presence, by setting the game to `None` and
@@ -239,20 +255,22 @@ impl Context {
/// struct Handler;
///
/// impl EventHandler for Handler {
- /// fn on_resume(&self, ctx: Context, _: ResumedEvent) {
+ /// fn resume(&self, ctx: Context, _: ResumedEvent) {
/// ctx.reset_presence();
/// }
/// }
///
- /// let mut client = Client::new("token", Handler); client.start().unwrap();
+ /// let mut client = Client::new("token", Handler).unwrap();
+ ///
+ /// client.start().unwrap();
/// ```
///
/// [`Event::Resumed`]: ../model/event/enum.Event.html#variant.Resumed
/// [`Online`]: ../model/enum.OnlineStatus.html#variant.Online
/// [`set_presence`]: #method.set_presence
+ #[inline]
pub fn reset_presence(&self) {
- let mut shard = self.shard.lock();
- shard.set_presence(None, OnlineStatus::Online, false)
+ self.shard.set_presence(None, OnlineStatus::Online);
}
/// Sets the current game, defaulting to an online status of [`Online`].
@@ -271,8 +289,9 @@ impl Context {
/// use serenity::model::Game;
///
/// struct Handler;
+ ///
/// impl EventHandler for Handler {
- /// fn on_message(&self, ctx: Context, msg: Message) {
+ /// fn message(&self, ctx: Context, msg: Message) {
/// let args = msg.content.splitn(2, ' ').collect::<Vec<&str>>();
///
/// if args.len() < 2 || *unsafe { args.get_unchecked(0) } != "~setgame" {
@@ -283,17 +302,19 @@ impl Context {
/// }
/// }
///
- /// let mut client = Client::new("token", Handler); client.start().unwrap();
+ /// let mut client = Client::new("token", Handler).unwrap();
+ ///
+ /// client.start().unwrap();
/// # }
- /// #
+ ///
/// # #[cfg(not(feature = "model"))]
- /// # fn main() { }
+ /// # fn main() {}
/// ```
///
/// [`Online`]: ../model/enum.OnlineStatus.html#variant.Online
+ #[inline]
pub fn set_game(&self, game: Game) {
- let mut shard = self.shard.lock();
- shard.set_presence(Some(game), OnlineStatus::Online, false);
+ self.shard.set_presence(Some(game), OnlineStatus::Online);
}
/// Sets the current game, passing in only its name. This will automatically
@@ -316,12 +337,13 @@ impl Context {
/// struct Handler;
///
/// impl EventHandler for Handler {
- /// fn on_ready(&self, ctx: Context, _: Ready) {
+ /// fn ready(&self, ctx: Context, _: Ready) {
/// ctx.set_game_name("test");
/// }
/// }
///
- /// let mut client = Client::new("token", Handler); client.start().unwrap();
+ /// let mut client = Client::new("token", Handler).unwrap();
+ /// client.start().unwrap();
/// ```
///
/// [`Event::Ready`]: ../model/event/enum.Event.html#variant.Ready
@@ -338,8 +360,7 @@ impl Context {
url: None,
};
- let mut shard = self.shard.lock();
- shard.set_presence(Some(game), OnlineStatus::Online, false);
+ self.shard.set_presence(Some(game), OnlineStatus::Online);
}
/// Sets the current user's presence, providing all fields to be passed.
@@ -355,13 +376,15 @@ impl Context {
/// struct Handler;
///
/// impl EventHandler for Handler {
- /// fn on_ready(&self, ctx: Context, _: Ready) {
+ /// fn ready(&self, ctx: Context, _: Ready) {
/// use serenity::model::OnlineStatus;
///
- /// ctx.set_presence(None, OnlineStatus::Idle, false);
+ /// ctx.set_presence(None, OnlineStatus::Idle);
/// }
/// }
- /// let mut client = Client::new("token", Handler); client.start().unwrap();
+ /// let mut client = Client::new("token", Handler).unwrap();
+ ///
+ /// client.start().unwrap();
/// ```
///
/// Setting the current user as playing `"Heroes of the Storm"`, while being
@@ -374,23 +397,26 @@ impl Context {
/// struct Handler;
///
/// impl EventHandler for Handler {
- /// fn on_ready(&self, context: Context, _: Ready) {
+ /// fn ready(&self, context: Context, _: Ready) {
/// use serenity::model::{Game, OnlineStatus};
///
/// let game = Game::playing("Heroes of the Storm");
/// let status = OnlineStatus::DoNotDisturb;
///
- /// context.set_presence(Some(game), status, false);
+ /// context.set_presence(Some(game), status);
/// }
/// }
- /// let mut client = Client::new("token", Handler); client.start().unwrap();
+ ///
+ /// let mut client = Client::new("token", Handler).unwrap();
+ ///
+ /// client.start().unwrap();
/// ```
///
/// [`DoNotDisturb`]: ../model/enum.OnlineStatus.html#variant.DoNotDisturb
/// [`Idle`]: ../model/enum.OnlineStatus.html#variant.Idle
- pub fn set_presence(&self, game: Option<Game>, status: OnlineStatus, afk: bool) {
- let mut shard = self.shard.lock();
- shard.set_presence(game, status, afk)
+ #[inline]
+ pub fn set_presence(&self, game: Option<Game>, status: OnlineStatus) {
+ self.shard.set_presence(game, status);
}
/// Disconnects the shard from the websocket, essentially "quiting" it.
@@ -399,9 +425,8 @@ impl Context {
/// until [`Client::start`] and vice versa are called again.
///
/// [`Client::start`]: ./struct.Client.html#method.start
- pub fn quit(&self) -> Result<()> {
- let mut shard = self.shard.lock();
-
- shard.shutdown_clean()
+ #[inline]
+ pub fn quit(&self) {
+ self.shard.shutdown_clean();
}
}
diff --git a/src/client/dispatch.rs b/src/client/dispatch.rs
index 351b4fb..9545a6f 100644
--- a/src/client/dispatch.rs
+++ b/src/client/dispatch.rs
@@ -1,9 +1,10 @@
use std::sync::Arc;
use parking_lot::Mutex;
+use super::bridge::gateway::ShardClientMessage;
use super::event_handler::EventHandler;
use super::Context;
+use std::sync::mpsc::Sender;
use typemap::ShareMap;
-use gateway::Shard;
use model::event::Event;
use model::{Channel, Message};
@@ -15,8 +16,6 @@ use framework::Framework;
use model::GuildId;
#[cfg(feature = "cache")]
use std::{thread, time};
-#[cfg(feature = "framework")]
-use std::sync;
#[cfg(feature = "cache")]
use super::CACHE;
@@ -26,7 +25,7 @@ macro_rules! update {
{
#[cfg(feature="cache")]
{
- CACHE.write().unwrap().update(&mut $event)
+ CACHE.write().update(&mut $event)
}
}
};
@@ -37,44 +36,54 @@ macro_rules! now {
() => (Utc::now().time().second() * 1000)
}
-fn context(conn: Arc<Mutex<Shard>>, data: Arc<Mutex<ShareMap>>) -> Context {
- Context::new(conn, data)
+fn context(
+ data: Arc<Mutex<ShareMap>>,
+ runner_tx: Sender<ShardClientMessage>,
+ shard_id: u64,
+) -> Context {
+ Context::new(data, runner_tx, shard_id)
}
#[cfg(feature = "framework")]
-pub fn dispatch<H: EventHandler + 'static>(event: Event,
- conn: Arc<Mutex<Shard>>,
- framework: Arc<sync::Mutex<Option<Box<Framework + Send>>>>,
- data: Arc<Mutex<ShareMap>>,
- event_handler: Arc<H>) {
+pub fn dispatch<H: EventHandler + 'static>(
+ event: Event,
+ framework: Arc<Mutex<Option<Box<Framework + Send>>>>,
+ data: Arc<Mutex<ShareMap>>,
+ event_handler: Arc<H>,
+ runner_tx: Sender<ShardClientMessage>,
+ shard_id: u64,
+) {
match event {
Event::MessageCreate(event) => {
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
dispatch_message(
context.clone(),
event.message.clone(),
event_handler,
);
- if let Some(ref mut framework) = *framework.lock().unwrap() {
+ if let Some(ref mut framework) = *framework.lock() {
framework.dispatch(context, event.message);
}
},
- other => handle_event(other, conn, data, event_handler),
+ other => handle_event(other, data, event_handler, runner_tx, shard_id),
}
}
#[cfg(not(feature = "framework"))]
-pub fn dispatch<H: EventHandler + 'static>(event: Event,
- conn: Arc<Mutex<Shard>>,
- data: Arc<Mutex<ShareMap>>,
- event_handler: Arc<H>) {
+pub fn dispatch<H: EventHandler + 'static>(
+ event: Event,
+ data: Arc<Mutex<ShareMap>>,
+ event_handler: Arc<H>,
+ runner_tx: Sender<ShardClientMessage>,
+ shard_id: u64,
+) {
match event {
Event::MessageCreate(event) => {
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
dispatch_message(context, event.message, event_handler);
},
- other => handle_event(other, conn, data, event_handler),
+ other => handle_event(other, data, event_handler, runner_tx, shard_id),
}
}
@@ -89,20 +98,23 @@ fn dispatch_message<H>(
message.transform_content();
}
- event_handler.on_message(context, message);
+ event_handler.message(context, message);
}
#[allow(cyclomatic_complexity, unused_assignments, unused_mut)]
-fn handle_event<H: EventHandler + 'static>(event: Event,
- conn: Arc<Mutex<Shard>>,
- data: Arc<Mutex<ShareMap>>,
- event_handler: Arc<H>) {
+fn handle_event<H: EventHandler + 'static>(
+ event: Event,
+ data: Arc<Mutex<ShareMap>>,
+ event_handler: Arc<H>,
+ runner_tx: Sender<ShardClientMessage>,
+ shard_id: u64,
+) {
#[cfg(feature = "cache")]
let mut last_guild_create_time = now!();
#[cfg(feature = "cache")]
let wait_for_guilds = move || -> ::Result<()> {
- let unavailable_guilds = CACHE.read().unwrap().unavailable_guilds.len();
+ let unavailable_guilds = CACHE.read().unavailable_guilds.len();
while unavailable_guilds != 0 && (now!() < last_guild_create_time + 2000) {
thread::sleep(time::Duration::from_millis(500));
@@ -115,84 +127,84 @@ fn handle_event<H: EventHandler + 'static>(event: Event,
Event::ChannelCreate(mut event) => {
update!(event);
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
// This different channel_create dispatching is only due to the fact that
// each time the bot receives a dm, this event is also fired.
// So in short, only exists to reduce unnecessary clutter.
match event.channel {
Channel::Private(channel) => {
- event_handler.on_private_channel_create(context, channel);
+ event_handler.private_channel_create(context, channel);
},
Channel::Group(_) => {},
Channel::Guild(channel) => {
- event_handler.on_channel_create(context, channel);
+ event_handler.channel_create(context, channel);
},
Channel::Category(channel) => {
- event_handler.on_category_create(context, channel);
+ event_handler.category_create(context, channel);
},
}
},
Event::ChannelDelete(mut event) => {
update!(event);
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
match event.channel {
Channel::Private(_) | Channel::Group(_) => {},
Channel::Guild(channel) => {
- event_handler.on_channel_delete(context, channel);
+ event_handler.channel_delete(context, channel);
},
Channel::Category(channel) => {
- event_handler.on_category_delete(context, channel);
+ event_handler.category_delete(context, channel);
},
}
},
Event::ChannelPinsUpdate(mut event) => {
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_channel_pins_update(context, event);
+ event_handler.channel_pins_update(context, event);
},
Event::ChannelRecipientAdd(mut event) => {
update!(event);
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_channel_recipient_addition(context, event.channel_id, event.user);
+ event_handler.channel_recipient_addition(context, event.channel_id, event.user);
},
Event::ChannelRecipientRemove(mut event) => {
update!(event);
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_channel_recipient_removal(context, event.channel_id, event.user);
+ event_handler.channel_recipient_removal(context, event.channel_id, event.user);
},
Event::ChannelUpdate(mut event) => {
update!(event);
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
feature_cache! {{
- let before = CACHE.read().unwrap().channel(event.channel.id());
- event_handler.on_channel_update(context, before, event.channel);
+ let before = CACHE.read().channel(event.channel.id());
+ event_handler.channel_update(context, before, event.channel);
} else {
- event_handler.on_channel_update(context, event.channel);
+ event_handler.channel_update(context, event.channel);
}}
},
Event::GuildBanAdd(mut event) => {
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_guild_ban_addition(context, event.guild_id, event.user);
+ event_handler.guild_ban_addition(context, event.guild_id, event.user);
},
Event::GuildBanRemove(mut event) => {
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_guild_ban_removal(context, event.guild_id, event.user);
+ event_handler.guild_ban_removal(context, event.guild_id, event.user);
},
Event::GuildCreate(mut event) => {
#[cfg(feature = "cache")]
let _is_new = {
- let cache = CACHE.read().unwrap();
+ let cache = CACHE.read();
!cache.unavailable_guilds.contains(&event.guild.id)
};
@@ -203,10 +215,10 @@ fn handle_event<H: EventHandler + 'static>(event: Event,
{
last_guild_create_time = now!();
- let cache = CACHE.read().unwrap();
+ let cache = CACHE.read();
if cache.unavailable_guilds.is_empty() {
- let context = context(Arc::clone(&conn), Arc::clone(&data));
+ let context = context(Arc::clone(&data), runner_tx.clone(), shard_id);
let guild_amount = cache
.guilds
@@ -214,179 +226,177 @@ fn handle_event<H: EventHandler + 'static>(event: Event,
.map(|(&id, _)| id)
.collect::<Vec<GuildId>>();
- event_handler.on_cached(context, guild_amount);
+ event_handler.cached(context, guild_amount);
}
}
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
feature_cache! {{
- event_handler.on_guild_create(context, event.guild, _is_new);
+ event_handler.guild_create(context, event.guild, _is_new);
} else {
- event_handler.on_guild_create(context, event.guild);
+ event_handler.guild_create(context, event.guild);
}}
},
Event::GuildDelete(mut event) => {
let _full = update!(event);
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
feature_cache! {{
- event_handler.on_guild_delete(context, event.guild, _full);
+ event_handler.guild_delete(context, event.guild, _full);
} else {
- event_handler.on_guild_delete(context, event.guild);
+ event_handler.guild_delete(context, event.guild);
}}
},
Event::GuildEmojisUpdate(mut event) => {
update!(event);
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_guild_emojis_update(context, event.guild_id, event.emojis);
+ event_handler.guild_emojis_update(context, event.guild_id, event.emojis);
},
Event::GuildIntegrationsUpdate(mut event) => {
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_guild_integrations_update(context, event.guild_id);
+ event_handler.guild_integrations_update(context, event.guild_id);
},
Event::GuildMemberAdd(mut event) => {
update!(event);
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_guild_member_addition(context, event.guild_id, event.member);
+ event_handler.guild_member_addition(context, event.guild_id, event.member);
},
Event::GuildMemberRemove(mut event) => {
let _member = update!(event);
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
feature_cache! {{
- event_handler.on_guild_member_removal(context, event.guild_id, event.user, _member);
+ event_handler.guild_member_removal(context, event.guild_id, event.user, _member);
} else {
- event_handler.on_guild_member_removal(context, event.guild_id, event.user);
+ event_handler.guild_member_removal(context, event.guild_id, event.user);
}}
},
Event::GuildMemberUpdate(mut event) => {
let _before = update!(event);
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
feature_cache! {{
// This is safe to unwrap, as the update would have created
// the member if it did not exist. So, there is be _no_ way
// that this could fail under any circumstance.
let after = CACHE.read()
- .unwrap()
.member(event.guild_id, event.user.id)
.unwrap()
.clone();
- event_handler.on_guild_member_update(context, _before, after);
+ event_handler.guild_member_update(context, _before, after);
} else {
- event_handler.on_guild_member_update(context, event);
+ event_handler.guild_member_update(context, event);
}}
},
Event::GuildMembersChunk(mut event) => {
update!(event);
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_guild_members_chunk(context, event.guild_id, event.members);
+ event_handler.guild_members_chunk(context, event.guild_id, event.members);
},
Event::GuildRoleCreate(mut event) => {
update!(event);
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_guild_role_create(context, event.guild_id, event.role);
+ event_handler.guild_role_create(context, event.guild_id, event.role);
},
Event::GuildRoleDelete(mut event) => {
let _role = update!(event);
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
feature_cache! {{
- event_handler.on_guild_role_delete(context, event.guild_id, event.role_id, _role);
+ event_handler.guild_role_delete(context, event.guild_id, event.role_id, _role);
} else {
- event_handler.on_guild_role_delete(context, event.guild_id, event.role_id);
+ event_handler.guild_role_delete(context, event.guild_id, event.role_id);
}}
},
Event::GuildRoleUpdate(mut event) => {
let _before = update!(event);
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
feature_cache! {{
- event_handler.on_guild_role_update(context, event.guild_id, _before, event.role);
+ event_handler.guild_role_update(context, event.guild_id, _before, event.role);
} else {
- event_handler.on_guild_role_update(context, event.guild_id, event.role);
+ event_handler.guild_role_update(context, event.guild_id, event.role);
}}
},
Event::GuildUnavailable(mut event) => {
update!(event);
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_guild_unavailable(context, event.guild_id);
+ event_handler.guild_unavailable(context, event.guild_id);
},
Event::GuildUpdate(mut event) => {
update!(event);
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
feature_cache! {{
let before = CACHE.read()
- .unwrap()
.guilds
.get(&event.guild.id)
.cloned();
- event_handler.on_guild_update(context, before, event.guild);
+ event_handler.guild_update(context, before, event.guild);
} else {
- event_handler.on_guild_update(context, event.guild);
+ event_handler.guild_update(context, event.guild);
}}
},
// Already handled by the framework check macro
Event::MessageCreate(_) => {},
Event::MessageDeleteBulk(mut event) => {
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_message_delete_bulk(context, event.channel_id, event.ids);
+ event_handler.message_delete_bulk(context, event.channel_id, event.ids);
},
Event::MessageDelete(mut event) => {
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_message_delete(context, event.channel_id, event.message_id);
+ event_handler.message_delete(context, event.channel_id, event.message_id);
},
Event::MessageUpdate(mut event) => {
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_message_update(context, event);
+ event_handler.message_update(context, event);
},
Event::PresencesReplace(mut event) => {
update!(event);
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_presence_replace(context, event.presences);
+ event_handler.presence_replace(context, event.presences);
},
Event::PresenceUpdate(mut event) => {
update!(event);
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_presence_update(context, event);
+ event_handler.presence_update(context, event);
},
Event::ReactionAdd(mut event) => {
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_reaction_add(context, event.reaction);
+ event_handler.reaction_add(context, event.reaction);
},
Event::ReactionRemove(mut event) => {
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_reaction_remove(context, event.reaction);
+ event_handler.reaction_remove(context, event.reaction);
},
Event::ReactionRemoveAll(mut event) => {
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_reaction_remove_all(context, event.channel_id, event.message_id);
+ event_handler.reaction_remove_all(context, event.channel_id, event.message_id);
},
Event::Ready(mut event) => {
update!(event);
@@ -397,58 +407,58 @@ fn handle_event<H: EventHandler + 'static>(event: Event,
let _ = wait_for_guilds()
.map(move |_| {
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_ready(context, event.ready);
+ event_handler.ready(context, event.ready);
});
} else {
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_ready(context, event.ready);
+ event_handler.ready(context, event.ready);
}
}
},
Event::Resumed(mut event) => {
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_resume(context, event);
+ event_handler.resume(context, event);
},
Event::TypingStart(mut event) => {
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_typing_start(context, event);
+ event_handler.typing_start(context, event);
},
Event::Unknown(mut event) => {
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_unknown(context, event.kind, event.value);
+ event_handler.unknown(context, event.kind, event.value);
},
Event::UserUpdate(mut event) => {
let _before = update!(event);
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
feature_cache! {{
- event_handler.on_user_update(context, _before.unwrap(), event.current_user);
+ event_handler.user_update(context, _before.unwrap(), event.current_user);
} else {
- event_handler.on_user_update(context, event.current_user);
+ event_handler.user_update(context, event.current_user);
}}
},
Event::VoiceServerUpdate(mut event) => {
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_voice_server_update(context, event);
+ event_handler.voice_server_update(context, event);
},
Event::VoiceStateUpdate(mut event) => {
update!(event);
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_voice_state_update(context, event.guild_id, event.voice_state);
+ event_handler.voice_state_update(context, event.guild_id, event.voice_state);
},
Event::WebhookUpdate(mut event) => {
- let context = context(conn, data);
+ let context = context(data, runner_tx, shard_id);
- event_handler.on_webhook_update(context, event.guild_id, event.channel_id);
+ event_handler.webhook_update(context, event.guild_id, event.channel_id);
},
}
}
diff --git a/src/client/event_handler.rs b/src/client/event_handler.rs
index 6e3c78e..5c2f131 100644
--- a/src/client/event_handler.rs
+++ b/src/client/event_handler.rs
@@ -1,3 +1,4 @@
+use parking_lot::RwLock;
use serde_json::Value;
use std::collections::HashMap;
use std::sync::Arc;
@@ -5,77 +6,75 @@ use super::context::Context;
use model::event::*;
use model::*;
-use std::sync::RwLock;
-
pub trait EventHandler {
#[cfg(feature = "cache")]
- fn on_cached(&self, _: Context, _: Vec<GuildId>) {}
- fn on_channel_create(&self, _: Context, _: Arc<RwLock<GuildChannel>>) {}
- fn on_category_create(&self, _: Context, _: Arc<RwLock<ChannelCategory>>) {}
- fn on_category_delete(&self, _: Context, _: Arc<RwLock<ChannelCategory>>) {}
- fn on_private_channel_create(&self, _: Context, _: Arc<RwLock<PrivateChannel>>) {}
- fn on_channel_delete(&self, _: Context, _: Arc<RwLock<GuildChannel>>) {}
- fn on_channel_pins_update(&self, _: Context, _: ChannelPinsUpdateEvent) {}
- fn on_channel_recipient_addition(&self, _: Context, _: ChannelId, _: User) {}
- fn on_channel_recipient_removal(&self, _: Context, _: ChannelId, _: User) {}
+ fn cached(&self, _: Context, _: Vec<GuildId>) {}
+ fn channel_create(&self, _: Context, _: Arc<RwLock<GuildChannel>>) {}
+ fn category_create(&self, _: Context, _: Arc<RwLock<ChannelCategory>>) {}
+ fn category_delete(&self, _: Context, _: Arc<RwLock<ChannelCategory>>) {}
+ fn private_channel_create(&self, _: Context, _: Arc<RwLock<PrivateChannel>>) {}
+ fn channel_delete(&self, _: Context, _: Arc<RwLock<GuildChannel>>) {}
+ fn channel_pins_update(&self, _: Context, _: ChannelPinsUpdateEvent) {}
+ fn channel_recipient_addition(&self, _: Context, _: ChannelId, _: User) {}
+ fn channel_recipient_removal(&self, _: Context, _: ChannelId, _: User) {}
#[cfg(feature = "cache")]
- fn on_channel_update(&self, _: Context, _: Option<Channel>, _: Channel) {}
+ fn channel_update(&self, _: Context, _: Option<Channel>, _: Channel) {}
#[cfg(not(feature = "cache"))]
- fn on_channel_update(&self, _: Context, _: Channel) {}
- fn on_guild_ban_addition(&self, _: Context, _: GuildId, _: User) {}
- fn on_guild_ban_removal(&self, _: Context, _: GuildId, _: User) {}
+ fn channel_update(&self, _: Context, _: Channel) {}
+ fn guild_ban_addition(&self, _: Context, _: GuildId, _: User) {}
+ fn guild_ban_removal(&self, _: Context, _: GuildId, _: User) {}
#[cfg(feature = "cache")]
- fn on_guild_create(&self, _: Context, _: Guild, _: bool) {}
+ fn guild_create(&self, _: Context, _: Guild, _: bool) {}
#[cfg(not(feature = "cache"))]
- fn on_guild_create(&self, _: Context, _: Guild) {}
+ fn guild_create(&self, _: Context, _: Guild) {}
#[cfg(feature = "cache")]
- fn on_guild_delete(&self, _: Context, _: PartialGuild, _: Option<Arc<RwLock<Guild>>>) {}
+ fn guild_delete(&self, _: Context, _: PartialGuild, _: Option<Arc<RwLock<Guild>>>) {}
#[cfg(not(feature = "cache"))]
- fn on_guild_delete(&self, _: Context, _: PartialGuild) {}
- fn on_guild_emojis_update(&self, _: Context, _: GuildId, _: HashMap<EmojiId, Emoji>) {}
- fn on_guild_integrations_update(&self, _: Context, _: GuildId) {}
- fn on_guild_member_addition(&self, _: Context, _: GuildId, _: Member) {}
+ fn guild_delete(&self, _: Context, _: PartialGuild) {}
+ fn guild_emojis_update(&self, _: Context, _: GuildId, _: HashMap<EmojiId, Emoji>) {}
+ fn guild_integrations_update(&self, _: Context, _: GuildId) {}
+ fn guild_member_addition(&self, _: Context, _: GuildId, _: Member) {}
#[cfg(feature = "cache")]
- fn on_guild_member_removal(&self, _: Context, _: GuildId, _: User, _: Option<Member>) {}
+ fn guild_member_removal(&self, _: Context, _: GuildId, _: User, _: Option<Member>) {}
#[cfg(not(feature = "cache"))]
- fn on_guild_member_removal(&self, _: Context, _: GuildId, _: User) {}
+ fn guild_member_removal(&self, _: Context, _: GuildId, _: User) {}
#[cfg(feature = "cache")]
- fn on_guild_member_update(&self, _: Context, _: Option<Member>, _: Member) {}
+ fn guild_member_update(&self, _: Context, _: Option<Member>, _: Member) {}
#[cfg(not(feature = "cache"))]
- fn on_guild_member_update(&self, _: Context, _: GuildMemberUpdateEvent) {}
- fn on_guild_members_chunk(&self, _: Context, _: GuildId, _: HashMap<UserId, Member>) {}
- fn on_guild_role_create(&self, _: Context, _: GuildId, _: Role) {}
+ fn guild_member_update(&self, _: Context, _: GuildMemberUpdateEvent) {}
+ fn guild_members_chunk(&self, _: Context, _: GuildId, _: HashMap<UserId, Member>) {}
+ fn guild_role_create(&self, _: Context, _: GuildId, _: Role) {}
#[cfg(feature = "cache")]
- fn on_guild_role_delete(&self, _: Context, _: GuildId, _: RoleId, _: Option<Role>) {}
+ fn guild_role_delete(&self, _: Context, _: GuildId, _: RoleId, _: Option<Role>) {}
#[cfg(not(feature = "cache"))]
- fn on_guild_role_delete(&self, _: Context, _: GuildId, _: RoleId) {}
+ fn guild_role_delete(&self, _: Context, _: GuildId, _: RoleId) {}
#[cfg(feature = "cache")]
- fn on_guild_role_update(&self, _: Context, _: GuildId, _: Option<Role>, _: Role) {}
+ fn guild_role_update(&self, _: Context, _: GuildId, _: Option<Role>, _: Role) {}
#[cfg(not(feature = "cache"))]
- fn on_guild_role_update(&self, _: Context, _: GuildId, _: Role) {}
- fn on_guild_unavailable(&self, _: Context, _: GuildId) {}
+ fn guild_role_update(&self, _: Context, _: GuildId, _: Role) {}
+ fn guild_unavailable(&self, _: Context, _: GuildId) {}
#[cfg(feature = "cache")]
- fn on_guild_update(&self, _: Context, _: Option<Arc<RwLock<Guild>>>, _: PartialGuild) {}
+ fn guild_update(&self, _: Context, _: Option<Arc<RwLock<Guild>>>, _: PartialGuild) {}
#[cfg(not(feature = "cache"))]
- fn on_guild_update(&self, _: Context, _: PartialGuild) {}
- fn on_message(&self, _: Context, _: Message) {}
- fn on_message_delete(&self, _: Context, _: ChannelId, _: MessageId) {}
- fn on_message_delete_bulk(&self, _: Context, _: ChannelId, _: Vec<MessageId>) {}
- fn on_reaction_add(&self, _: Context, _: Reaction) {}
- fn on_reaction_remove(&self, _: Context, _: Reaction) {}
- fn on_reaction_remove_all(&self, _: Context, _: ChannelId, _: MessageId) {}
- fn on_message_update(&self, _: Context, _: MessageUpdateEvent) {}
- fn on_presence_replace(&self, _: Context, _: Vec<Presence>) {}
- fn on_presence_update(&self, _: Context, _: PresenceUpdateEvent) {}
- fn on_ready(&self, _: Context, _: Ready) {}
- fn on_resume(&self, _: Context, _: ResumedEvent) {}
- fn on_typing_start(&self, _: Context, _: TypingStartEvent) {}
- fn on_unknown(&self, _: Context, _: String, _: Value) {}
+ fn guild_update(&self, _: Context, _: PartialGuild) {}
+ fn message(&self, _: Context, _: Message) {}
+ fn message_delete(&self, _: Context, _: ChannelId, _: MessageId) {}
+ fn message_delete_bulk(&self, _: Context, _: ChannelId, _: Vec<MessageId>) {}
+ fn reaction_add(&self, _: Context, _: Reaction) {}
+ fn reaction_remove(&self, _: Context, _: Reaction) {}
+ fn reaction_remove_all(&self, _: Context, _: ChannelId, _: MessageId) {}
+ fn message_update(&self, _: Context, _: MessageUpdateEvent) {}
+ fn presence_replace(&self, _: Context, _: Vec<Presence>) {}
+ fn presence_update(&self, _: Context, _: PresenceUpdateEvent) {}
+ fn ready(&self, _: Context, _: Ready) {}
+ fn resume(&self, _: Context, _: ResumedEvent) {}
+ fn typing_start(&self, _: Context, _: TypingStartEvent) {}
+ fn unknown(&self, _: Context, _: String, _: Value) {}
#[cfg(feature = "cache")]
- fn on_user_update(&self, _: Context, _: CurrentUser, _: CurrentUser) {}
+ fn user_update(&self, _: Context, _: CurrentUser, _: CurrentUser) {}
#[cfg(not(feature = "cache"))]
- fn on_user_update(&self, _: Context, _: CurrentUser) {}
- fn on_voice_server_update(&self, _: Context, _: VoiceServerUpdateEvent) {}
- fn on_voice_state_update(&self, _: Context, _: Option<GuildId>, _: VoiceState) {}
- fn on_webhook_update(&self, _: Context, _: GuildId, _: ChannelId) {}
+ fn user_update(&self, _: Context, _: CurrentUser) {}
+ fn voice_server_update(&self, _: Context, _: VoiceServerUpdateEvent) {}
+ fn voice_state_update(&self, _: Context, _: Option<GuildId>, _: VoiceState) {}
+ fn webhook_update(&self, _: Context, _: GuildId, _: ChannelId) {}
}
diff --git a/src/client/mod.rs b/src/client/mod.rs
index 6711287..a227920 100644
--- a/src/client/mod.rs
+++ b/src/client/mod.rs
@@ -37,13 +37,11 @@ pub use http as rest;
#[cfg(feature = "cache")]
pub use CACHE;
-use self::bridge::gateway::{ShardId, ShardManager, ShardRunnerInfo};
+use self::bridge::gateway::{ShardManager, ShardManagerMonitor};
use self::dispatch::dispatch;
-use std::sync::{self, Arc};
+use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT};
use parking_lot::Mutex;
-use std::collections::HashMap;
-use std::mem;
use threadpool::ThreadPool;
use typemap::ShareMap;
use http;
@@ -103,8 +101,7 @@ impl CloseHandle {
/// [`on_message`]: #method.on_message
/// [`Event::MessageCreate`]: ../model/event/enum.Event.html#variant.MessageCreate
/// [sharding docs]: gateway/index.html#sharding
-#[derive(Clone)]
-pub struct Client<H: EventHandler + Send + Sync + 'static> {
+pub struct Client {
/// A ShareMap which requires types to be Send + Sync. This is a map that
/// can be safely shared across contexts.
///
@@ -191,8 +188,7 @@ pub struct Client<H: EventHandler + Send + Sync + 'static> {
///
/// [`Event::Ready`]: ../model/event/enum.Event.html#variant.Ready
/// [`on_ready`]: #method.on_ready
- event_handler: Arc<H>,
- #[cfg(feature = "framework")] framework: Arc<sync::Mutex<Option<Box<Framework + Send>>>>,
+ #[cfg(feature = "framework")] framework: Arc<Mutex<Option<Box<Framework + Send>>>>,
/// A HashMap of all shards instantiated by the Client.
///
/// The key is the shard ID and the value is the shard itself.
@@ -222,14 +218,14 @@ pub struct Client<H: EventHandler + Send + Sync + 'static> {
///
/// impl EventHandler for Handler { }
///
- /// let mut client = Client::new(&env::var("DISCORD_TOKEN")?, Handler);
+ /// let mut client = Client::new(&env::var("DISCORD_TOKEN")?, Handler)?;
///
- /// let shard_runners = client.shard_runners.clone();
+ /// let shard_manager = client.shard_manager.clone();
///
/// thread::spawn(move || {
/// loop {
/// println!("Shard count instantiated: {}",
- /// shard_runners.lock().len());
+ /// shard_manager.lock().shards_instantiated().len());
///
/// thread::sleep(Duration::from_millis(5000));
/// }
@@ -244,16 +240,26 @@ pub struct Client<H: EventHandler + Send + Sync + 'static> {
///
/// [`Client::start_shard`]: #method.start_shard
/// [`Client::start_shards`]: #method.start_shards
- pub shard_runners: Arc<Mutex<HashMap<ShardId, ShardRunnerInfo>>>,
+ pub shard_manager: Arc<Mutex<ShardManager>>,
+ shard_manager_worker: ShardManagerMonitor,
/// The threadpool shared by all shards.
///
/// Defaults to 5 threads, which should suffice small bots. Consider
/// increasing this number as your bot grows.
pub threadpool: ThreadPool,
- token: Arc<sync::Mutex<String>>,
+ /// The token in use by the client.
+ pub token: Arc<Mutex<String>>,
+ /// URI that the client's shards will use to connect to the gateway.
+ ///
+ /// This is likely not important for production usage and is, at best, used
+ /// for debugging.
+ ///
+ /// This is wrapped in an `Arc<Mutex<T>>` so all shards will have an updated
+ /// value available.
+ pub ws_uri: Arc<Mutex<String>>,
}
-impl<H: EventHandler + Send + Sync + 'static> Client<H> {
+impl Client {
/// Creates a Client for a bot user.
///
/// Discord has a requirement of prefixing bot tokens with `"Bot "`, which
@@ -275,7 +281,7 @@ impl<H: EventHandler + Send + Sync + 'static> Client<H> {
/// use std::env;
///
/// let token = env::var("DISCORD_TOKEN")?;
- /// let client = Client::new(&token, Handler);
+ /// let client = Client::new(&token, Handler)?;
/// # Ok(())
/// # }
/// #
@@ -283,7 +289,8 @@ impl<H: EventHandler + Send + Sync + 'static> Client<H> {
/// # try_main().unwrap();
/// # }
/// ```
- pub fn new(token: &str, handler: H) -> Self {
+ pub fn new<H>(token: &str, handler: H) -> Result<Self>
+ where H: EventHandler + Send + Sync + 'static {
let token = if token.starts_with("Bot ") {
token.to_string()
} else {
@@ -291,29 +298,59 @@ impl<H: EventHandler + Send + Sync + 'static> Client<H> {
};
http::set_token(&token);
- let locked = Arc::new(sync::Mutex::new(token));
+ let locked = Arc::new(Mutex::new(token));
let name = "serenity client".to_owned();
let threadpool = ThreadPool::with_name(name, 5);
+ let url = Arc::new(Mutex::new(http::get_gateway()?.url));
+ let data = Arc::new(Mutex::new(ShareMap::custom()));
+ let event_handler = Arc::new(handler);
+
+ Ok(feature_framework! {{
+ let framework = Arc::new(Mutex::new(None));
+
+ let (shard_manager, shard_manager_worker) = ShardManager::new(
+ 0,
+ 0,
+ 0,
+ Arc::clone(&url),
+ Arc::clone(&locked),
+ Arc::clone(&data),
+ Arc::clone(&event_handler),
+ Arc::clone(&framework),
+ threadpool.clone(),
+ );
- feature_framework! {{
Client {
- data: Arc::new(Mutex::new(ShareMap::custom())),
- event_handler: Arc::new(handler),
- framework: Arc::new(sync::Mutex::new(None)),
- shard_runners: Arc::new(Mutex::new(HashMap::new())),
- threadpool,
token: locked,
+ ws_uri: url,
+ framework,
+ data,
+ shard_manager,
+ shard_manager_worker,
+ threadpool,
}
} else {
+ let (shard_manager, shard_manager_worker) = ShardManager::new(
+ 0,
+ 0,
+ 0,
+ Arc::clone(&url),
+ locked.clone(),
+ data.clone(),
+ Arc::clone(&event_handler),
+ threadpool.clone(),
+ );
+
Client {
- data: Arc::new(Mutex::new(ShareMap::custom())),
- event_handler: Arc::new(handler),
- shard_runners: Arc::new(Mutex::new(HashMap::new())),
- threadpool,
token: locked,
+ ws_uri: url,
+ data,
+ shard_manager,
+ shard_manager_worker,
+ threadpool,
}
- }}
+ }})
}
/// Sets a framework to be used with the client. All message events will be
@@ -340,7 +377,7 @@ impl<H: EventHandler + Send + Sync + 'static> Client<H> {
/// use serenity::Client;
/// use std::env;
///
- /// let mut client = Client::new(&env::var("DISCORD_TOKEN")?, Handler);
+ /// let mut client = Client::new(&env::var("DISCORD_TOKEN")?, Handler)?;
/// client.with_framework(StandardFramework::new()
/// .configure(|c| c.prefix("~"))
/// .command("ping", |c| c.exec_str("Pong!")));
@@ -392,12 +429,11 @@ impl<H: EventHandler + Send + Sync + 'static> Client<H> {
///
/// impl EventHandler for Handler {}
///
- ///
/// # fn try_main() -> Result<(), Box<Error>> {
/// use serenity::Client;
/// use std::env;
///
- /// let mut client = Client::new(&env::var("DISCORD_TOKEN")?, Handler);
+ /// let mut client = Client::new(&token, Handler).unwrap();
/// client.with_framework(MyFramework { commands: {
/// let mut map = HashMap::new();
/// map.insert("ping".to_string(), Box::new(|msg, _| msg.channel_id.say("pong!")));
@@ -417,7 +453,7 @@ impl<H: EventHandler + Send + Sync + 'static> Client<H> {
/// [framework docs]: ../framework/index.html
#[cfg(feature = "framework")]
pub fn with_framework<F: Framework + Send + 'static>(&mut self, f: F) {
- self.framework = Arc::new(sync::Mutex::new(Some(Box::new(f))));
+ *self.framework.lock() = Some(Box::new(f));
}
/// Establish the connection and start listening for events.
@@ -447,7 +483,8 @@ impl<H: EventHandler + Send + Sync + 'static> Client<H> {
/// use serenity::client::Client;
/// use std::env;
///
- /// let mut client = Client::new(&env::var("DISCORD_TOKEN")?, Handler);
+ /// let token = env::var("DISCORD_TOKEN")?;
+ /// let mut client = Client::new(&token, Handler).unwrap();
///
/// if let Err(why) = client.start() {
/// println!("Err with client: {:?}", why);
@@ -462,7 +499,7 @@ impl<H: EventHandler + Send + Sync + 'static> Client<H> {
///
/// [gateway docs]: gateway/index.html#sharding
pub fn start(&mut self) -> Result<()> {
- self.start_connection([0, 0, 1], http::get_gateway()?.url)
+ self.start_connection([0, 0, 1])
}
/// Establish the connection(s) and start listening for events.
@@ -492,7 +529,8 @@ impl<H: EventHandler + Send + Sync + 'static> Client<H> {
/// use serenity::client::Client;
/// use std::env;
///
- /// let mut client = Client::new(&env::var("DISCORD_TOKEN")?, Handler);
+ /// let token = env::var("DISCORD_TOKEN")?;
+ /// let mut client = Client::new(&token, Handler).unwrap();
///
/// if let Err(why) = client.start_autosharded() {
/// println!("Err with client: {:?}", why);
@@ -513,15 +551,13 @@ impl<H: EventHandler + Send + Sync + 'static> Client<H> {
/// [`ClientError::Shutdown`]: enum.ClientError.html#variant.Shutdown
/// [gateway docs]: gateway/index.html#sharding
pub fn start_autosharded(&mut self) -> Result<()> {
- let mut res = http::get_bot_gateway()?;
-
- let x = res.shards as u64 - 1;
- let y = res.shards as u64;
- let url = mem::replace(&mut res.url, String::default());
+ let (x, y) = {
+ let res = http::get_bot_gateway()?;
- drop(res);
+ (res.shards as u64 - 1, res.shards as u64)
+ };
- self.start_connection([0, x, y], url)
+ self.start_connection([0, x, y])
}
/// Establish a sharded connection and start listening for events.
@@ -551,7 +587,8 @@ impl<H: EventHandler + Send + Sync + 'static> Client<H> {
/// use serenity::client::Client;
/// use std::env;
///
- /// let mut client = Client::new(&env::var("DISCORD_TOKEN")?, Handler);
+ /// let token = env::var("DISCORD_TOKEN")?;
+ /// let mut client = Client::new(&token, Handler).unwrap();
///
/// if let Err(why) = client.start_shard(3, 5) {
/// println!("Err with client: {:?}", why);
@@ -578,7 +615,7 @@ impl<H: EventHandler + Send + Sync + 'static> Client<H> {
/// use serenity::client::Client;
/// use std::env;
///
- /// let mut client = Client::new(&env::var("DISCORD_TOKEN")?, Handler);
+ /// let mut client = Client::new(&env::var("DISCORD_TOKEN")?, Handler)?;
///
/// if let Err(why) = client.start_shard(0, 1) {
/// println!("Err with client: {:?}", why);
@@ -601,7 +638,7 @@ impl<H: EventHandler + Send + Sync + 'static> Client<H> {
/// [`start_autosharded`]: #method.start_autosharded
/// [gateway docs]: gateway/index.html#sharding
pub fn start_shard(&mut self, shard: u64, shards: u64) -> Result<()> {
- self.start_connection([shard, shard, shards], http::get_gateway()?.url)
+ self.start_connection([shard, shard, shards])
}
/// Establish sharded connections and start listening for events.
@@ -631,7 +668,8 @@ impl<H: EventHandler + Send + Sync + 'static> Client<H> {
/// use serenity::client::Client;
/// use std::env;
///
- /// let mut client = Client::new(&env::var("DISCORD_TOKEN")?, Handler);
+ /// let token = env::var("DISCORD_TOKEN")?;
+ /// let mut client = Client::new(&token, Handler).unwrap();
///
/// if let Err(why) = client.start_shards(8) {
/// println!("Err with client: {:?}", why);
@@ -654,10 +692,7 @@ impl<H: EventHandler + Send + Sync + 'static> Client<H> {
/// [`start_shard_range`]: #method.start_shard_range
/// [Gateway docs]: gateway/index.html#sharding
pub fn start_shards(&mut self, total_shards: u64) -> Result<()> {
- self.start_connection(
- [0, total_shards - 1, total_shards],
- http::get_gateway()?.url,
- )
+ self.start_connection([0, total_shards - 1, total_shards])
}
/// Establish a range of sharded connections and start listening for events.
@@ -702,7 +737,8 @@ impl<H: EventHandler + Send + Sync + 'static> Client<H> {
/// use serenity::client::Client;
/// use std::env;
///
- /// let mut client = Client::new(&env::var("DISCORD_TOKEN")?, Handler);
+ /// let token = env::var("DISCORD_TOKEN")?;
+ /// let mut client = Client::new(&token, Handler).unwrap();
///
/// if let Err(why) = client.start_shard_range([4, 7], 10) {
/// println!("Err with client: {:?}", why);
@@ -726,7 +762,7 @@ impl<H: EventHandler + Send + Sync + 'static> Client<H> {
/// [`start_shards`]: #method.start_shards
/// [Gateway docs]: gateway/index.html#sharding
pub fn start_shard_range(&mut self, range: [u64; 2], total_shards: u64) -> Result<()> {
- self.start_connection([range[0], range[1], total_shards], http::get_gateway()?.url)
+ self.start_connection([range[0], range[1], total_shards])
}
/// Returns a thread-safe handle for closing shards.
@@ -745,7 +781,7 @@ impl<H: EventHandler + Send + Sync + 'static> Client<H> {
// an error.
//
// [`ClientError::Shutdown`]: enum.ClientError.html#variant.Shutdown
- fn start_connection(&mut self, shard_data: [u64; 3], url: String) -> Result<()> {
+ fn start_connection(&mut self, shard_data: [u64; 3]) -> Result<()> {
HANDLE_STILL.store(true, Ordering::Relaxed);
// Update the framework's current user if the feature is enabled.
@@ -755,44 +791,42 @@ impl<H: EventHandler + Send + Sync + 'static> Client<H> {
{
let user = http::get_current_user()?;
- if let Some(ref mut framework) = *self.framework.lock().unwrap() {
+ if let Some(ref mut framework) = *self.framework.lock() {
framework.update_current_user(user.id, user.bot);
}
}
- let gateway_url = Arc::new(sync::Mutex::new(url));
+ {
+ let mut manager = self.shard_manager.lock();
+
+ let init = shard_data[1] - shard_data[0] + 1;
- let mut manager = ShardManager::new(
- shard_data[0],
- shard_data[1] - shard_data[0] + 1,
- shard_data[2],
- Arc::clone(&gateway_url),
- Arc::clone(&self.token),
- Arc::clone(&self.data),
- Arc::clone(&self.event_handler),
- #[cfg(feature = "framework")]
- Arc::clone(&self.framework),
- self.threadpool.clone(),
- );
+ manager.set_shards(shard_data[0], init, shard_data[2]);
- self.shard_runners = Arc::clone(&manager.runners);
+ debug!(
+ "Initializing shard info: {} - {}/{}",
+ shard_data[0],
+ init,
+ shard_data[2],
+ );
- if let Err(why) = manager.initialize() {
- error!("Failed to boot a shard: {:?}", why);
- info!("Shutting down all shards");
+ if let Err(why) = manager.initialize() {
+ error!("Failed to boot a shard: {:?}", why);
+ info!("Shutting down all shards");
- manager.shutdown_all();
+ manager.shutdown_all();
- return Err(Error::Client(ClientError::ShardBootFailure));
+ return Err(Error::Client(ClientError::ShardBootFailure));
+ }
}
- manager.run();
+ self.shard_manager_worker.run();
Err(Error::Client(ClientError::Shutdown))
}
}
-impl<H: EventHandler + Send + Sync + 'static> Drop for Client<H> {
+impl Drop for Client {
fn drop(&mut self) { self.close_handle().close(); }
}
diff --git a/src/framework/standard/args.rs b/src/framework/standard/args.rs
index 7fb82e4..fab27a5 100644
--- a/src/framework/standard/args.rs
+++ b/src/framework/standard/args.rs
@@ -64,7 +64,12 @@ pub struct Args {
}
impl Args {
- pub fn new(message: &str, delimiter: &str) -> Self {
+ pub fn new(message: &str, possible_delimiters: Vec<String>) -> Self {
+ let delimiter = possible_delimiters
+ .iter()
+ .find(|&d| message.contains(d))
+ .map_or(possible_delimiters[0].as_str(), |s| s.as_str());
+
let split = if message.trim().is_empty() {
Vec::new()
} else {
@@ -78,14 +83,14 @@ impl Args {
}
/// Removes the first element, parses it to a specific type if necessary, returns.
- ///
+ ///
/// # Examples
- ///
+ ///
/// ```rust
/// use serenity::framework::standard::Args;
- ///
- /// let mut args = Args::new("42 69", " ");
- ///
+ ///
+ /// let mut args = Args::new("42 69", vec![" ".to_owned()]);
+ ///
/// assert_eq!(args.single::<i32>().unwrap(), 42);
/// assert_eq!(args, ["69"]);
/// ```
@@ -107,18 +112,18 @@ impl Args {
///
/// [`single`]: #method.single
/// [`FromStrZc`]: trait.FromStrZc.html
- pub fn single_zc<'a, T: FromStrZc<'a> + 'a>(&'a mut self) -> Result<T, T::Err>
+ pub fn single_zc<'a, T: FromStrZc<'a> + 'a>(&'a mut self) -> Result<T, T::Err>
where T::Err: StdError {
-
+
// This is a hack as to mitigate some nasty lifetime errors.
//
// (Culprit `Vec::remove`s return type)
fn get_and_remove(b: &mut Vec<String>) -> Option<&str> {
struct GetThenRemove<'a>(&'a mut Vec<String>);
-
+
impl<'a> Drop for GetThenRemove<'a> {
fn drop(&mut self) {
- if !self.0.is_empty() {
+ if !self.0.is_empty() {
self.0.remove(0);
}
}
@@ -134,16 +139,16 @@ impl Args {
/// Like [`single`], but doesn't remove the element.
///
/// # Examples
- ///
+ ///
/// ```rust
/// use serenity::framework::standard::Args;
- ///
- /// let args = Args::new("42 69", " ");
- ///
+ ///
+ /// let args = Args::new("42 69", vec![" ".to_owned()]);
+ ///
/// assert_eq!(args.single_n::<i32>().unwrap(), 42);
/// assert_eq!(args, ["42", "69"]);
/// ```
- ///
+ ///
/// [`single`]: #method.single
pub fn single_n<T: FromStr>(&self) -> Result<T, T::Err>
where T::Err: StdError {
@@ -158,14 +163,14 @@ impl Args {
}
/// Skips if there's a first element, but also returns it.
- ///
+ ///
/// # Examples
- ///
+ ///
/// ```rust
/// use serenity::framework::standard::Args;
- ///
- /// let mut args = Args::new("42 69", " ");
- ///
+ ///
+ /// let mut args = Args::new("42 69", vec![" ".to_owned()]);
+ ///
/// assert_eq!(args.skip().unwrap(), "42");
/// assert_eq!(args, ["69"]);
/// ```
@@ -176,13 +181,13 @@ impl Args {
/// # Examples
/// ```rust
/// use serenity::framework::standard::Args;
- ///
- /// let mut args = Args::new("42 69 88 99", " ");
- ///
+ ///
+ /// let mut args = Args::new("42 69 88 99", vec![" ".to_owned()]);
+ ///
/// assert_eq!(*args.skip_for(3).unwrap(), ["42".to_string(), "69".to_string(), "88".to_string()]);
/// assert_eq!(args, ["99"]);
/// ```
- ///
+ ///
/// [`skip`]: #method.skip
pub fn skip_for(&mut self, i: u32) -> Option<Vec<String>> {
let mut vec = Vec::with_capacity(i as usize);
@@ -195,18 +200,18 @@ impl Args {
}
/// Like [`single`], but takes quotes into account.
- ///
+ ///
/// # Examples
- ///
+ ///
/// ```rust
/// use serenity::framework::standard::Args;
- ///
- /// let mut args = Args::new(r#""42"#, " ");
- ///
+ ///
+ /// let mut args = Args::new(r#""42"#, vec![" ".to_owned()]);
+ ///
/// assert_eq!(args.single_quoted::<i32>().unwrap(), 42);
/// assert!(args.is_empty());
/// ```
- ///
+ ///
/// [`single`]: #method.single
pub fn single_quoted<T: FromStr>(&mut self) -> Result<T, T::Err>
where T::Err: StdError {
@@ -241,30 +246,30 @@ impl Args {
}
/// Empty outs the internal vector while parsing (if necessary) and returning them
- ///
+ ///
/// # Examples
- ///
+ ///
/// ```rust
/// use serenity::framework::standard::Args;
- ///
- /// let mut args = Args::new("42 69", " ");
- ///
+ ///
+ /// let mut args = Args::new("42 69", vec![" ".to_owned()]);
+ ///
/// assert_eq!(*args.list::<i32>().unwrap(), [42, 69]);
- /// ```
+ /// ```
pub fn list<T: FromStr>(mut self) -> Result<Vec<T>, T::Err>
where T::Err: StdError {
Iter::<T>::new(&mut self).collect()
}
/// Provides an iterator of items: (`T: FromStr`) `Result<T, T::Err>`.
- ///
+ ///
/// # Examples
- ///
+ ///
/// ```rust
/// use serenity::framework::standard::Args;
- ///
- /// let mut args = Args::new("3 4", " ");
- ///
+ ///
+ /// let mut args = Args::new("3 4", vec![" ".to_owned()]);
+ ///
/// assert_eq!(*args.iter::<i32>().map(|num| num.unwrap().pow(2)).collect::<Vec<_>>(), [9, 16]);
/// assert!(args.is_empty());
/// ```
@@ -273,27 +278,27 @@ impl Args {
}
/// This method is just `internal_vector.join(delimiter)`
- ///
+ ///
/// # Examples
- ///
+ ///
/// ```rust
/// use serenity::framework::standard::Args;
- ///
- /// let mut args = Args::new("42 69", " ");
- ///
+ ///
+ /// let mut args = Args::new("42 69", vec![" ".to_owned()]);
+ ///
/// assert_eq!(args.full(), "42 69");
/// ```
pub fn full(&self) -> String { self.delimiter_split.join(&self.delimiter) }
/// Returns the first argument that can be converted and removes it from the list.
- ///
+ ///
/// # Examples
- ///
+ ///
/// ```rust
/// use serenity::framework::standard::Args;
- ///
- /// let mut args = Args::new("c47 69", " ");
- ///
+ ///
+ /// let mut args = Args::new("c47 69", vec![" ".to_owned()]);
+ ///
/// assert_eq!(args.find::<i32>().unwrap(), 69);
/// assert_eq!(args, ["c47"]);
/// ```
@@ -322,14 +327,14 @@ impl Args {
}
/// Returns the first argument that can be converted and does not remove it from the list.
- ///
+ ///
/// # Examples
- ///
+ ///
/// ```rust
/// use serenity::framework::standard::Args;
- ///
- /// let args = Args::new("c47 69", " ");
- ///
+ ///
+ /// let args = Args::new("c47 69", vec![" ".to_owned()]);
+ ///
/// assert_eq!(args.find_n::<i32>().unwrap(), 69);
/// assert_eq!(args, ["c47", "69"]);
/// ```
@@ -348,29 +353,29 @@ impl Args {
}
/// A version of `FromStr` that allows for "zero-copy" parsing.
-///
+///
/// # Examples
-///
+///
/// ```rust,ignore
/// use serenity::framework::standard::{Args, FromStrZc};
/// use std::fmt;
-///
+///
/// struct NameDiscrim<'a>(&'a str, Option<&'a str>);
-///
+///
/// #[derive(Debug)]
/// struct Error(&'static str);
-///
+///
/// impl std::error::Error for Error {
/// fn description(&self) -> &str { self.0 }
/// }
-///
+///
/// impl fmt::Display for Error {
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) }
/// }
-///
+///
/// impl<'a> FromStrZc<'a> for NameDiscrim<'a> {
/// type Err = Error;
-///
+///
/// fn from_str(s: &'a str) -> Result<NameDiscrim<'a>, Error> {
/// let mut it = s.split("#");
/// let name = it.next().ok_or(Error("name must be specified"))?;
@@ -378,16 +383,16 @@ impl Args {
/// Ok(NameDiscrim(name, discrim))
/// }
/// }
-///
-/// let mut args = Args::new("abc#1234", " ");
+///
+/// let mut args = Args::new("abc#1234", vec![" ".to_owned()]);
/// let NameDiscrim(name, discrim) = args.single_zc::<NameDiscrim>().unwrap();
-///
+///
/// assert_eq!(name, "abc");
/// assert_eq!(discrim, Some("1234"));
-/// ```
+/// ```
pub trait FromStrZc<'a>: Sized {
type Err;
-
+
fn from_str(s: &'a str) -> ::std::result::Result<Self, Self::Err>;
}
@@ -408,14 +413,14 @@ impl ::std::ops::Deref for Args {
impl<'a> PartialEq<[&'a str]> for Args {
fn eq(&self, other: &[&str]) -> bool {
let mut b = true;
-
+
for (s, o) in self.delimiter_split.iter().zip(other.iter()) {
if s != o {
b = false;
break;
}
}
-
+
b
}
}
@@ -427,7 +432,7 @@ macro_rules! impl_slices {
self.delimiter_split.is_empty()
}
}
-
+
$(
impl<'a> PartialEq<[&'a str; $num]> for Args {
fn eq(&self, other: &[&str; $num]) -> bool {
diff --git a/src/framework/standard/command.rs b/src/framework/standard/command.rs
index b53ed90..be7ec2a 100644
--- a/src/framework/standard/command.rs
+++ b/src/framework/standard/command.rs
@@ -9,12 +9,9 @@ pub type Check = Fn(&mut Context, &Message, &mut Args, &Arc<Command>) -> bool
+ Send
+ Sync
+ 'static;
-pub type Exec = Fn(&mut Context, &Message, Args) -> Result<(), Error> + Send + Sync + 'static;
-pub type Help = Fn(&mut Context, &Message, HashMap<String, Arc<CommandGroup>>, Args)
- -> Result<(), Error>
- + Send
- + Sync
- + 'static;
+pub type Exec = fn(&mut Context, &Message, Args) -> Result<(), Error>;
+pub type Help = fn(&mut Context, &Message, HashMap<String, Arc<CommandGroup>>, Args)
+ -> Result<(), Error>;
pub type BeforeHook = Fn(&mut Context, &Message, &str) -> bool + Send + Sync + 'static;
pub type AfterHook = Fn(&mut Context, &Message, &str, Result<(), Error>) + Send + Sync + 'static;
pub(crate) type InternalCommand = Arc<Command>;
@@ -40,8 +37,8 @@ impl<D: fmt::Display> From<D> for Error {
/// your commands.
pub enum CommandType {
StringResponse(String),
- Basic(Box<Exec>),
- WithCommands(Box<Help>),
+ Basic(Exec),
+ WithCommands(Help),
}
pub struct CommandGroup {
@@ -88,14 +85,14 @@ pub struct Command {
pub guild_only: bool,
/// Whether command can only be used by owners or not.
pub owners_only: bool,
- pub(crate) aliases: Vec<String>,
+ /// Other names that can be used to call this command instead.
+ pub aliases: Vec<String>,
}
impl Command {
- pub fn new<F>(f: F) -> Self
- where F: Fn(&mut Context, &Message, Args) -> Result<(), Error> + Send + Sync + 'static {
+ pub fn new(f: fn(&mut Context, &Message, Args) -> Result<(), Error>) -> Self {
Command {
- exec: CommandType::Basic(Box::new(f)),
+ exec: CommandType::Basic(f),
..Command::default()
}
}
@@ -106,7 +103,7 @@ impl Default for Command {
Command {
aliases: Vec::new(),
checks: Vec::default(),
- exec: CommandType::Basic(Box::new(|_, _, _| Ok(()))),
+ exec: CommandType::Basic(|_, _, _| Ok(())),
desc: None,
usage: None,
example: None,
diff --git a/src/framework/standard/configuration.rs b/src/framework/standard/configuration.rs
index c731d31..6cb5767 100644
--- a/src/framework/standard/configuration.rs
+++ b/src/framework/standard/configuration.rs
@@ -20,11 +20,13 @@ use model::{GuildId, Message, UserId};
/// struct Handler;
///
/// impl EventHandler for Handler {}
+///
/// use serenity::Client;
/// use std::env;
/// use serenity::framework::StandardFramework;
///
-/// let mut client = Client::new(&env::var("DISCORD_TOKEN").unwrap(), Handler);
+/// let token = env::var("DISCORD_BOT_TOKEN").unwrap();
+/// let mut client = Client::new(&token, Handler).unwrap();
///
/// client.with_framework(StandardFramework::new()
/// .configure(|c| c.on_mention(true).prefix("~")));
@@ -98,7 +100,7 @@ impl Configuration {
/// # struct Handler;
/// #
/// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
+ /// # let mut client = Client::new("token", Handler).unwrap();
/// use serenity::model::GuildId;
/// use serenity::framework::StandardFramework;
///
@@ -123,7 +125,7 @@ impl Configuration {
/// # struct Handler;
/// #
/// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
+ /// # let mut client = Client::new("token", Handler).unwrap();
/// use serenity::model::UserId;
/// use serenity::framework::StandardFramework;
///
@@ -161,7 +163,7 @@ impl Configuration {
/// # struct Handler;
/// #
/// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
+ /// # let mut client = Client::new("token", Handler).unwrap();
/// use serenity::framework::StandardFramework;
///
/// let disabled = vec!["ping"].into_iter().map(|x| x.to_string()).collect();
@@ -191,7 +193,7 @@ impl Configuration {
/// # struct Handler;
/// #
/// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
+ /// # let mut client = Client::new("token", Handler).unwrap();
/// use serenity::framework::StandardFramework;
///
/// client.with_framework(StandardFramework::new()
@@ -276,7 +278,7 @@ impl Configuration {
/// # struct Handler;
/// #
/// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
+ /// # let mut client = Client::new("token", Handler).unwrap();
/// use serenity::model::UserId;
/// use serenity::framework::StandardFramework;
///
@@ -291,7 +293,7 @@ impl Configuration {
/// # struct Handler;
/// #
/// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
+ /// # let mut client = Client::new("token", Handler).unwrap();
/// use serenity::model::UserId;
/// use std::collections::HashSet;
/// use serenity::framework::StandardFramework;
@@ -320,7 +322,7 @@ impl Configuration {
/// # struct Handler;
/// #
/// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
+ /// # let mut client = Client::new("token", Handler).unwrap();
/// #
/// use serenity::framework::StandardFramework;
///
@@ -345,7 +347,7 @@ impl Configuration {
/// # struct Handler;
/// #
/// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
+ /// # let mut client = Client::new("token", Handler).unwrap();
/// #
/// use serenity::framework::StandardFramework;
///
@@ -369,7 +371,7 @@ impl Configuration {
/// # struct Handler;
/// #
/// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
+ /// # let mut client = Client::new("token", Handler).unwrap();
/// #
/// use serenity::framework::StandardFramework;
///
@@ -394,7 +396,7 @@ impl Configuration {
/// # struct Handler;
/// #
/// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
+ /// # let mut client = Client::new("token", Handler).unwrap();
/// #
/// use serenity::framework::StandardFramework;
///
diff --git a/src/framework/standard/create_command.rs b/src/framework/standard/create_command.rs
index 9df9c82..c1558ce 100644
--- a/src/framework/standard/create_command.rs
+++ b/src/framework/standard/create_command.rs
@@ -47,7 +47,8 @@ impl CreateCommand {
/// use std::env;
/// use std::sync::Arc;
///
- /// let mut client = Client::new(&env::var("DISCORD_TOKEN").unwrap(), Handler);
+ /// let token = env::var("DISCORD_TOKEN").unwrap();
+ /// let mut client = Client::new(&token, Handler).unwrap();
///
/// client.with_framework(StandardFramework::new()
/// .configure(|c| c.prefix("~"))
@@ -106,9 +107,8 @@ impl CreateCommand {
/// See [`exec_str`] if you _only_ need to return a string on command use.
///
/// [`exec_str`]: #method.exec_str
- pub fn exec<F>(mut self, func: F) -> Self
- where F: Fn(&mut Context, &Message, Args) -> Result<(), CommandError> + Send + Sync + 'static {
- self.0.exec = CommandType::Basic(Box::new(func));
+ pub fn exec(mut self, func: fn(&mut Context, &Message, Args) -> Result<(), CommandError>) -> Self {
+ self.0.exec = CommandType::Basic(func);
self
}
@@ -117,14 +117,11 @@ impl CreateCommand {
/// the internal HashMap of commands, used specifically for creating a help
/// command.
///
- /// You can return `Err(Custom(string))` if there's an error.
- pub fn exec_help<F>(mut self, f: F) -> Self
- where F: Fn(&mut Context, &Message, HashMap<String, Arc<CommandGroup>>, Args)
- -> Result<(), CommandError>
- + Send
- + Sync
- + 'static {
- self.0.exec = CommandType::WithCommands(Box::new(f));
+ /// You can return `Err(From::from(string))` if there's an error.
+ pub fn exec_help(mut self, f:
+ fn(&mut Context, &Message, HashMap<String, Arc<CommandGroup>>, Args)
+ -> Result<(), CommandError>) -> Self {
+ self.0.exec = CommandType::WithCommands(f);
self
}
diff --git a/src/framework/standard/create_group.rs b/src/framework/standard/create_group.rs
index 911d2ea..39fbcc6 100644
--- a/src/framework/standard/create_group.rs
+++ b/src/framework/standard/create_group.rs
@@ -70,14 +70,14 @@ impl CreateGroup {
}
/// Adds a command to group with simplified API.
- /// You can return Err(string) if there's an error.
- pub fn on<F>(mut self, command_name: &str, f: F) -> Self
- where F: Fn(&mut Context, &Message, Args) -> Result<(), CommandError> + Send + Sync + 'static {
+ /// You can return Err(From::from(string)) if there's an error.
+ pub fn on(mut self, name: &str,
+ f: fn(&mut Context, &Message, Args) -> Result<(), CommandError>) -> Self {
let cmd = Arc::new(Command::new(f));
self.0
.commands
- .insert(command_name.to_string(), CommandOrAlias::Command(cmd));
+ .insert(name.to_string(), CommandOrAlias::Command(cmd));
self
}
diff --git a/src/framework/standard/help_commands.rs b/src/framework/standard/help_commands.rs
index 6b85239..8cbcb21 100644
--- a/src/framework/standard/help_commands.rs
+++ b/src/framework/standard/help_commands.rs
@@ -55,7 +55,7 @@ fn remove_aliases(cmds: &HashMap<String, CommandOrAlias>) -> HashMap<&String, &I
/// and given the required permissions.
pub fn has_all_requirements(cmd: &Command, msg: &Message) -> bool {
if let Some(guild) = msg.guild() {
- let guild = guild.read().unwrap();
+ let guild = guild.read();
if let Some(member) = guild.members.get(&msg.author.id) {
@@ -69,7 +69,7 @@ pub fn has_all_requirements(cmd: &Command, msg: &Message) -> bool {
}
}
}
- false
+ !cmd.guild_only
}
/// Posts an embed showing each individual command group and its commands.
@@ -83,7 +83,7 @@ pub fn has_all_requirements(cmd: &Command, msg: &Message) -> bool {
/// # struct Handler;
/// #
/// # impl EventHandler for Handler {}
-/// # let mut client = Client::new("token", Handler);
+/// # let mut client = Client::new("token", Handler).unwrap();
/// #
/// use serenity::framework::standard::{StandardFramework, help_commands};
///
@@ -155,26 +155,25 @@ pub fn with_embeds(_: &mut Context,
}
if let Some(ref usage) = command.usage {
- embed = embed.field(|f| {
- f.name("Usage")
- .value(&format!("`{} {}`", command_name, usage))
- });
+ let value = format!("`{} {}`", command_name, usage);
+
+ embed = embed.field("Usage", value, true);
}
if let Some(ref example) = command.example {
- embed = embed.field(|f| {
- f.name("Sample usage")
- .value(&format!("`{} {}`", command_name, example))
- });
+ let value = format!("`{} {}`", command_name, example);
+
+ embed = embed.field("Sample usage", value, true);
}
if group_name != "Ungrouped" {
- embed = embed.field(|f| f.name("Group").value(&group_name));
+ embed = embed.field("Group", group_name, true);
}
if !command.aliases.is_empty() {
let aliases = command.aliases.join(", ");
- embed = embed.field(|f| f.name("Aliases").value(&aliases));
+
+ embed = embed.field("Aliases", aliases, true);
}
let available = if command.dm_only {
@@ -185,7 +184,7 @@ pub fn with_embeds(_: &mut Context,
"In DM and guilds"
};
- embed = embed.field(|f| f.name("Available").value(available));
+ embed = embed.field("Available", available, true);
embed
})
@@ -235,7 +234,7 @@ pub fn with_embeds(_: &mut Context,
}
if has_commands {
- e = e.field(|f| f.name(group_name).value(&desc));
+ e = e.field(&group_name[..], &desc[..], true);
}
}
e
@@ -256,7 +255,7 @@ pub fn with_embeds(_: &mut Context,
/// # struct Handler;
/// #
/// # impl EventHandler for Handler {}
-/// # let mut client = Client::new("token", Handler);
+/// # let mut client = Client::new("token", Handler).unwrap();
/// #
/// use serenity::framework::standard::{StandardFramework, help_commands};
///
diff --git a/src/framework/standard/mod.rs b/src/framework/standard/mod.rs
index a806817..fdab3a8 100644
--- a/src/framework/standard/mod.rs
+++ b/src/framework/standard/mod.rs
@@ -218,7 +218,8 @@ impl StandardFramework {
/// use serenity::framework::StandardFramework;
/// use std::env;
///
- /// let mut client = Client::new(&env::var("DISCORD_TOKEN").unwrap(), Handler);
+ /// let token = env::var("DISCORD_TOKEN").unwrap();
+ /// let mut client = Client::new(&token, Handler).unwrap();
/// client.with_framework(StandardFramework::new()
/// .configure(|c| c
/// .depth(3)
@@ -251,7 +252,7 @@ impl StandardFramework {
/// # struct Handler;
/// #
/// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
+ /// # let mut client = Client::new("token", Handler).unwrap();
/// #
/// use serenity::framework::StandardFramework;
///
@@ -287,7 +288,7 @@ impl StandardFramework {
/// # struct Handler;
/// #
/// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
+ /// # let mut client = Client::new("token", Handler).unwrap();
/// #
/// use serenity::framework::StandardFramework;
///
@@ -396,7 +397,7 @@ impl StandardFramework {
/// # struct Handler;
/// #
/// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
+ /// # let mut client = Client::new("token", Handler).unwrap();
/// #
/// use serenity::framework::StandardFramework;
///
@@ -425,7 +426,7 @@ impl StandardFramework {
#[cfg(feature = "cache")]
fn is_blocked_guild(&self, message: &Message) -> bool {
- if let Some(Channel::Guild(channel)) = CACHE.read().unwrap().channel(message.channel_id) {
+ if let Some(Channel::Guild(channel)) = CACHE.read().channel(message.channel_id) {
let guild_id = channel.with(|g| g.guild_id);
if self.configuration.blocked_guilds.contains(&guild_id) {
return true;
@@ -536,7 +537,7 @@ impl StandardFramework {
} else {
if !command.allowed_roles.is_empty() {
if let Some(guild) = message.guild() {
- let guild = guild.read().unwrap();
+ let guild = guild.read();
if let Some(member) = guild.members.get(&message.author.id) {
if let Ok(permissions) = member.permissions() {
@@ -592,7 +593,7 @@ impl StandardFramework {
/// # struct Handler;
/// #
/// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
+ /// # let mut client = Client::new("token", Handler).unwrap();
/// #
/// use serenity::framework::StandardFramework;
///
@@ -603,20 +604,18 @@ impl StandardFramework {
/// });
/// # }
/// ```
- pub fn on<F, S>(mut self, command_name: S, f: F) -> Self
- where F: Fn(&mut Context, &Message, Args) -> Result<(), CommandError> + Send + Sync + 'static,
- S: Into<String> {
+ pub fn on(mut self, name: &str,
+ f: fn(&mut Context, &Message, Args)
+ -> Result<(), CommandError>) -> Self {
{
let ungrouped = self.groups
.entry("Ungrouped".to_string())
.or_insert_with(|| Arc::new(CommandGroup::default()));
if let Some(ref mut group) = Arc::get_mut(ungrouped) {
- let name = command_name.into();
-
group
.commands
- .insert(name, CommandOrAlias::Command(Arc::new(Command::new(f))));
+ .insert(name.to_string(), CommandOrAlias::Command(Arc::new(Command::new(f))));
}
}
@@ -686,7 +685,7 @@ impl StandardFramework {
/// # struct Handler;
/// #
/// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
+ /// # let mut client = Client::new("token", Handler).unwrap();
/// #
/// use serenity::framework::StandardFramework;
///
@@ -719,7 +718,7 @@ impl StandardFramework {
/// # struct Handler;
/// #
/// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
+ /// # let mut client = Client::new("token", Handler).unwrap();
/// use serenity::framework::standard::DispatchError::{NotEnoughArguments,
/// TooManyArguments};
/// use serenity::framework::StandardFramework;
@@ -760,7 +759,7 @@ impl StandardFramework {
/// # struct Handler;
/// #
/// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
+ /// # let mut client = Client::new("token", Handler).unwrap();
/// #
/// use serenity::framework::StandardFramework;
///
@@ -778,7 +777,7 @@ impl StandardFramework {
/// # struct Handler;
/// #
/// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
+ /// # let mut client = Client::new("token", Handler).unwrap();
/// #
/// use serenity::framework::StandardFramework;
///
@@ -816,7 +815,7 @@ impl StandardFramework {
/// # struct Handler;
/// #
/// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
+ /// # let mut client = Client::new("token", Handler).unwrap();
/// #
/// use serenity::framework::StandardFramework;
///
@@ -910,13 +909,7 @@ impl Framework for StandardFramework {
let mut content = message.content[position..].trim();
content = content[command_length..].trim();
- let delimiter = self.configuration
- .delimiters
- .iter()
- .find(|&d| content.contains(d))
- .map_or(" ", |s| s.as_str());
-
- Args::new(content, delimiter)
+ Args::new(&content, self.configuration.delimiters.clone())
};
if let Some(error) = self.should_fail(
diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs
index ed54bca..c2acaac 100644
--- a/src/gateway/mod.rs
+++ b/src/gateway/mod.rs
@@ -51,9 +51,18 @@
mod error;
mod shard;
+mod ws_client_ext;
pub use self::error::Error as GatewayError;
pub use self::shard::Shard;
+pub use self::ws_client_ext::WebSocketGatewayClientExt;
+
+use model::{Game, OnlineStatus};
+use websocket::sync::client::Client;
+use websocket::sync::stream::{TcpStream, TlsStream};
+
+pub type CurrentPresence = (Option<Game>, OnlineStatus);
+pub type WsClient = Client<TlsStream<TcpStream>>;
/// Indicates the current connection stage of a [`Shard`].
///
@@ -136,3 +145,11 @@ impl ConnectionStage {
}
}
}
+
+pub enum ShardAction {
+ Autoreconnect,
+ Heartbeat,
+ Identify,
+ Reconnect,
+ Resume,
+}
diff --git a/src/gateway/shard.rs b/src/gateway/shard.rs
index bd60b57..340e2e8 100644
--- a/src/gateway/shard.rs
+++ b/src/gateway/shard.rs
@@ -1,38 +1,31 @@
-use chrono::Utc;
-use serde_json::Value;
-use std::env::consts;
-use std::io::Write;
-use std::net::Shutdown;
-use std::sync::{Arc, Mutex};
+use parking_lot::Mutex;
+use std::sync::Arc;
use std::time::{Duration as StdDuration, Instant};
-use std::thread;
-use super::{ConnectionStage, GatewayError};
+use super::{
+ ConnectionStage,
+ CurrentPresence,
+ ShardAction,
+ GatewayError,
+ WsClient,
+ WebSocketGatewayClientExt,
+};
use websocket::client::Url;
-use websocket::message::{CloseData, OwnedMessage};
use websocket::stream::sync::AsTcpStream;
-use websocket::sync::client::{Client, ClientBuilder};
-use websocket::sync::stream::{TcpStream, TlsStream};
+use websocket::sync::client::ClientBuilder;
use websocket::WebSocketError;
-use constants::{self, close_codes, OpCode};
+use constants::{self, close_codes};
use internal::prelude::*;
-use internal::ws_impl::SenderExt;
use model::event::{Event, GatewayEvent};
use model::{Game, GuildId, OnlineStatus};
#[cfg(feature = "voice")]
+use serde_json::Value;
+#[cfg(feature = "voice")]
use std::sync::mpsc::{self, Receiver as MpscReceiver};
-#[cfg(feature = "cache")]
-use client::CACHE;
#[cfg(feature = "voice")]
use voice::Manager as VoiceManager;
#[cfg(feature = "voice")]
use http;
-#[cfg(feature = "cache")]
-use utils;
-
-pub type WsClient = Client<TlsStream<TcpStream>>;
-
-type CurrentPresence = (Option<Game>, OnlineStatus, bool);
/// A Shard is a higher-level handler for a websocket connection to Discord's
/// gateway. The shard allows for sending and receiving messages over the
@@ -92,7 +85,7 @@ pub struct Shard {
/// Whether the shard has permanently shutdown.
shutdown: bool,
stage: ConnectionStage,
- token: Arc<Mutex<String>>,
+ pub token: Arc<Mutex<String>>,
ws_url: Arc<Mutex<String>>,
/// The voice connections that this Shard is responsible for. The Shard will
/// update the voice connections' states.
@@ -111,27 +104,44 @@ impl Shard {
/// Instantiating a new Shard manually for a bot with no shards, and
/// then listening for events:
///
- /// ```rust,ignore
+ /// ```rust,no_run
+ /// extern crate parking_lot;
+ /// extern crate serenity;
+ /// #
+ /// # use std::error::Error;
+ /// #
+ /// # fn try_main() -> Result<(), Box<Error>> {
+ /// #
+ /// use parking_lot::Mutex;
/// use serenity::gateway::Shard;
/// use serenity::http;
/// use std::env;
+ /// use std::sync::Arc;
///
- /// let token = env::var("DISCORD_TOKEN").expect("Token in environment");
+ /// let token = Arc::new(Mutex::new(env::var("DISCORD_BOT_TOKEN")?));
/// // retrieve the gateway response, which contains the URL to connect to
- /// let gateway = http::get_gateway().expect("Valid gateway response").url;
- /// let shard = Shard::new(&gateway, &token, None)
- /// .expect("Working shard");
+ /// let gateway = Arc::new(Mutex::new(http::get_gateway()?.url));
+ /// let shard = Shard::new(gateway, token, [0, 1])?;
///
/// // at this point, you can create a `loop`, and receive events and match
/// // their variants
+ /// # Ok(())
+ /// # }
+ /// #
+ /// # fn main() {
+ /// # try_main().unwrap();
+ /// # }
/// ```
- pub fn new(ws_url: Arc<Mutex<String>>,
- token: Arc<Mutex<String>>,
- shard_info: [u64; 2])
- -> Result<Shard> {
- let client = connecting(&*ws_url.lock().unwrap());
+ pub fn new(
+ ws_url: Arc<Mutex<String>>,
+ token: Arc<Mutex<String>>,
+ shard_info: [u64; 2],
+ ) -> Result<Shard> {
+ let mut client = connect(&*ws_url.lock())?;
- let current_presence = (None, OnlineStatus::Online, false);
+ let _ = set_client_timeout(&mut client);
+
+ let current_presence = (None, OnlineStatus::Online);
let heartbeat_instants = (None, None);
let heartbeat_interval = None;
let last_heartbeat_acknowledged = true;
@@ -146,7 +156,12 @@ impl Shard {
let user = http::get_current_user()?;
Shard {
+<<<<<<< HEAD
+ manager: VoiceManager::new(tx, user.id),
+ manager_rx: rx,
+=======
shutdown: false,
+>>>>>>> v0.4.3
client,
current_presence,
heartbeat_instants,
@@ -158,8 +173,6 @@ impl Shard {
shard_info,
session_id,
ws_url,
- manager: VoiceManager::new(tx, user.id),
- manager_rx: rx,
}
} else {
Shard {
@@ -180,6 +193,13 @@ impl Shard {
})
}
+<<<<<<< HEAD
+ /// Retrieves the current presence of the shard.
+ #[inline]
+ pub fn current_presence(&self) -> &CurrentPresence {
+ &self.current_presence
+ }
+=======
/// Whether the shard has permanently shutdown.
///
/// This should normally happen due to manual calling of [`shutdown`] or
@@ -215,25 +235,90 @@ impl Shard {
/// assert_eq!(shard.shard_info(), [1, 2]);
/// ```
pub fn shard_info(&self) -> [u64; 2] { self.shard_info }
+>>>>>>> v0.4.3
- /// Sets whether the current user is afk. This helps Discord determine where
- /// to send notifications.
+ /// Retrieves the heartbeat instants of the shard.
///
- /// Other presence settings are maintained.
- pub fn set_afk(&mut self, afk: bool) {
- self.current_presence.2 = afk;
+ /// This is the time of when a heartbeat was sent and when an
+ /// acknowledgement was last received.
+ #[inline]
+ pub fn heartbeat_instants(&self) -> &(Option<Instant>, Option<Instant>) {
+ &self.heartbeat_instants
+ }
- self.update_presence();
+ /// Retrieves the value of when the last heartbeat was sent.
+ #[inline]
+ pub fn last_heartbeat_sent(&self) -> Option<&Instant> {
+ self.heartbeat_instants.0.as_ref()
}
- /// Sets the user's current game, if any.
+ /// Retrieves the value of when the last heartbeat ack was received.
+ #[inline]
+ pub fn last_heartbeat_ack(&self) -> Option<&Instant> {
+ self.heartbeat_instants.1.as_ref()
+ }
+
+ /// Sends a heartbeat to the gateway with the current sequence.
///
- /// Other presence settings are maintained.
+ /// This sets the last heartbeat time to now, and
+ /// `last_heartbeat_acknowledged` to `false`.
///
- /// # Examples
+ /// # Errors
///
- /// Setting the current game to playing `"Heroes of the Storm"`:
+<<<<<<< HEAD
+ /// Returns [`GatewayError::HeartbeatFailed`] if there was an error sending
+ /// a heartbeat.
///
+ /// [`GatewayError::HeartbeatFailed`]: enum.GatewayError.html#variant.HeartbeatFailed
+ pub fn heartbeat(&mut self) -> Result<()> {
+ match self.client.send_heartbeat(&self.shard_info, Some(self.seq)) {
+ Ok(()) => {
+ self.heartbeat_instants.0 = Some(Instant::now());
+ self.last_heartbeat_acknowledged = false;
+
+ Ok(())
+ },
+ Err(why) => {
+ match why {
+ Error::WebSocket(WebSocketError::IoError(err)) => if err.raw_os_error() != Some(32) {
+ debug!("[Shard {:?}] Err heartbeating: {:?}",
+ self.shard_info,
+ err);
+ },
+ other => {
+ warn!("[Shard {:?}] Other err w/ keepalive: {:?}",
+ self.shard_info,
+ other);
+ },
+ }
+
+ Err(Error::Gateway(GatewayError::HeartbeatFailed))
+ }
+ }
+ }
+
+ #[inline]
+ pub fn heartbeat_interval(&self) -> Option<&u64> {
+ self.heartbeat_interval.as_ref()
+ }
+
+ #[inline]
+ pub fn last_heartbeat_acknowledged(&self) -> bool {
+ self.last_heartbeat_acknowledged
+ }
+
+ #[inline]
+ pub fn seq(&self) -> u64 {
+ self.seq
+ }
+
+ #[inline]
+ pub fn session_id(&self) -> Option<&String> {
+ self.session_id.as_ref()
+ }
+
+ #[inline]
+=======
/// ```rust,no_run
/// # #[cfg(feature = "model")]
/// # fn main() {
@@ -252,68 +337,66 @@ impl Shard {
/// # #[cfg(not(feature = "model"))]
/// # fn main() { }
/// ```
+>>>>>>> v0.4.3
pub fn set_game(&mut self, game: Option<Game>) {
self.current_presence.0 = game;
+ }
- self.update_presence();
+ #[inline]
+ pub fn set_presence(&mut self, status: OnlineStatus, game: Option<Game>) {
+ self.set_game(game);
+ self.set_status(status);
}
- /// Sets the user's current online status.
- ///
- /// Note that [`Offline`] is not a valid online status, so it is
- /// automatically converted to [`Invisible`].
- ///
- /// Other presence settings are maintained.
- ///
- /// # Examples
- ///
- /// Setting the current online status for the shard to [`DoNotDisturb`].
- ///
- /// ```rust,no_run
- /// # use serenity::client::gateway::Shard;
- /// # use std::sync::{Arc, Mutex};
- /// #
- /// # let mutex = Arc::new(Mutex::new("".to_string()));
- /// #
- /// # let mut shard = Shard::new(mutex.clone(), mutex, [0, 1]).unwrap();
- /// #
- /// use serenity::model::OnlineStatus;
- ///
- /// shard.set_status(OnlineStatus::DoNotDisturb);
- /// ```
- ///
- /// [`DoNotDisturb`]: ../../model/enum.OnlineStatus.html#variant.DoNotDisturb
- /// [`Invisible`]: ../../model/enum.OnlineStatus.html#variant.Invisible
- /// [`Offline`]: ../../model/enum.OnlineStatus.html#variant.Offline
- pub fn set_status(&mut self, online_status: OnlineStatus) {
- self.current_presence.1 = match online_status {
- OnlineStatus::Offline => OnlineStatus::Invisible,
- other => other,
- };
+ #[inline]
+ pub fn set_status(&mut self, mut status: OnlineStatus) {
+ if status == OnlineStatus::Offline {
+ status = OnlineStatus::Invisible;
+ }
- self.update_presence();
+ self.current_presence.1 = status;
}
- /// Sets the user's full presence information.
+ /// Retrieves a copy of the current shard information.
+ ///
+ /// The first element is the _current_ shard - 0-indexed - while the second
+ /// element is the _total number_ of shards -- 1-indexed.
///
- /// Consider using the individual setters if you only need to modify one of
- /// these.
+ /// For example, if using 3 shards in total, and if this is shard 1, then it
+ /// can be read as "the second of three shards".
///
/// # Examples
///
- /// Set the current user as playing `"Heroes of the Storm"`, being online,
- /// and not being afk:
+ /// Retrieving the shard info for the second shard, out of two shards total:
///
/// ```rust,no_run
+<<<<<<< HEAD
+ /// # extern crate parking_lot;
+ /// # extern crate serenity;
+ /// #
+ /// # use parking_lot::Mutex;
+=======
/// # #[cfg(feature = "model")]
/// # fn main() {
+>>>>>>> v0.4.3
/// # use serenity::client::gateway::Shard;
- /// # use std::sync::{Arc, Mutex};
+ /// # use std::error::Error;
+ /// # use std::sync::Arc;
/// #
- /// # let mutex = Arc::new(Mutex::new("".to_string()));
+ /// # fn try_main() -> Result<(), Box<Error>> {
+ /// # let mutex = Arc::new(Mutex::new("".to_string()));
/// #
- /// # let mut shard = Shard::new(mutex.clone(), mutex, [0, 1]).unwrap();
+ /// # let shard = Shard::new(mutex.clone(), mutex, [1, 2]).unwrap();
/// #
+<<<<<<< HEAD
+ /// assert_eq!(shard.shard_info(), [1, 2]);
+ /// # Ok(())
+ /// # }
+ /// #
+ /// # fn main() {
+ /// # try_main().unwrap();
+ /// # }
+=======
/// use serenity::model::{Game, OnlineStatus};
///
/// shard.set_presence(Some(Game::playing("Heroes of the Storm")), OnlineStatus::Online, false);
@@ -321,16 +404,9 @@ impl Shard {
/// #
/// # #[cfg(not(feature = "model"))]
/// # fn main() { }
+>>>>>>> v0.4.3
/// ```
- pub fn set_presence(&mut self, game: Option<Game>, mut status: OnlineStatus, afk: bool) {
- if status == OnlineStatus::Offline {
- status = OnlineStatus::Invisible;
- }
-
- self.current_presence = (game, status, afk);
-
- self.update_presence();
- }
+ pub fn shard_info(&self) -> [u64; 2] { self.shard_info }
/// Returns the current connection stage of the shard.
pub fn stage(&self) -> ConnectionStage {
@@ -362,21 +438,24 @@ impl Shard {
/// Returns a `GatewayError::OverloadedShard` if the shard would have too
/// many guilds assigned to it.
#[allow(cyclomatic_complexity)]
- pub(crate) fn handle_event(&mut self, event: Result<GatewayEvent>) -> Result<Option<Event>> {
- match event {
- Ok(GatewayEvent::Dispatch(seq, event)) => {
+ pub(crate) fn handle_event(&mut self, event: &Result<GatewayEvent>)
+ -> Result<Option<ShardAction>> {
+ match *event {
+ Ok(GatewayEvent::Dispatch(seq, ref event)) => {
if seq > self.seq + 1 {
warn!("[Shard {:?}] Heartbeat off; them: {}, us: {}", self.shard_info, seq, self.seq);
}
- match event {
+ match *event {
Event::Ready(ref ready) => {
debug!("[Shard {:?}] Received Ready", self.shard_info);
self.session_id = Some(ready.ready.session_id.clone());
self.stage = ConnectionStage::Connected;
+ /*
set_client_timeout(&mut self.client)?;
+ */
},
Event::Resumed(_) => {
info!("[Shard {:?}] Resumed", self.shard_info);
@@ -396,7 +475,7 @@ impl Shard {
self.seq = seq;
- Ok(Some(event))
+ Ok(None)
},
Ok(GatewayEvent::Heartbeat(s)) => {
info!("[Shard {:?}] Received shard heartbeat", self.shard_info);
@@ -413,24 +492,18 @@ impl Shard {
if self.stage == ConnectionStage::Handshake {
self.stage = ConnectionStage::Identifying;
- self.identify()?;
+ return Ok(Some(ShardAction::Identify));
} else {
warn!(
"[Shard {:?}] Heartbeat during non-Handshake; auto-reconnecting",
self.shard_info
);
- return self.autoreconnect().and(Ok(None));
+ return Ok(Some(ShardAction::Autoreconnect));
}
}
- let map = json!({
- "d": Value::Null,
- "op": OpCode::Heartbeat.num(),
- });
- self.client.send_json(&map)?;
-
- Ok(None)
+ Ok(Some(ShardAction::Heartbeat))
},
Ok(GatewayEvent::HeartbeatAck) => {
self.heartbeat_instants.1 = Some(Instant::now());
@@ -453,30 +526,30 @@ impl Shard {
self.heartbeat_interval = Some(interval);
}
- if self.stage == ConnectionStage::Handshake {
- self.identify().and(Ok(None))
+ Ok(Some(if self.stage == ConnectionStage::Handshake {
+ ShardAction::Identify
} else {
debug!("[Shard {:?}] Received late Hello; autoreconnecting",
self.shard_info);
- self.autoreconnect().and(Ok(None))
- }
+ ShardAction::Autoreconnect
+ }))
},
- Ok(GatewayEvent::InvalidateSession) => {
+ Ok(GatewayEvent::InvalidateSession(resumable)) => {
info!(
- "[Shard {:?}] Received session invalidation; re-identifying",
- self.shard_info
+ "[Shard {:?}] Received session invalidation",
+ self.shard_info,
);
- self.seq = 0;
- self.session_id = None;
-
- self.identify().and(Ok(None))
+ Ok(Some(if resumable {
+ ShardAction::Resume
+ } else {
+ ShardAction::Reconnect
+ }))
},
- Ok(GatewayEvent::Reconnect) => self.reconnect().and(Ok(None)),
- Err(Error::Gateway(GatewayError::Closed(data))) => {
+ Ok(GatewayEvent::Reconnect) => Ok(Some(ShardAction::Reconnect)),
+ Err(Error::Gateway(GatewayError::Closed(ref data))) => {
let num = data.as_ref().map(|d| d.status_code);
- let reason = data.map(|d| d.reason);
let clean = num == Some(1000);
match num {
@@ -536,7 +609,7 @@ impl Shard {
"[Shard {:?}] Unknown unclean close {}: {:?}",
self.shard_info,
other,
- reason
+ data.as_ref().map(|d| &d.reason),
);
},
_ => {},
@@ -547,14 +620,14 @@ impl Shard {
self.session_id.is_some()
}).unwrap_or(true);
- if resume {
- self.resume().or_else(|_| self.reconnect()).and(Ok(None))
+ Ok(Some(if resume {
+ ShardAction::Resume
} else {
- self.reconnect().and(Ok(None))
- }
+ ShardAction::Reconnect
+ }))
},
- Err(Error::WebSocket(why)) => {
- if let WebSocketError::NoDataAvailable = why {
+ Err(Error::WebSocket(ref why)) => {
+ if let WebSocketError::NoDataAvailable = *why {
if self.heartbeat_instants.1.is_none() {
return Ok(None);
}
@@ -566,13 +639,64 @@ impl Shard {
info!("[Shard {:?}] Will attempt to auto-reconnect",
self.shard_info);
- self.autoreconnect().and(Ok(None))
+ Ok(Some(ShardAction::Autoreconnect))
},
- Err(error) => Err(error),
+ _ => Ok(None),
+ }
+ }
+
+ pub fn check_heartbeat(&mut self) -> Result<()> {
+ let wait = {
+ let heartbeat_interval = match self.heartbeat_interval {
+ Some(heartbeat_interval) => heartbeat_interval,
+ None => return Ok(()),
+ };
+
+ StdDuration::from_secs(heartbeat_interval / 1000)
+ };
+
+ // If a duration of time less than the heartbeat_interval has passed,
+ // then don't perform a keepalive or attempt to reconnect.
+ if let Some(last_sent) = self.heartbeat_instants.0 {
+ if last_sent.elapsed() <= wait {
+ return Ok(());
+ }
+ }
+
+ // If the last heartbeat didn't receive an acknowledgement, then
+ // auto-reconnect.
+ if !self.last_heartbeat_acknowledged {
+ debug!(
+ "[Shard {:?}] Last heartbeat not acknowledged; re-connecting",
+ self.shard_info,
+ );
+
+ return self.reconnect().map_err(|why| {
+ warn!(
+ "[Shard {:?}] Err auto-reconnecting from heartbeat check: {:?}",
+ self.shard_info,
+ why,
+ );
+
+ why
+ });
+ }
+
+ // Otherwise, we're good to heartbeat.
+ if let Err(why) = self.heartbeat() {
+ warn!("[Shard {:?}] Err heartbeating: {:?}", self.shard_info, why);
+
+ self.reconnect()
+ } else {
+ trace!("[Shard {:?}] Heartbeated", self.shard_info);
+
+ Ok(())
}
}
/// Calculates the heartbeat latency between the shard and the gateway.
+<<<<<<< HEAD
+=======
///
/// # Examples
///
@@ -608,6 +732,7 @@ impl Shard {
///
/// [`Client`]: ../struct.Client.html
/// [`EventHandler::on_message`]: ../event_handler/trait.EventHandler.html#method.on_message
+>>>>>>> v0.4.3
// Shamelessly stolen from brayzure's commit in eris:
// <https://github.com/abalabahaha/eris/commit/0ce296ae9a542bcec0edf1c999ee2d9986bed5a6>
pub fn latency(&self) -> Option<StdDuration> {
@@ -618,41 +743,79 @@ impl Shard {
}
}
- /// Shuts down the receiver by attempting to cleanly close the
- /// connection.
- pub fn shutdown_clean(&mut self) -> Result<()> {
- {
- let data = CloseData {
- status_code: 1000,
- reason: String::new(),
- };
-
- let message = OwnedMessage::Close(Some(data));
-
- self.client.send_message(&message)?;
+ #[cfg(feature = "voice")]
+ fn voice_dispatch(&mut self, event: &Event) {
+ if let Event::VoiceStateUpdate(ref update) = *event {
+ if let Some(guild_id) = update.guild_id {
+ if let Some(handler) = self.manager.get(guild_id) {
+ handler.update_state(&update.voice_state);
+ }
+ }
}
- let mut stream = self.client.stream_ref().as_tcp();
+ if let Event::VoiceServerUpdate(ref update) = *event {
+ if let Some(guild_id) = update.guild_id {
+ if let Some(handler) = self.manager.get(guild_id) {
+ handler.update_server(&update.endpoint, &update.token);
+ }
+ }
+ }
+ }
- stream.flush()?;
- stream.shutdown(Shutdown::Both)?;
+ #[cfg(feature = "voice")]
+ pub(crate) fn cycle_voice_recv(&mut self) -> Vec<Value> {
+ let mut messages = vec![];
+<<<<<<< HEAD
+ while let Ok(v) = self.manager_rx.try_recv() {
+ messages.push(v);
+ }
+=======
self.shutdown = true;
debug!("[Shard {:?}] Cleanly shutdown shard", self.shard_info);
+>>>>>>> v0.4.3
- Ok(())
+ messages
}
- /// Uncleanly shuts down the receiver by not sending a close code.
- pub fn shutdown(&mut self) -> Result<()> {
- let mut stream = self.client.stream_ref().as_tcp();
+ /// Performs a deterministic reconnect.
+ ///
+ /// The type of reconnect is deterministic on whether a [`session_id`].
+ ///
+ /// If the `session_id` still exists, then a RESUME is sent. If not, then
+ /// an IDENTIFY is sent.
+ ///
+ /// Note that, if the shard is already in a stage of
+ /// [`ConnectionStage::Connecting`], then no action will be performed.
+ ///
+ /// [`ConnectionStage::Connecting`]: ../../../gateway/enum.ConnectionStage.html#variant.Connecting
+ /// [`session_id`]: ../../../gateway/struct.Shard.html#method.session_id
+ pub fn autoreconnect(&mut self) -> Result<()> {
+ if self.stage == ConnectionStage::Connecting {
+ return Ok(());
+ }
+
+ if self.session_id().is_some() {
+ debug!(
+ "[Shard {:?}] Autoreconnector choosing to resume",
+ self.shard_info,
+ );
- stream.flush()?;
- stream.shutdown(Shutdown::Both)?;
+ self.resume()
+ } else {
+ debug!(
+ "[Shard {:?}] Autoreconnector choosing to reconnect",
+ self.shard_info,
+ );
+<<<<<<< HEAD
+ self.reconnect()
+ }
+=======
self.shutdown = true;
Ok(())
+>>>>>>> v0.4.3
}
/// Requests that one or multiple [`Guild`]s be chunked.
@@ -674,304 +837,100 @@ impl Shard {
/// specifying a query parameter:
///
/// ```rust,no_run
+ /// # extern crate parking_lot;
+ /// # extern crate serenity;
+ /// #
+ /// # use parking_lot::Mutex;
/// # use serenity::client::gateway::Shard;
- /// # use std::sync::{Arc, Mutex};
+ /// # use std::error::Error;
+ /// # use std::sync::Arc;
/// #
- /// # let mutex = Arc::new(Mutex::new("".to_string()));
+ /// # fn try_main() -> Result<(), Box<Error>> {
+ /// # let mutex = Arc::new(Mutex::new("".to_string()));
/// #
- /// # let mut shard = Shard::new(mutex.clone(), mutex, [0, 1]).unwrap();
+ /// # let mut shard = Shard::new(mutex.clone(), mutex, [0, 1])?;
/// #
/// use serenity::model::GuildId;
///
/// let guild_ids = vec![GuildId(81384788765712384)];
///
- /// shard.chunk_guilds(&guild_ids, Some(2000), None);
+ /// shard.chunk_guilds(guild_ids, Some(2000), None);
+ /// # Ok(())
+ /// # }
+ /// #
+ /// # fn main() {
+ /// # try_main().unwrap();
+ /// # }
/// ```
///
/// Chunk a single guild by Id, limiting to 20 members, and specifying a
/// query parameter of `"do"`:
///
/// ```rust,no_run
+ /// # extern crate parking_lot;
+ /// # extern crate serenity;
+ /// #
+ /// # use parking_lot::Mutex;
/// # use serenity::client::gateway::Shard;
- /// # use std::sync::{Arc, Mutex};
+ /// # use std::error::Error;
+ /// # use std::sync::Arc;
/// #
- /// # let mutex = Arc::new(Mutex::new("".to_string()));
+ /// # fn try_main() -> Result<(), Box<Error>> {
+ /// # let mutex = Arc::new(Mutex::new("".to_string()));
/// #
- /// # let mut shard = Shard::new(mutex.clone(), mutex, [0, 1]).unwrap();
+ /// # let mut shard = Shard::new(mutex.clone(), mutex, [0, 1])?;
/// #
/// use serenity::model::GuildId;
///
/// let guild_ids = vec![GuildId(81384788765712384)];
///
- /// shard.chunk_guilds(&guild_ids, Some(20), Some("do"));
+ /// shard.chunk_guilds(guild_ids, Some(20), Some("do"));
+ /// # Ok(())
+ /// # }
+ /// #
+ /// # fn main() {
+ /// # try_main().unwrap();
+ /// # }
/// ```
///
/// [`Event::GuildMembersChunk`]:
/// ../../model/event/enum.Event.html#variant.GuildMembersChunk
/// [`Guild`]: ../../model/struct.Guild.html
/// [`Member`]: ../../model/struct.Member.html
- pub fn chunk_guilds<T: AsRef<GuildId>, It>(&mut self, guild_ids: It, limit: Option<u16>, query: Option<&str>)
- where It: IntoIterator<Item=T> {
+ pub fn chunk_guilds<It>(
+ &mut self,
+ guild_ids: It,
+ limit: Option<u16>,
+ query: Option<&str>,
+ ) -> Result<()> where It: IntoIterator<Item=GuildId> {
debug!("[Shard {:?}] Requesting member chunks", self.shard_info);
- let msg = json!({
- "op": OpCode::GetGuildMembers.num(),
- "d": {
- "guild_id": guild_ids.into_iter().map(|x| x.as_ref().0).collect::<Vec<u64>>(),
- "limit": limit.unwrap_or(0),
- "query": query.unwrap_or(""),
- },
- });
-
- let _ = self.client.send_json(&msg);
+ self.client.send_chunk_guilds(
+ guild_ids,
+ &self.shard_info,
+ limit,
+ query,
+ )
}
- /// Calculates the number of guilds that the shard is responsible for.
- ///
- /// If sharding is not being used (i.e. 1 shard), then the total number of
- /// [`Guild`] in the [`Cache`] will be used.
- ///
- /// **Note**: Requires the `cache` feature be enabled.
- ///
- /// # Examples
- ///
- /// Retrieve the number of guilds a shard is responsible for:
- ///
- /// ```rust,no_run
- /// # use serenity::client::gateway::Shard;
- /// # use std::sync::{Arc, Mutex};
- /// #
- /// # let mutex = Arc::new(Mutex::new("will anyone read this".to_string()));
- /// #
- /// # let shard = Shard::new(mutex.clone(), mutex, [0, 1]).unwrap();
- /// #
- /// let info = shard.shard_info();
- /// let guilds = shard.guilds_handled();
- ///
- /// println!("Shard {:?} is responsible for {} guilds", info, guilds);
- /// ```
- ///
- /// [`Cache`]: ../ext/cache/struct.Cache.html
- /// [`Guild`]: ../model/struct.Guild.html
- #[cfg(feature = "cache")]
- pub fn guilds_handled(&self) -> u16 {
- let cache = CACHE.read().unwrap();
-
- let (shard_id, shard_count) = (self.shard_info[0], self.shard_info[1]);
-
- cache
- .guilds
- .keys()
- .filter(|guild_id| {
- utils::shard_id(guild_id.0, shard_count) == shard_id
- })
- .count() as u16
- }
-
- #[cfg(feature = "voice")]
- fn voice_dispatch(&mut self, event: &Event) {
- if let Event::VoiceStateUpdate(ref update) = *event {
- if let Some(guild_id) = update.guild_id {
- if let Some(handler) = self.manager.get(guild_id) {
- handler.update_state(&update.voice_state);
- }
- }
- }
-
- if let Event::VoiceServerUpdate(ref update) = *event {
- if let Some(guild_id) = update.guild_id {
- if let Some(handler) = self.manager.get(guild_id) {
- handler.update_server(&update.endpoint, &update.token);
- }
- }
- }
- }
-
- #[cfg(feature = "voice")]
- pub(crate) fn cycle_voice_recv(&mut self) {
- if let Ok(v) = self.manager_rx.try_recv() {
- if let Err(why) = self.client.send_json(&v) {
- warn!("[Shard {:?}] Err sending voice msg: {:?}",
- self.shard_info,
- why);
- }
- }
- }
-
- pub(crate) fn heartbeat(&mut self) -> Result<()> {
- let map = json!({
- "d": self.seq,
- "op": OpCode::Heartbeat.num(),
- });
-
- trace!("[Shard {:?}] Sending heartbeat d: {}",
- self.shard_info,
- self.seq);
-
- match self.client.send_json(&map) {
- Ok(_) => {
- self.heartbeat_instants.0 = Some(Instant::now());
- self.last_heartbeat_acknowledged = false;
-
- trace!("[Shard {:?}] Successfully heartbeated",
- self.shard_info);
-
- Ok(())
- },
- Err(why) => {
- match why {
- Error::WebSocket(WebSocketError::IoError(err)) => if err.raw_os_error() != Some(32) {
- debug!("[Shard {:?}] Err heartbeating: {:?}",
- self.shard_info,
- err);
- },
- other => {
- warn!("[Shard {:?}] Other err w/ keepalive: {:?}",
- self.shard_info,
- other);
- },
- }
-
- Err(Error::Gateway(GatewayError::HeartbeatFailed))
- },
- }
- }
-
- pub(crate) fn check_heartbeat(&mut self) -> Result<()> {
- let heartbeat_interval = match self.heartbeat_interval {
- Some(heartbeat_interval) => heartbeat_interval,
- None => return Ok(()),
- };
-
- let wait = StdDuration::from_secs(heartbeat_interval / 1000);
-
- // If a duration of time less than the heartbeat_interval has passed,
- // then don't perform a keepalive or attempt to reconnect.
- if let Some(last_sent) = self.heartbeat_instants.0 {
- if last_sent.elapsed() <= wait {
- return Ok(());
- }
- }
-
- // If the last heartbeat didn't receive an acknowledgement, then
- // auto-reconnect.
- if !self.last_heartbeat_acknowledged {
- debug!(
- "[Shard {:?}] Last heartbeat not acknowledged; re-connecting",
- self.shard_info,
- );
-
- return self.reconnect().map_err(|why| {
- warn!(
- "[Shard {:?}] Err auto-reconnecting from heartbeat check: {:?}",
- self.shard_info,
- why,
- );
-
- why
- });
- }
-
- // Otherwise, we're good to heartbeat.
- trace!("[Shard {:?}] Heartbeating", self.shard_info);
-
- if let Err(why) = self.heartbeat() {
- warn!("[Shard {:?}] Err heartbeating: {:?}", self.shard_info, why);
-
- self.reconnect()
- } else {
- trace!("[Shard {:?}] Heartbeated", self.shard_info);
- self.heartbeat_instants.0 = Some(Instant::now());
-
- Ok(())
- }
- }
-
- pub(crate) fn autoreconnect(&mut self) -> Result<()> {
- if self.stage == ConnectionStage::Connecting {
- return Ok(());
- }
-
- if self.session_id.is_some() {
- debug!("[Shard {:?}] Autoreconnector choosing to resume",
- self.shard_info);
-
- self.resume()
- } else {
- debug!("[Shard {:?}] Autoreconnector choosing to reconnect",
- self.shard_info);
-
- self.reconnect()
- }
- }
-
- /// Retrieves the `heartbeat_interval`.
- #[inline]
- pub(crate) fn heartbeat_interval(&self) -> Option<u64> {
- self.heartbeat_interval
- }
-
- /// Retrieves the value of when the last heartbeat ack was received.
- #[inline]
- pub(crate) fn last_heartbeat_ack(&self) -> Option<Instant> {
- self.heartbeat_instants.1
- }
-
- fn reconnect(&mut self) -> Result<()> {
- info!("[Shard {:?}] Attempting to reconnect", self.shard_info);
- self.reset();
-
- self.initialize()
- }
-
- // Attempts to send a RESUME message.
- //
- // # Examples
+ // Sets the shard as going into identifying stage, which sets:
//
- // Returns a `GatewayError::NoSessionId` is there is no `session_id`,
- // indicating that the shard should instead [`reconnect`].
- //
- // [`reconnect`]: #method.reconnect
- fn resume(&mut self) -> Result<()> {
- debug!("Shard {:?}] Attempting to resume", self.shard_info);
-
- self.initialize()?;
- self.stage = ConnectionStage::Resuming;
-
- self.send_resume().or_else(|why| {
- warn!("[Shard {:?}] Err sending resume: {:?}",
- self.shard_info,
- why);
+ // - the time that the last heartbeat sent as being now
+ // - the `stage` to `Identifying`
+ pub fn identify(&mut self) -> Result<()> {
+ self.client.send_identify(&self.shard_info, &self.token.lock())?;
- self.reconnect()
- })
- }
-
- fn send_resume(&mut self) -> Result<()> {
- let session_id = match self.session_id.clone() {
- Some(session_id) => session_id,
- None => return Err(Error::Gateway(GatewayError::NoSessionId)),
- };
-
- debug!("[Shard {:?}] Sending resume; seq: {}",
- self.shard_info,
- self.seq);
+ self.heartbeat_instants.0 = Some(Instant::now());
+ self.stage = ConnectionStage::Identifying;
- self.client.send_json(&json!({
- "op": OpCode::Resume.num(),
- "d": {
- "session_id": session_id,
- "seq": self.seq,
- "token": &*self.token.lock().unwrap(),
- },
- }))
+ Ok(())
}
/// Initializes a new WebSocket client.
///
/// This will set the stage of the shard before and after instantiation of
/// the client.
- fn initialize(&mut self) -> Result<()> {
+ pub fn initialize(&mut self) -> Result<WsClient> {
debug!("[Shard {:?}] Initializing", self.shard_info);
// We need to do two, sort of three things here:
@@ -983,81 +942,56 @@ impl Shard {
// This is used to accurately assess whether the state of the shard is
// accurate when a Hello is received.
self.stage = ConnectionStage::Connecting;
- self.client = connect(&self.ws_url.lock().unwrap())?;
+ let mut client = connect(&self.ws_url.lock())?;
self.stage = ConnectionStage::Handshake;
- Ok(())
- }
-
- fn identify(&mut self) -> Result<()> {
- let identification = json!({
- "op": OpCode::Identify.num(),
- "d": {
- "compression": true,
- "large_threshold": constants::LARGE_THRESHOLD,
- "shard": self.shard_info,
- "token": &*self.token.lock().unwrap(),
- "v": constants::GATEWAY_VERSION,
- "properties": {
- "$browser": "serenity",
- "$device": "serenity",
- "$os": consts::OS,
- },
- },
- });
-
- self.heartbeat_instants.0 = Some(Instant::now());
- self.stage = ConnectionStage::Identifying;
+ let _ = set_client_timeout(&mut client);
- debug!("[Shard {:?}] Identifying", self.shard_info);
-
- self.client.send_json(&identification)
+ Ok(client)
}
- fn reset(&mut self) {
+ pub fn reset(&mut self) {
self.heartbeat_instants = (Some(Instant::now()), None);
self.heartbeat_interval = None;
self.last_heartbeat_acknowledged = true;
+ self.session_id = None;
self.stage = ConnectionStage::Disconnected;
self.seq = 0;
}
- fn update_presence(&mut self) {
- let (ref game, status, afk) = self.current_presence;
- let now = Utc::now().timestamp() as u64;
-
- let msg = json!({
- "op": OpCode::StatusUpdate.num(),
- "d": {
- "afk": afk,
- "since": now,
- "status": status.name(),
- "game": game.as_ref().map(|x| json!({
- "name": x.name,
- "type": x.kind,
- "url": x.url,
- })),
- },
- });
+ pub fn resume(&mut self) -> Result<()> {
+ debug!("Shard {:?}] Attempting to resume", self.shard_info);
- debug!("[Shard {:?}] Sending presence update", self.shard_info);
+ self.client = self.initialize()?;
+ self.stage = ConnectionStage::Resuming;
- if let Err(why) = self.client.send_json(&msg) {
- warn!("[Shard {:?}] Err sending presence update: {:?}",
- self.shard_info,
- why);
+ match self.session_id.as_ref() {
+ Some(session_id) => {
+ self.client.send_resume(
+ &self.shard_info,
+ session_id,
+ &self.seq,
+ &self.token.lock(),
+ )
+ },
+ None => Err(Error::Gateway(GatewayError::NoSessionId)),
}
+ }
- #[cfg(feature = "cache")]
- {
- let mut cache = CACHE.write().unwrap();
- let current_user_id = cache.user.id;
+ pub fn reconnect(&mut self) -> Result<()> {
+ info!("[Shard {:?}] Attempting to reconnect", self.shard_info());
- cache.presences.get_mut(&current_user_id).map(|presence| {
- presence.game = game.clone();
- presence.last_modified = Some(now);
- });
- }
+ self.reset();
+ self.client = self.initialize()?;
+
+ Ok(())
+ }
+
+ pub fn update_presence(&mut self) -> Result<()> {
+ self.client.send_presence_update(
+ &self.shard_info,
+ &self.current_presence,
+ )
}
}
@@ -1084,18 +1018,3 @@ fn build_gateway_url(base: &str) -> Result<Url> {
Error::Gateway(GatewayError::BuildingUrl)
})
}
-
-/// Tries to connect and upon failure, retries.
-fn connecting(uri: &str) -> WsClient {
- let waiting_time = 30;
-
- loop {
- match connect(&uri) {
- Ok(client) => return client,
- Err(why) => {
- warn!("Connecting failed: {:?}\n Will retry in {} seconds.", why, waiting_time);
- thread::sleep(StdDuration::from_secs(waiting_time));
- },
- };
- }
-}
diff --git a/src/gateway/ws_client_ext.rs b/src/gateway/ws_client_ext.rs
new file mode 100644
index 0000000..873d114
--- /dev/null
+++ b/src/gateway/ws_client_ext.rs
@@ -0,0 +1,133 @@
+use chrono::Utc;
+use constants::{self, OpCode};
+use gateway::{CurrentPresence, WsClient};
+use internal::prelude::*;
+use internal::ws_impl::SenderExt;
+use model::GuildId;
+use std::env::consts;
+
+pub trait WebSocketGatewayClientExt {
+ fn send_chunk_guilds<It>(
+ &mut self,
+ guild_ids: It,
+ shard_info: &[u64; 2],
+ limit: Option<u16>,
+ query: Option<&str>,
+ ) -> Result<()> where It: IntoIterator<Item=GuildId>;
+
+ fn send_heartbeat(&mut self, shard_info: &[u64; 2], seq: Option<u64>)
+ -> Result<()>;
+
+ fn send_identify(&mut self, shard_info: &[u64; 2], token: &str)
+ -> Result<()>;
+
+ fn send_presence_update(
+ &mut self,
+ shard_info: &[u64; 2],
+ current_presence: &CurrentPresence,
+ ) -> Result<()>;
+
+ fn send_resume(
+ &mut self,
+ shard_info: &[u64; 2],
+ session_id: &str,
+ seq: &u64,
+ token: &str,
+ ) -> Result<()>;
+}
+
+impl WebSocketGatewayClientExt for WsClient {
+ fn send_chunk_guilds<It>(
+ &mut self,
+ guild_ids: It,
+ shard_info: &[u64; 2],
+ limit: Option<u16>,
+ query: Option<&str>,
+ ) -> Result<()> where It: IntoIterator<Item=GuildId> {
+ debug!("[Shard {:?}] Requesting member chunks", shard_info);
+
+ self.send_json(&json!({
+ "op": OpCode::GetGuildMembers.num(),
+ "d": {
+ "guild_id": guild_ids.into_iter().map(|x| x.as_ref().0).collect::<Vec<u64>>(),
+ "limit": limit.unwrap_or(0),
+ "query": query.unwrap_or(""),
+ },
+ })).map_err(From::from)
+ }
+
+ fn send_heartbeat(&mut self, shard_info: &[u64; 2], seq: Option<u64>)
+ -> Result<()> {
+ trace!("[Shard {:?}] Sending heartbeat d: {:?}", shard_info, seq);
+
+ self.send_json(&json!({
+ "d": seq,
+ "op": OpCode::Heartbeat.num(),
+ })).map_err(From::from)
+ }
+
+ fn send_identify(&mut self, shard_info: &[u64; 2], token: &str)
+ -> Result<()> {
+ debug!("[Shard {:?}] Identifying", shard_info);
+
+ self.send_json(&json!({
+ "op": OpCode::Identify.num(),
+ "d": {
+ "compression": true,
+ "large_threshold": constants::LARGE_THRESHOLD,
+ "shard": shard_info,
+ "token": token,
+ "v": constants::GATEWAY_VERSION,
+ "properties": {
+ "$browser": "serenity",
+ "$device": "serenity",
+ "$os": consts::OS,
+ },
+ },
+ }))
+ }
+
+ fn send_presence_update(
+ &mut self,
+ shard_info: &[u64; 2],
+ current_presence: &CurrentPresence,
+ ) -> Result<()> {
+ let &(ref game, ref status) = current_presence;
+ let now = Utc::now().timestamp() as u64;
+
+ debug!("[Shard {:?}] Sending presence update", shard_info);
+
+ self.send_json(&json!({
+ "op": OpCode::StatusUpdate.num(),
+ "d": {
+ "afk": false,
+ "since": now,
+ "status": status.name(),
+ "game": game.as_ref().map(|x| json!({
+ "name": x.name,
+ "type": x.kind,
+ "url": x.url,
+ })),
+ },
+ }))
+ }
+
+ fn send_resume(
+ &mut self,
+ shard_info: &[u64; 2],
+ session_id: &str,
+ seq: &u64,
+ token: &str,
+ ) -> Result<()> {
+ debug!("[Shard {:?}] Sending resume; seq: {}", shard_info, seq);
+
+ self.send_json(&json!({
+ "op": OpCode::Resume.num(),
+ "d": {
+ "session_id": session_id,
+ "seq": seq,
+ "token": token,
+ },
+ })).map_err(From::from)
+ }
+}
diff --git a/src/http/mod.rs b/src/http/mod.rs
index 099a33c..239c777 100644
--- a/src/http/mod.rs
+++ b/src/http/mod.rs
@@ -38,6 +38,7 @@ use hyper::net::HttpsConnector;
use hyper::{header, Error as HyperError, Result as HyperResult, Url};
use hyper_native_tls::NativeTlsClient;
use multipart::client::Multipart;
+use parking_lot::Mutex;
use self::ratelimiting::Route;
use serde_json;
use std::collections::BTreeMap;
@@ -46,7 +47,7 @@ use std::fmt::Write as FmtWrite;
use std::fs::File;
use std::io::{ErrorKind as IoErrorKind, Read};
use std::path::{Path, PathBuf};
-use std::sync::{Arc, Mutex};
+use std::sync::Arc;
use constants;
use internal::prelude::*;
use model::*;
@@ -98,7 +99,7 @@ lazy_static! {
/// # fn main() {
/// # try_main().unwrap();
/// # }
-pub fn set_token(token: &str) { TOKEN.lock().unwrap().clone_from(&token.to_string()); }
+pub fn set_token(token: &str) { TOKEN.lock().clone_from(&token.to_string()); }
/// Adds a [`User`] as a recipient to a [`Group`].
///
@@ -625,7 +626,12 @@ pub fn delete_role(guild_id: u64, role_id: u64) -> Result<()> {
pub fn delete_webhook(webhook_id: u64) -> Result<()> {
verify(
204,
- request!(Route::WebhooksId, delete, "/webhooks/{}", webhook_id),
+ request!(
+ Route::WebhooksId(webhook_id),
+ delete,
+ "/webhooks/{}",
+ webhook_id,
+ ),
)
}
@@ -788,7 +794,7 @@ pub fn edit_profile(map: &JsonMap) -> Result<CurrentUser> {
let mut value = serde_json::from_reader::<HyperResponse, Value>(response)?;
if let Some(map) = value.as_object_mut() {
- if !TOKEN.lock().unwrap().starts_with("Bot ") {
+ if !TOKEN.lock().starts_with("Bot ") {
if let Some(Value::String(token)) = map.remove("token") {
set_token(&token);
}
@@ -873,7 +879,12 @@ pub fn edit_role_position(guild_id: u64, role_id: u64, position: u64) -> Result<
// external crates being incredibly messy and misleading in the end user's view.
pub fn edit_webhook(webhook_id: u64, map: &Value) -> Result<Webhook> {
let body = map.to_string();
- let response = request!(Route::WebhooksId, patch(body), "/webhooks/{}", webhook_id);
+ let response = request!(
+ Route::WebhooksId(webhook_id),
+ patch(body),
+ "/webhooks/{}",
+ webhook_id,
+ );
serde_json::from_reader::<HyperResponse, Webhook>(response)
.map_err(From::from)
@@ -1044,12 +1055,20 @@ pub fn get_bans(guild_id: u64) -> Result<Vec<Ban>> {
}
/// Gets all audit logs in a specific guild.
-pub fn get_audit_logs(guild_id: u64) -> Result<AuditLogs> {
+pub fn get_audit_logs(guild_id: u64,
+ action_type: Option<u8>,
+ user_id: Option<u64>,
+ before: Option<u64>,
+ limit: Option<u8>) -> Result<AuditLogs> {
let response = request!(
Route::GuildsIdAuditLogs(guild_id),
get,
- "/guilds/{}/audit-logs",
- guild_id
+ "/guilds/{}/audit-logs?user_id={}&action_type={}&before={}&limit={}",
+ guild_id,
+ user_id.unwrap_or(0),
+ action_type.unwrap_or(0),
+ before.unwrap_or(0),
+ limit.unwrap_or(50),
);
serde_json::from_reader::<HyperResponse, AuditLogs>(response)
@@ -1536,7 +1555,12 @@ pub fn get_voice_regions() -> Result<Vec<VoiceRegion>> {
///
/// [`get_webhook_with_token`]: fn.get_webhook_with_token.html
pub fn get_webhook(webhook_id: u64) -> Result<Webhook> {
- let response = request!(Route::WebhooksId, get, "/webhooks/{}", webhook_id);
+ let response = request!(
+ Route::WebhooksId(webhook_id),
+ get,
+ "/webhooks/{}",
+ webhook_id,
+ );
serde_json::from_reader::<HyperResponse, Webhook>(response)
.map_err(From::from)
@@ -1642,7 +1666,7 @@ pub fn send_files<'a, T, It: IntoIterator<Item=T>>(channel_id: u64, files: It, m
let mut request = Request::with_connector(Method::Post, url, &connector)?;
request
.headers_mut()
- .set(header::Authorization(TOKEN.lock().unwrap().clone()));
+ .set(header::Authorization(TOKEN.lock().clone()));
request
.headers_mut()
.set(header::UserAgent(constants::USER_AGENT.to_string()));
@@ -1650,7 +1674,7 @@ pub fn send_files<'a, T, It: IntoIterator<Item=T>>(channel_id: u64, files: It, m
let mut request = Multipart::from_request(request)?;
let mut file_num = "0".to_string();
- for file in files {
+ for file in files.into_iter() {
match file.into() {
AttachmentType::Bytes((mut bytes, filename)) => {
request
@@ -1801,7 +1825,7 @@ pub fn unpin_message(channel_id: u64, message_id: u64) -> Result<()> {
fn request<'a, F>(route: Route, f: F) -> Result<HyperResponse>
where F: Fn() -> RequestBuilder<'a> {
let response = ratelimiting::perform(route, || {
- f().header(header::Authorization(TOKEN.lock().unwrap().clone()))
+ f().header(header::Authorization(TOKEN.lock().clone()))
.header(header::ContentType::json())
})?;
diff --git a/src/http/ratelimiting.rs b/src/http/ratelimiting.rs
index 8008616..08dc9ee 100644
--- a/src/http/ratelimiting.rs
+++ b/src/http/ratelimiting.rs
@@ -44,8 +44,9 @@ use chrono::Utc;
use hyper::client::{RequestBuilder, Response};
use hyper::header::Headers;
use hyper::status::StatusCode;
+use parking_lot::Mutex;
use std::collections::HashMap;
-use std::sync::{Arc, Mutex};
+use std::sync::Arc;
use std::time::Duration;
use std::{str, thread, i64};
use super::{HttpError, LightMethod};
@@ -80,10 +81,8 @@ lazy_static! {
/// ```rust,no_run
/// use serenity::http::ratelimiting::{ROUTES, Route};
///
- /// let routes = ROUTES.lock().unwrap();
- ///
- /// if let Some(route) = routes.get(&Route::ChannelsId(7)) {
- /// println!("Reset time at: {}", route.lock().unwrap().reset);
+ /// if let Some(route) = ROUTES.lock().get(&Route::ChannelsId(7)) {
+ /// println!("Reset time at: {}", route.lock().reset);
/// }
/// ```
///
@@ -338,9 +337,9 @@ pub enum Route {
/// Route for the `/voice/regions` path.
VoiceRegions,
/// Route for the `/webhooks/:webhook_id` path.
- WebhooksId,
- /// Route where no ratelimit headers are in place (e.g. current application
- /// info retrieval).
+ WebhooksId(u64),
+ /// Route where no ratelimit headers are in place (i.e. user account-only
+ /// routes).
///
/// This is a special case, in that if the route is `None` then pre- and
/// post-hooks are not executed.
@@ -352,7 +351,7 @@ pub(crate) fn perform<'a, F>(route: Route, f: F) -> Result<Response>
loop {
// This will block if another thread already has the global
// unlocked already (due to receiving an x-ratelimit-global).
- let _ = GLOBAL.lock().expect("global route lock poisoned");
+ let _ = GLOBAL.lock();
// Perform pre-checking here:
//
@@ -364,7 +363,6 @@ pub(crate) fn perform<'a, F>(route: Route, f: F) -> Result<Response>
// - then, perform the request
let bucket = Arc::clone(ROUTES
.lock()
- .expect("routes poisoned")
.entry(route)
.or_insert_with(|| {
Arc::new(Mutex::new(RateLimit {
@@ -374,7 +372,7 @@ pub(crate) fn perform<'a, F>(route: Route, f: F) -> Result<Response>
}))
}));
- let mut lock = bucket.lock().unwrap();
+ let mut lock = bucket.lock();
lock.pre_hook(&route);
let response = super::retry(&f)?;
@@ -396,7 +394,7 @@ pub(crate) fn perform<'a, F>(route: Route, f: F) -> Result<Response>
return Ok(response);
} else {
let redo = if response.headers.get_raw("x-ratelimit-global").is_some() {
- let _ = GLOBAL.lock().expect("global route lock poisoned");
+ let _ = GLOBAL.lock();
Ok(
if let Some(retry_after) = parse_header(&response.headers, "retry-after")? {
diff --git a/src/internal/macros.rs b/src/internal/macros.rs
index 92a21c1..3bcc5c5 100644
--- a/src/internal/macros.rs
+++ b/src/internal/macros.rs
@@ -182,6 +182,7 @@ macro_rules! enum_number {
}
}
+#[allow(unused_macros)]
macro_rules! try_opt {
($x:expr) => (match $x {
Some(v) => v,
diff --git a/src/internal/rwlock_ext.rs b/src/internal/rwlock_ext.rs
index 8266cdf..6235370 100644
--- a/src/internal/rwlock_ext.rs
+++ b/src/internal/rwlock_ext.rs
@@ -1,3 +1,4 @@
+use parking_lot::RwLock as ParkingLotRwLock;
use std::sync::{Arc, RwLock};
pub trait RwLockExt<T> {
@@ -16,3 +17,15 @@ impl<T> RwLockExt<T> for Arc<RwLock<T>> {
f(&mut w)
}
}
+
+impl<T> RwLockExt<T> for Arc<ParkingLotRwLock<T>> {
+ fn with<Y, F: Fn(&T) -> Y>(&self, f: F) -> Y {
+ let r = self.read();
+ f(&r)
+ }
+
+ fn with_mut<Y, F: FnMut(&mut T) -> Y>(&self, mut f: F) -> Y {
+ let mut w = self.write();
+ f(&mut w)
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 7e5609a..c207e44 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -109,6 +109,7 @@ extern crate serde_json;
extern crate lazy_static;
extern crate chrono;
+extern crate parking_lot;
extern crate serde;
#[cfg(feature = "base64")]
@@ -127,8 +128,6 @@ extern crate multipart;
extern crate native_tls;
#[cfg(feature = "opus")]
extern crate opus;
-#[cfg(feature = "parking_lot")]
-extern crate parking_lot;
#[cfg(feature = "sodiumoxide")]
extern crate sodiumoxide;
#[cfg(feature = "threadpool")]
@@ -174,7 +173,7 @@ pub use client::Client;
#[cfg(feature = "cache")]
use cache::Cache;
#[cfg(feature = "cache")]
-use std::sync::RwLock;
+use parking_lot::RwLock;
#[cfg(feature = "cache")]
lazy_static! {
@@ -199,16 +198,9 @@ lazy_static! {
/// ```rust,ignore
/// use serenity::CACHE;
///
- /// println!("{}", CACHE.read().unwrap().user.id);
+ /// println!("{}", CACHE.read().user.id);
/// ```
///
- /// By `unwrap()`ing, the thread managing an event dispatch will be blocked
- /// until the guard can be opened.
- ///
- /// If you do not want to block the current thread, you may instead use
- /// `RwLock::try_read`. Refer to `RwLock`'s documentation in the stdlib for
- /// more information.
- ///
/// [`CurrentUser`]: model/struct.CurrentUser.html
/// [`Cache`]: cache/struct.Cache.html
/// [cache module documentation]: cache/index.html
diff --git a/src/model/channel/attachment.rs b/src/model/channel/attachment.rs
index eac513f..f520cd4 100644
--- a/src/model/channel/attachment.rs
+++ b/src/model/channel/attachment.rs
@@ -54,7 +54,7 @@ impl Attachment {
///
///
/// impl EventHandler for Handler {
- /// fn on_message(&self, _: Context, message: Message) {
+ /// fn message(&self, _: Context, message: Message) {
/// for attachment in message.attachments {
/// let content = match attachment.download() {
/// Ok(content) => content,
@@ -86,12 +86,14 @@ impl Attachment {
/// }
/// }
///
- /// fn on_ready(&self, _: Context, ready: Ready) {
+ /// fn ready(&self, _: Context, ready: Ready) {
/// println!("{} is connected!", ready.user.name);
/// }
/// }
/// let token = env::var("DISCORD_TOKEN").expect("token in environment");
- /// let mut client = Client::new(&token, Handler); client.start().unwrap();
+ /// let mut client = Client::new(&token, Handler).unwrap();
+ ///
+ /// client.start().unwrap();
/// ```
///
/// # Errors
diff --git a/src/model/channel/channel_category.rs b/src/model/channel/channel_category.rs
index f567cee..73c50a7 100644
--- a/src/model/channel/channel_category.rs
+++ b/src/model/channel/channel_category.rs
@@ -93,20 +93,14 @@ impl ChannelCategory {
}
}
- let mut map = Map::new();
- map.insert("name".to_string(), Value::String(self.name.clone()));
- map.insert(
- "position".to_string(),
- Value::Number(Number::from(self.position)),
- );
- map.insert(
- "type".to_string(),
- Value::String(self.kind.name().to_string()),
- );
+ let mut map = HashMap::new();
+ map.insert("name", Value::String(self.name.clone()));
+ map.insert("position", Value::Number(Number::from(self.position)));
+ map.insert("type", Value::String(self.kind.name().to_string()));
- let edited = f(EditChannel(map)).0;
+ let map = serenity_utils::hashmap_to_json_map(f(EditChannel(map)).0);
- http::edit_channel(self.id.0, &edited).map(|channel| {
+ http::edit_channel(self.id.0, &map).map(|channel| {
let GuildChannel {
id,
category_id,
diff --git a/src/model/channel/channel_id.rs b/src/model/channel/channel_id.rs
index 2e415dc..158ebcf 100644
--- a/src/model/channel/channel_id.rs
+++ b/src/model/channel/channel_id.rs
@@ -11,6 +11,8 @@ use builder::{CreateMessage, EditChannel, GetMessages};
use CACHE;
#[cfg(feature = "model")]
use http::{self, AttachmentType};
+#[cfg(feature = "model")]
+use utils;
#[cfg(feature = "model")]
impl ChannelId {
@@ -189,7 +191,9 @@ impl ChannelId {
/// [Manage Channel]: permissions/constant.MANAGE_CHANNELS.html
#[inline]
pub fn edit<F: FnOnce(EditChannel) -> EditChannel>(&self, f: F) -> Result<GuildChannel> {
- http::edit_channel(self.0, &f(EditChannel::default()).0)
+ let map = utils::hashmap_to_json_map(f(EditChannel::default()).0);
+
+ http::edit_channel(self.0, &map)
}
/// Edits a [`Message`] in the channel given its Id.
@@ -213,9 +217,9 @@ impl ChannelId {
/// [`the limit`]: ../builder/struct.CreateMessage.html#method.content
pub fn edit_message<F, M>(&self, message_id: M, f: F) -> Result<Message>
where F: FnOnce(CreateMessage) -> CreateMessage, M: Into<MessageId> {
- let map = f(CreateMessage::default()).0;
+ let msg = f(CreateMessage::default());
- if let Some(content) = map.get("content") {
+ if let Some(content) = msg.0.get("content") {
if let Value::String(ref content) = *content {
if let Some(length_over) = Message::overflow_length(content) {
return Err(Error::Model(ModelError::MessageTooLong(length_over)));
@@ -223,19 +227,21 @@ impl ChannelId {
}
}
+ let map = utils::hashmap_to_json_map(msg.0);
+
http::edit_message(self.0, message_id.into().0, &Value::Object(map))
}
/// Search the cache for the channel with the Id.
#[cfg(feature = "cache")]
- pub fn find(&self) -> Option<Channel> { CACHE.read().unwrap().channel(*self) }
+ pub fn find(&self) -> Option<Channel> { CACHE.read().channel(*self) }
/// Search the cache for the channel. If it can't be found, the channel is
/// requested over REST.
pub fn get(&self) -> Result<Channel> {
#[cfg(feature = "cache")]
{
- if let Some(channel) = CACHE.read().unwrap().channel(*self) {
+ if let Some(channel) = CACHE.read().channel(*self) {
return Ok(channel);
}
}
@@ -315,13 +321,13 @@ impl ChannelId {
};
Some(match channel {
- Guild(channel) => channel.read().unwrap().name().to_string(),
- Group(channel) => match channel.read().unwrap().name() {
+ Guild(channel) => channel.read().name().to_string(),
+ Group(channel) => match channel.read().name() {
Cow::Borrowed(name) => name.to_string(),
Cow::Owned(name) => name,
},
- Category(category) => category.read().unwrap().name().to_string(),
- Private(channel) => channel.read().unwrap().name(),
+ Category(category) => category.read().name().to_string(),
+ Private(channel) => channel.read().name(),
})
}
@@ -445,9 +451,9 @@ impl ChannelId {
/// [Send Messages]: permissions/constant.SEND_MESSAGES.html
pub fn send_files<'a, F, T, It: IntoIterator<Item=T>>(&self, files: It, f: F) -> Result<Message>
where F: FnOnce(CreateMessage) -> CreateMessage, T: Into<AttachmentType<'a>> {
- let mut map = f(CreateMessage::default()).0;
+ let mut msg = f(CreateMessage::default());
- if let Some(content) = map.get("content") {
+ if let Some(content) = msg.0.get("content") {
if let Value::String(ref content) = *content {
if let Some(length_over) = Message::overflow_length(content) {
return Err(Error::Model(ModelError::MessageTooLong(length_over)));
@@ -455,7 +461,8 @@ impl ChannelId {
}
}
- let _ = map.remove("embed");
+ let _ = msg.0.remove("embed");
+ let map = utils::hashmap_to_json_map(msg.0);
http::send_files(self.0, files, map)
}
@@ -481,14 +488,15 @@ impl ChannelId {
/// [Send Messages]: permissions/constant.SEND_MESSAGES.html
pub fn send_message<F>(&self, f: F) -> Result<Message>
where F: FnOnce(CreateMessage) -> CreateMessage {
- let CreateMessage(map, reactions) = f(CreateMessage::default());
+ let msg = f(CreateMessage::default());
+ let map = utils::hashmap_to_json_map(msg.0);
Message::check_content_length(&map)?;
Message::check_embed_length(&map)?;
let message = http::send_message(self.0, &Value::Object(map))?;
- if let Some(reactions) = reactions {
+ if let Some(reactions) = msg.1 {
for reaction in reactions {
self.create_reaction(message.id, reaction)?;
}
diff --git a/src/model/channel/embed.rs b/src/model/channel/embed.rs
index 435f706..401f10d 100644
--- a/src/model/channel/embed.rs
+++ b/src/model/channel/embed.rs
@@ -4,6 +4,8 @@ use utils::Colour;
use internal::prelude::*;
#[cfg(feature = "model")]
use builder::CreateEmbed;
+#[cfg(feature = "model")]
+use utils;
/// Represents a rich embed which allows using richer markdown, multiple fields
/// and more. This was heavily inspired by [slack's attachments].
@@ -81,15 +83,14 @@ impl Embed {
/// let embed = Embed::fake(|e| e
/// .title("Embed title")
/// .description("Making a basic embed")
- /// .field(|f| f
- /// .name("A field")
- /// .value("Has some content.")
- /// .inline(false)));
+ /// .field("A field", "Has some content.", false));
/// ```
#[inline]
pub fn fake<F>(f: F) -> Value
where F: FnOnce(CreateEmbed) -> CreateEmbed {
- Value::Object(f(CreateEmbed::default()).0)
+ let map = utils::hashmap_to_json_map(f(CreateEmbed::default()).0);
+
+ Value::Object(map)
}
}
@@ -123,6 +124,24 @@ pub struct EmbedField {
pub value: String,
}
+impl EmbedField {
+ /// Creates a new embed field.
+ ///
+ /// **Note**: Refer to the [`name`] and [`value`] documentation for maximum
+ /// lengths.
+ ///
+ /// [`name`]: #structfield.name
+ /// [`value`]: #structfield.value
+ pub fn new<T, U>(name: T, value: U, inline: bool) -> Self
+ where T: Into<String>, U: Into<String> {
+ Self {
+ name: name.into(),
+ value: value.into(),
+ inline,
+ }
+ }
+}
+
/// Footer information for an embed.
#[derive(Clone, Debug, Deserialize)]
pub struct EmbedFooter {
diff --git a/src/model/channel/guild_channel.rs b/src/model/channel/guild_channel.rs
index b7295fc..76cea47 100644
--- a/src/model/channel/guild_channel.rs
+++ b/src/model/channel/guild_channel.rs
@@ -116,7 +116,9 @@ impl GuildChannel {
}
}
- http::create_invite(self.id.0, &f(CreateInvite::default()).0)
+ let map = serenity_utils::hashmap_to_json_map(f(CreateInvite::default()).0);
+
+ http::create_invite(self.id.0, &map)
}
/// Creates a [permission overwrite][`PermissionOverwrite`] for either a
@@ -157,12 +159,12 @@ impl GuildChannel {
/// kind: PermissionOverwriteType::Member(user_id),
/// };
///
- /// let cache = CACHE.read().unwrap();
+ /// let cache = CACHE.read();
/// let channel = cache
/// .guild_channel(channel_id)
/// .ok_or(ModelError::ItemMissing)?;
///
- /// channel.read().unwrap().create_permission(&overwrite)?;
+ /// channel.read().create_permission(&overwrite)?;
/// # Ok(())
/// # }
/// #
@@ -199,12 +201,12 @@ impl GuildChannel {
/// kind: PermissionOverwriteType::Member(user_id),
/// };
///
- /// let cache = CACHE.read().unwrap();
+ /// let cache = CACHE.read();
/// let channel = cache
/// .guild_channel(channel_id)
/// .ok_or(ModelError::ItemMissing)?;
///
- /// channel.read().unwrap().create_permission(&overwrite)?;
+ /// channel.read().create_permission(&overwrite)?;
/// # Ok(())
/// # }
/// #
@@ -312,18 +314,12 @@ impl GuildChannel {
}
}
- let mut map = Map::new();
- map.insert("name".to_string(), Value::String(self.name.clone()));
- map.insert(
- "position".to_string(),
- Value::Number(Number::from(self.position)),
- );
- map.insert(
- "type".to_string(),
- Value::String(self.kind.name().to_string()),
- );
+ let mut map = HashMap::new();
+ map.insert("name", Value::String(self.name.clone()));
+ map.insert("position", Value::Number(Number::from(self.position)));
+ map.insert("type", Value::String(self.kind.name().to_string()));
- let edited = f(EditChannel(map)).0;
+ let edited = serenity_utils::hashmap_to_json_map(f(EditChannel(map)).0);
match http::edit_channel(self.id.0, &edited) {
Ok(channel) => {
@@ -365,7 +361,7 @@ impl GuildChannel {
/// **Note**: Right now this performs a clone of the guild. This will be
/// optimized in the future.
#[cfg(feature = "cache")]
- pub fn guild(&self) -> Option<Arc<RwLock<Guild>>> { CACHE.read().unwrap().guild(self.guild_id) }
+ pub fn guild(&self) -> Option<Arc<RwLock<Guild>>> { CACHE.read().guild(self.guild_id) }
/// Gets all of the channel's invites.
///
@@ -435,18 +431,20 @@ impl GuildChannel {
/// use serenity::CACHE;
///
/// impl EventHandler for Handler {
- /// fn on_message(&self, _: Context, msg: Message) {
- /// let channel = match CACHE.read().unwrap().guild_channel(msg.channel_id) {
+ /// fn message(&self, _: Context, msg: Message) {
+ /// let channel = match CACHE.read().guild_channel(msg.channel_id) {
/// Some(channel) => channel,
/// None => return,
/// };
///
- /// let permissions = channel.read().unwrap().permissions_for(&msg.author).unwrap();
+ /// let permissions = channel.read().permissions_for(&msg.author).unwrap();
///
/// println!("The user's permissions: {:?}", permissions);
/// }
/// }
- /// let mut client = Client::new("token", Handler); client.start().unwrap();
+ /// let mut client = Client::new("token", Handler).unwrap();
+ ///
+ /// client.start().unwrap();
/// ```
///
/// Check if the current user has the [Attach Files] and [Send Messages]
@@ -463,15 +461,15 @@ impl GuildChannel {
/// struct Handler;
///
/// impl EventHandler for Handler {
- /// fn on_message(&self, _: Context, msg: Message) {
- /// let channel = match CACHE.read().unwrap().guild_channel(msg.channel_id) {
+ /// fn message(&self, _: Context, msg: Message) {
+ /// let channel = match CACHE.read().guild_channel(msg.channel_id) {
/// Some(channel) => channel,
/// None => return,
/// };
///
- /// let current_user_id = CACHE.read().unwrap().user.id;
+ /// let current_user_id = CACHE.read().user.id;
/// let permissions =
- /// channel.read().unwrap().permissions_for(current_user_id).unwrap();
+ /// channel.read().permissions_for(current_user_id).unwrap();
///
/// if !permissions.contains(Permissions::ATTACH_FILES |
/// Permissions::SEND_MESSAGES) {
@@ -492,7 +490,9 @@ impl GuildChannel {
/// }
/// }
///
- /// let mut client = Client::new("token", Handler); client.start().unwrap();
+ /// let mut client = Client::new("token", Handler).unwrap();
+ ///
+ /// client.start().unwrap();
/// ```
///
/// # Errors
@@ -512,7 +512,7 @@ impl GuildChannel {
pub fn permissions_for<U: Into<UserId>>(&self, user_id: U) -> Result<Permissions> {
self.guild()
.ok_or_else(|| Error::Model(ModelError::GuildNotFound))
- .map(|g| g.read().unwrap().permissions_in(self.id, user_id))
+ .map(|g| g.read().permissions_in(self.id, user_id))
}
/// Pins a [`Message`] to the channel.
diff --git a/src/model/channel/message.rs b/src/model/channel/message.rs
index 480c15d..984e274 100644
--- a/src/model/channel/message.rs
+++ b/src/model/channel/message.rs
@@ -14,6 +14,8 @@ use constants;
use CACHE;
#[cfg(feature = "model")]
use http;
+#[cfg(feature = "model")]
+use utils as serenity_utils;
/// A representation of a message over a guild's text channel, a group, or a
/// private channel.
@@ -86,7 +88,7 @@ impl Message {
/// # struct Handler;
/// #
/// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
+ /// # let mut client = Client::new("token", Handler).unwrap();
/// #
/// use serenity::model::Channel;
/// use serenity::framework::StandardFramework;
@@ -97,12 +99,12 @@ impl Message {
///
/// command!(channel_name(_ctx, msg) {
/// let _ = match msg.channel() {
- /// Some(Channel::Category(c)) => msg.reply(&c.read().unwrap().name),
- /// Some(Channel::Group(c)) => msg.reply(&c.read().unwrap().name()),
- /// Some(Channel::Guild(c)) => msg.reply(&c.read().unwrap().name),
+ /// Some(Channel::Category(c)) => msg.reply(&c.read().name),
+ /// Some(Channel::Group(c)) => msg.reply(&c.read().name()),
+ /// Some(Channel::Guild(c)) => msg.reply(&c.read().name),
/// Some(Channel::Private(c)) => {
- /// let channel = c.read().unwrap();
- /// let user = channel.recipient.read().unwrap();
+ /// let channel = c.read();
+ /// let user = channel.recipient.read();
///
/// msg.reply(&format!("DM with {}", user.name.clone()))
/// },
@@ -113,12 +115,12 @@ impl Message {
/// ```
#[cfg(feature = "cache")]
#[inline]
- pub fn channel(&self) -> Option<Channel> { CACHE.read().unwrap().channel(self.channel_id) }
+ pub fn channel(&self) -> Option<Channel> { CACHE.read().channel(self.channel_id) }
/// A util function for determining whether this message was sent by someone else, or the
/// bot.
#[cfg(all(feature = "cache", feature = "utils"))]
- pub fn is_own(&self) -> bool { self.author.id == CACHE.read().unwrap().user.id }
+ pub fn is_own(&self) -> bool { self.author.id == CACHE.read().user.id }
/// Deletes the message.
///
@@ -138,7 +140,7 @@ impl Message {
#[cfg(feature = "cache")]
{
let req = Permissions::MANAGE_MESSAGES;
- let is_author = self.author.id == CACHE.read().unwrap().user.id;
+ let is_author = self.author.id == CACHE.read().user.id;
let has_perms = utils::user_has_perms(self.channel_id, req)?;
if !is_author && !has_perms {
@@ -211,7 +213,7 @@ impl Message {
where F: FnOnce(CreateMessage) -> CreateMessage {
#[cfg(feature = "cache")]
{
- if self.author.id != CACHE.read().unwrap().user.id {
+ if self.author.id != CACHE.read().user.id {
return Err(Error::Model(ModelError::InvalidUser));
}
}
@@ -230,7 +232,7 @@ impl Message {
builder = builder.tts(true);
}
- let map = f(builder).0;
+ let map = serenity_utils::hashmap_to_json_map(f(builder).0);
match http::edit_message(self.channel_id.0, self.id.0, &Value::Object(map)) {
Ok(edited) => {
@@ -335,7 +337,7 @@ impl Message {
#[cfg(feature = "cache")]
pub fn guild(&self) -> Option<Arc<RwLock<Guild>>> {
self.guild_id()
- .and_then(|guild_id| CACHE.read().unwrap().guild(guild_id))
+ .and_then(|guild_id| CACHE.read().guild(guild_id))
}
/// Retrieves the Id of the guild that the message was sent in, if sent in
@@ -345,8 +347,8 @@ impl Message {
/// cache.
#[cfg(feature = "cache")]
pub fn guild_id(&self) -> Option<GuildId> {
- match CACHE.read().unwrap().channel(self.channel_id) {
- Some(Channel::Guild(ch)) => Some(ch.read().unwrap().guild_id),
+ match CACHE.read().channel(self.channel_id) {
+ Some(Channel::Guild(ch)) => Some(ch.read().guild_id),
_ => None,
}
}
@@ -354,7 +356,7 @@ impl Message {
/// True if message was sent using direct messages.
#[cfg(feature = "cache")]
pub fn is_private(&self) -> bool {
- match CACHE.read().unwrap().channel(self.channel_id) {
+ match CACHE.read().channel(self.channel_id) {
Some(Channel::Group(_)) | Some(Channel::Private(_)) => true,
_ => false,
}
diff --git a/src/model/channel/mod.rs b/src/model/channel/mod.rs
index 07a73d7..ae46805 100644
--- a/src/model/channel/mod.rs
+++ b/src/model/channel/mod.rs
@@ -84,16 +84,16 @@ impl Channel {
pub fn delete(&self) -> Result<()> {
match *self {
Channel::Group(ref group) => {
- let _ = group.read().unwrap().leave()?;
+ let _ = group.read().leave()?;
},
Channel::Guild(ref public_channel) => {
- let _ = public_channel.read().unwrap().delete()?;
+ let _ = public_channel.read().delete()?;
},
Channel::Private(ref private_channel) => {
- let _ = private_channel.read().unwrap().delete()?;
+ let _ = private_channel.read().delete()?;
},
Channel::Category(ref category) => {
- category.read().unwrap().delete()?;
+ category.read().delete()?;
},
}
@@ -386,15 +386,15 @@ impl Display for Channel {
/// [`PrivateChannel`]: struct.PrivateChannel.html
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match *self {
- Channel::Group(ref group) => Display::fmt(&group.read().unwrap().name(), f),
- Channel::Guild(ref ch) => Display::fmt(&ch.read().unwrap().id.mention(), f),
+ Channel::Group(ref group) => Display::fmt(&group.read().name(), f),
+ Channel::Guild(ref ch) => Display::fmt(&ch.read().id.mention(), f),
Channel::Private(ref ch) => {
- let channel = ch.read().unwrap();
- let recipient = channel.recipient.read().unwrap();
+ let channel = ch.read();
+ let recipient = channel.recipient.read();
Display::fmt(&recipient.name, f)
},
- Channel::Category(ref category) => Display::fmt(&category.read().unwrap().name, f),
+ Channel::Category(ref category) => Display::fmt(&category.read().name, f),
}
}
}
diff --git a/src/model/channel/private_channel.rs b/src/model/channel/private_channel.rs
index d6ba781..01b387a 100644
--- a/src/model/channel/private_channel.rs
+++ b/src/model/channel/private_channel.rs
@@ -279,6 +279,6 @@ impl PrivateChannel {
impl Display for PrivateChannel {
/// Formats the private channel, displaying the recipient's username.
fn fmt(&self, f: &mut Formatter) -> FmtResult {
- f.write_str(&self.recipient.read().unwrap().name)
+ f.write_str(&self.recipient.read().name)
}
}
diff --git a/src/model/channel/reaction.rs b/src/model/channel/reaction.rs
index 8edc2e9..1a40ecb 100644
--- a/src/model/channel/reaction.rs
+++ b/src/model/channel/reaction.rs
@@ -48,7 +48,7 @@ impl Reaction {
pub fn delete(&self) -> Result<()> {
let user_id = feature_cache! {
{
- let user = if self.user_id == CACHE.read().unwrap().user.id {
+ let user = if self.user_id == CACHE.read().user.id {
None
} else {
Some(self.user_id.0)
diff --git a/src/model/error.rs b/src/model/error.rs
index 454b94b..c2f568b 100644
--- a/src/model/error.rs
+++ b/src/model/error.rs
@@ -27,7 +27,7 @@ use super::Permissions;
/// struct Handler;
///
/// impl EventHandler for Handler {
-/// fn on_guild_ban_removal(&self, context: Context, guild_id: GuildId, user: User) {
+/// fn guild_ban_removal(&self, context: Context, guild_id: GuildId, user: User) {
/// // If the user has an even discriminator, don't re-ban them.
/// if user.discriminator % 2 == 0 {
/// return;
@@ -46,8 +46,10 @@ use super::Permissions;
/// }
/// }
/// }
-/// let token = env::var("DISCORD_TOKEN")?;
-/// let mut client = Client::new(&token, Handler); client.start()?;
+/// let token = env::var("DISCORD_BOT_TOKEN")?;
+/// let mut client = Client::new(&token, Handler).unwrap();
+///
+/// client.start()?;
/// # Ok(())
/// # }
/// #
diff --git a/src/model/event.rs b/src/model/event.rs
index d4148b9..aea05b1 100644
--- a/src/model/event.rs
+++ b/src/model/event.rs
@@ -61,7 +61,7 @@ impl CacheUpdate for ChannelCreateEvent {
let channel_id = group.with_mut(|writer| {
for (recipient_id, recipient) in &mut writer.recipients {
- cache.update_user_entry(&recipient.read().unwrap());
+ cache.update_user_entry(&recipient.read());
*recipient = Arc::clone(&cache.users[recipient_id]);
}
@@ -110,7 +110,7 @@ impl CacheUpdate for ChannelCreateEvent {
},
Channel::Category(ref category) => cache
.categories
- .insert(category.read().unwrap().id, Arc::clone(category))
+ .insert(category.read().id, Arc::clone(category))
.map(Channel::Category),
}
}
@@ -214,7 +214,7 @@ impl CacheUpdate for ChannelRecipientAddEvent {
let user = Arc::clone(&cache.users[&self.user.id]);
cache.groups.get_mut(&self.channel_id).map(|group| {
- group.write().unwrap().recipients.insert(self.user.id, user);
+ group.write().recipients.insert(self.user.id, user);
});
None
@@ -263,16 +263,16 @@ impl CacheUpdate for ChannelUpdateEvent {
e.insert(Arc::clone(group));
},
Entry::Occupied(mut e) => {
- let mut dest = e.get_mut().write().unwrap();
+ let mut dest = e.get_mut().write();
if no_recipients {
let recipients = mem::replace(&mut dest.recipients, HashMap::new());
- dest.clone_from(&group.read().unwrap());
+ dest.clone_from(&group.read());
dest.recipients = recipients;
} else {
- dest.clone_from(&group.read().unwrap());
+ dest.clone_from(&group.read());
}
},
}
@@ -289,13 +289,13 @@ impl CacheUpdate for ChannelUpdateEvent {
Channel::Private(ref channel) => {
cache
.private_channels
- .get_mut(&channel.read().unwrap().id)
+ .get_mut(&channel.read().id)
.map(|private| private.clone_from(channel));
},
Channel::Category(ref category) => {
cache
.categories
- .get_mut(&category.read().unwrap().id)
+ .get_mut(&category.read().id)
.map(|c| c.clone_from(category));
},
}
@@ -340,7 +340,7 @@ impl CacheUpdate for GuildCreateEvent {
let mut guild = self.guild.clone();
for (user_id, member) in &mut guild.members {
- cache.update_user_entry(&member.user.read().unwrap());
+ cache.update_user_entry(&member.user.read());
let user = Arc::clone(&cache.users[user_id]);
member.user = Arc::clone(&user);
@@ -375,7 +375,7 @@ impl CacheUpdate for GuildDeleteEvent {
fn update(&mut self, cache: &mut Cache) -> Option<Self::Output> {
// Remove channel entries for the guild if the guild is found.
cache.guilds.remove(&self.guild.id).map(|guild| {
- for channel_id in guild.write().unwrap().channels.keys() {
+ for channel_id in guild.write().channels.keys() {
cache.channels.remove(channel_id);
}
@@ -430,7 +430,7 @@ impl CacheUpdate for GuildMemberAddEvent {
fn update(&mut self, cache: &mut Cache) -> Option<()> {
let user_id = self.member.user.with(|u| u.id);
- cache.update_user_entry(&self.member.user.read().unwrap());
+ cache.update_user_entry(&self.member.user.read());
// Always safe due to being inserted above.
self.member.user = Arc::clone(&cache.users[&user_id]);
@@ -500,7 +500,7 @@ impl CacheUpdate for GuildMemberUpdateEvent {
cache.update_user_entry(&self.user);
if let Some(guild) = cache.guilds.get_mut(&self.guild_id) {
- let mut guild = guild.write().unwrap();
+ let mut guild = guild.write();
let mut found = false;
@@ -509,7 +509,7 @@ impl CacheUpdate for GuildMemberUpdateEvent {
member.nick.clone_from(&self.nick);
member.roles.clone_from(&self.roles);
- member.user.write().unwrap().clone_from(&self.user);
+ member.user.write().clone_from(&self.user);
found = true;
@@ -552,7 +552,7 @@ impl CacheUpdate for GuildMembersChunkEvent {
fn update(&mut self, cache: &mut Cache) -> Option<()> {
for member in self.members.values() {
- cache.update_user_entry(&member.user.read().unwrap());
+ cache.update_user_entry(&member.user.read());
}
cache.guilds.get_mut(&self.guild_id).map(|guild| {
@@ -609,7 +609,6 @@ impl CacheUpdate for GuildRoleCreateEvent {
cache.guilds.get_mut(&self.guild_id).map(|guild| {
guild
.write()
- .unwrap()
.roles
.insert(self.role.id, self.role.clone())
});
@@ -685,7 +684,7 @@ impl CacheUpdate for GuildUpdateEvent {
fn update(&mut self, cache: &mut Cache) -> Option<()> {
cache.guilds.get_mut(&self.guild.id).map(|guild| {
- let mut guild = guild.write().unwrap();
+ let mut guild = guild.write();
guild.afk_timeout = self.guild.afk_timeout;
guild.afk_channel_id.clone_from(&self.guild.afk_channel_id);
@@ -768,13 +767,13 @@ impl CacheUpdate for PresenceUpdateEvent {
let user_id = self.presence.user_id;
if let Some(user) = self.presence.user.as_mut() {
- cache.update_user_entry(&user.read().unwrap());
+ cache.update_user_entry(&user.read());
*user = Arc::clone(&cache.users[&user_id]);
}
if let Some(guild_id) = self.guild_id {
if let Some(guild) = cache.guilds.get_mut(&guild_id) {
- let mut guild = guild.write().unwrap();
+ let mut guild = guild.write();
// If the member went offline, remove them from the presence list.
if self.presence.status == OnlineStatus::Offline {
@@ -920,7 +919,7 @@ impl CacheUpdate for ReadyEvent {
for (user_id, presence) in &mut ready.presences {
if let Some(ref user) = presence.user {
- cache.update_user_entry(&user.read().unwrap());
+ cache.update_user_entry(&user.read());
}
presence.user = cache.users.get(user_id).cloned();
@@ -1004,7 +1003,7 @@ impl CacheUpdate for VoiceStateUpdateEvent {
fn update(&mut self, cache: &mut Cache) -> Option<()> {
if let Some(guild_id) = self.guild_id {
if let Some(guild) = cache.guilds.get_mut(&guild_id) {
- let mut guild = guild.write().unwrap();
+ let mut guild = guild.write();
if self.voice_state.channel_id.is_some() {
// Update or add to the voice state list
@@ -1062,7 +1061,8 @@ pub enum GatewayEvent {
Dispatch(u64, Event),
Heartbeat(u64),
Reconnect,
- InvalidateSession,
+ /// Whether the session can be resumed.
+ InvalidateSession(bool),
Hello(u64),
HeartbeatAck,
}
@@ -1098,7 +1098,15 @@ impl GatewayEvent {
GatewayEvent::Heartbeat(s)
},
OpCode::Reconnect => GatewayEvent::Reconnect,
- OpCode::InvalidSession => GatewayEvent::InvalidateSession,
+ OpCode::InvalidSession => {
+ let resumable = map.remove("d")
+ .ok_or_else(|| {
+ DeError::custom("expected gateway invalid session d")
+ })
+ .and_then(bool::deserialize)?;
+
+ GatewayEvent::InvalidateSession(resumable)
+ },
OpCode::Hello => {
let mut d = map.remove("d")
.ok_or_else(|| DeError::custom("expected gateway hello d"))
diff --git a/src/model/gateway.rs b/src/model/gateway.rs
index 4edc20e..ca846e8 100644
--- a/src/model/gateway.rs
+++ b/src/model/gateway.rs
@@ -1,6 +1,7 @@
+use parking_lot::RwLock;
use serde::de::Error as DeError;
use serde_json;
-use std::sync::{Arc, RwLock};
+use std::sync::Arc;
use super::utils::*;
use super::*;
diff --git a/src/model/guild/audit_log.rs b/src/model/guild/audit_log.rs
index d019b61..b3caa73 100644
--- a/src/model/guild/audit_log.rs
+++ b/src/model/guild/audit_log.rs
@@ -1,4 +1,4 @@
-use super::super::{AuditLogEntryId, UserId};
+use super::super::{AuditLogEntryId, User, UserId, ChannelId, Webhook};
use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor};
use std::fmt;
use std::collections::HashMap;
@@ -6,7 +6,7 @@ use std::mem::transmute;
/// Determines to what entity an action was used on.
#[derive(Debug)]
-#[repr(i32)]
+#[repr(u8)]
pub enum Target {
Guild = 10,
Channel = 20,
@@ -28,10 +28,11 @@ pub enum Action {
Invite(ActionInvite),
Webhook(ActionWebhook),
Emoji(ActionEmoji),
+ MessageDelete,
}
#[derive(Debug)]
-#[repr(i32)]
+#[repr(u8)]
pub enum ActionChannel {
Create = 10,
Update = 11,
@@ -39,7 +40,7 @@ pub enum ActionChannel {
}
#[derive(Debug)]
-#[repr(i32)]
+#[repr(u8)]
pub enum ActionChannelOverwrite {
Create = 13,
Update = 14,
@@ -47,7 +48,7 @@ pub enum ActionChannelOverwrite {
}
#[derive(Debug)]
-#[repr(i32)]
+#[repr(u8)]
pub enum ActionMember {
Kick = 20,
Prune = 21,
@@ -58,7 +59,7 @@ pub enum ActionMember {
}
#[derive(Debug)]
-#[repr(i32)]
+#[repr(u8)]
pub enum ActionRole {
Create = 30,
Update = 31,
@@ -66,7 +67,7 @@ pub enum ActionRole {
}
#[derive(Debug)]
-#[repr(i32)]
+#[repr(u8)]
pub enum ActionInvite {
Create = 40,
Update = 41,
@@ -74,7 +75,7 @@ pub enum ActionInvite {
}
#[derive(Debug)]
-#[repr(i32)]
+#[repr(u8)]
pub enum ActionWebhook {
Create = 50,
Update = 51,
@@ -82,7 +83,7 @@ pub enum ActionWebhook {
}
#[derive(Debug)]
-#[repr(i32)]
+#[repr(u8)]
pub enum ActionEmoji {
Create = 60,
Delete = 61,
@@ -92,6 +93,7 @@ pub enum ActionEmoji {
#[derive(Debug, Deserialize)]
pub struct Change {
#[serde(rename = "key")] pub name: String,
+ // TODO: Change these to an actual type.
#[serde(rename = "old_value")] pub old: String,
#[serde(rename = "new_value")] pub new: String,
}
@@ -99,6 +101,8 @@ pub struct Change {
#[derive(Debug)]
pub struct AuditLogs {
pub entries: HashMap<AuditLogEntryId, AuditLogEntry>,
+ pub webhooks: Vec<Webhook>,
+ pub users: Vec<User>,
}
#[derive(Debug, Deserialize)]
@@ -106,8 +110,7 @@ pub struct AuditLogEntry {
/// Determines to what entity an [`action`] was used on.
///
/// [`action`]: #structfield.action
- #[serde(deserialize_with = "deserialize_target", rename = "target_type")]
- pub target: Target,
+ pub target_id: u64,
/// Determines what action was done on a [`target`]
///
/// [`target`]: #structfield.target
@@ -118,30 +121,30 @@ pub struct AuditLogEntry {
/// The user that did this action on a target.
pub user_id: UserId,
/// What changes were made.
- pub changes: Vec<Change>,
+ pub changes: Option<Vec<Change>>,
/// The id of this entry.
pub id: AuditLogEntryId,
+ /// Some optional data assosiated with this entry.
+ pub options: Option<Options>,
}
-fn deserialize_target<'de, D: Deserializer<'de>>(de: D) -> Result<Target, D::Error> {
- struct TargetVisitor;
-
- impl<'de> Visitor<'de> for TargetVisitor {
- type Value = Target;
-
- fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
- formatter.write_str("an integer between 0 to 70")
- }
-
- fn visit_i32<E: de::Error>(self, value: i32) -> Result<Target, E> {
- Ok(match value {
- 10...70 => unsafe { transmute(value) },
- _ => return Err(E::custom(format!("unexpected target number: {}", value))),
- })
- }
- }
+#[derive(Debug, Deserialize)]
+pub struct Options {
+ /// Number of days after which inactive members were kicked.
+ pub delete_member_days: String,
+ /// Number of members removed by the prune
+ pub members_removed: String,
+ /// Channel in which the messages were deleted
+ pub channel_id: ChannelId,
+ /// Number of deleted messages.
+ pub count: u32,
+ /// Id of the overwritten entity
+ pub id: u64,
+ /// Type of overwritten entity ("member" or "role").
+ #[serde(rename = "type")] pub kind: String,
+ /// Name of the role if type is "role"
+ pub role_name: String,
- de.deserialize_i32(TargetVisitor)
}
fn deserialize_action<'de, D: Deserializer<'de>>(de: D) -> Result<Action, D::Error> {
@@ -151,10 +154,10 @@ fn deserialize_action<'de, D: Deserializer<'de>>(de: D) -> Result<Action, D::Err
type Value = Action;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
- formatter.write_str("an integer between 1 to 62")
+ formatter.write_str("an integer between 1 to 72")
}
- fn visit_i32<E: de::Error>(self, value: i32) -> Result<Action, E> {
+ fn visit_u8<E: de::Error>(self, value: u8) -> Result<Action, E> {
Ok(match value {
1 => Action::GuildUpdate,
10...12 => Action::Channel(unsafe { transmute(value) }),
@@ -164,12 +167,13 @@ fn deserialize_action<'de, D: Deserializer<'de>>(de: D) -> Result<Action, D::Err
40...42 => Action::Invite(unsafe { transmute(value) }),
50...52 => Action::Webhook(unsafe { transmute(value) }),
60...62 => Action::Emoji(unsafe { transmute(value) }),
+ 72 => Action::MessageDelete,
_ => return Err(E::custom(format!("Unexpected action number: {}", value))),
})
}
}
- de.deserialize_i32(ActionVisitor)
+ de.deserialize_u8(ActionVisitor)
}
impl<'de> Deserialize<'de> for AuditLogs {
@@ -178,6 +182,8 @@ impl<'de> Deserialize<'de> for AuditLogs {
#[serde(field_identifier)]
enum Field {
#[serde(rename = "audit_log_entries")] Entries,
+ #[serde(rename = "webhooks")] Webhooks,
+ #[serde(rename = "users")] Users,
}
struct EntriesVisitor;
@@ -190,22 +196,49 @@ impl<'de> Deserialize<'de> for AuditLogs {
}
fn visit_map<V: MapAccess<'de>>(self, mut map: V) -> Result<AuditLogs, V::Error> {
- let audit_log_entries = loop {
- if let Some(Field::Entries) = map.next_key()? {
- break map.next_value::<Vec<AuditLogEntry>>()?;
+ let mut audit_log_entries = None;
+ let mut users = None;
+ let mut webhooks = None;
+
+ while let Some(field) = map.next_key()? {
+ match field {
+ Field::Entries => {
+ if audit_log_entries.is_some() {
+ return Err(de::Error::duplicate_field("entries"));
+ }
+
+ audit_log_entries = Some(map.next_value::<Vec<AuditLogEntry>>()?);
+ },
+ Field::Webhooks => {
+ if webhooks.is_some() {
+ return Err(de::Error::duplicate_field("webhooks"));
+ }
+
+ webhooks = Some(map.next_value::<Vec<Webhook>>()?);
+ },
+ Field::Users => {
+ if users.is_some() {
+ return Err(de::Error::duplicate_field("users"));
+ }
+
+ users = Some(map.next_value::<Vec<User>>()?);
+ },
}
- };
+ }
Ok(AuditLogs {
entries: audit_log_entries
+ .unwrap()
.into_iter()
.map(|entry| (entry.id, entry))
.collect(),
+ webhooks: webhooks.unwrap(),
+ users: users.unwrap(),
})
}
}
- const FIELD: &'static [&'static str] = &["audit_log_entries"];
+ const FIELD: &[&str] = &["audit_log_entries"];
de.deserialize_struct("AuditLogs", FIELD, EntriesVisitor)
}
}
diff --git a/src/model/guild/emoji.rs b/src/model/guild/emoji.rs
index 4a2190f..7f332ae 100644
--- a/src/model/guild/emoji.rs
+++ b/src/model/guild/emoji.rs
@@ -151,8 +151,8 @@ impl Emoji {
/// ```
#[cfg(feature = "cache")]
pub fn find_guild_id(&self) -> Option<GuildId> {
- for guild in CACHE.read().unwrap().guilds.values() {
- let guild = guild.read().unwrap();
+ for guild in CACHE.read().guilds.values() {
+ let guild = guild.read();
if guild.emojis.contains_key(&self.id) {
return Some(guild.id);
diff --git a/src/model/guild/feature.rs b/src/model/guild/feature.rs
deleted file mode 100644
index 40fd3fd..0000000
--- a/src/model/guild/feature.rs
+++ /dev/null
@@ -1,25 +0,0 @@
-/// A special feature, such as for VIP guilds, that a [`Guild`] has had granted
-/// to them.
-///
-/// [`Guild`]: struct.Guild.html
-#[derive(Copy, Clone, Debug, Deserialize, Hash, Eq, PartialEq)]
-pub enum Feature {
- /// The [`Guild`] can set a custom [`splash`][`Guild::splash`] image on
- /// invite URLs.
- ///
- /// [`Guild`]: struct.Guild.html
- /// [`Guild::splash`]: struct.Guild.html#structfield.splash
- #[serde(rename = "INVITE_SPLASH")]
- InviteSplash,
- /// The [`Guild`] can set a Vanity URL, which is a custom-named permanent
- /// invite code.
- ///
- /// [`Guild`]: struct.Guild.html
- #[serde(rename = "VANITY_URL")]
- VanityUrl,
- /// The [`Guild`] has access to VIP voice channel regions.
- ///
- /// [`Guild`]: struct.Guild.html
- #[serde(rename = "VIP_REGIONS")]
- VipRegions,
-}
diff --git a/src/model/guild/guild_id.rs b/src/model/guild/guild_id.rs
index 622d059..37b3886 100644
--- a/src/model/guild/guild_id.rs
+++ b/src/model/guild/guild_id.rs
@@ -5,11 +5,11 @@ use CACHE;
#[cfg(feature = "model")]
use builder::{EditGuild, EditMember, EditRole};
#[cfg(feature = "model")]
-use http;
-#[cfg(feature = "model")]
use internal::prelude::*;
#[cfg(feature = "model")]
use model::guild::BanOptions;
+#[cfg(feature = "model")]
+use {http, utils};
#[cfg(feature = "model")]
impl GuildId {
@@ -73,7 +73,12 @@ impl GuildId {
/// Gets a list of the guild's audit log entries
#[inline]
- pub fn audit_logs(&self) -> Result<AuditLogs> { http::get_audit_logs(self.0) }
+ pub fn audit_logs(&self, action_type: Option<u8>,
+ user_id: Option<UserId>,
+ before: Option<AuditLogEntryId>,
+ limit: Option<u8>) -> Result<AuditLogs> {
+ http::get_audit_logs(self.0, action_type, user_id.map(|u| u.0), before.map(|a| a.0), limit)
+ }
/// Gets all of the guild's channels over the REST API.
///
@@ -168,7 +173,9 @@ impl GuildId {
/// [Manage Roles]: permissions/constant.MANAGE_ROLES.html
#[inline]
pub fn create_role<F: FnOnce(EditRole) -> EditRole>(&self, f: F) -> Result<Role> {
- http::create_role(self.0, &f(EditRole::default()).0)
+ let map = utils::hashmap_to_json_map(f(EditRole::default()).0);
+
+ http::create_role(self.0, &map)
}
/// Deletes the current guild if the current account is the owner of the
@@ -229,7 +236,9 @@ impl GuildId {
/// [Manage Guild]: permissions/constant.MANAGE_GUILD.html
#[inline]
pub fn edit<F: FnOnce(EditGuild) -> EditGuild>(&mut self, f: F) -> Result<PartialGuild> {
- http::edit_guild(self.0, &f(EditGuild::default()).0)
+ let map = utils::hashmap_to_json_map(f(EditGuild::default()).0);
+
+ http::edit_guild(self.0, &map)
}
/// Edits an [`Emoji`]'s name in the guild.
@@ -266,7 +275,9 @@ impl GuildId {
#[inline]
pub fn edit_member<F, U>(&self, user_id: U, f: F) -> Result<()>
where F: FnOnce(EditMember) -> EditMember, U: Into<UserId> {
- http::edit_member(self.0, user_id.into().0, &f(EditMember::default()).0)
+ let map = utils::hashmap_to_json_map(f(EditMember::default()).0);
+
+ http::edit_member(self.0, user_id.into().0, &map)
}
/// Edits the current user's nickname for the guild.
@@ -300,7 +311,9 @@ impl GuildId {
#[inline]
pub fn edit_role<F, R>(&self, role_id: R, f: F) -> Result<Role>
where F: FnOnce(EditRole) -> EditRole, R: Into<RoleId> {
- http::edit_role(self.0, role_id.into().0, &f(EditRole::default()).0)
+ let map = utils::hashmap_to_json_map(f(EditRole::default()).0);
+
+ http::edit_role(self.0, role_id.into().0, &map)
}
/// Edits the order of [`Role`]s
@@ -326,7 +339,7 @@ impl GuildId {
/// Search the cache for the guild.
#[cfg(feature = "cache")]
- pub fn find(&self) -> Option<Arc<RwLock<Guild>>> { CACHE.read().unwrap().guild(*self) }
+ pub fn find(&self) -> Option<Arc<RwLock<Guild>>> { CACHE.read().guild(*self) }
/// Requests the guild over REST.
///
@@ -429,7 +442,7 @@ impl GuildId {
/// [`utils::shard_id`]: ../utils/fn.shard_id.html
#[cfg(all(feature = "cache", feature = "utils"))]
#[inline]
- pub fn shard_id(&self) -> u64 { ::utils::shard_id(self.0, CACHE.read().unwrap().shard_count) }
+ pub fn shard_id(&self) -> u64 { ::utils::shard_id(self.0, CACHE.read().shard_count) }
/// Returns the Id of the shard associated with the guild.
///
diff --git a/src/model/guild/member.rs b/src/model/guild/member.rs
index c81e2a3..e5feee7 100644
--- a/src/model/guild/member.rs
+++ b/src/model/guild/member.rs
@@ -6,15 +6,13 @@ use model::*;
#[cfg(feature = "model")]
use std::borrow::Cow;
#[cfg(all(feature = "cache", feature = "model"))]
-use CACHE;
-#[cfg(all(feature = "cache", feature = "model"))]
use internal::prelude::*;
-#[cfg(all(feature = "cache", feature = "model"))]
-use http;
#[cfg(all(feature = "builder", feature = "cache", feature = "model"))]
use builder::EditMember;
#[cfg(all(feature = "cache", feature = "model", feature = "utils"))]
use utils::Colour;
+#[cfg(all(feature = "cache", feature = "model"))]
+use {CACHE, http, utils};
/// A trait for allowing both u8 or &str or (u8, &str) to be passed into the `ban` methods in `Guild` and `Member`.
pub trait BanOptions {
@@ -89,7 +87,7 @@ impl Member {
return Ok(());
}
- match http::add_member_role(self.guild_id.0, self.user.read().unwrap().id.0, role_id.0) {
+ match http::add_member_role(self.guild_id.0, self.user.read().id.0, role_id.0) {
Ok(()) => {
self.roles.push(role_id);
@@ -110,9 +108,10 @@ impl Member {
pub fn add_roles(&mut self, role_ids: &[RoleId]) -> Result<()> {
self.roles.extend_from_slice(role_ids);
- let map = EditMember::default().roles(&self.roles).0;
+ let builder = EditMember::default().roles(&self.roles);
+ let map = utils::hashmap_to_json_map(builder.0);
- match http::edit_member(self.guild_id.0, self.user.read().unwrap().id.0, &map) {
+ match http::edit_member(self.guild_id.0, self.user.read().id.0, &map) {
Ok(()) => Ok(()),
Err(why) => {
self.roles.retain(|r| !role_ids.contains(r));
@@ -150,7 +149,7 @@ impl Member {
http::ban_user(
self.guild_id.0,
- self.user.read().unwrap().id.0,
+ self.user.read().id.0,
dmd,
&*reason,
)
@@ -159,8 +158,8 @@ impl Member {
/// Determines the member's colour.
#[cfg(all(feature = "cache", feature = "utils"))]
pub fn colour(&self) -> Option<Colour> {
- let cache = CACHE.read().unwrap();
- let guild = try_opt!(cache.guilds.get(&self.guild_id)).read().unwrap();
+ let cache = CACHE.read();
+ let guild = try_opt!(cache.guilds.get(&self.guild_id)).read();
let mut roles = self.roles
.iter()
@@ -176,6 +175,27 @@ impl Member {
.map(|r| r.colour)
}
+ /// Returns the "default channel" of the guild for the member.
+ /// (This returns the first channel that can be read by the member, if there isn't
+ /// one returns `None`)
+ #[cfg(feature = "cache")]
+ pub fn default_channel(&self) -> Option<Arc<RwLock<GuildChannel>>> {
+ let guild = match self.guild_id.find() {
+ Some(guild) => guild,
+ None => return None,
+ };
+
+ let reader = guild.read();
+
+ for (cid, channel) in &reader.channels {
+ if reader.permissions_for(*cid, self.user.read().id).read_messages() {
+ return Some(channel.clone());
+ }
+ }
+
+ None
+ }
+
/// Calculates the member's display name.
///
/// The nickname takes priority over the member's username if it exists.
@@ -184,7 +204,7 @@ impl Member {
self.nick
.as_ref()
.map(Cow::Borrowed)
- .unwrap_or_else(|| Cow::Owned(self.user.read().unwrap().name.clone()))
+ .unwrap_or_else(|| Cow::Owned(self.user.read().name.clone()))
}
/// Returns the DiscordTag of a Member, taking possible nickname into account.
@@ -193,7 +213,7 @@ impl Member {
format!(
"{}#{}",
self.display_name(),
- self.user.read().unwrap().discriminator
+ self.user.read().discriminator
)
}
@@ -207,9 +227,9 @@ impl Member {
/// [`EditMember`]: ../builder/struct.EditMember.html
#[cfg(feature = "cache")]
pub fn edit<F: FnOnce(EditMember) -> EditMember>(&self, f: F) -> Result<()> {
- let map = f(EditMember::default()).0;
+ let map = utils::hashmap_to_json_map(f(EditMember::default()).0);
- http::edit_member(self.guild_id.0, self.user.read().unwrap().id.0, &map)
+ http::edit_member(self.guild_id.0, self.user.read().id.0, &map)
}
/// Kick the member from the guild.
@@ -252,17 +272,16 @@ impl Member {
let has_perms = CACHE
.read()
- .unwrap()
.guilds
.get(&self.guild_id)
- .map(|guild| guild.read().unwrap().has_perms(req));
+ .map(|guild| guild.read().has_perms(req));
if let Some(false) = has_perms {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
- self.guild_id.kick(self.user.read().unwrap().id)
+ self.guild_id.kick(self.user.read().id)
}
/// Returns the guild-level permissions for the member.
@@ -312,7 +331,7 @@ impl Member {
return Ok(());
}
- match http::remove_member_role(self.guild_id.0, self.user.read().unwrap().id.0, role_id.0) {
+ match http::remove_member_role(self.guild_id.0, self.user.read().id.0, role_id.0) {
Ok(()) => {
self.roles.retain(|r| r.0 != role_id.0);
@@ -332,9 +351,10 @@ impl Member {
pub fn remove_roles(&mut self, role_ids: &[RoleId]) -> Result<()> {
self.roles.retain(|r| !role_ids.contains(r));
- let map = EditMember::default().roles(&self.roles).0;
+ let builder = EditMember::default().roles(&self.roles);
+ let map = utils::hashmap_to_json_map(builder.0);
- match http::edit_member(self.guild_id.0, self.user.read().unwrap().id.0, &map) {
+ match http::edit_member(self.guild_id.0, self.user.read().id.0, &map) {
Ok(()) => Ok(()),
Err(why) => {
self.roles.extend_from_slice(role_ids);
@@ -356,7 +376,6 @@ impl Member {
.find()
.map(|g| g
.read()
- .unwrap()
.roles
.values()
.filter(|role| self.roles.contains(&role.id))
@@ -378,7 +397,7 @@ impl Member {
/// [Ban Members]: permissions/constant.BAN_MEMBERS.html
#[cfg(feature = "cache")]
pub fn unban(&self) -> Result<()> {
- http::remove_ban(self.guild_id.0, self.user.read().unwrap().id.0)
+ http::remove_ban(self.guild_id.0, self.user.read().id.0)
}
}
@@ -394,6 +413,6 @@ impl Display for Member {
///
// This is in the format of `<@USER_ID>`.
fn fmt(&self, f: &mut Formatter) -> FmtResult {
- Display::fmt(&self.user.read().unwrap().mention(), f)
+ Display::fmt(&self.user.read().mention(), f)
}
}
diff --git a/src/model/guild/mod.rs b/src/model/guild/mod.rs
index fe9bc6a..ab74109 100644
--- a/src/model/guild/mod.rs
+++ b/src/model/guild/mod.rs
@@ -1,5 +1,4 @@
mod emoji;
-mod feature;
mod guild_id;
mod integration;
mod member;
@@ -8,7 +7,6 @@ mod role;
mod audit_log;
pub use self::emoji::*;
-pub use self::feature::*;
pub use self::guild_id::*;
pub use self::integration::*;
pub use self::member::*;
@@ -21,7 +19,6 @@ use serde::de::Error as DeError;
use serde_json;
use super::utils::*;
use model::*;
-use std;
#[cfg(all(feature = "cache", feature = "model"))]
use CACHE;
@@ -31,6 +28,8 @@ use http;
use builder::{EditGuild, EditMember, EditRole};
#[cfg(feature = "model")]
use constants::LARGE_THRESHOLD;
+#[cfg(feature = "model")]
+use std;
/// A representation of a banning of a user.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)]
@@ -62,8 +61,15 @@ pub struct Guild {
/// VIP features enabled for the guild. Can be obtained through the
/// [Discord Partnership] website.
///
+ /// The following is a list of known features:
+ ///
+ /// - `INVITE_SPLASH`
+ /// - `VANITY_URL`
+ /// - `VERIFIED`
+ /// - `VIP_REGIONS`
+ ///
/// [Discord Partnership]: https://discordapp.com/partners
- pub features: Vec<Feature>,
+ pub features: Vec<String>,
/// The hash of the icon used by the guild.
///
/// In the client, this appears on the guild list on the left-hand side.
@@ -124,13 +130,10 @@ pub struct Guild {
#[cfg(feature = "model")]
impl Guild {
- #[cfg(feature = "cache")]
- /// Returns the "default" channel of the guild.
- /// (This returns the first channel that can be read by the bot, if there isn't one,
+ /// Returns the "default" channel of the guild for the passed user id.
+ /// (This returns the first channel that can be read by the user, if there isn't one,
/// returns `None`)
- pub fn default_channel(&self) -> Option<GuildChannel> {
- let uid = CACHE.read().unwrap().user.id;
-
+ pub fn default_channel(&self, uid: UserId) -> Option<Arc<RwLock<GuildChannel>>> {
for (cid, channel) in &self.channels {
if self.permissions_in(*cid, uid).read_messages() {
return Some(channel.read().unwrap().clone());
@@ -145,7 +148,7 @@ impl Guild {
/// returns `None`)
/// Note however that this is very costy if used in a server with lots of channels,
/// members, or both.
- pub fn default_channel_guaranteed(&self) -> Option<GuildChannel> {
+ pub fn default_channel_guaranteed(&self) -> Option<Arc<RwLock<GuildChannel>>> {
for (cid, channel) in &self.channels {
for memid in self.members.keys() {
if self.permissions_in(*cid, *memid).read_messages() {
@@ -239,7 +242,12 @@ impl Guild {
///
/// [`AuditLogs`]: audit_log/struct.AuditLogs.html
#[inline]
- pub fn audit_logs(&self) -> Result<AuditLogs> { self.id.audit_logs() }
+ pub fn audit_logs(&self, action_type: Option<u8>,
+ user_id: Option<UserId>,
+ before: Option<AuditLogEntryId>,
+ limit: Option<u8>) -> Result<AuditLogs> {
+ self.id.audit_logs(action_type, user_id, before, limit)
+ }
/// Gets all of the guild's channels over the REST API.
///
@@ -402,7 +410,7 @@ impl Guild {
pub fn delete(&self) -> Result<PartialGuild> {
#[cfg(feature = "cache")]
{
- if self.owner_id != CACHE.read().unwrap().user.id {
+ if self.owner_id != CACHE.read().user.id {
let req = Permissions::MANAGE_GUILD;
return Err(Error::Model(ModelError::InvalidPermissions(req)));
@@ -739,9 +747,9 @@ impl Guild {
self.members
.values()
.find(|member| {
- let name_matches = member.user.read().unwrap().name == name;
+ let name_matches = member.user.read().name == name;
let discrim_matches = match discrim {
- Some(discrim) => member.user.read().unwrap().discriminator == discrim,
+ Some(discrim) => member.user.read().discriminator == discrim,
None => true,
};
@@ -756,11 +764,6 @@ 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:
@@ -775,9 +778,9 @@ impl Guild {
.filter(|member|
if case_sensitive {
- member.user.read().unwrap().name.starts_with(prefix)
+ member.user.read().name.starts_with(prefix)
} else {
- starts_with_case_insensitive(&member.user.read().unwrap().name, prefix)
+ starts_with_case_insensitive(&member.user.read().name, prefix)
}
|| member.nick.as_ref()
@@ -794,24 +797,24 @@ impl Guild {
.sort_by(|a, b| {
let name_a = match a.nick {
Some(ref nick) => {
- if contains_case_insensitive(&a.user.read().unwrap().name[..], prefix) {
- a.user.read().unwrap().name.clone()
+ if contains_case_insensitive(&a.user.read().name[..], prefix) {
+ a.user.read().name.clone()
} else {
nick.clone()
}
},
- None => a.user.read().unwrap().name.clone(),
+ None => a.user.read().name.clone(),
};
let name_b = match b.nick {
Some(ref nick) => {
- if contains_case_insensitive(&b.user.read().unwrap().name[..], prefix) {
- b.user.read().unwrap().name.clone()
+ if contains_case_insensitive(&b.user.read().name[..], prefix) {
+ b.user.read().name.clone()
} else {
nick.clone()
}
},
- None => b.user.read().unwrap().name.clone(),
+ None => b.user.read().name.clone(),
};
closest_to_origin(prefix, &name_a[..], &name_b[..])
@@ -850,9 +853,9 @@ impl Guild {
.filter(|member|
if case_sensitive {
- member.user.read().unwrap().name.contains(substring)
+ member.user.read().name.contains(substring)
} else {
- contains_case_insensitive(&member.user.read().unwrap().name, substring)
+ contains_case_insensitive(&member.user.read().name, substring)
}
|| member.nick.as_ref()
@@ -870,24 +873,24 @@ impl Guild {
.sort_by(|a, b| {
let name_a = match a.nick {
Some(ref nick) => {
- if contains_case_insensitive(&a.user.read().unwrap().name[..], substring) {
- a.user.read().unwrap().name.clone()
+ if contains_case_insensitive(&a.user.read().name[..], substring) {
+ a.user.read().name.clone()
} else {
nick.clone()
}
},
- None => a.user.read().unwrap().name.clone(),
+ None => a.user.read().name.clone(),
};
let name_b = match b.nick {
Some(ref nick) => {
- if contains_case_insensitive(&b.user.read().unwrap().name[..], substring) {
- b.user.read().unwrap().name.clone()
+ if contains_case_insensitive(&b.user.read().name[..], substring) {
+ b.user.read().name.clone()
} else {
nick.clone()
}
},
- None => b.user.read().unwrap().name.clone(),
+ None => b.user.read().name.clone(),
};
closest_to_origin(substring, &name_a[..], &name_b[..])
@@ -919,17 +922,17 @@ impl Guild {
.values()
.filter(|member| {
if case_sensitive {
- member.user.read().unwrap().name.contains(substring)
+ member.user.read().name.contains(substring)
} else {
- contains_case_insensitive(&member.user.read().unwrap().name, substring)
+ contains_case_insensitive(&member.user.read().name, substring)
}
}).collect();
if sorted {
members
.sort_by(|a, b| {
- let name_a = &a.user.read().unwrap().name;
- let name_b = &b.user.read().unwrap().name;
+ let name_a = &a.user.read().name;
+ let name_b = &b.user.read().name;
closest_to_origin(substring, &name_a[..], &name_b[..])
});
members
@@ -978,14 +981,14 @@ impl Guild {
Some(ref nick) => {
nick.clone()
},
- None => a.user.read().unwrap().name.clone(),
+ None => a.user.read().name.clone(),
};
let name_b = match b.nick {
Some(ref nick) => {
nick.clone()
},
- None => b.user.read().unwrap().name.clone(),
+ None => b.user.read().name.clone(),
};
closest_to_origin(substring, &name_a[..], &name_b[..])
@@ -1111,7 +1114,7 @@ impl Guild {
} else {
warn!(
"(╯°□°)╯︵ ┻━┻ {} on {} has non-existent role {:?}",
- member.user.read().unwrap().id,
+ member.user.read().id,
self.id,
role
);
@@ -1124,7 +1127,7 @@ impl Guild {
}
if let Some(channel) = self.channels.get(&channel_id) {
- let channel = channel.read().unwrap();
+ let channel = channel.read();
// If this is a text channel, then throw out voice permissions.
if channel.kind == ChannelType::Text {
@@ -1362,16 +1365,16 @@ impl Guild {
/// use serenity::CACHE;
///
/// impl EventHandler for Handler {
- /// fn on_message(&self, _: Context, msg: Message) {
+ /// fn message(&self, _: Context, msg: Message) {
/// if let Some(arc) = msg.guild_id().unwrap().find() {
- /// if let Some(role) = arc.read().unwrap().role_by_name("role_name") {
+ /// if let Some(role) = arc.read().role_by_name("role_name") {
/// println!("{:?}", role);
/// }
/// }
/// }
/// }
///
- /// let mut client = Client::new("token", Handler);
+ /// let mut client = Client::new("token", Handler).unwrap();
///
/// client.start().unwrap();
/// ```
@@ -1433,7 +1436,7 @@ impl<'de> Deserialize<'de> for Guild {
.map_err(DeError::custom)?;
let features = map.remove("features")
.ok_or_else(|| DeError::custom("expected guild features"))
- .and_then(serde_json::from_value::<Vec<Feature>>)
+ .and_then(serde_json::from_value::<Vec<String>>)
.map_err(DeError::custom)?;
let icon = match map.remove("icon") {
Some(v) => Option::<String>::deserialize(v).map_err(DeError::custom)?,
@@ -1523,11 +1526,13 @@ impl<'de> Deserialize<'de> for Guild {
}
/// Checks if a `&str` contains another `&str`.
+#[cfg(feature = "model")]
fn contains_case_insensitive(to_look_at: &str, to_find: &str) -> bool {
to_look_at.to_lowercase().contains(to_find)
}
/// Checks if a `&str` starts with another `&str`.
+#[cfg(feature = "model")]
fn starts_with_case_insensitive(to_look_at: &str, to_find: &str) -> bool {
to_look_at.to_lowercase().starts_with(to_find)
}
@@ -1539,6 +1544,7 @@ fn starts_with_case_insensitive(to_look_at: &str, to_find: &str) -> bool {
/// expected to contain `origin` as substring.
/// If not, using `closest_to_origin` would sort these
/// the end.
+#[cfg(feature = "model")]
fn closest_to_origin(origin: &str, word_a: &str, word_b: &str) -> std::cmp::Ordering {
let value_a = match word_a.find(origin) {
Some(value) => value + word_a.len(),
diff --git a/src/model/guild/partial_guild.rs b/src/model/guild/partial_guild.rs
index cb2f8ef..c4181fd 100644
--- a/src/model/guild/partial_guild.rs
+++ b/src/model/guild/partial_guild.rs
@@ -17,7 +17,12 @@ pub struct PartialGuild {
pub embed_channel_id: Option<ChannelId>,
pub embed_enabled: bool,
#[serde(deserialize_with = "deserialize_emojis")] pub emojis: HashMap<EmojiId, Emoji>,
- pub features: Vec<Feature>,
+ /// Features enabled for the guild.
+ ///
+ /// Refer to [`Guild::features`] for more information.
+ ///
+ /// [`Guild::features`]: struct.Guild.html#structfield.features
+ pub features: Vec<String>,
pub icon: Option<String>,
pub mfa_level: u64,
pub name: String,
@@ -451,7 +456,7 @@ impl PartialGuild {
/// use serenity::CACHE;
///
/// impl EventHandler for Handler {
- /// fn on_message(&self, _: Context, msg: Message) {
+ /// fn message(&self, _: Context, msg: Message) {
/// if let Some(role) =
/// msg.guild_id().unwrap().get().unwrap().role_by_name("role_name") {
/// println!("Obtained role's reference: {:?}", role);
@@ -459,7 +464,7 @@ impl PartialGuild {
/// }
/// }
///
- /// let mut client = Client::new("token", Handler);
+ /// let mut client = Client::new("token", Handler).unwrap();
///
/// client.start().unwrap();
/// ```
diff --git a/src/model/guild/role.rs b/src/model/guild/role.rs
index 7ac7019..3ed718e 100644
--- a/src/model/guild/role.rs
+++ b/src/model/guild/role.rs
@@ -106,8 +106,8 @@ impl Role {
/// [`ModelError::GuildNotFound`]: enum.ModelError.html#variant.GuildNotFound
#[cfg(feature = "cache")]
pub fn find_guild(&self) -> Result<GuildId> {
- for guild in CACHE.read().unwrap().guilds.values() {
- let guild = guild.read().unwrap();
+ for guild in CACHE.read().guilds.values() {
+ let guild = guild.read();
if guild.roles.contains_key(&RoleId(self.id.0)) {
return Ok(guild.id);
@@ -168,10 +168,10 @@ impl RoleId {
/// Search the cache for the role.
#[cfg(feature = "cache")]
pub fn find(&self) -> Option<Role> {
- let cache = CACHE.read().unwrap();
+ let cache = CACHE.read();
for guild in cache.guilds.values() {
- let guild = guild.read().unwrap();
+ let guild = guild.read();
if !guild.roles.contains_key(self) {
continue;
diff --git a/src/model/invite.rs b/src/model/invite.rs
index b4f326c..8ef848c 100644
--- a/src/model/invite.rs
+++ b/src/model/invite.rs
@@ -6,9 +6,9 @@ use super::{Permissions, utils as model_utils};
#[cfg(feature = "model")]
use builder::CreateInvite;
#[cfg(feature = "model")]
-use http;
-#[cfg(feature = "model")]
use internal::prelude::*;
+#[cfg(feature = "model")]
+use {http, utils};
/// Information about an invite code.
///
@@ -73,7 +73,9 @@ impl Invite {
}
}
- http::create_invite(channel_id.0, &f(CreateInvite::default()).0)
+ let map = utils::hashmap_to_json_map(f(CreateInvite::default()).0);
+
+ http::create_invite(channel_id.0, &map)
}
/// Deletes the invite.
diff --git a/src/model/misc.rs b/src/model/misc.rs
index 625e202..51c304d 100644
--- a/src/model/misc.rs
+++ b/src/model/misc.rs
@@ -174,7 +174,7 @@ impl FromStr for Role {
#[cfg(all(feature = "model", feature = "utils"))]
#[derive(Debug)]
pub enum RoleIdParseError {
- NotPresentInCache,
+ InvalidFormat,
}
#[cfg(all(feature = "model", feature = "utils"))]
@@ -188,7 +188,7 @@ impl StdError for RoleIdParseError {
use self::RoleIdParseError::*;
match *self {
- NotPresentInCache => "not present in cache",
+ InvalidFormat => "invalid role id format",
}
}
}
@@ -198,9 +198,10 @@ impl FromStr for RoleId {
type Err = RoleIdParseError;
fn from_str(s: &str) -> StdResult<Self, Self::Err> {
- utils::parse_role(s)
- .ok_or_else(|| RoleIdParseError::NotPresentInCache)
- .map(RoleId)
+ Ok(match utils::parse_role(s) {
+ Some(id) => RoleId(id),
+ None => s.parse::<u64>().map(RoleId).map_err(|_| RoleIdParseError::InvalidFormat)?,
+ })
}
}
diff --git a/src/model/mod.rs b/src/model/mod.rs
index 0817094..64987e1 100644
--- a/src/model/mod.rs
+++ b/src/model/mod.rs
@@ -32,11 +32,12 @@ pub use self::voice::*;
pub use self::webhook::*;
use chrono::NaiveDateTime;
+use parking_lot::RwLock;
use self::utils::*;
use serde::de::Visitor;
use std::collections::HashMap;
use std::fmt::{Display, Formatter, Result as FmtResult};
-use std::sync::{Arc, RwLock};
+use std::sync::Arc;
use internal::prelude::*;
#[cfg(feature = "utils")]
@@ -67,7 +68,7 @@ macro_rules! id_u64 {
self
}
}
-
+
impl From<u64> for $name {
fn from(id_as_u64: u64) -> $name {
$name(id_as_u64)
diff --git a/src/model/user.rs b/src/model/user.rs
index bf725cc..9f36d0f 100644
--- a/src/model/user.rs
+++ b/src/model/user.rs
@@ -7,18 +7,22 @@ use model::misc::Mentionable;
#[cfg(feature = "model")]
use chrono::NaiveDateTime;
+#[cfg(all(feature = "cache", feature = "model"))]
+use parking_lot::RwLock;
#[cfg(feature = "model")]
use std::fmt::Write;
#[cfg(feature = "model")]
use std::mem;
#[cfg(all(feature = "cache", feature = "model"))]
-use std::sync::{Arc, RwLock};
+use std::sync::Arc;
#[cfg(feature = "model")]
use builder::{CreateMessage, EditProfile};
#[cfg(all(feature = "cache", feature = "model"))]
use CACHE;
#[cfg(feature = "model")]
use http::{self, GuildPagination};
+#[cfg(feature = "model")]
+use utils;
/// Information about the current user.
#[derive(Clone, Default, Debug, Deserialize)]
@@ -46,7 +50,7 @@ impl CurrentUser {
/// ```rust,no_run
/// # use serenity::CACHE;
/// #
- /// # let cache = CACHE.read().unwrap();
+ /// # let cache = CACHE.read();
/// #
/// // assuming the cache has been unlocked
/// let user = &cache.user;
@@ -80,18 +84,20 @@ impl CurrentUser {
///
/// let avatar = serenity::utils::read_image("./avatar.png").unwrap();
///
- /// CACHE.write().unwrap().user.edit(|p| p.avatar(Some(&avatar)));
+ /// CACHE.write().user.edit(|p| p.avatar(Some(&avatar)));
/// ```
pub fn edit<F>(&mut self, f: F) -> Result<()>
where F: FnOnce(EditProfile) -> EditProfile {
- let mut map = Map::new();
- map.insert("username".to_string(), Value::String(self.name.clone()));
+ let mut map = HashMap::new();
+ map.insert("username", Value::String(self.name.clone()));
if let Some(email) = self.email.as_ref() {
- map.insert("email".to_string(), Value::String(email.clone()));
+ map.insert("email", Value::String(email.clone()));
}
- match http::edit_profile(&f(EditProfile(map)).0) {
+ let map = utils::hashmap_to_json_map(f(EditProfile(map)).0);
+
+ match http::edit_profile(&map) {
Ok(new) => {
let _ = mem::replace(self, new);
@@ -123,7 +129,7 @@ impl CurrentUser {
/// ```rust,no_run
/// # use serenity::CACHE;
/// #
- /// # let cache = CACHE.read().unwrap();
+ /// # let cache = CACHE.read();
/// #
/// // assuming the cache has been unlocked
/// let user = &cache.user;
@@ -151,7 +157,7 @@ impl CurrentUser {
/// ```rust,no_run
/// # use serenity::CACHE;
/// #
- /// # let mut cache = CACHE.write().unwrap();
+ /// # let mut cache = CACHE.write();
///
/// use serenity::model::permissions::Permissions;
///
@@ -174,7 +180,7 @@ impl CurrentUser {
/// ```rust,no_run
/// # use serenity::CACHE;
/// #
- /// # let mut cache = CACHE.write().unwrap();
+ /// # let mut cache = CACHE.write();
///
/// use serenity::model::Permissions;
///
@@ -230,7 +236,7 @@ impl CurrentUser {
/// ```rust,no_run
/// # use serenity::CACHE;
/// #
- /// # let cache = CACHE.read().unwrap();
+ /// # let cache = CACHE.read();
/// #
/// // assuming the cache has been unlocked
/// let user = &cache.user;
@@ -254,7 +260,7 @@ impl CurrentUser {
/// ```rust,no_run
/// # use serenity::CACHE;
/// #
- /// # let cache = CACHE.read().unwrap();
+ /// # let cache = CACHE.read();
/// #
/// // assuming the cache has been unlocked
/// println!("The current user's distinct identifier is {}", cache.user.tag());
@@ -424,27 +430,23 @@ impl User {
/// struct Handler;
///
/// impl EventHandler for Handler {
- /// fn on_message(&self, _: Context, msg: Message) {
+ /// fn message(&self, _: Context, msg: Message) {
/// if msg.content == "~help" {
- /// let url = match CACHE.read() {
- /// Ok(v) => {
- /// match v.user.invite_url(Permissions::empty()) {
- /// Ok(v) => v,
- /// Err(why) => {
- /// println!("Error creating invite url: {:?}", why);
- ///
- /// return;
- /// },
- /// }
- /// },
+ /// let cache = CACHE.read();
+ ///
+ /// let url = match cache.user.invite_url(Permissions::empty()) {
+ /// Ok(v) => v,
/// Err(why) => {
- /// println!("Error reading from CACHE: {:?}", why);
+ /// println!("Error creating invite url: {:?}", why);
///
/// return;
- /// }
+ /// },
/// };
- /// let help = format!("Helpful info here. Invite me with this link: <{}>",
- /// url);
+ ///
+ /// let help = format!(
+ /// "Helpful info here. Invite me with this link: <{}>",
+ /// url,
+ /// );
///
/// match msg.author.direct_message(|m| m.content(&help)) {
/// Ok(_) => {
@@ -496,12 +498,12 @@ impl User {
let private_channel_id = feature_cache! {
{
let finding = {
- let cache = CACHE.read().unwrap();
+ let cache = CACHE.read();
let finding = cache.private_channels
.values()
- .map(|ch| ch.read().unwrap())
- .find(|ch| ch.recipient.read().unwrap().id == self.id)
+ .map(|ch| ch.read())
+ .find(|ch| ch.recipient.read().id == self.id)
.map(|ch| ch.id);
finding
@@ -596,11 +598,10 @@ impl User {
GuildContainer::Id(_guild_id) => {
feature_cache! {{
CACHE.read()
- .unwrap()
.guilds
.get(&_guild_id)
.map(|g| {
- g.read().unwrap().members.get(&self.id)
+ g.read().members.get(&self.id)
.map(|m| m.roles.contains(&role_id))
.unwrap_or(false)
})
@@ -629,11 +630,12 @@ impl User {
/// struct Handler;
///
/// impl EventHandler for Handler {
- /// fn on_message(&self, _: Context, _: Message) {
+ /// fn message(&self, _: Context, _: Message) {
/// // normal message handling here
/// }
/// }
- /// let mut client = Client::new("token", Handler);
+ ///
+ /// let mut client = Client::new("token", Handler).unwrap();
/// #
/// use serenity::model::UserId;
/// use serenity::CACHE;
@@ -651,11 +653,11 @@ impl User {
/// loop {
/// thread::sleep(duration);
///
- /// let cache = CACHE.read().unwrap();
+ /// let cache = CACHE.read();
///
/// for id in &special_users {
/// if let Some(user) = cache.user(*id) {
- /// if let Err(why) = user.write().unwrap().refresh() {
+ /// if let Err(why) = user.write().refresh() {
/// println!("Error refreshing {}: {:?}", id, why);
/// }
/// }
@@ -700,7 +702,7 @@ impl User {
/// struct Handler;
///
/// impl EventHandler for Handler {
- /// fn on_message(&self, _: Context, msg: Message) {
+ /// fn message(&self, _: Context, msg: Message) {
/// if msg.content == "!mytag" {
/// let content = MessageBuilder::new()
/// .push("Your tag is ")
@@ -711,7 +713,9 @@ impl User {
/// }
/// }
/// }
- /// let mut client = Client::new("token", Handler); client.start().unwrap();
+ /// let mut client = Client::new("token", Handler).unwrap();
+ ///
+ /// client.start().unwrap();
/// ```
#[inline]
pub fn tag(&self) -> String { tag(&self.name, self.discriminator) }
@@ -741,7 +745,7 @@ impl UserId {
/// Search the cache for the user with the Id.
#[cfg(feature = "cache")]
- pub fn find(&self) -> Option<Arc<RwLock<User>>> { CACHE.read().unwrap().user(*self) }
+ pub fn find(&self) -> Option<Arc<RwLock<User>>> { CACHE.read().user(*self) }
/// Gets a user by its Id over the REST API.
///
@@ -750,8 +754,8 @@ impl UserId {
pub fn get(&self) -> Result<User> {
#[cfg(feature = "cache")]
{
- if let Some(user) = CACHE.read().unwrap().user(*self) {
- return Ok(user.read().unwrap().clone());
+ if let Some(user) = CACHE.read().user(*self) {
+ return Ok(user.read().clone());
}
}
@@ -771,12 +775,12 @@ impl<'a> From<&'a CurrentUser> for UserId {
impl From<Member> for UserId {
/// Gets the Id of a `Member`.
- fn from(member: Member) -> UserId { member.user.read().unwrap().id }
+ fn from(member: Member) -> UserId { member.user.read().id }
}
impl<'a> From<&'a Member> for UserId {
/// Gets the Id of a `Member`.
- fn from(member: &Member) -> UserId { member.user.read().unwrap().id }
+ fn from(member: &Member) -> UserId { member.user.read().id }
}
impl From<User> for UserId {
diff --git a/src/model/utils.rs b/src/model/utils.rs
index a64156b..d366e5f 100644
--- a/src/model/utils.rs
+++ b/src/model/utils.rs
@@ -1,6 +1,7 @@
+use parking_lot::RwLock;
use serde::de::Error as DeError;
use std::collections::HashMap;
-use std::sync::{Arc, RwLock};
+use std::sync::Arc;
use super::*;
#[cfg(feature = "cache")]
@@ -44,7 +45,7 @@ pub fn deserialize_members<'de, D: Deserializer<'de>>(
let mut members = HashMap::new();
for member in vec {
- let user_id = member.user.read().unwrap().id;
+ let user_id = member.user.read().id;
members.insert(user_id, member);
}
@@ -73,8 +74,8 @@ pub fn deserialize_private_channels<'de, D: Deserializer<'de>>(
for private_channel in vec {
let id = match private_channel {
- Channel::Group(ref group) => group.read().unwrap().channel_id,
- Channel::Private(ref channel) => channel.read().unwrap().id,
+ Channel::Group(ref group) => group.read().channel_id,
+ Channel::Private(ref channel) => channel.read().id,
Channel::Guild(_) => unreachable!("Guild private channel decode"),
Channel::Category(_) => unreachable!("Channel category private channel decode"),
};
@@ -147,7 +148,7 @@ pub fn deserialize_voice_states<'de, D: Deserializer<'de>>(
#[cfg(all(feature = "cache", feature = "model"))]
pub fn user_has_perms(channel_id: ChannelId, mut permissions: Permissions) -> Result<bool> {
- let cache = CACHE.read().unwrap();
+ let cache = CACHE.read();
let current_user = &cache.user;
let channel = match cache.channel(channel_id) {
@@ -156,7 +157,7 @@ pub fn user_has_perms(channel_id: ChannelId, mut permissions: Permissions) -> Re
};
let guild_id = match channel {
- Channel::Guild(channel) => channel.read().unwrap().guild_id,
+ Channel::Guild(channel) => channel.read().guild_id,
Channel::Group(_) | Channel::Private(_) | Channel::Category(_) => {
// Both users in DMs, and all users in groups and maybe all channels in categories will
// have the same
diff --git a/src/model/webhook.rs b/src/model/webhook.rs
index c8ae1b0..e00d018 100644
--- a/src/model/webhook.rs
+++ b/src/model/webhook.rs
@@ -7,7 +7,7 @@ use builder::ExecuteWebhook;
#[cfg(feature = "model")]
use internal::prelude::*;
#[cfg(feature = "model")]
-use http;
+use {http, utils};
/// A representation of a webhook, which is a low-effort way to post messages to
/// channels. They do not necessarily require a bot user or authentication to
@@ -183,12 +183,9 @@ impl Webhook {
wait: bool,
f: F)
-> Result<Option<Message>> {
- http::execute_webhook(
- self.id.0,
- &self.token,
- wait,
- &f(ExecuteWebhook::default()).0,
- )
+ let map = utils::hashmap_to_json_map(f(ExecuteWebhook::default()).0);
+
+ http::execute_webhook(self.id.0, &self.token, wait, &map)
}
/// Retrieves the latest information about the webhook, editing the
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index 4b36b73..5783c41 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -13,18 +13,32 @@ pub use self::message_builder::{Content, ContentModifier, MessageBuilder};
pub use super::builder;
use base64;
+use internal::prelude::*;
+use model::{EmojiId, EmojiIdentifier};
+use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs::File;
+use std::hash::Hash;
use std::io::Read;
use std::path::Path;
-use internal::prelude::*;
-use model::{EmojiId, EmojiIdentifier};
#[cfg(feature = "cache")]
use cache::Cache;
#[cfg(feature = "cache")]
use CACHE;
+/// Converts a HashMap into a final `serde_json::Map` representation.
+pub fn hashmap_to_json_map<T>(map: HashMap<T, Value>) -> Map<String, Value>
+ where T: Eq + Hash + ToString {
+ let mut json_map = Map::new();
+
+ for (key, value) in map.into_iter() {
+ json_map.insert(key.to_string(), value);
+ }
+
+ json_map
+}
+
/// Determines if a name is NSFW.
///
/// This checks that the name is either `"nsfw"` or, for names longer than that,
@@ -455,7 +469,7 @@ pub fn shard_id(guild_id: u64, shard_count: u64) -> u64 { (guild_id >> 22) % sha
#[cfg(feature = "cache")]
pub fn with_cache<T, F>(f: F) -> T
where F: Fn(&Cache) -> T {
- let cache = CACHE.read().unwrap();
+ let cache = CACHE.read();
f(&cache)
}
@@ -476,6 +490,6 @@ pub fn with_cache<T, F>(f: F) -> T
#[cfg(feature = "cache")]
pub fn with_cache_mut<T, F>(mut f: F) -> T
where F: FnMut(&mut Cache) -> T {
- let mut cache = CACHE.write().unwrap();
+ let mut cache = CACHE.write();
f(&mut cache)
}
diff --git a/src/voice/connection.rs b/src/voice/connection.rs
index 07033cc..e507f41 100644
--- a/src/voice/connection.rs
+++ b/src/voice/connection.rs
@@ -1,4 +1,5 @@
use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt};
+use parking_lot::Mutex;
use opus::{
packet as opus_packet,
Application as CodingMode,
@@ -11,7 +12,7 @@ use std::collections::HashMap;
use std::io::Write;
use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};
use std::sync::mpsc::{self, Receiver as MpscReceiver, Sender as MpscSender};
-use std::sync::{Arc, Mutex};
+use std::sync::Arc;
use std::thread::{self, Builder as ThreadBuilder, JoinHandle};
use std::time::Duration;
use super::audio::{AudioReceiver, AudioSource, AudioType, HEADER_LEN, SAMPLE_RATE};
@@ -223,10 +224,7 @@ impl Connection {
// Send the voice websocket keepalive if it's time
if self.keepalive_timer.check() {
- self.client
- .lock()
- .unwrap()
- .send_json(&payload::build_keepalive())?;
+ self.client.lock().send_json(&payload::build_keepalive())?;
}
// Send UDP keepalive if it's time
@@ -357,10 +355,7 @@ impl Connection {
self.speaking = speaking;
- self.client
- .lock()
- .unwrap()
- .send_json(&payload::build_speaking(speaking))
+ self.client.lock().send_json(&payload::build_speaking(speaking))
}
}
@@ -452,7 +447,7 @@ fn start_threads(client: Arc<Mutex<Client>>, udp: &UdpSocket) -> Result<ThreadIt
let ws_thread = ThreadBuilder::new()
.name(format!("{} WS", thread_name))
.spawn(move || loop {
- while let Ok(Some(msg)) = client.lock().unwrap().recv_json(VoiceEvent::decode) {
+ while let Ok(Some(msg)) = client.lock().recv_json(VoiceEvent::decode) {
if tx_clone.send(ReceiverStatus::Websocket(msg)).is_ok() {
return;
}
diff --git a/tests/resources/guild_create_features.json b/tests/resources/guild_create_features.json
new file mode 100644
index 0000000..c522d7f
--- /dev/null
+++ b/tests/resources/guild_create_features.json
@@ -0,0 +1,32 @@
+{
+ "voice_states": [],
+ "verification_level": 2,
+ "unavailable": false,
+ "system_channel_id": "9876543210",
+ "splash": "abc",
+ "roles": [],
+ "region": "us-east",
+ "presences": [],
+ "owner_id": "157203992353570816",
+ "name": "abcdefg, hijklmnop, qrs, tuv, wx, y and z, now i know my abc's, next time won't you sing with me",
+ "mfa_level": 0,
+ "members": [],
+ "member_count": 585,
+ "large": true,
+ "joined_at": "2017-01-15T04:31:06.080000+00:00",
+ "id": "0123456789",
+ "icon": "def",
+ "features": [
+ "VIP_REGIONS",
+ "VERIFIED",
+ "VANITY_URL",
+ "INVITE_SPLASH"
+ ],
+ "explicit_content_filter": 0,
+ "emojis": [],
+ "default_message_notifications": 1,
+ "channels": [],
+ "application_id": null,
+ "afk_timeout": 300,
+ "afk_channel_id": null
+}
diff --git a/tests/test_channels.rs b/tests/test_channels.rs
index d871d5d..86523dc 100644
--- a/tests/test_channels.rs
+++ b/tests/test_channels.rs
@@ -1,12 +1,14 @@
#![cfg(feature = "model")]
+extern crate parking_lot;
extern crate serenity;
#[cfg(feature = "utils")]
mod utils {
+ use parking_lot::RwLock;
use serenity::model::*;
use std::collections::HashMap;
- use std::sync::{Arc, RwLock};
+ use std::sync::Arc;
fn group() -> Group {
Group {
diff --git a/tests/test_create_embed.rs b/tests/test_create_embed.rs
index 5c51dc6..dd84154 100644
--- a/tests/test_create_embed.rs
+++ b/tests/test_create_embed.rs
@@ -8,7 +8,7 @@ extern crate serenity;
use serde_json::Value;
use serenity::model::{Embed, EmbedField, EmbedImage};
use serenity::utils::builder::CreateEmbed;
-use serenity::utils::Colour;
+use serenity::utils::{self, Colour};
#[test]
fn test_from_embed() {
@@ -51,7 +51,7 @@ fn test_from_embed() {
.title("still a hakase")
.url("https://i.imgur.com/XfWpfCV.gif");
- let built = Value::Object(builder.0);
+ let built = Value::Object(utils::hashmap_to_json_map(builder.0));
let obj = json!({
"color": 0xFF0011,
diff --git a/tests/test_deser.rs b/tests/test_deser.rs
index 1024492..5288da2 100644
--- a/tests/test_deser.rs
+++ b/tests/test_deser.rs
@@ -165,3 +165,11 @@ fn webhooks_update() {
fn message_type_7() {
p!(MessageCreateEvent, "message_type_7");
}
+
+// Ensure string features are properly deserialized.
+//
+// Change made due to a new feature being added and not being in the enum.
+#[test]
+fn guild_features_deser() {
+ p!(GuildCreateEvent, "guild_create_features");
+}
diff --git a/tests/test_formatters.rs b/tests/test_formatters.rs
index b48c648..fbc60ca 100644
--- a/tests/test_formatters.rs
+++ b/tests/test_formatters.rs
@@ -1,3 +1,4 @@
+extern crate parking_lot;
extern crate serenity;
use serenity::model::*;
@@ -14,8 +15,9 @@ fn test_formatters() {
#[cfg(feature = "utils")]
#[test]
fn test_mention() {
+ use parking_lot::RwLock;
use serenity::utils::Colour;
- use std::sync::{Arc, RwLock};
+ use std::sync::Arc;
let channel = Channel::Guild(Arc::new(RwLock::new(GuildChannel {
bitrate: None,