diff options
Diffstat (limited to 'src')
51 files changed, 2844 insertions, 1552 deletions
diff --git a/src/client/context.rs b/src/client/context.rs index d208140..27e5ab9 100644 --- a/src/client/context.rs +++ b/src/client/context.rs @@ -1,4 +1,3 @@ -use serde_json::builder::ObjectBuilder; use std::sync::{Arc, Mutex}; use super::gateway::Shard; use super::rest; @@ -78,29 +77,27 @@ impl Context { /// context.edit_profile(|p| p.username("Hakase")); /// ``` pub fn edit_profile<F: FnOnce(EditProfile) -> EditProfile>(&self, f: F) -> Result<CurrentUser> { - let mut map = ObjectBuilder::new(); + let mut map = Map::new(); feature_cache! {{ let cache = CACHE.read().unwrap(); - map = map.insert("avatar", &cache.user.avatar) - .insert("username", &cache.user.name); + map.insert("username".to_owned(), Value::String(cache.user.name.clone())); if let Some(email) = cache.user.email.as_ref() { - map = map.insert("email", email); + map.insert("email".to_owned(), Value::String(email.clone())); } } else { let user = rest::get_current_user()?; - map = map.insert("avatar", user.avatar) - .insert("username", user.name); + map.insert("username".to_owned(), Value::String(user.name.clone())); if let Some(email) = user.email.as_ref() { - map = map.insert("email", email); + map.insert("email".to_owned(), Value::String(email.clone())); } }} - let edited = f(EditProfile(map)).0.build(); + let edited = f(EditProfile(map)).0; rest::edit_profile(&edited) } diff --git a/src/client/event_store.rs b/src/client/event_store.rs index 9ec465c..f3767a1 100644 --- a/src/client/event_store.rs +++ b/src/client/event_store.rs @@ -1,5 +1,5 @@ use serde_json::Value; -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; use std::sync::Arc; use super::context::Context; use ::model::event::*; @@ -85,7 +85,7 @@ pub struct EventStore { pub on_ready: Option<Arc<Fn(Context, Ready) + Send + Sync + 'static>>, pub on_resume: Option<Arc<Fn(Context, ResumedEvent) + Send + Sync + 'static>>, pub on_typing_start: Option<Arc<Fn(Context, TypingStartEvent) + Send + Sync + 'static>>, - pub on_unknown: Option<Arc<Fn(Context, String, BTreeMap<String, Value>) + Send + Sync + 'static>>, + pub on_unknown: Option<Arc<Fn(Context, String, Value) + Send + Sync + 'static>>, #[cfg(feature="cache")] pub on_user_update: Option<Arc<Fn(Context, CurrentUser, CurrentUser) + Send + Sync + 'static>>, #[cfg(not(feature="cache"))] diff --git a/src/client/gateway/prep.rs b/src/client/gateway/prep.rs index 97c7dee..4a9b5de 100644 --- a/src/client/gateway/prep.rs +++ b/src/client/gateway/prep.rs @@ -1,4 +1,3 @@ -use serde_json::builder::ObjectBuilder; use serde_json::Value; use std::sync::mpsc::{ Receiver as MpscReceiver, @@ -55,32 +54,21 @@ pub fn parse_ready(event: GatewayEvent, } pub fn identify(token: &str, shard_info: Option<[u64; 2]>) -> Value { - ObjectBuilder::new() - .insert("op", OpCode::Identify.num()) - .insert_object("d", |mut object| { - object = identify_compression(object) - .insert("large_threshold", LARGE_THRESHOLD) // max value - .insert_object("properties", |object| object - .insert("$browser", "serenity") - .insert("$device", "serenity") - .insert("$os", env::consts::OS)) - .insert("token", token) - .insert("v", constants::GATEWAY_VERSION); - - if let Some(shard_info) = shard_info { - object = object.insert_array("shard", |a| a - .push(shard_info[0]) - .push(shard_info[1])); - } - - object - }) - .build() -} - -#[inline(always)] -pub fn identify_compression(object: ObjectBuilder) -> ObjectBuilder { - object.insert("compression", !cfg!(feature="debug")) + json!({ + "op": OpCode::Identify.num(), + "d": { + "compression": !cfg!(feature="debug"), + "large_threshold": LARGE_THRESHOLD, + "shard": shard_info, + "token": token, + "v": constants::GATEWAY_VERSION, + "properties": { + "$browser": "serenity", + "$device": "serenity", + "$os": env::consts::OS, + }, + }, + }) } pub fn build_gateway_url(base: &str) -> Result<RequestUrl> { @@ -125,10 +113,10 @@ pub fn keepalive(interval: u64, if time::get_time() >= next_tick { next_tick = next_tick + base_interval; - let map = ObjectBuilder::new() - .insert("d", last_sequence) - .insert("op", OpCode::Heartbeat.num()) - .build(); + let map = json!({ + "d": last_sequence, + "op": OpCode::Heartbeat.num(), + }); trace!("Sending heartbeat d: {}", last_sequence); diff --git a/src/client/gateway/shard.rs b/src/client/gateway/shard.rs index 627ba52..4706397 100644 --- a/src/client/gateway/shard.rs +++ b/src/client/gateway/shard.rs @@ -1,4 +1,3 @@ -use serde_json::builder::ObjectBuilder; use std::io::Write; use std::net::Shutdown; use std::sync::mpsc::{self, Sender as MpscSender}; @@ -304,10 +303,10 @@ impl Shard { }; } - let map = ObjectBuilder::new() - .insert("d", Value::Null) - .insert("op", OpCode::Heartbeat.num()) - .build(); + let map = json!({ + "d": Value::Null, + "op": OpCode::Heartbeat.num(), + }); let status = GatewayStatus::SendMessage(map); let _ = self.keepalive_channel.send(status); @@ -496,13 +495,14 @@ impl Shard { /// If the `cache` feature is enabled, the cache will automatically be /// updated with member chunks. pub fn chunk_guilds(&self, guild_ids: &[GuildId], limit: Option<u16>, query: Option<&str>) { - let msg = ObjectBuilder::new() - .insert("op", OpCode::GetGuildMembers.num()) - .insert_object("d", |obj| obj - .insert_array("guild_id", |a| guild_ids.iter().fold(a, |a, s| a.push(s.0))) - .insert("limit", limit.unwrap_or(0)) - .insert("query", query.unwrap_or(""))) - .build(); + let msg = json!({ + "op": OpCode::GetGuildMembers.num(), + "d": { + "guild_id": guild_ids.iter().map(|x| x.0).collect::<Vec<u64>>(), + "limit": limit.unwrap_or(0), + "query": query.unwrap_or(""), + }, + }); let _ = self.keepalive_channel.send(GatewayStatus::SendMessage(msg)); } @@ -578,13 +578,14 @@ impl Shard { let (mut sender, mut receiver) = response.begin().split(); - sender.send_json(&ObjectBuilder::new() - .insert_object("d", |o| o - .insert("session_id", session_id) - .insert("seq", self.seq) - .insert("token", &self.token)) - .insert("op", OpCode::Resume.num()) - .build())?; + sender.send_json(&json!({ + "op": OpCode::Resume.num(), + "d": { + "session_id": session_id, + "seq": self.seq, + "token": self.token, + }, + }))?; // Note to self when this gets accepted in a decade: // https://github.com/rust-lang/rfcs/issues/961 @@ -629,21 +630,17 @@ impl Shard { let (ref game, status, afk) = self.current_presence; let now = time::get_time().sec as u64; - let msg = ObjectBuilder::new() - .insert("op", OpCode::StatusUpdate.num()) - .insert_object("d", move |mut object| { - object = object.insert("afk", afk) - .insert("since", now) - .insert("status", status.name()); - - match game.as_ref() { - Some(game) => { - object.insert_object("game", move |o| o.insert("name", &game.name)) - }, - None => object.insert("game", Value::Null), - } - }) - .build(); + 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, + })), + }, + }); let _ = self.keepalive_channel.send(GatewayStatus::SendMessage(msg)); diff --git a/src/client/mod.rs b/src/client/mod.rs index 8c073b1..50e0451 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -34,7 +34,7 @@ pub use self::error::Error as ClientError; use self::dispatch::dispatch; use self::event_store::EventStore; use self::gateway::Shard; -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; use std::sync::{Arc, Mutex, RwLock}; use std::time::Duration; use std::{mem, thread}; @@ -42,7 +42,7 @@ use typemap::ShareMap; use websocket::client::Receiver; use websocket::result::WebSocketError; use websocket::stream::WebSocketStream; -use ::internal::prelude::{Error, Result, Value}; +use ::internal::prelude::*; use ::internal::ws_impl::ReceiverExt; use ::model::event::*; use ::model::*; @@ -637,7 +637,7 @@ impl Client { /// /// [`Unknown`]: ../model/event/enum.Event.html#variant.Unknown pub fn on_unknown<F>(&mut self, handler: F) - where F: Fn(Context, String, BTreeMap<String, Value>) + Send + Sync + 'static { + where F: Fn(Context, String, Value) + Send + Sync + 'static { self.event_store.write() .unwrap() .on_unknown = Some(Arc::new(handler)); diff --git a/src/client/rest/mod.rs b/src/client/rest/mod.rs index 8757114..9cd57fa 100644 --- a/src/client/rest/mod.rs +++ b/src/client/rest/mod.rs @@ -38,7 +38,6 @@ use hyper::method::Method; use hyper::{Error as HyperError, Result as HyperResult, Url, header}; use multipart::client::Multipart; use self::ratelimiting::Route; -use serde_json::builder::ObjectBuilder; use serde_json; use std::collections::BTreeMap; use std::default::Default; @@ -48,7 +47,6 @@ use std::sync::{Arc, Mutex}; use ::constants; use ::internal::prelude::*; use ::model::*; -use ::utils::{decode_array, into_array}; /// An method used for ratelimiting special routes. /// @@ -168,7 +166,7 @@ pub fn create_channel(guild_id: u64, map: &Value) -> Result<GuildChannel> { "/guilds/{}/channels", guild_id); - GuildChannel::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, GuildChannel>(response).map_err(From::from) } /// Creates an emoji in the given [`Guild`] with the given data. @@ -188,7 +186,7 @@ pub fn create_emoji(guild_id: u64, map: &Value) -> Result<Emoji> { "/guilds/{}/emojis", guild_id); - Emoji::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, Emoji>(response).map_err(From::from) } /// Creates a guild with the data provided. @@ -230,7 +228,7 @@ pub fn create_guild(map: &Value) -> Result<PartialGuild> { let body = map.to_string(); let response = request!(Route::Guilds, post(body), "/guilds"); - PartialGuild::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, PartialGuild>(response).map_err(From::from) } /// Creates an [`Integration`] for a [`Guild`]. @@ -265,14 +263,14 @@ pub fn create_guild_integration(guild_id: u64, integration_id: u64, map: &Value) /// [`RichInvite`]: ../../model/struct.RichInvite.html /// [Create Invite]: ../../model/permissions/constant.CREATE_INVITE.html /// [docs]: https://discordapp.com/developers/docs/resources/channel#create-channel-invite -pub fn create_invite(channel_id: u64, map: &Value) -> Result<RichInvite> { - let body = map.to_string(); +pub fn create_invite(channel_id: u64, map: &JsonMap) -> Result<RichInvite> { + let body = serde_json::to_string(map)?; let response = request!(Route::ChannelsIdInvites(channel_id), post(body), "/channels/{}/invites", channel_id); - RichInvite::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, RichInvite>(response).map_err(From::from) } /// Creates a permission override for a member or a role in a channel. @@ -293,7 +291,7 @@ pub fn create_private_channel(map: &Value) -> Result<PrivateChannel> { post(body), "/users/@me/channels"); - PrivateChannel::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, PrivateChannel>(response).map_err(From::from) } /// Reacts to a message. @@ -310,14 +308,14 @@ pub fn create_reaction(channel_id: u64, } /// Creates a role. -pub fn create_role(guild_id: u64, map: &Value) -> Result<Role> { - let body = map.to_string(); +pub fn create_role(guild_id: u64, map: &JsonMap) -> Result<Role> { + let body = serde_json::to_string(map)?; let response = request!(Route::GuildsIdRoles(guild_id), post(body), "/guilds/{}/roles", guild_id); - Role::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, Role>(response).map_err(From::from) } /// Creates a webhook for the given [channel][`GuildChannel`]'s Id, passing in @@ -357,7 +355,7 @@ pub fn create_webhook(channel_id: u64, map: &Value) -> Result<Webhook> { "/channels/{}/webhooks", channel_id); - Webhook::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, Webhook>(response).map_err(From::from) } /// Deletes a private channel or a channel in a guild. @@ -367,7 +365,7 @@ pub fn delete_channel(channel_id: u64) -> Result<Channel> { "/channels/{}", channel_id); - Channel::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, Channel>(response).map_err(From::from) } /// Deletes an emoji from a server. @@ -386,7 +384,7 @@ pub fn delete_guild(guild_id: u64) -> Result<PartialGuild> { "/guilds/{}", guild_id); - PartialGuild::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, PartialGuild>(response).map_err(From::from) } /// Remvoes an integration from a guild. @@ -402,7 +400,7 @@ pub fn delete_guild_integration(guild_id: u64, integration_id: u64) -> Result<() pub fn delete_invite(code: &str) -> Result<Invite> { let response = request!(Route::InvitesCode, delete, "/invites/{}", code); - Invite::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, Invite>(response).map_err(From::from) } /// Deletes a message if created by us or we have @@ -537,14 +535,14 @@ pub fn delete_webhook_with_token(webhook_id: u64, token: &str) -> Result<()> { } /// Changes channel information. -pub fn edit_channel(channel_id: u64, map: &Value) -> Result<GuildChannel> { - let body = map.to_string(); +pub fn edit_channel(channel_id: u64, map: &JsonMap) -> Result<GuildChannel> { + let body = serde_json::to_string(map)?; let response = request!(Route::ChannelsId(channel_id), patch(body), "/channels/{}", channel_id); - GuildChannel::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, GuildChannel>(response).map_err(From::from) } /// Changes emoji information. @@ -556,18 +554,18 @@ pub fn edit_emoji(guild_id: u64, emoji_id: u64, map: &Value) -> Result<Emoji> { guild_id, emoji_id); - Emoji::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, Emoji>(response).map_err(From::from) } /// Changes guild information. -pub fn edit_guild(guild_id: u64, map: &Value) -> Result<PartialGuild> { - let body = map.to_string(); +pub fn edit_guild(guild_id: u64, map: &JsonMap) -> Result<PartialGuild> { + let body = serde_json::to_string(map)?; let response = request!(Route::GuildsId(guild_id), patch(body), "/guilds/{}", guild_id); - PartialGuild::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, PartialGuild>(response).map_err(From::from) } /// Edits a [`Guild`]'s embed setting. @@ -580,12 +578,12 @@ pub fn edit_guild_embed(guild_id: u64, map: &Value) -> Result<GuildEmbed> { "/guilds/{}/embed", guild_id); - GuildEmbed::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, GuildEmbed>(response).map_err(From::from) } /// Does specific actions to a member. -pub fn edit_member(guild_id: u64, user_id: u64, map: &Value) -> Result<()> { - let body = map.to_string(); +pub fn edit_member(guild_id: u64, user_id: u64, map: &JsonMap) -> Result<()> { + let body = serde_json::to_string(map)?; verify(204, request!(Route::GuildsIdMembersId(guild_id), patch(body), @@ -605,7 +603,7 @@ pub fn edit_message(channel_id: u64, message_id: u64, map: &Value) -> Result<Mes channel_id, message_id); - Message::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, Message>(response).map_err(From::from) } /// Edits the current user's nickname for the provided [`Guild`] via its Id. @@ -614,7 +612,9 @@ pub fn edit_message(channel_id: u64, message_id: u64, map: &Value) -> Result<Mes /// /// [`Guild`]: ../../model/struct.Guild.html pub fn edit_nickname(guild_id: u64, new_nickname: Option<&str>) -> Result<()> { - let map = ObjectBuilder::new().insert("nick", new_nickname).build(); + let map = json!({ + "nick": new_nickname + }); let body = map.to_string(); let response = request!(Route::GuildsIdMembersMeNick(guild_id), patch(body), @@ -636,32 +636,33 @@ pub fn edit_nickname(guild_id: u64, new_nickname: Option<&str>) -> Result<()> { /// **Note**: this token change may cause requests made between the actual token /// change and when the token is internally changed to be invalid requests, as /// the token may be outdated. -pub fn edit_profile(map: &Value) -> Result<CurrentUser> { - let body = map.to_string(); +pub fn edit_profile(map: &JsonMap) -> Result<CurrentUser> { + let body = serde_json::to_string(map)?; let response = request!(Route::UsersMe, patch(body), "/users/@me"); - let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?; + let mut value = serde_json::from_reader::<HyperResponse, Value>(response)?; - if !TOKEN.lock().unwrap().starts_with("Bot ") { - if let Some(Value::String(token)) = map.remove("token") { - set_token(&token); + if let Some(map) = value.as_object_mut() { + if !TOKEN.lock().unwrap().starts_with("Bot ") { + if let Some(Value::String(token)) = map.remove("token") { + set_token(&token); + } } } - CurrentUser::decode(Value::Object(map)) + serde_json::from_value::<CurrentUser>(value).map_err(From::from) } /// Changes a role in a guild. -pub fn edit_role(guild_id: u64, role_id: u64, map: &Value) - -> Result<Role> { - let body = map.to_string(); +pub fn edit_role(guild_id: u64, role_id: u64, map: &JsonMap) -> Result<Role> { + let body = serde_json::to_string(map)?; let response = request!(Route::GuildsIdRolesId(guild_id), patch(body), "/guilds/{}/roles/{}", guild_id, role_id); - Role::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, Role>(response).map_err(From::from) } /// Edits a the webhook with the given data. @@ -710,7 +711,7 @@ pub fn edit_webhook(webhook_id: u64, map: &Value) -> Result<Webhook> { "/webhooks/{}", webhook_id); - Webhook::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, Webhook>(response).map_err(From::from) } /// Edits the webhook with the given data. @@ -739,15 +740,15 @@ pub fn edit_webhook(webhook_id: u64, map: &Value) -> Result<Webhook> { /// ``` /// /// [`edit_webhook`]: fn.edit_webhook.html -pub fn edit_webhook_with_token(webhook_id: u64, token: &str, map: &Value) -> Result<Webhook> { - let body = map.to_string(); +pub fn edit_webhook_with_token(webhook_id: u64, token: &str, map: &JsonMap) -> Result<Webhook> { + let body = serde_json::to_string(map)?; let client = HyperClient::new(); let response = retry(|| client .patch(&format!(api!("/webhooks/{}/{}"), webhook_id, token)) .body(&body)) .map_err(Error::Hyper)?; - Webhook::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, Webhook>(response).map_err(From::from) } /// Executes a webhook, posting a [`Message`] in the webhook's associated @@ -804,15 +805,15 @@ pub fn edit_webhook_with_token(webhook_id: u64, token: &str, map: &Value) -> Res /// [`Channel`]: ../../model/enum.Channel.html /// [`Message`]: ../../model/struct.Message.html /// [Discord docs]: https://discordapp.com/developers/docs/resources/webhook#querystring-params -pub fn execute_webhook(webhook_id: u64, token: &str, map: &Value) -> Result<Message> { - let body = map.to_string(); +pub fn execute_webhook(webhook_id: u64, token: &str, map: &JsonMap) -> Result<Message> { + let body = serde_json::to_string(map)?; let client = HyperClient::new(); let response = retry(|| client .post(&format!(api!("/webhooks/{}/{}"), webhook_id, token)) .body(&body)) .map_err(Error::Hyper)?; - Message::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, Message>(response).map_err(From::from) } /// Gets the active maintenances from Discord's Status API. @@ -826,7 +827,7 @@ pub fn get_active_maintenances() -> Result<Vec<Maintenance>> { let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?; match map.remove("scheduled_maintenances") { - Some(v) => decode_array(v, Maintenance::decode), + Some(v) => serde_json::from_value::<Vec<Maintenance>>(v).map_err(From::from), None => Ok(vec![]), } } @@ -837,7 +838,7 @@ pub fn get_active_maintenances() -> Result<Vec<Maintenance>> { pub fn get_application_info(id: u64) -> Result<ApplicationInfo> { let response = request!(Route::None, get, "/oauth2/applications/{}", id); - ApplicationInfo::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, ApplicationInfo>(response).map_err(From::from) } /// Gets all oauth2 applications we've made. @@ -845,9 +846,8 @@ pub fn get_application_info(id: u64) -> Result<ApplicationInfo> { /// **Note**: Only user accounts may use this endpoint. pub fn get_applications() -> Result<Vec<ApplicationInfo>> { let response = request!(Route::None, get, "/oauth2/applications"); - let decoded = serde_json::from_reader(response)?; - decode_array(decoded, ApplicationInfo::decode) + serde_json::from_reader::<HyperResponse, Vec<ApplicationInfo>>(response).map_err(From::from) } /// Gets all the users that are banned in specific guild. @@ -857,14 +857,14 @@ pub fn get_bans(guild_id: u64) -> Result<Vec<Ban>> { "/guilds/{}/bans", guild_id); - decode_array(serde_json::from_reader(response)?, Ban::decode) + serde_json::from_reader::<HyperResponse, Vec<Ban>>(response).map_err(From::from) } /// Gets current bot gateway. pub fn get_bot_gateway() -> Result<BotGateway> { let response = request!(Route::GatewayBot, get, "/gateway/bot"); - BotGateway::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, BotGateway>(response).map_err(From::from) } /// Gets all invites for a channel. @@ -874,8 +874,7 @@ pub fn get_channel_invites(channel_id: u64) -> Result<Vec<RichInvite>> { "/channels/{}/invites", channel_id); - decode_array(serde_json::from_reader(response)?, - RichInvite::decode) + serde_json::from_reader::<HyperResponse, Vec<RichInvite>>(response).map_err(From::from) } /// Retrieves the webhooks for the given [channel][`GuildChannel`]'s Id. @@ -902,7 +901,7 @@ pub fn get_channel_webhooks(channel_id: u64) -> Result<Vec<Webhook>> { "/channels/{}/webhooks", channel_id); - decode_array(serde_json::from_reader(response)?, Webhook::decode) + serde_json::from_reader::<HyperResponse, Vec<Webhook>>(response).map_err(From::from) } /// Gets channel information. @@ -912,7 +911,7 @@ pub fn get_channel(channel_id: u64) -> Result<Channel> { "/channels/{}", channel_id); - Channel::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, Channel>(response).map_err(From::from) } /// Gets all channels in a guild. @@ -922,8 +921,7 @@ pub fn get_channels(guild_id: u64) -> Result<Vec<GuildChannel>> { "/guilds/{}/channels", guild_id); - decode_array(serde_json::from_reader(response)?, - GuildChannel::decode) + serde_json::from_reader::<HyperResponse, Vec<GuildChannel>>(response).map_err(From::from) } /// Gets information about the current application. @@ -932,21 +930,21 @@ pub fn get_channels(guild_id: u64) -> Result<Vec<GuildChannel>> { pub fn get_current_application_info() -> Result<CurrentApplicationInfo> { let response = request!(Route::None, get, "/oauth2/applications/@me"); - CurrentApplicationInfo::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, CurrentApplicationInfo>(response).map_err(From::from) } /// Gets information about the user we're connected with. pub fn get_current_user() -> Result<CurrentUser> { let response = request!(Route::UsersMe, get, "/users/@me"); - CurrentUser::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, CurrentUser>(response).map_err(From::from) } /// Gets current gateway. pub fn get_gateway() -> Result<Gateway> { let response = request!(Route::Gateway, get, "/gateway"); - Gateway::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, Gateway>(response).map_err(From::from) } /// Gets information about an emoji. @@ -957,7 +955,7 @@ pub fn get_emoji(guild_id: u64, emoji_id: u64) -> Result<Emoji> { guild_id, emoji_id); - Emoji::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, Emoji>(response).map_err(From::from) } /// Gets all emojis in a guild. @@ -967,7 +965,7 @@ pub fn get_emojis(guild_id: u64) -> Result<Vec<Emoji>> { "/guilds/{}/emojis", guild_id); - decode_array(serde_json::from_reader(response)?, Emoji::decode) + serde_json::from_reader::<HyperResponse, Vec<Emoji>>(response).map_err(From::from) } /// Gets guild information. @@ -977,7 +975,7 @@ pub fn get_guild(guild_id: u64) -> Result<PartialGuild> { "/guilds/{}", guild_id); - PartialGuild::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, PartialGuild>(response).map_err(From::from) } /// Gets a guild embed information. @@ -987,7 +985,7 @@ pub fn get_guild_embed(guild_id: u64) -> Result<GuildEmbed> { "/guilds/{}/embeds", guild_id); - GuildEmbed::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, GuildEmbed>(response).map_err(From::from) } /// Gets integrations that a guild has. @@ -997,7 +995,7 @@ pub fn get_guild_integrations(guild_id: u64) -> Result<Vec<Integration>> { "/guilds/{}/integrations", guild_id); - decode_array(serde_json::from_reader(response)?, Integration::decode) + serde_json::from_reader::<HyperResponse, Vec<Integration>>(response).map_err(From::from) } /// Gets all invites to a guild. @@ -1007,7 +1005,7 @@ pub fn get_guild_invites(guild_id: u64) -> Result<Vec<RichInvite>> { "/guilds/{}/invites", guild_id); - decode_array(serde_json::from_reader(response)?, RichInvite::decode) + serde_json::from_reader::<HyperResponse, Vec<RichInvite>>(response).map_err(From::from) } /// Gets the members of a guild. Optionally pass a `limit` and the Id of the @@ -1021,10 +1019,19 @@ pub fn get_guild_members(guild_id: u64, limit: Option<u64>, after: Option<u64>) limit.unwrap_or(500), after.unwrap_or(0)); - into_array(serde_json::from_reader(response)?) - .and_then(|x| x.into_iter() - .map(|v| Member::decode_guild(GuildId(guild_id), v)) - .collect()) + let mut v = serde_json::from_reader::<HyperResponse, Value>(response)?; + + if let Some(values) = v.as_array_mut() { + let num = Value::Number(Number::from(guild_id)); + + for value in values { + if let Some(element) = value.as_object_mut() { + element.insert("guild_id".to_owned(), num.clone()); + } + } + } + + serde_json::from_value::<Vec<Member>>(v).map_err(From::from) } /// Gets the amount of users that can be pruned. @@ -1035,7 +1042,7 @@ pub fn get_guild_prune_count(guild_id: u64, map: &Value) -> Result<GuildPrune> { "/guilds/{}/prune", guild_id); - GuildPrune::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, GuildPrune>(response).map_err(From::from) } /// Gets regions that a guild can use. If a guild has [`Feature::VipRegions`] @@ -1048,7 +1055,7 @@ pub fn get_guild_regions(guild_id: u64) -> Result<Vec<VoiceRegion>> { "/guilds/{}/regions", guild_id); - decode_array(serde_json::from_reader(response)?, VoiceRegion::decode) + serde_json::from_reader::<HyperResponse, Vec<VoiceRegion>>(response).map_err(From::from) } /// Retrieves a list of roles in a [`Guild`]. @@ -1060,7 +1067,7 @@ pub fn get_guild_roles(guild_id: u64) -> Result<Vec<Role>> { "/guilds/{}/roles", guild_id); - decode_array(serde_json::from_reader(response)?, Role::decode) + serde_json::from_reader::<HyperResponse, Vec<Role>>(response).map_err(From::from) } /// Retrieves the webhooks for the given [guild][`Guild`]'s Id. @@ -1087,7 +1094,7 @@ pub fn get_guild_webhooks(guild_id: u64) -> Result<Vec<Webhook>> { "/guilds/{}/webhooks", guild_id); - decode_array(serde_json::from_reader(response)?, Webhook::decode) + serde_json::from_reader::<HyperResponse, Vec<Webhook>>(response).map_err(From::from) } /// Gets a paginated list of the current user's guilds. @@ -1124,7 +1131,7 @@ pub fn get_guilds(target: &GuildPagination, limit: u64) -> Result<Vec<GuildInfo> let response = request!(Route::UsersMeGuilds, get, "{}", uri); - decode_array(serde_json::from_reader(response)?, GuildInfo::decode) + serde_json::from_reader::<HyperResponse, Vec<GuildInfo>>(response).map_err(From::from) } /// Gets information about a specific invite. @@ -1132,7 +1139,7 @@ pub fn get_invite(code: &str) -> Result<Invite> { let invite = ::utils::parse_invite(code); let response = request!(Route::InvitesCode, get, "/invites/{}", invite); - Invite::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, Invite>(response).map_err(From::from) } /// Gets member of a guild. @@ -1143,7 +1150,13 @@ pub fn get_member(guild_id: u64, user_id: u64) -> Result<Member> { guild_id, user_id); - Member::decode_guild(GuildId(guild_id), serde_json::from_reader(response)?) + let mut v = serde_json::from_reader::<HyperResponse, Value>(response)?; + + if let Some(map) = v.as_object_mut() { + map.insert("guild_id".to_owned(), Value::Number(Number::from(guild_id))); + } + + serde_json::from_value::<Member>(v).map_err(From::from) } /// Gets a message by an Id, bots only. @@ -1154,7 +1167,7 @@ pub fn get_message(channel_id: u64, message_id: u64) -> Result<Message> { channel_id, message_id); - Message::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, Message>(response).map_err(From::from) } /// Gets X messages from a channel. @@ -1167,7 +1180,7 @@ pub fn get_messages(channel_id: u64, query: &str) let response = request(Route::ChannelsIdMessages(channel_id), || client.get(&url))?; - decode_array(serde_json::from_reader(response)?, Message::decode) + serde_json::from_reader::<HyperResponse, Vec<Message>>(response).map_err(From::from) } /// Gets all pins of a channel. @@ -1177,7 +1190,7 @@ pub fn get_pins(channel_id: u64) -> Result<Vec<Message>> { "/channels/{}/pins", channel_id); - decode_array(serde_json::from_reader(response)?, Message::decode) + serde_json::from_reader::<HyperResponse, Vec<Message>>(response).map_err(From::from) } /// Gets user Ids based on their reaction to a message. This endpoint is dumb. @@ -1202,7 +1215,7 @@ pub fn get_reaction_users(channel_id: u64, "{}", uri); - decode_array(serde_json::from_reader(response)?, User::decode) + serde_json::from_reader::<HyperResponse, Vec<User>>(response).map_err(From::from) } /// Gets the current unresolved incidents from Discord's Status API. @@ -1216,7 +1229,7 @@ pub fn get_unresolved_incidents() -> Result<Vec<Incident>> { let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?; match map.remove("incidents") { - Some(incidents) => decode_array(incidents, Incident::decode), + Some(v) => serde_json::from_value::<Vec<Incident>>(v).map_err(From::from), None => Ok(vec![]), } } @@ -1232,7 +1245,7 @@ pub fn get_upcoming_maintenances() -> Result<Vec<Maintenance>> { let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?; match map.remove("scheduled_maintenances") { - Some(v) => decode_array(v, Maintenance::decode), + Some(v) => serde_json::from_value::<Vec<Maintenance>>(v).map_err(From::from), None => Ok(vec![]), } } @@ -1241,21 +1254,21 @@ pub fn get_upcoming_maintenances() -> Result<Vec<Maintenance>> { pub fn get_user(user_id: u64) -> Result<User> { let response = request!(Route::UsersId, get, "/users/{}", user_id); - User::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, User>(response).map_err(From::from) } /// Gets our DM channels. pub fn get_user_dm_channels() -> Result<Vec<PrivateChannel>> { let response = request!(Route::UsersMeChannels, get, "/users/@me/channels"); - decode_array(serde_json::from_reader(response)?, PrivateChannel::decode) + serde_json::from_reader::<HyperResponse, Vec<PrivateChannel>>(response).map_err(From::from) } /// Gets all voice regions. pub fn get_voice_regions() -> Result<Vec<VoiceRegion>> { let response = request!(Route::VoiceRegions, get, "/voice/regions"); - decode_array(serde_json::from_reader(response)?, VoiceRegion::decode) + serde_json::from_reader::<HyperResponse, Vec<VoiceRegion>>(response).map_err(From::from) } /// Retrieves a webhook given its Id. @@ -1278,7 +1291,7 @@ pub fn get_voice_regions() -> Result<Vec<VoiceRegion>> { pub fn get_webhook(webhook_id: u64) -> Result<Webhook> { let response = request!(Route::WebhooksId, get, "/webhooks/{}", webhook_id); - Webhook::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, Webhook>(response).map_err(From::from) } /// Retrieves a webhook given its Id and unique token. @@ -1304,7 +1317,7 @@ pub fn get_webhook_with_token(webhook_id: u64, token: &str) -> Result<Webhook> { .get(&format!(api!("/webhooks/{}/{}"), webhook_id, token))) .map_err(Error::Hyper)?; - Webhook::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, Webhook>(response).map_err(From::from) } /// Kicks a member from a guild. @@ -1323,7 +1336,7 @@ pub fn leave_group(guild_id: u64) -> Result<Group> { "/channels/{}", guild_id); - Group::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, Group>(response).map_err(From::from) } /// Leaves a guild. @@ -1333,7 +1346,7 @@ pub fn leave_guild(guild_id: u64) -> Result<PartialGuild> { "/users/@me/guilds/{}", guild_id); - PartialGuild::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, PartialGuild>(response).map_err(From::from) } /// Deletes a user from group DM. @@ -1346,11 +1359,8 @@ pub fn remove_group_recipient(group_id: u64, user_id: u64) -> Result<()> { } /// Sends a file to a channel. -pub fn send_file<R: Read>(channel_id: u64, - mut file: R, - filename: &str, - map: BTreeMap<String, Value>) - -> Result<Message> { +pub fn send_file<R: Read>(channel_id: u64, mut file: R, filename: &str, map: JsonMap) + -> Result<Message> { let uri = format!(api!("/channels/{}/messages"), channel_id); let url = match Url::parse(&uri) { Ok(url) => url, @@ -1371,14 +1381,15 @@ pub fn send_file<R: Read>(channel_id: u64, let _ = match v { Value::Bool(false) => request.write_text(&k, "false")?, Value::Bool(true) => request.write_text(&k, "true")?, - Value::I64(inner) => request.write_text(&k, inner.to_string())?, - Value::U64(inner) => request.write_text(&k, inner.to_string())?, + Value::Number(inner) => request.write_text(&k, inner.to_string())?, Value::String(inner) => request.write_text(&k, inner)?, _ => continue, }; } - Message::decode(serde_json::from_reader(request.send()?)?) + let response = request.send()?; + + serde_json::from_reader::<HyperResponse, Message>(response).map_err(From::from) } /// Sends a message to a channel. @@ -1389,7 +1400,7 @@ pub fn send_message(channel_id: u64, map: &Value) -> Result<Message> { "/channels/{}/messages", channel_id); - Message::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, Message>(response).map_err(From::from) } /// Pins a message in a channel. @@ -1436,12 +1447,11 @@ pub fn start_guild_prune(guild_id: u64, map: &Value) -> Result<GuildPrune> { "/guilds/{}/prune", guild_id); - GuildPrune::decode(serde_json::from_reader(response)?) + serde_json::from_reader::<HyperResponse, GuildPrune>(response).map_err(From::from) } /// Starts syncing an integration with a guild. -pub fn start_integration_sync(guild_id: u64, integration_id: u64) - -> Result<()> { +pub fn start_integration_sync(guild_id: u64, integration_id: u64) -> Result<()> { verify(204, request!(Route::GuildsIdIntegrationsIdSync(guild_id), post, "/guilds/{}/integrations/{}/sync", diff --git a/src/constants.rs b/src/constants.rs index 99327b7..0ec973c 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,3 +1,5 @@ +use std::result::Result as StdResult; + /// The gateway version used by the library. The gateway URI is retrieved via /// the REST API. pub const GATEWAY_VERSION: u8 = 6; @@ -56,41 +58,37 @@ pub enum ErrorCode { UnknownUser, } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum OpCode { - Event, - Heartbeat, - Identify, - StatusUpdate, - VoiceStateUpdate, - VoiceServerPing, - Resume, - Reconnect, - GetGuildMembers, - InvalidSession, - Hello, - HeartbeatAck, -} - -impl OpCode { - pub fn from_num(num: u64) -> Option<Self> { - match num { - 0 => Some(OpCode::Event), - 1 => Some(OpCode::Heartbeat), - 2 => Some(OpCode::Identify), - 3 => Some(OpCode::StatusUpdate), - 4 => Some(OpCode::VoiceStateUpdate), - 5 => Some(OpCode::VoiceServerPing), - 6 => Some(OpCode::Resume), - 7 => Some(OpCode::Reconnect), - 8 => Some(OpCode::GetGuildMembers), - 9 => Some(OpCode::InvalidSession), - 10 => Some(OpCode::Hello), - 11 => Some(OpCode::HeartbeatAck), - _ => None, - } +enum_number!( + /// Enum to map gateway opcodes. + OpCode { + /// Dispatches an event. + Event = 0, + /// Used for ping checking. + Heartbeat = 1, + /// Used for client handshake. + Identify = 2, + /// Used to update the client status. + StatusUpdate = 3, + /// Used to join/move/leave voice channels. + VoiceStateUpdate = 4, + /// Used for voice ping checking. + VoiceServerPing = 5, + /// Used to resume a closed connection. + Resume = 6, + /// Used to tell clients to reconnect to the gateway. + Reconnect = 7, + /// Used to request guild members. + GetGuildMembers = 8, + /// Used to notify clients that they have an invalid session Id. + InvalidSession = 9, + /// Sent immediately after connection, contains heartbeat + server info. + Hello = 10, + /// Sent immediately following a client heartbeat that was received. + HeartbeatAck = 11, } +); +impl OpCode { pub fn num(&self) -> u64 { match *self { OpCode::Event => 0, @@ -109,31 +107,27 @@ impl OpCode { } } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum VoiceOpCode { - Identify, - Heartbeat, - Hello, - KeepAlive, - SelectProtocol, - SessionDescription, - Speaking, -} - -impl VoiceOpCode { - pub fn from_num(num: u64) -> Option<Self> { - match num { - 0 => Some(VoiceOpCode::Identify), - 1 => Some(VoiceOpCode::SelectProtocol), - 2 => Some(VoiceOpCode::Hello), - 3 => Some(VoiceOpCode::KeepAlive), - 4 => Some(VoiceOpCode::SessionDescription), - 5 => Some(VoiceOpCode::Speaking), - 8 => Some(VoiceOpCode::Heartbeat), - _ => None, - } +enum_number!( + /// Enum to map voice opcodes. + VoiceOpCode { + /// Used to begin a voice websocket connection. + Identify = 0, + /// Used to select the voice protocol. + SelectProtocol = 1, + /// Used to complete the websocket handshake. + Hello = 2, + /// Used to keep the websocket connection alive. + KeepAlive = 3, + /// Used to describe the session. + SessionDescription = 4, + /// Used to indicate which users are speaking. + Speaking = 5, + /// Used to heartbeat. + Heartbeat = 8, } +); +impl VoiceOpCode { pub fn num(&self) -> u64 { match *self { VoiceOpCode::Identify => 0, diff --git a/src/ext/cache/mod.rs b/src/ext/cache/mod.rs index ee26816..84c21a7 100644 --- a/src/ext/cache/mod.rs +++ b/src/ext/cache/mod.rs @@ -842,14 +842,15 @@ impl Cache { for guild in ready.guilds { match guild { - PossibleGuild::Offline(guild_id) => { - self.unavailable_guilds.insert(guild_id); - self.guilds.remove(&guild_id); + GuildStatus::Offline(unavailable) => { + self.guilds.remove(&unavailable.id); + self.unavailable_guilds.insert(unavailable.id); }, - PossibleGuild::Online(guild) => { - self.channels.extend(guild.channels.clone()); + GuildStatus::OnlineGuild(guild) => { + self.unavailable_guilds.remove(&guild.id); self.guilds.insert(guild.id, Arc::new(RwLock::new(guild))); }, + GuildStatus::OnlinePartialGuild(_) => {}, } } diff --git a/src/ext/voice/handler.rs b/src/ext/voice/handler.rs index 60cc6e6..c81a57f 100644 --- a/src/ext/voice/handler.rs +++ b/src/ext/voice/handler.rs @@ -1,4 +1,3 @@ -use serde_json::builder::ObjectBuilder; use std::sync::mpsc::{self, Sender as MpscSender}; use super::{AudioReceiver, AudioSource}; use super::connection_info::ConnectionInfo; @@ -162,11 +161,11 @@ impl Handler { // Safe as all of these being present was already checked. self.send(VoiceStatus::Connect(ConnectionInfo { - endpoint, - guild_id, - session_id, - token, - user_id, + endpoint: endpoint, + guild_id: guild_id, + session_id: session_id, + token: token, + user_id: user_id, })); true @@ -409,14 +408,15 @@ impl Handler { /// [`standalone`]: #method.standalone fn update(&self) { if let Some(ref ws) = self.ws { - let map = ObjectBuilder::new() - .insert("op", VoiceOpCode::SessionDescription.num()) - .insert_object("d", |o| o - .insert("channel_id", self.channel_id.map(|c| c.0)) - .insert("guild_id", self.guild_id.0) - .insert("self_deaf", self.self_deaf) - .insert("self_mute", self.self_mute)) - .build(); + let map = json!({ + "op": VoiceOpCode::SessionDescription.num(), + "d": { + "channel_id": self.channel_id.map(|c| c.0), + "guild_id": self.guild_id.0, + "self_deaf": self.self_deaf, + "self_mute": self.self_mute, + } + }); let _ = ws.send(GatewayStatus::SendMessage(map)); } diff --git a/src/ext/voice/payload.rs b/src/ext/voice/payload.rs index 964d6d2..c2e7c0c 100644 --- a/src/ext/voice/payload.rs +++ b/src/ext/voice/payload.rs @@ -1,47 +1,50 @@ -use serde_json::builder::ObjectBuilder; use serde_json::Value; use super::connection_info::ConnectionInfo; use ::constants::VoiceOpCode; #[inline] pub fn build_identify(info: &ConnectionInfo) -> Value { - ObjectBuilder::new() - .insert("op", VoiceOpCode::Identify.num()) - .insert_object("d", |o| o - .insert("server_id", info.guild_id.0) - .insert("session_id", &info.session_id) - .insert("token", &info.token) - .insert("user_id", info.user_id.0)) - .build() + json!({ + "op": VoiceOpCode::Identify.num(), + "d": { + "server_id": info.guild_id.0, + "session_id": &info.session_id, + "token": &info.token, + "user_id": info.user_id.0, + } + }) } #[inline] pub fn build_keepalive() -> Value { - ObjectBuilder::new() - .insert("op", VoiceOpCode::KeepAlive.num()) - .insert("d", Value::Null) - .build() + json!({ + "op": VoiceOpCode::KeepAlive.num(), + "d": Value::Null, + }) } #[inline] pub fn build_select_protocol(address: ::std::borrow::Cow<str>, port: u16) -> Value { - ObjectBuilder::new() - .insert("op", VoiceOpCode::SelectProtocol.num()) - .insert_object("d", |o| o - .insert("protocol", "udp") - .insert_object("data", |o| o - .insert("address", address) - .insert("mode", super::CRYPTO_MODE) - .insert("port", port))) - .build() + json!({ + "op": VoiceOpCode::SelectProtocol.num(), + "d": { + "protocol": "udp", + "data": { + "address": address, + "mode": super::CRYPTO_MODE, + "port": port, + } + } + }) } #[inline] pub fn build_speaking(speaking: bool) -> Value { - ObjectBuilder::new() - .insert("op", VoiceOpCode::Speaking.num()) - .insert_object("d", |o| o - .insert("delay", 0) - .insert("speaking", speaking)) - .build() + json!({ + "op": VoiceOpCode::Speaking.num(), + "d": { + "delay": 0, + "speaking": speaking, + } + }) } diff --git a/src/internal/prelude.rs b/src/internal/prelude.rs index 9b6b993..46cd337 100644 --- a/src/internal/prelude.rs +++ b/src/internal/prelude.rs @@ -4,6 +4,9 @@ //! These are not publicly re-exported to the end user, and must stay as a //! private module. -pub use serde_json::Value; +pub type JsonMap = Map<String, Value>; + +pub use serde_json::{Map, Number, Value}; +pub use std::result::Result as StdResult; pub use ::client::ClientError; pub use ::error::{Error, Result}; @@ -85,7 +85,7 @@ //! [examples]: https://github.com/zeyla/serenity/tree/master/examples //! [gateway docs]: client/gateway/index.html #![allow(doc_markdown, inline_always, unknown_lints)] -#![doc(html_logo_url="https://docs.austinhellyer.me/serenity/docs_header.png")] +#![doc(html_logo_url="https://zey.moe/u/serenity$header.png")] #![warn(enum_glob_use, if_not_else)] #[macro_use] @@ -94,13 +94,17 @@ extern crate bitflags; extern crate lazy_static; #[macro_use] extern crate log; +#[macro_use] +extern crate serde_derive; +#[macro_use] +extern crate serde_json; extern crate base64; extern crate byteorder; extern crate flate2; extern crate hyper; extern crate multipart; -extern crate serde_json; +extern crate serde; extern crate time; extern crate typemap; extern crate websocket; @@ -118,9 +122,11 @@ pub mod ext; pub mod model; pub mod prelude; +#[macro_use] +mod internal; + mod constants; mod error; -mod internal; pub use client::Client; pub use error::{Error, Result}; diff --git a/src/model/channel/attachment.rs b/src/model/channel/attachment.rs index b6ce53d..17e3076 100644 --- a/src/model/channel/attachment.rs +++ b/src/model/channel/attachment.rs @@ -1,7 +1,28 @@ use hyper::Client as HyperClient; use std::io::Read; use ::internal::prelude::*; -use ::model::Attachment; + +/// A file uploaded with a message. Not to be confused with [`Embed`]s. +/// +/// [`Embed`]: struct.Embed.html +#[derive(Clone, Debug, Deserialize)] +pub struct Attachment { + /// The unique ID given to this attachment. + pub id: String, + /// The filename of the file that was uploaded. This is equivalent to what + /// the uploader had their file named. + pub filename: String, + /// If the attachment is an image, then the height of the image is provided. + pub height: Option<u64>, + /// The proxy URL. + pub proxy_url: String, + /// The size of the file in bytes. + pub size: u64, + /// The URL of the uploaded attachment. + pub url: String, + /// If the attachment is an image, then the widfth of the image is provided. + pub width: Option<u64>, +} impl Attachment { /// If this attachment is an image, then a tuple of the width and height diff --git a/src/model/channel/channel_id.rs b/src/model/channel/channel_id.rs index 7a2fb3a..f0834de 100644 --- a/src/model/channel/channel_id.rs +++ b/src/model/channel/channel_id.rs @@ -1,4 +1,3 @@ -use serde_json::builder::ObjectBuilder; use std::fmt::{Display, Formatter, Result as FmtResult, Write as FmtWrite}; use std::io::Read; use ::client::rest; @@ -54,12 +53,12 @@ impl ChannelId { PermissionOverwriteType::Role(id) => (id.0, "role"), }; - let map = ObjectBuilder::new() - .insert("allow", target.allow.bits()) - .insert("deny", target.deny.bits()) - .insert("id", id) - .insert("type", kind) - .build(); + let map = json!({ + "allow": target.allow.bits(), + "deny": target.deny.bits(), + "id": id, + "type": kind, + }); rest::create_permission(self.0, id, &map) } @@ -122,7 +121,9 @@ impl ChannelId { .map(|message_id| message_id.0) .collect::<Vec<u64>>(); - let map = ObjectBuilder::new().insert("messages", ids).build(); + let map = json!({ + "messages": ids + }); rest::delete_messages(self.0, &map) } @@ -181,7 +182,7 @@ impl ChannelId { /// [Manage Channel]: permissions/constant.MANAGE_CHANNELS.html #[inline] pub fn edit<F: FnOnce(EditChannel) -> EditChannel>(&self, f: F) -> Result<GuildChannel> { - rest::edit_channel(self.0, &f(EditChannel::default()).0.build()) + rest::edit_channel(self.0, &f(EditChannel::default()).0) } /// Edits a [`Message`] in the channel given its Id. diff --git a/src/model/channel/embed.rs b/src/model/channel/embed.rs index 32c3722..b278622 100644 --- a/src/model/channel/embed.rs +++ b/src/model/channel/embed.rs @@ -1,6 +1,57 @@ -use serde_json::Value; -use ::model::Embed; use ::utils::builder::CreateEmbed; +use ::utils::Colour; +use ::internal::prelude::*; + +/// Represents a rich embed which allows using richer markdown, multiple fields +/// and more. This was heavily inspired by [slack's attachments]. +/// +/// You can include an attachment in your own message by a user or a bot, or in +/// a webhook. +/// +/// **Note**: Maximum amount of characters you can put is 256 in a field name, +/// 1024 in a field value, and 2048 in a description. +/// +/// [slack's attachments]: https://api.slack.com/docs/message-attachments +#[derive(Clone, Debug, Deserialize)] +pub struct Embed { + /// Information about the author of the embed. + pub author: Option<EmbedAuthor>, + /// The colour code of the embed. + #[serde(default, rename="color")] + pub colour: Colour, + /// The description of the embed. + /// + /// The maximum value for this field is 2048 unicode codepoints. + pub description: Option<String>, + /// The array of fields. + /// + /// The maximum number of fields is 25. + #[serde(default)] + pub fields: Vec<EmbedField>, + /// Image information of the embed. + pub image: Option<EmbedImage>, + /// The type of the embed. For embeds not generated by Discord's backend, + /// this will always be "rich". + #[serde(rename="type")] + pub kind: String, + /// Provider information for the embed. + /// + /// For example, if the embed [`kind`] is `"video"`, the provider might + /// contain information about YouTube. + pub provider: Option<EmbedProvider>, + /// Thumbnail information of the embed. + pub thumbnail: Option<EmbedThumbnail>, + /// Timestamp information. + pub timestamp: Option<String>, + /// The title of the embed. + pub title: Option<String>, + /// The URL of the embed. + pub url: Option<String>, + /// The embed's video information. + /// + /// This is present if the [`kind`] is `"video"`. + pub video: Option<EmbedVideo>, +} impl Embed { /// Creates a fake Embed, giving back a `serde_json` map. @@ -13,3 +64,96 @@ impl Embed { Value::Object(f(CreateEmbed::default()).0) } } + +/// An author object in an embed. +#[derive(Clone, Debug, Deserialize)] +pub struct EmbedAuthor { + /// The URL of the author icon. + /// + /// This only supports HTTP(S). + pub icon_url: Option<String>, + /// The name of the author. + pub name: String, + /// A proxied URL of the author icon. + pub proxy_icon_url: Option<String>, + /// The URL of the author. + pub url: Option<String>, +} + +/// A field object in an embed. +#[derive(Clone, Debug, Deserialize)] +pub struct EmbedField { + /// Indicator of whether the field should display as inline. + pub inline: bool, + /// The name of the field. + /// + /// The maximum length of this field is 512 unicode codepoints. + pub name: String, + /// The value of the field. + /// + /// The maxiumum length of this field is 1024 unicode codepoints. + pub value: String, +} + +/// Footer information for an embed. +#[derive(Clone, Debug, Deserialize)] +pub struct EmbedFooter { + /// The URL of the footer icon. + /// + /// This only supports HTTP(S). + pub icon_url: String, + /// A proxied URL of the footer icon. + pub proxy_icon_url: String, + /// The associated text with the footer. + pub text: String, +} + +/// An image object in an embed. +#[derive(Clone, Debug, Deserialize)] +pub struct EmbedImage { + /// The height of the image. + pub height: u64, + /// A proxied URL of the image. + pub proxy_url: String, + /// Source URL of the image. + /// + /// This only supports HTTP(S). + pub url: String, + /// The width of the image. + pub width: u64, +} + +/// The provider of an embed. +#[derive(Clone, Debug, Deserialize)] +pub struct EmbedProvider { + /// The name of the provider. + pub name: String, + /// The URL of the provider. + pub url: Option<String>, +} + +/// The dimensions and URL of an embed thumbnail. +#[derive(Clone, Debug, Deserialize)] +pub struct EmbedThumbnail { + /// The height of the thumbnail in pixels. + pub height: u64, + /// A proxied URL of the thumbnail. + pub proxy_url: String, + /// The source URL of the thumbnail. + /// + /// This only supports HTTP(S). + pub url: String, + /// The width of the thumbnail in pixels. + pub width: u64, +} + +/// Video information for an embed. +#[derive(Clone, Debug, Deserialize)] +pub struct EmbedVideo { + /// The height of the video in pixels. + pub height: u64, + /// The source URL of the video. + pub url: String, + /// The width of the video in pixels. + pub width: u64, +} diff --git a/src/model/channel/group.rs b/src/model/channel/group.rs index f287e9a..21e9606 100644 --- a/src/model/channel/group.rs +++ b/src/model/channel/group.rs @@ -5,6 +5,31 @@ use ::client::rest; use ::model::*; use ::utils::builder::{CreateMessage, GetMessages}; +/// A group channel - potentially including other [`User`]s - separate from a +/// [`Guild`]. +/// +/// [`Guild`]: struct.Guild.html +/// [`User`]: struct.User.html +#[derive(Clone, Debug, Deserialize)] +pub struct Group { + /// The Id of the group channel. + #[serde(rename="id")] + pub channel_id: ChannelId, + /// The optional icon of the group channel. + pub icon: Option<String>, + /// The Id of the last message sent. + pub last_message_id: Option<MessageId>, + /// Timestamp of the latest pinned message. + pub last_pin_timestamp: Option<String>, + /// The name of the group channel. + pub name: Option<String>, + /// The Id of the group owner. + pub owner_id: UserId, + /// A map of the group's recipients. + #[serde(deserialize_with="deserialize_users")] + pub recipients: HashMap<UserId, Arc<RwLock<User>>>, +} + impl Group { /// Adds the given user to the group. If the user is already in the group, /// then nothing is done. diff --git a/src/model/channel/guild_channel.rs b/src/model/channel/guild_channel.rs index e4a84c7..eaefb70 100644 --- a/src/model/channel/guild_channel.rs +++ b/src/model/channel/guild_channel.rs @@ -1,4 +1,3 @@ -use serde_json::builder::ObjectBuilder; use std::fmt::{Display, Formatter, Result as FmtResult}; use std::io::Read; use std::mem; @@ -10,6 +9,58 @@ use ::utils::builder::{CreateInvite, CreateMessage, EditChannel, GetMessages}; #[cfg(feature="cache")] use ::client::CACHE; +/// Represents a guild's text or voice channel. Some methods are available only +/// for voice channels and some are only available for text channels. +#[derive(Clone, Debug, Deserialize)] +pub struct GuildChannel { + /// The unique Id of the channel. + /// + /// The default channel Id shares the Id of the guild and the default role. + pub id: ChannelId, + /// The bitrate of the channel. + /// + /// **Note**: This is only available for voice channels. + pub bitrate: Option<u64>, + /// The Id of the guild the channel is located in. + /// + /// If this matches with the [`id`], then this is the default text channel. + /// + /// The original voice channel has an Id equal to the guild's Id, + /// incremented by one. + pub guild_id: GuildId, + /// The type of the channel. + #[serde(rename="type")] + pub kind: ChannelType, + /// The Id of the last message sent in the channel. + /// + /// **Note**: This is only available for text channels. + pub last_message_id: Option<MessageId>, + /// The timestamp of the time a pin was most recently made. + /// + /// **Note**: This is only available for text channels. + pub last_pin_timestamp: Option<String>, + /// The name of the channel. + pub name: String, + /// Permission overwrites for [`Member`]s and for [`Role`]s. + /// + /// [`Member`]: struct.Member.html + /// [`Role`]: struct.Role.html + pub permission_overwrites: Vec<PermissionOverwrite>, + /// The position of the channel. + /// + /// The default text channel will _almost always_ have a position of `-1` or + /// `0`. + pub position: i64, + /// The topic of the channel. + /// + /// **Note**: This is only available for text channels. + pub topic: Option<String>, + /// The maximum number of members allowed in the channel. + /// + /// **Note**: This is only available for voice channels. + pub user_limit: Option<u64>, +} + impl GuildChannel { /// Broadcasts to the channel that the current user is typing. /// @@ -48,9 +99,7 @@ impl GuildChannel { } } - let map = f(CreateInvite::default()).0.build(); - - rest::create_invite(self.id.0, &map) + rest::create_invite(self.id.0, &f(CreateInvite::default()).0) } /// Creates a [permission overwrite][`PermissionOverwrite`] for either a @@ -130,34 +179,6 @@ impl GuildChannel { self.id.create_permission(target) } - #[doc(hidden)] - pub fn decode(value: Value) -> Result<GuildChannel> { - let mut map = into_map(value)?; - - let id = remove(&mut map, "guild_id").and_then(GuildId::decode)?; - - GuildChannel::decode_guild(Value::Object(map), id) - } - - #[doc(hidden)] - pub fn decode_guild(value: Value, guild_id: GuildId) -> Result<GuildChannel> { - let mut map = into_map(value)?; - - Ok(GuildChannel { - id: remove(&mut map, "id").and_then(ChannelId::decode)?, - name: remove(&mut map, "name").and_then(into_string)?, - guild_id: guild_id, - topic: opt(&mut map, "topic", into_string)?, - position: req!(remove(&mut map, "position")?.as_i64()), - kind: remove(&mut map, "type").and_then(ChannelType::decode)?, - last_message_id: opt(&mut map, "last_message_id", MessageId::decode)?, - permission_overwrites: decode_array(remove(&mut map, "permission_overwrites")?, PermissionOverwrite::decode)?, - bitrate: remove(&mut map, "bitrate").ok().and_then(|v| v.as_u64()), - user_limit: remove(&mut map, "user_limit").ok().and_then(|v| v.as_u64()), - last_pin_timestamp: opt(&mut map, "last_pin_timestamp", into_string)?, - }) - } - /// Deletes this channel, returning the channel on a successful deletion. pub fn delete(&self) -> Result<Channel> { #[cfg(feature="cache")] @@ -238,12 +259,12 @@ impl GuildChannel { } } - let map = ObjectBuilder::new() - .insert("name", &self.name) - .insert("position", self.position) - .insert("type", self.kind.name()); + let mut map = Map::new(); + map.insert("name".to_owned(), Value::String(self.name.clone())); + map.insert("position".to_owned(), Value::Number(Number::from(self.position))); + map.insert("type".to_owned(), Value::String(self.kind.name().to_owned())); - let edited = f(EditChannel(map)).0.build(); + let edited = f(EditChannel(map)).0; match rest::edit_channel(self.id.0, &edited) { Ok(channel) => { diff --git a/src/model/channel/message.rs b/src/model/channel/message.rs index 709ce9a..029914d 100644 --- a/src/model/channel/message.rs +++ b/src/model/channel/message.rs @@ -1,4 +1,3 @@ -use serde_json::builder::ObjectBuilder; use std::mem; use ::constants; use ::client::rest; @@ -8,6 +7,63 @@ use ::utils::builder::{CreateEmbed, CreateMessage}; #[cfg(feature="cache")] use ::client::CACHE; +/// A representation of a message over a guild's text channel, a group, or a +/// private channel. +#[derive(Clone, Debug, Deserialize)] +pub struct Message { + /// The unique Id of the message. Can be used to calculate the creation date + /// of the message. + pub id: MessageId, + /// An array of the files attached to a message. + pub attachments: Vec<Attachment>, + /// The user that sent the message. + pub author: User, + /// The Id of the [`Channel`] that the message was sent to. + /// + /// [`Channel`]: enum.Channel.html + pub channel_id: ChannelId, + /// The content of the message. + pub content: String, + /// The timestamp of the last time the message was updated, if it was. + pub edited_timestamp: Option<String>, + /// Array of embeds sent with the message. + pub embeds: Vec<Embed>, + /// Whether the message is the "found" message in a search. + /// + /// Note that this is only relevant in the context of searches, and will + /// otherwise always be `false`. + #[serde(default)] + pub hit: bool, + /// Indicator of the type of message this is, i.e. whether it is a regular + /// message or a system message. + #[serde(rename="type")] + pub kind: MessageType, + /// Indicator of whether the message mentions everyone. + pub mention_everyone: bool, + /// Array of [`Role`]s' Ids mentioned in the message. + /// + /// [`Role`]: struct.Role.html + pub mention_roles: Vec<RoleId>, + /// Array of users mentioned in the message. + pub mentions: Vec<User>, + /// Non-repeating number used for ensuring message order. + pub nonce: Option<String>, + /// Indicator of whether the message is pinned. + pub pinned: bool, + /// Array of reactions performed on the message. + #[serde(default)] + pub reactions: Vec<MessageReaction>, + /// Initial message creation timestamp, calculated from its Id. + pub timestamp: String, + /// Indicator of whether the command is to be played back via + /// text-to-speech. + /// + /// In the client, this is done via the `/tts` slash command. + pub tts: bool, + /// The Id of the webhook that sent this message, if one did. + pub webhook_id: Option<WebhookId>, +} + impl Message { /// Deletes the message. /// @@ -328,10 +384,10 @@ impl Message { gen.push_str(": "); gen.push_str(content); - let map = ObjectBuilder::new() - .insert("content", gen) - .insert("tts", false) - .build(); + let map = json!({ + "content": gen, + "tts": false, + }); rest::send_message(self.channel_id.0, &map) } @@ -368,3 +424,62 @@ impl From<Message> for MessageId { message.id } } + +/// A representation of a reaction to a message. +/// +/// Multiple of the same [reaction type] are sent into one `MessageReaction`, +/// with an associated [`count`]. +/// +/// [`count`]: #structfield.count +/// [reaction type]: enum.ReactionType.html +#[derive(Clone, Debug, Deserialize)] +pub struct MessageReaction { + /// The amount of the type of reaction that have been sent for the + /// associated message. + pub count: u64, + /// Indicator of whether the current user has sent the type of reaction. + pub me: bool, + /// The type of reaction. + #[serde(rename="emoji")] + pub reaction_type: ReactionType, +} + +enum_number!( + /// Differentiates between regular and different types of system messages. + MessageType { + /// A regular message. + Regular = 0, + /// An indicator that a recipient was added by the author. + GroupRecipientAddition = 1, + /// An indicator that a recipient was removed by the author. + GroupRecipientRemoval = 2, + /// An indicator that a call was started by the author. + GroupCallCreation = 3, + /// An indicator that the group name was modified by the author. + GroupNameUpdate = 4, + /// An indicator that the group icon was modified by the author. + GroupIconUpdate = 5, + /// An indicator that a message was pinned by the author. + PinsAdd = 6, + } +); + +/// An emoji reaction to a message. +#[derive(Clone, Debug, Deserialize)] +pub struct Reaction { + /// The [`Channel`] of the associated [`Message`]. + /// + /// [`Channel`]: enum.Channel.html + /// [`Message`]: struct.Message.html + pub channel_id: ChannelId, + /// The reactive emoji used. + pub emoji: ReactionType, + /// The Id of the [`Message`] that was reacted to. + /// + /// [`Message`]: struct.Message.html + pub message_id: MessageId, + /// The Id of the [`User`] that sent the reaction. + /// + /// [`User`]: struct.User.html + pub user_id: UserId, +} diff --git a/src/model/channel/mod.rs b/src/model/channel/mod.rs index 3ea765a..4877785 100644 --- a/src/model/channel/mod.rs +++ b/src/model/channel/mod.rs @@ -16,11 +16,31 @@ pub use self::message::*; pub use self::private_channel::*; pub use self::reaction::*; +use serde::de::Error as DeError; +use serde_json; use std::fmt::{Display, Formatter, Result as FmtResult}; use std::io::Read; use ::model::*; use ::utils::builder::{CreateMessage, GetMessages}; +/// A container for any channel. +#[derive(Clone, Debug)] +pub enum Channel { + /// A group. A group comprises of only one channel. + Group(Arc<RwLock<Group>>), + /// A [text] or [voice] channel within a [`Guild`]. + /// + /// [`Guild`]: struct.Guild.html + /// [text]: enum.ChannelType.html#variant.Text + /// [voice]: enum.ChannelType.html#variant.Voice + Guild(Arc<RwLock<GuildChannel>>), + /// A private channel to another [`User`]. No other users may access the + /// channel. For multi-user "private channels", use a group. + /// + /// [`User`]: struct.User.html + Private(Arc<RwLock<PrivateChannel>>), +} + impl Channel { /// React to a [`Message`] with a custom [`Emoji`] or unicode character. /// @@ -40,21 +60,6 @@ impl Channel { self.id().create_reaction(message_id, reaction_type) } - #[doc(hidden)] - pub fn decode(value: Value) -> Result<Channel> { - let map = into_map(value)?; - match req!(map.get("type").and_then(|x| x.as_u64())) { - 0 | 2 => GuildChannel::decode(Value::Object(map)) - .map(|x| Channel::Guild(Arc::new(RwLock::new(x)))), - 1 => PrivateChannel::decode(Value::Object(map)) - .map(|x| Channel::Private(Arc::new(RwLock::new(x)))), - 3 => Group::decode(Value::Object(map)) - .map(|x| Channel::Group(Arc::new(RwLock::new(x)))), - other => Err(Error::Decode("Expected value Channel type", - Value::U64(other))), - } - } - /// Deletes the inner channel. /// /// **Note**: There is no real function as _deleting_ a [`Group`]. The @@ -307,6 +312,30 @@ impl Channel { } } +impl Deserialize for Channel { + fn deserialize<D: Deserializer>(deserializer: D) -> StdResult<Self, D::Error> { + let v = JsonMap::deserialize(deserializer)?; + let kind = { + let kind = v.get("type").ok_or_else(|| DeError::missing_field("type"))?; + + kind.as_u64().unwrap() + }; + + match kind { + 0 | 2 => serde_json::from_value::<GuildChannel>(Value::Object(v)) + .map(|x| Channel::Guild(Arc::new(RwLock::new(x)))) + .map_err(DeError::custom), + 1 => serde_json::from_value::<PrivateChannel>(Value::Object(v)) + .map(|x| Channel::Private(Arc::new(RwLock::new(x)))) + .map_err(DeError::custom), + 3 => serde_json::from_value::<Group>(Value::Object(v)) + .map(|x| Channel::Group(Arc::new(RwLock::new(x)))) + .map_err(DeError::custom), + _ => Err(DeError::custom("Unknown channel type")), + } + } +} + impl Display for Channel { /// Formats the channel into a "mentioned" string. /// @@ -339,22 +368,101 @@ impl Display for Channel { } } -impl PermissionOverwrite { - #[doc(hidden)] - pub fn decode(value: Value) -> Result<PermissionOverwrite> { - let mut map = into_map(value)?; - let id = remove(&mut map, "id").and_then(decode_id)?; - let kind = remove(&mut map, "type").and_then(into_string)?; - let kind = match &*kind { - "member" => PermissionOverwriteType::Member(UserId(id)), - "role" => PermissionOverwriteType::Role(RoleId(id)), - _ => return Err(Error::Decode("Expected valid PermissionOverwrite type", Value::String(kind))), +enum_number!( + /// A representation of a type of channel. + ChannelType { + #[doc="An indicator that the channel is a text [`GuildChannel`]. + +[`GuildChannel`]: struct.GuildChannel.html"] + Text = 0, + #[doc="An indicator that the channel is a [`PrivateChannel`]. + +[`PrivateChannel`]: struct.PrivateChannel.html"] + Private = 1, + #[doc="An indicator that the channel is a voice [`GuildChannel`]. + +[`GuildChannel`]: struct.GuildChannel.html"] + Voice = 2, + #[doc="An indicator that the channel is the channel of a [`Group`]. + +[`Group`]: struct.Group.html"] + Group = 3, + } +); + +impl ChannelType { + pub fn name(&self) -> &str { + match *self { + ChannelType::Group => "group", + ChannelType::Private => "private", + ChannelType::Text => "text", + ChannelType::Voice => "voice", + } + } +} + +#[derive(Deserialize)] +struct PermissionOverwriteData { + allow: Permissions, + deny: Permissions, + id: u64, + #[serde(rename="type")] + kind: String, +} + +/// A channel-specific permission overwrite for a member or role. +#[derive(Clone, Debug)] +pub struct PermissionOverwrite { + pub allow: Permissions, + pub deny: Permissions, + pub kind: PermissionOverwriteType, +} + +impl Deserialize for PermissionOverwrite { + fn deserialize<D: Deserializer>(deserializer: D) -> StdResult<PermissionOverwrite, D::Error> { + let data = PermissionOverwriteData::deserialize(deserializer)?; + + let kind = match &data.kind[..] { + "member" => PermissionOverwriteType::Member(UserId(data.id)), + "role" => PermissionOverwriteType::Role(RoleId(data.id)), + _ => return Err(DeError::custom("Unknown PermissionOverwriteType")), }; Ok(PermissionOverwrite { + allow: data.allow, + deny: data.deny, kind: kind, - allow: remove(&mut map, "allow").and_then(Permissions::decode)?, - deny: remove(&mut map, "deny").and_then(Permissions::decode)?, }) } } + +/// The type of edit being made to a Channel's permissions. +/// +/// This is for use with methods such as `Context::create_permission`. +/// +/// [`Context::create_permission`]: ../client/ +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum PermissionOverwriteType { + /// A member which is having its permission overwrites edited. + Member(UserId), + /// A role which is having its permission overwrites edited. + Role(RoleId), +} + +/// The results of a search, including the total results and a vector of +/// messages. +#[derive(Clone, Debug, Deserialize)] +pub struct SearchResult { + /// An amount of messages returned from the result. + /// + /// Note that this is a vectof of a vector of messages. Each "set" of + /// messages contains the "found" message, as well as optional surrounding + /// messages for context. + #[serde(rename="messages")] + pub results: Vec<Vec<Message>>, + /// The number of messages directly related to the search. + /// + /// This does not count contextual messages. + #[serde(rename="total_results")] + pub total: u64, +} diff --git a/src/model/channel/private_channel.rs b/src/model/channel/private_channel.rs index 8ce520c..802f84a 100644 --- a/src/model/channel/private_channel.rs +++ b/src/model/channel/private_channel.rs @@ -1,8 +1,34 @@ use std::fmt::{Display, Formatter, Result as FmtResult}; use std::io::Read; +use super::deserialize_single_recipient; use ::model::*; use ::utils::builder::{CreateMessage, GetMessages}; +/// A Direct Message text channel with another user. +#[derive(Clone, Debug, Deserialize)] +pub struct PrivateChannel { + /// The unique Id of the private channel. + /// + /// Can be used to calculate the first message's creation date. + pub id: ChannelId, + /// The Id of the last message sent. + pub last_message_id: Option<MessageId>, + /// Timestamp of the last time a [`Message`] was pinned. + /// + /// [`Message`]: struct.Message.html + pub last_pin_timestamp: Option<String>, + /// Indicator of the type of channel this is. + /// + /// This should always be [`ChannelType::Private`]. + /// + /// [`ChannelType::Private`]: enum.ChannelType.html#variant.Private + #[serde(rename="type")] + pub kind: ChannelType, + /// The recipient to the private channel. + #[serde(deserialize_with="deserialize_single_recipient", rename="recipients")] + pub recipient: Arc<RwLock<User>>, +} + impl PrivateChannel { /// Broadcasts that the current user is typing to the recipient. pub fn broadcast_typing(&self) -> Result<()> { @@ -26,21 +52,6 @@ impl PrivateChannel { self.id.create_reaction(message_id, reaction_type) } - #[doc(hidden)] - pub fn decode(value: Value) -> Result<PrivateChannel> { - let mut map = into_map(value)?; - let mut recipients = decode_array(remove(&mut map, "recipients")?, - User::decode)?; - - Ok(PrivateChannel { - id: remove(&mut map, "id").and_then(ChannelId::decode)?, - kind: remove(&mut map, "type").and_then(ChannelType::decode)?, - last_message_id: opt(&mut map, "last_message_id", MessageId::decode)?, - last_pin_timestamp: opt(&mut map, "last_pin_timestamp", into_string)?, - recipient: Arc::new(RwLock::new(recipients.remove(0))), - }) - } - /// Deletes the channel. This does not delete the contents of the channel, /// and is equivalent to closing a private channel on the client, which can /// be re-opened. diff --git a/src/model/channel/reaction.rs b/src/model/channel/reaction.rs index aa4f339..3abe7b8 100644 --- a/src/model/channel/reaction.rs +++ b/src/model/channel/reaction.rs @@ -96,7 +96,8 @@ impl Reaction { /// The type of a [`Reaction`] sent. /// /// [`Reaction`]: struct.Reaction.html -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize)] +#[serde(untagged)] pub enum ReactionType { /// A reaction with a [`Guild`]s custom [`Emoji`], which is unique to the /// guild. @@ -113,6 +114,7 @@ pub enum ReactionType { name: String, }, /// A reaction with a twemoji. + #[serde(rename="name")] Unicode(String), } @@ -131,21 +133,6 @@ impl ReactionType { ReactionType::Unicode(ref unicode) => unicode.clone(), } } - - #[doc(hidden)] - pub fn decode(value: Value) -> Result<Self> { - let mut map = into_map(value)?; - let name = remove(&mut map, "name").and_then(into_string)?; - - // Only custom emoji reactions (`ReactionType::Custom`) have an Id. - Ok(match opt(&mut map, "id", EmojiId::decode)? { - Some(id) => ReactionType::Custom { - id: id, - name: name, - }, - None => ReactionType::Unicode(name), - }) - } } impl From<Emoji> for ReactionType { diff --git a/src/model/event.rs b/src/model/event.rs index f57cb55..e0f1993 100644 --- a/src/model/event.rs +++ b/src/model/event.rs @@ -1,13 +1,11 @@ //! All the events this library handles. -use std::collections::{BTreeMap, HashMap}; -use super::utils::*; +use serde::de::Error as DeError; +use serde_json::{self, Error as JsonError}; +use std::collections::HashMap; use super::*; use ::constants::{OpCode, VoiceOpCode}; use ::internal::prelude::*; -use ::utils::decode_array; - -type Map = BTreeMap<String, Value>; /// Event data for the channel creation event. /// @@ -27,147 +25,85 @@ pub struct ChannelCreateEvent { pub channel: Channel, } -impl ChannelCreateEvent { - #[doc(hidden)] - #[inline] - pub fn decode(map: Map) -> Result<Self> { - Ok(ChannelCreateEvent { - channel: Channel::decode(Value::Object(map))?, +impl Deserialize for ChannelCreateEvent { + fn deserialize<D: Deserializer>(deserializer: D) -> StdResult<Self, D::Error> { + Ok(Self { + channel: Channel::deserialize(deserializer)?, }) } } - #[derive(Clone, Debug)] pub struct ChannelDeleteEvent { pub channel: Channel, } -impl ChannelDeleteEvent { - #[doc(hidden)] - #[inline] - pub fn decode(map: Map) -> Result<Self> { - Ok(ChannelDeleteEvent { - channel: Channel::decode(Value::Object(map))?, +impl Deserialize for ChannelDeleteEvent { + fn deserialize<D: Deserializer>(deserializer: D) -> StdResult<Self, D::Error> { + Ok(Self { + channel: Channel::deserialize(deserializer)?, }) } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize)] pub struct ChannelPinsAckEvent { pub channel_id: ChannelId, pub timestamp: String, } -impl ChannelPinsAckEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(ChannelPinsAckEvent { - channel_id: remove(&mut map, "channel_id").and_then(ChannelId::decode)?, - timestamp: remove(&mut map, "timestamp").and_then(into_string)?, - }) - } -} - -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize)] pub struct ChannelPinsUpdateEvent { pub channel_id: ChannelId, pub last_pin_timestamp: Option<String>, } -impl ChannelPinsUpdateEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(ChannelPinsUpdateEvent { - channel_id: remove(&mut map, "channel_id").and_then(ChannelId::decode)?, - last_pin_timestamp: opt(&mut map, "last_pin_timestamp", into_string)?, - }) - } -} - -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize)] pub struct ChannelRecipientAddEvent { pub channel_id: ChannelId, pub user: User, } -impl ChannelRecipientAddEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(ChannelRecipientAddEvent { - channel_id: remove(&mut map, "channel_id").and_then(ChannelId::decode)?, - user: remove(&mut map, "user").and_then(User::decode)?, - }) - } -} - -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize)] pub struct ChannelRecipientRemoveEvent { pub channel_id: ChannelId, pub user: User, } -impl ChannelRecipientRemoveEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(ChannelRecipientRemoveEvent { - channel_id: remove(&mut map, "channel_id").and_then(ChannelId::decode)?, - user: remove(&mut map, "user").and_then(User::decode)?, - }) - } -} - #[derive(Clone, Debug)] pub struct ChannelUpdateEvent { pub channel: Channel, } -impl ChannelUpdateEvent { - #[doc(hidden)] - #[inline] - pub fn decode(map: Map) -> Result<Self> { - Ok(ChannelUpdateEvent { - channel: Channel::decode(Value::Object(map))?, +impl Deserialize for ChannelUpdateEvent { + fn deserialize<D: Deserializer>(deserializer: D) -> StdResult<Self, D::Error> { + Ok(Self { + channel: Channel::deserialize(deserializer)?, }) } } -#[derive(Clone, Debug)] -pub struct GuildBanAddEvent { - pub guild_id: GuildId, - pub user: User, +#[derive(Clone, Debug, Deserialize)] +pub struct FriendSuggestionCreateEvent { + pub reasons: Vec<SuggestionReason>, + pub suggested_user: User, } -impl GuildBanAddEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(GuildBanAddEvent { - guild_id: remove(&mut map, "guild_id").and_then(GuildId::decode)?, - user: remove(&mut map, "user").and_then(User::decode)?, - }) - } +#[derive(Clone, Copy, Debug, Deserialize)] +pub struct FriendSuggestionDeleteEvent { + pub suggested_user_id: UserId, } -#[derive(Clone, Debug)] -pub struct GuildBanRemoveEvent { +#[derive(Clone, Debug, Deserialize)] +pub struct GuildBanAddEvent { pub guild_id: GuildId, pub user: User, } -impl GuildBanRemoveEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(GuildBanRemoveEvent { - guild_id: remove(&mut map, "guild_id").and_then(GuildId::decode)?, - user: remove(&mut map, "user").and_then(User::decode)?, - }) - } +#[derive(Clone, Debug, Deserialize)] +pub struct GuildBanRemoveEvent { + pub guild_id: GuildId, + pub user: User, } #[derive(Clone, Debug)] @@ -175,12 +111,10 @@ pub struct GuildCreateEvent { pub guild: Guild, } -impl GuildCreateEvent { - #[doc(hidden)] - #[inline] - pub fn decode(map: Map) -> Result<Self> { - Ok(GuildCreateEvent { - guild: Guild::decode(Value::Object(map))?, +impl Deserialize for GuildCreateEvent { + fn deserialize<D: Deserializer>(deserializer: D) -> StdResult<Self, D::Error> { + Ok(Self { + guild: Guild::deserialize(deserializer)?, }) } } @@ -190,85 +124,54 @@ pub struct GuildDeleteEvent { pub guild: PartialGuild, } -impl GuildDeleteEvent { - #[doc(hidden)] - #[inline] - pub fn decode(map: Map) -> Result<Self> { - Ok(GuildDeleteEvent { - guild: PartialGuild::decode(Value::Object(map))?, +impl Deserialize for GuildDeleteEvent { + fn deserialize<D: Deserializer>(deserializer: D) -> StdResult<Self, D::Error> { + Ok(Self { + guild: PartialGuild::deserialize(deserializer)?, }) } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize)] pub struct GuildEmojisUpdateEvent { pub emojis: HashMap<EmojiId, Emoji>, pub guild_id: GuildId, } -impl GuildEmojisUpdateEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(GuildEmojisUpdateEvent { - emojis: remove(&mut map, "emojis").and_then(decode_emojis)?, - guild_id: remove(&mut map, "guild_id").and_then(GuildId::decode)?, - }) - } -} - -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize)] pub struct GuildIntegrationsUpdateEvent { pub guild_id: GuildId, } -impl GuildIntegrationsUpdateEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(GuildIntegrationsUpdateEvent { - guild_id: remove(&mut map, "guild_id").and_then(GuildId::decode)?, - }) - } -} - #[derive(Clone, Debug)] pub struct GuildMemberAddEvent { pub guild_id: GuildId, pub member: Member, } -impl GuildMemberAddEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - let guild_id = remove(&mut map, "guild_id").and_then(GuildId::decode)?; +impl Deserialize for GuildMemberAddEvent { + fn deserialize<D: Deserializer>(deserializer: D) -> StdResult<Self, D::Error> { + let map = JsonMap::deserialize(deserializer)?; + + let guild_id = map.get("guild_id") + .ok_or_else(|| DeError::custom("missing member add guild id")) + .and_then(|v| GuildId::deserialize(v.clone())) + .map_err(DeError::custom)?; Ok(GuildMemberAddEvent { guild_id: guild_id, - member: Member::decode_guild(guild_id, Value::Object(map))?, + member: Member::deserialize(Value::Object(map)).map_err(DeError::custom)?, }) } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize)] pub struct GuildMemberRemoveEvent { pub guild_id: GuildId, pub user: User, } -impl GuildMemberRemoveEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(GuildMemberRemoveEvent { - guild_id: remove(&mut map, "guild_id").and_then(GuildId::decode)?, - user: remove(&mut map, "user").and_then(User::decode)?, - }) - } -} - -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize)] pub struct GuildMemberUpdateEvent { pub guild_id: GuildId, pub nick: Option<String>, @@ -276,115 +179,76 @@ pub struct GuildMemberUpdateEvent { pub user: User, } -impl GuildMemberUpdateEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(GuildMemberUpdateEvent { - guild_id: remove(&mut map, "guild_id").and_then(GuildId::decode)?, - nick: opt(&mut map, "nick", into_string)?, - roles: decode_array(remove(&mut map, "roles")?, RoleId::decode)?, - user: remove(&mut map, "user").and_then(User::decode)?, - }) - } -} - #[derive(Clone, Debug)] pub struct GuildMembersChunkEvent { pub guild_id: GuildId, pub members: HashMap<UserId, Member>, } -impl GuildMembersChunkEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - let guild_id = remove(&mut map, "guild_id").and_then(GuildId::decode)?; +impl Deserialize for GuildMembersChunkEvent { + fn deserialize<D: Deserializer>(deserializer: D) -> StdResult<Self, D::Error> { + let mut map = JsonMap::deserialize(deserializer)?; + + let guild_id = map.get("guild_id") + .ok_or_else(|| DeError::custom("missing member chunk guild id")) + .and_then(|v| GuildId::deserialize(v.clone())) + .map_err(DeError::custom)?; + + let mut members = map.remove("members").ok_or_else(|| DeError::custom("missing member chunk members"))?; + + if let Some(members) = members.as_array_mut() { + let num = Value::Number(Number::from(guild_id.0)); + + for member in members { + if let Some(map) = member.as_object_mut() { + map.insert("guild_id".to_owned(), num.clone()); + } + } + } + + let members: HashMap<UserId, Member> = Deserialize::deserialize(members) + .map_err(DeError::custom)?; Ok(GuildMembersChunkEvent { guild_id: guild_id, - members: remove(&mut map, "members").and_then(|x| decode_guild_members(guild_id, x))?, + members: members, }) } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize)] pub struct GuildRoleCreateEvent { pub guild_id: GuildId, pub role: Role, } -impl GuildRoleCreateEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(GuildRoleCreateEvent { - guild_id: remove(&mut map, "guild_id").and_then(GuildId::decode)?, - role: remove(&mut map, "role").and_then(Role::decode)?, - }) - } -} - -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize)] pub struct GuildRoleDeleteEvent { pub guild_id: GuildId, pub role_id: RoleId, } -impl GuildRoleDeleteEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(GuildRoleDeleteEvent { - guild_id: remove(&mut map, "guild_id").and_then(GuildId::decode)?, - role_id: remove(&mut map, "role_id").and_then(RoleId::decode)?, - }) - } -} - -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize)] pub struct GuildRoleUpdateEvent { pub guild_id: GuildId, pub role: Role, } -impl GuildRoleUpdateEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(GuildRoleUpdateEvent { - guild_id: remove(&mut map, "guild_id").and_then(GuildId::decode)?, - role: remove(&mut map, "role").and_then(Role::decode)?, - }) - } -} - -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize)] pub struct GuildUnavailableEvent { + #[serde(rename="id")] pub guild_id: GuildId, } -impl GuildUnavailableEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(GuildUnavailableEvent { - guild_id: remove(&mut map, "id").and_then(GuildId::decode)?, - }) - } -} - #[derive(Clone, Debug)] pub struct GuildUpdateEvent { pub guild: PartialGuild, } -impl GuildUpdateEvent { - #[doc(hidden)] - #[inline] - pub fn decode(map: Map) -> Result<Self> { - Ok(GuildUpdateEvent { - guild: PartialGuild::decode(Value::Object(map))?, +impl Deserialize for GuildUpdateEvent { + fn deserialize<D: Deserializer>(deserializer: D) -> StdResult<Self, D::Error> { + Ok(Self { + guild: PartialGuild::deserialize(deserializer)?, }) } } @@ -394,51 +258,28 @@ pub struct MessageCreateEvent { pub message: Message, } -impl MessageCreateEvent { - #[doc(hidden)] - #[inline] - pub fn decode(map: Map) -> Result<Self> { - Ok(MessageCreateEvent { - message: Message::decode(Value::Object(map))?, +impl Deserialize for MessageCreateEvent { + fn deserialize<D: Deserializer>(deserializer: D) -> StdResult<Self, D::Error> { + Ok(Self { + message: Message::deserialize(deserializer)?, }) } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize)] pub struct MessageDeleteBulkEvent { pub channel_id: ChannelId, pub ids: Vec<MessageId>, } -impl MessageDeleteBulkEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(MessageDeleteBulkEvent { - channel_id: remove(&mut map, "channel_id").and_then(ChannelId::decode)?, - ids: decode_array(remove(&mut map, "ids")?, MessageId::decode)?, - }) - } -} - -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Deserialize)] pub struct MessageDeleteEvent { pub channel_id: ChannelId, + #[serde(rename="id")] pub message_id: MessageId, } -impl MessageDeleteEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(MessageDeleteEvent { - channel_id: remove(&mut map, "channel_id").and_then(ChannelId::decode)?, - message_id: remove(&mut map, "id").and_then(MessageId::decode)?, - }) - } -} - -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize)] pub struct MessageUpdateEvent { pub id: MessageId, pub channel_id: ChannelId, @@ -457,30 +298,6 @@ pub struct MessageUpdateEvent { pub embeds: Option<Vec<Value>>, } -impl MessageUpdateEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(MessageUpdateEvent { - id: remove(&mut map, "id").and_then(MessageId::decode)?, - channel_id: remove(&mut map, "channel_id").and_then(ChannelId::decode)?, - kind: opt(&mut map, "type", MessageType::decode)?, - content: opt(&mut map, "content", into_string)?, - nonce: remove(&mut map, "nonce").and_then(into_string).ok(), - tts: remove(&mut map, "tts").ok().and_then(|v| v.as_bool()), - pinned: remove(&mut map, "pinned").ok().and_then(|v| v.as_bool()), - timestamp: opt(&mut map, "timestamp", into_string)?, - edited_timestamp: opt(&mut map, "edited_timestamp", into_string)?, - author: opt(&mut map, "author", User::decode)?, - mention_everyone: remove(&mut map, "mention_everyone").ok().and_then(|v| v.as_bool()), - mentions: opt(&mut map, "mentions", |v| decode_array(v, User::decode))?, - mention_roles: opt(&mut map, "mention_roles", |v| decode_array(v, RoleId::decode))?, - attachments: opt(&mut map, "attachments", |v| decode_array(v, Attachment::decode))?, - embeds: opt(&mut map, "embeds", |v| decode_array(v, Ok))?, - }) - } -} - #[derive(Clone, Debug)] pub struct PresenceUpdateEvent { pub guild_id: Option<GuildId>, @@ -488,14 +305,21 @@ pub struct PresenceUpdateEvent { pub roles: Option<Vec<RoleId>>, } -impl PresenceUpdateEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - let guild_id = opt(&mut map, "guild_id", GuildId::decode)?; - let roles = opt(&mut map, "roles", |v| decode_array(v, RoleId::decode))?; - let presence = Presence::decode(Value::Object(map))?; - Ok(PresenceUpdateEvent { +impl Deserialize for PresenceUpdateEvent { + fn deserialize<D: Deserializer>(deserializer: D) -> StdResult<Self, D::Error> { + let mut map = JsonMap::deserialize(deserializer)?; + + let guild_id = match map.remove("guild_id") { + Some(v) => serde_json::from_value::<Option<GuildId>>(v).map_err(DeError::custom)?, + None => None, + }; + let roles = match map.remove("roles") { + Some(v) => serde_json::from_value::<Option<Vec<RoleId>>>(v).map_err(DeError::custom)?, + None => None, + }; + let presence = Presence::deserialize(Value::Object(map)).map_err(DeError::custom)?; + + Ok(Self { guild_id: guild_id, presence: presence, roles: roles, @@ -508,12 +332,12 @@ pub struct PresencesReplaceEvent { pub presences: Vec<Presence>, } -impl PresencesReplaceEvent { - #[doc(hidden)] - #[inline] - pub fn decode(value: Value) -> Result<Self> { - Ok(PresencesReplaceEvent { - presences: decode_array(value, Presence::decode)?, +impl Deserialize for PresencesReplaceEvent { + fn deserialize<D: Deserializer>(deserializer: D) -> StdResult<Self, D::Error> { + let presences: Vec<Presence> = Deserialize::deserialize(deserializer)?; + + Ok(Self { + presences: presences, }) } } @@ -523,12 +347,10 @@ pub struct ReactionAddEvent { pub reaction: Reaction, } -impl ReactionAddEvent { - #[doc(hidden)] - #[inline] - pub fn decode(map: Map) -> Result<Self> { - Ok(ReactionAddEvent { - reaction: Reaction::decode(Value::Object(map))? +impl Deserialize for ReactionAddEvent { + fn deserialize<D: Deserializer>(deserializer: D) -> StdResult<Self, D::Error> { + Ok(Self { + reaction: Reaction::deserialize(deserializer)?, }) } } @@ -538,87 +360,51 @@ pub struct ReactionRemoveEvent { pub reaction: Reaction, } -impl ReactionRemoveEvent { - #[doc(hidden)] - #[inline] - pub fn decode(map: Map) -> Result<Self> { - Ok(ReactionRemoveEvent { - reaction: Reaction::decode(Value::Object(map))? +impl Deserialize for ReactionRemoveEvent { + fn deserialize<D: Deserializer>(deserializer: D) -> StdResult<Self, D::Error> { + Ok(Self { + reaction: Reaction::deserialize(deserializer)?, }) } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Deserialize)] pub struct ReactionRemoveAllEvent { pub channel_id: ChannelId, pub message_id: MessageId, } -impl ReactionRemoveAllEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(ReactionRemoveAllEvent { - channel_id: remove(&mut map, "channel_id").and_then(ChannelId::decode)?, - message_id: remove(&mut map, "message_id").and_then(MessageId::decode)?, - }) - } -} - /// The "Ready" event, containing initial ready cache #[derive(Clone, Debug)] pub struct ReadyEvent { pub ready: Ready, } -impl ReadyEvent { - #[doc(hidden)] - #[inline] - pub fn decode(map: Map) -> Result<Self> { - Ok(ReadyEvent { - ready: Ready::decode(Value::Object(map))?, +impl Deserialize for ReadyEvent { + fn deserialize<D: Deserializer>(deserializer: D) -> StdResult<Self, D::Error> { + Ok(Self { + ready: Ready::deserialize(deserializer)?, }) } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize)] pub struct ResumedEvent { + #[serde(rename="_trace")] pub trace: Vec<Option<String>>, } -impl ResumedEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(ResumedEvent { - trace: remove(&mut map, "_trace").and_then(|v| decode_array(v, |v| Ok(into_string(v).ok())))?, - }) - } -} - -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize)] pub struct TypingStartEvent { pub channel_id: ChannelId, pub timestamp: u64, pub user_id: UserId, } -impl TypingStartEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(TypingStartEvent { - channel_id: remove(&mut map, "channel_id").and_then(ChannelId::decode)?, - timestamp: req!(remove(&mut map, "timestamp")?.as_u64()), - user_id: remove(&mut map, "user_id").and_then(UserId::decode)?, - }) - } -} - #[derive(Clone, Debug)] pub struct UnknownEvent { pub kind: String, - pub value: BTreeMap<String, Value> + pub value: Value, } #[derive(Clone, Debug)] @@ -626,17 +412,15 @@ pub struct UserUpdateEvent { pub current_user: CurrentUser, } -impl UserUpdateEvent { - #[doc(hidden)] - #[inline] - pub fn decode(map: Map) -> Result<Self> { - Ok(UserUpdateEvent { - current_user: CurrentUser::decode(Value::Object(map))?, +impl Deserialize for UserUpdateEvent { + fn deserialize<D: Deserializer>(deserializer: D) -> StdResult<Self, D::Error> { + Ok(Self { + current_user: CurrentUser::deserialize(deserializer)?, }) } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize)] pub struct VoiceServerUpdateEvent { pub channel_id: Option<ChannelId>, pub endpoint: Option<String>, @@ -644,53 +428,33 @@ pub struct VoiceServerUpdateEvent { pub token: String, } -impl VoiceServerUpdateEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(VoiceServerUpdateEvent { - guild_id: opt(&mut map, "guild_id", GuildId::decode)?, - channel_id: opt(&mut map, "channel_id", ChannelId::decode)?, - endpoint: opt(&mut map, "endpoint", into_string)?, - token: remove(&mut map, "token").and_then(into_string)?, - }) - } -} - #[derive(Clone, Debug)] pub struct VoiceStateUpdateEvent { pub guild_id: Option<GuildId>, pub voice_state: VoiceState, } -impl VoiceStateUpdateEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { +impl Deserialize for VoiceStateUpdateEvent { + fn deserialize<D: Deserializer>(deserializer: D) -> StdResult<Self, D::Error> { + let map = JsonMap::deserialize(deserializer)?; + let guild_id = match map.get("guild_id") { + Some(v) => Some(GuildId::deserialize(v.clone()).map_err(DeError::custom)?), + None => None, + }; + Ok(VoiceStateUpdateEvent { - guild_id: opt(&mut map, "guild_id", GuildId::decode)?, - voice_state: VoiceState::decode(Value::Object(map))?, + guild_id: guild_id, + voice_state: VoiceState::deserialize(Value::Object(map)).map_err(DeError::custom)?, }) } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize)] pub struct WebhookUpdateEvent { pub channel_id: ChannelId, pub guild_id: GuildId, } -impl WebhookUpdateEvent { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(WebhookUpdateEvent { - channel_id: remove(&mut map, "channel_id").and_then(ChannelId::decode)?, - guild_id: remove(&mut map, "guild_id").and_then(GuildId::decode)?, - }) - } -} - #[allow(large_enum_variant)] #[derive(Debug, Clone)] pub enum GatewayEvent { @@ -704,32 +468,47 @@ pub enum GatewayEvent { impl GatewayEvent { pub fn decode(value: Value) -> Result<Self> { - let mut value = into_map(value)?; - - let op = req!(value.get("op").and_then(|x| x.as_u64())); - - match OpCode::from_num(op).ok_or(Error::Client(ClientError::InvalidOpCode))? { - OpCode::Event => Ok(GatewayEvent::Dispatch( - req!(remove(&mut value, "s")?.as_u64()), - Event::decode( - remove(&mut value, "t").and_then(into_string)?, - remove(&mut value, "d")? - )? - )), + let mut map = JsonMap::deserialize(value)?; + + let op = map.remove("op") + .ok_or_else(|| DeError::custom("expected gateway event op")) + .and_then(OpCode::deserialize)?; + + Ok(match op { + OpCode::Event => { + let s = map.remove("s") + .ok_or_else(|| DeError::custom("expected gateway event sequence")) + .and_then(u64::deserialize)?; + let t = map.remove("t") + .ok_or_else(|| DeError::custom("expected gateway event type")) + .and_then(String::deserialize)?; + let d = map.remove("d") + .ok_or_else(|| Error::Decode("expected gateway event d", Value::Object(map)))?; + + GatewayEvent::Dispatch(s, Event::decode(t, d)?) + }, OpCode::Heartbeat => { - Ok(GatewayEvent::Heartbeat(req!(remove(&mut value, "s")? - .as_u64()))) + let s = map.remove("s") + .ok_or_else(|| DeError::custom("Expected heartbeat s")) + .and_then(u64::deserialize)?; + + GatewayEvent::Heartbeat(s) }, - OpCode::Reconnect => Ok(GatewayEvent::Reconnect), - OpCode::InvalidSession => Ok(GatewayEvent::InvalidateSession), + OpCode::Reconnect => GatewayEvent::Reconnect, + OpCode::InvalidSession => GatewayEvent::InvalidateSession, OpCode::Hello => { - let mut data = remove(&mut value, "d").and_then(into_map)?; - let interval = req!(remove(&mut data, "heartbeat_interval")?.as_u64()); - Ok(GatewayEvent::Hello(interval)) + let mut d = map.remove("d") + .ok_or_else(|| DeError::custom("expected gateway hello d")) + .and_then(JsonMap::deserialize)?; + let interval = d.remove("heartbeat_interval") + .ok_or_else(|| DeError::custom("expected gateway hello interval")) + .and_then(u64::deserialize)?; + + GatewayEvent::Hello(interval) }, - OpCode::HeartbeatAck => Ok(GatewayEvent::HeartbeatAck), - _ => Err(Error::Decode("Unexpected opcode", Value::Object(value))), - } + OpCode::HeartbeatAck => GatewayEvent::HeartbeatAck, + _ => return Err(Error::Client(ClientError::InvalidOpCode)), + }) } } @@ -857,61 +636,60 @@ pub enum Event { impl Event { #[allow(cyclomatic_complexity)] fn decode(kind: String, value: Value) -> Result<Event> { - if kind == "PRESENCES_REPLACE" { - return Ok(Event::PresencesReplace(PresencesReplaceEvent::decode(value)?)); - } - - let mut value = into_map(value)?; - Ok(match &kind[..] { - "CHANNEL_CREATE" => Event::ChannelCreate(ChannelCreateEvent::decode(value)?), - "CHANNEL_DELETE" => Event::ChannelDelete(ChannelDeleteEvent::decode(value)?), - "CHANNEL_PINS_ACK" => Event::ChannelPinsAck(ChannelPinsAckEvent::decode(value)?), - "CHANNEL_PINS_UPDATE" => Event::ChannelPinsUpdate(ChannelPinsUpdateEvent::decode(value)?), - "CHANNEL_RECIPIENT_ADD" => Event::ChannelRecipientAdd(ChannelRecipientAddEvent::decode(value)?), - "CHANNEL_RECIPIENT_REMOVE" => Event::ChannelRecipientRemove(ChannelRecipientRemoveEvent::decode(value)?), - "CHANNEL_UPDATE" => Event::ChannelUpdate(ChannelUpdateEvent::decode(value)?), - "GUILD_BAN_ADD" => Event::GuildBanAdd(GuildBanAddEvent::decode(value)?), - "GUILD_BAN_REMOVE" => Event::GuildBanRemove(GuildBanRemoveEvent::decode(value)?), + "CHANNEL_CREATE" => Event::ChannelCreate(ChannelCreateEvent::deserialize(value)?), + "CHANNEL_DELETE" => Event::ChannelDelete(ChannelDeleteEvent::deserialize(value)?), + "CHANNEL_PINS_ACK" => Event::ChannelPinsAck(ChannelPinsAckEvent::deserialize(value)?), + "CHANNEL_PINS_UPDATE" => Event::ChannelPinsUpdate(ChannelPinsUpdateEvent::deserialize(value)?), + "CHANNEL_RECIPIENT_ADD" => Event::ChannelRecipientAdd(ChannelRecipientAddEvent::deserialize(value)?), + "CHANNEL_RECIPIENT_REMOVE" => Event::ChannelRecipientRemove(ChannelRecipientRemoveEvent::deserialize(value)?), + "CHANNEL_UPDATE" => Event::ChannelUpdate(ChannelUpdateEvent::deserialize(value)?), + "GUILD_BAN_ADD" => Event::GuildBanAdd(GuildBanAddEvent::deserialize(value)?), + "GUILD_BAN_REMOVE" => Event::GuildBanRemove(GuildBanRemoveEvent::deserialize(value)?), "GUILD_CREATE" => { - if remove(&mut value, "unavailable").ok().and_then(|v| v.as_bool()).unwrap_or(false) { - Event::GuildUnavailable(GuildUnavailableEvent::decode(value)?) + let mut map = JsonMap::deserialize(value)?; + + if map.remove("unavailable").and_then(|v| v.as_bool()).unwrap_or(false) { + Event::GuildUnavailable(GuildUnavailableEvent::deserialize(Value::Object(map))?) } else { - Event::GuildCreate(GuildCreateEvent::decode(value)?) + Event::GuildCreate(GuildCreateEvent::deserialize(Value::Object(map))?) } }, "GUILD_DELETE" => { - if remove(&mut value, "unavailable").ok().and_then(|v| v.as_bool()).unwrap_or(false) { - Event::GuildUnavailable(GuildUnavailableEvent::decode(value)?) + let mut map = JsonMap::deserialize(value)?; + + if map.remove("unavailable").and_then(|v| v.as_bool()).unwrap_or(false) { + Event::GuildUnavailable(GuildUnavailableEvent::deserialize(Value::Object(map))?) } else { - Event::GuildDelete(GuildDeleteEvent::decode(value)?) + Event::GuildDelete(GuildDeleteEvent::deserialize(Value::Object(map))?) } }, - "GUILD_EMOJIS_UPDATE" => Event::GuildEmojisUpdate(GuildEmojisUpdateEvent::decode(value)?), - "GUILD_INTEGRATIONS_UPDATE" => Event::GuildIntegrationsUpdate(GuildIntegrationsUpdateEvent::decode(value)?), - "GUILD_MEMBER_ADD" => Event::GuildMemberAdd(GuildMemberAddEvent::decode(value)?), - "GUILD_MEMBER_REMOVE" => Event::GuildMemberRemove(GuildMemberRemoveEvent::decode(value)?), - "GUILD_MEMBER_UPDATE" => Event::GuildMemberUpdate(GuildMemberUpdateEvent::decode(value)?), - "GUILD_MEMBERS_CHUNK" => Event::GuildMembersChunk(GuildMembersChunkEvent::decode(value)?), - "GUILD_ROLE_CREATE" => Event::GuildRoleCreate(GuildRoleCreateEvent::decode(value)?), - "GUILD_ROLE_DELETE" => Event::GuildRoleDelete(GuildRoleDeleteEvent::decode(value)?), - "GUILD_ROLE_UPDATE" => Event::GuildRoleUpdate(GuildRoleUpdateEvent::decode(value)?), - "GUILD_UPDATE" => Event::GuildUpdate(GuildUpdateEvent::decode(value)?), - "MESSAGE_CREATE" => Event::MessageCreate(MessageCreateEvent::decode(value)?), - "MESSAGE_DELETE" => Event::MessageDelete(MessageDeleteEvent::decode(value)?), - "MESSAGE_DELETE_BULK" => Event::MessageDeleteBulk(MessageDeleteBulkEvent::decode(value)?), - "MESSAGE_REACTION_ADD" => Event::ReactionAdd(ReactionAddEvent::decode(value)?), - "MESSAGE_REACTION_REMOVE" => Event::ReactionRemove(ReactionRemoveEvent::decode(value)?), - "MESSAGE_REACTION_REMOVE_ALL" => Event::ReactionRemoveAll(ReactionRemoveAllEvent::decode(value)?), - "MESSAGE_UPDATE" => Event::MessageUpdate(MessageUpdateEvent::decode(value)?), - "PRESENCE_UPDATE" => Event::PresenceUpdate(PresenceUpdateEvent::decode(value)?), - "READY" => Event::Ready(ReadyEvent::decode(value)?), - "RESUMED" => Event::Resumed(ResumedEvent::decode(value)?), - "TYPING_START" => Event::TypingStart(TypingStartEvent::decode(value)?), - "USER_UPDATE" => Event::UserUpdate(UserUpdateEvent::decode(value)?), - "VOICE_SERVER_UPDATE" => Event::VoiceServerUpdate(VoiceServerUpdateEvent::decode(value)?), - "VOICE_STATE_UPDATE" => Event::VoiceStateUpdate(VoiceStateUpdateEvent::decode(value)?), - "WEBHOOKS_UPDATE" => Event::WebhookUpdate(WebhookUpdateEvent::decode(value)?), + "GUILD_EMOJIS_UPDATE" => Event::GuildEmojisUpdate(GuildEmojisUpdateEvent::deserialize(value)?), + "GUILD_INTEGRATIONS_UPDATE" => Event::GuildIntegrationsUpdate(GuildIntegrationsUpdateEvent::deserialize(value)?), + "GUILD_MEMBER_ADD" => Event::GuildMemberAdd(GuildMemberAddEvent::deserialize(value)?), + "GUILD_MEMBER_REMOVE" => Event::GuildMemberRemove(GuildMemberRemoveEvent::deserialize(value)?), + "GUILD_MEMBER_UPDATE" => Event::GuildMemberUpdate(GuildMemberUpdateEvent::deserialize(value)?), + "GUILD_MEMBERS_CHUNK" => Event::GuildMembersChunk(GuildMembersChunkEvent::deserialize(value)?), + "GUILD_ROLE_CREATE" => Event::GuildRoleCreate(GuildRoleCreateEvent::deserialize(value)?), + "GUILD_ROLE_DELETE" => Event::GuildRoleDelete(GuildRoleDeleteEvent::deserialize(value)?), + "GUILD_ROLE_UPDATE" => Event::GuildRoleUpdate(GuildRoleUpdateEvent::deserialize(value)?), + "GUILD_UPDATE" => Event::GuildUpdate(GuildUpdateEvent::deserialize(value)?), + "MESSAGE_CREATE" => Event::MessageCreate(MessageCreateEvent::deserialize(value)?), + "MESSAGE_DELETE" => Event::MessageDelete(MessageDeleteEvent::deserialize(value)?), + "MESSAGE_DELETE_BULK" => Event::MessageDeleteBulk(MessageDeleteBulkEvent::deserialize(value)?), + "MESSAGE_REACTION_ADD" => Event::ReactionAdd(ReactionAddEvent::deserialize(value)?), + "MESSAGE_REACTION_REMOVE" => Event::ReactionRemove(ReactionRemoveEvent::deserialize(value)?), + "MESSAGE_REACTION_REMOVE_ALL" => Event::ReactionRemoveAll(ReactionRemoveAllEvent::deserialize(value)?), + "MESSAGE_UPDATE" => Event::MessageUpdate(MessageUpdateEvent::deserialize(value)?), + "PRESENCE_UPDATE" => Event::PresenceUpdate(PresenceUpdateEvent::deserialize(value)?), + "PRESENCES_REPLACE" => Event::PresencesReplace(PresencesReplaceEvent::deserialize(value)?), + "READY" => Event::Ready(ReadyEvent::deserialize(value)?), + "RESUMED" => Event::Resumed(ResumedEvent::deserialize(value)?), + "TYPING_START" => Event::TypingStart(TypingStartEvent::deserialize(value)?), + "USER_UPDATE" => Event::UserUpdate(UserUpdateEvent::deserialize(value)?), + "VOICE_SERVER_UPDATE" => Event::VoiceServerUpdate(VoiceServerUpdateEvent::deserialize(value)?), + "VOICE_STATE_UPDATE" => Event::VoiceStateUpdate(VoiceStateUpdateEvent::deserialize(value)?), + "WEBHOOKS_UPDATE" => Event::WebhookUpdate(WebhookUpdateEvent::deserialize(value)?), _ => Event::Unknown(UnknownEvent { kind: kind, value: value, @@ -921,23 +699,13 @@ impl Event { } #[allow(missing_docs)] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Deserialize)] pub struct VoiceHeartbeat { pub heartbeat_interval: u64, } -impl VoiceHeartbeat { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(VoiceHeartbeat { - heartbeat_interval: req!(remove(&mut map, "heartbeat_interval")?.as_u64()), - }) - } -} - #[allow(missing_docs)] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize)] pub struct VoiceHello { pub heartbeat_interval: u64, pub ip: String, @@ -946,61 +714,21 @@ pub struct VoiceHello { pub ssrc: u32, } -impl VoiceHello { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(VoiceHello { - heartbeat_interval: req!(remove(&mut map, "heartbeat_interval")?.as_u64()), - ip: remove(&mut map, "ip").and_then(into_string)?, - modes: decode_array(remove(&mut map, "modes")?, into_string)?, - port: req!(remove(&mut map, "port")?.as_u64()) as u16, - ssrc: req!(remove(&mut map, "ssrc")?.as_u64()) as u32, - }) - } -} - #[allow(missing_docs)] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize)] pub struct VoiceSessionDescription { pub mode: String, pub secret_key: Vec<u8>, } -impl VoiceSessionDescription { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(VoiceSessionDescription { - mode: remove(&mut map, "mode") - .and_then(into_string)?, - secret_key: decode_array(remove(&mut map, "secret_key")?, - |v| Ok(req!(v.as_u64()) as u8) - )?, - }) - } -} - #[allow(missing_docs)] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Deserialize)] pub struct VoiceSpeaking { pub speaking: bool, pub ssrc: u32, pub user_id: UserId, } -impl VoiceSpeaking { - #[doc(hidden)] - #[inline] - pub fn decode(mut map: Map) -> Result<Self> { - Ok(VoiceSpeaking { - speaking: req!(remove(&mut map, "speaking")?.as_bool()), - ssrc: req!(remove(&mut map, "ssrc")?.as_u64()) as u32, - user_id: remove(&mut map, "user_id").and_then(UserId::decode)?, - }) - } -} - /// A representation of data received for [`voice`] events. /// /// [`voice`]: ../../ext/voice/index.html @@ -1026,20 +754,26 @@ pub enum VoiceEvent { impl VoiceEvent { #[doc(hidden)] pub fn decode(value: Value) -> Result<VoiceEvent> { - let mut value = into_map(value)?; - let op = req!(remove(&mut value, "op")?.as_u64()); - let map = remove(&mut value, "d").and_then(into_map)?; - - let opcode = VoiceOpCode::from_num(op) - .ok_or(Error::Client(ClientError::InvalidOpCode))?; - - Ok(match opcode { - VoiceOpCode::Heartbeat => VoiceEvent::Heartbeat(VoiceHeartbeat::decode(map)?), - VoiceOpCode::Hello => VoiceEvent::Hello(VoiceHello::decode(map)?), + let mut map = JsonMap::deserialize(value)?; + + let op = match map.remove("op") { + Some(v) => VoiceOpCode::deserialize(v).map_err(JsonError::from).map_err(Error::from)?, + None => return Err(Error::Decode("expected voice event op", Value::Object(map))), + }; + + let d = match map.remove("d") { + Some(v) => JsonMap::deserialize(v).map_err(JsonError::from).map_err(Error::from)?, + None => return Err(Error::Decode("expected voice gateway d", Value::Object(map))), + }; + let v = Value::Object(d); + + Ok(match op { + VoiceOpCode::Heartbeat => VoiceEvent::Heartbeat(VoiceHeartbeat::deserialize(v)?), + VoiceOpCode::Hello => VoiceEvent::Hello(VoiceHello::deserialize(v)?), VoiceOpCode::KeepAlive => VoiceEvent::KeepAlive, - VoiceOpCode::SessionDescription => VoiceEvent::Ready(VoiceSessionDescription::decode(map)?), - VoiceOpCode::Speaking => VoiceEvent::Speaking(VoiceSpeaking::decode(map)?), - other => VoiceEvent::Unknown(other, Value::Object(map)), + VoiceOpCode::SessionDescription => VoiceEvent::Ready(VoiceSessionDescription::deserialize(v)?), + VoiceOpCode::Speaking => VoiceEvent::Speaking(VoiceSpeaking::deserialize(v)?), + other => VoiceEvent::Unknown(other, v), }) } } diff --git a/src/model/gateway.rs b/src/model/gateway.rs index dc25d0d..826f723 100644 --- a/src/model/gateway.rs +++ b/src/model/gateway.rs @@ -1,29 +1,40 @@ +use serde::de::Error as DeError; +use serde_json; use std::sync::{Arc, RwLock}; use super::utils::*; use super::*; -use ::internal::prelude::*; -impl Game { - #[doc(hidden)] - pub fn decode(value: Value) -> Result<Option<Game>> { - let mut map = into_map(value)?; - - let name = match map.remove("name") { - Some(Value::Null) | None => return Ok(None), - Some(v) => into_string(v)?, - }; - - if name.trim().is_empty() { - return Ok(None); - } +/// A representation of the data retrieved from the bot gateway endpoint. +/// +/// This is different from the [`Gateway`], as this includes the number of +/// shards that Discord recommends to use for a bot user. +/// +/// This is only applicable to bot users. +#[derive(Clone, Debug, Deserialize)] +pub struct BotGateway { + /// The number of shards that is recommended to be used by the current bot + /// user. + pub shards: u64, + /// The gateway to connect to. + pub url: String, +} - Ok(Some(Game { - name: name, - kind: opt(&mut map, "type", GameType::decode)?.unwrap_or(GameType::Playing), - url: opt(&mut map, "url", into_string)?, - })) - } +/// Representation of a game that a [`User`] is playing -- or streaming in the +/// case that a stream URL is provided. +#[derive(Clone, Debug)] +pub struct Game { + /// The type of game status. + pub kind: GameType, + /// The name of the game being played. + pub name: String, + /// The Stream URL if [`kind`] is [`GameType::Streaming`]. + /// + /// [`GameType::Streaming`]: enum.GameType.html#variant.Streaming + /// [`kind`]: #structfield.kind + pub url: Option<String>, +} +impl Game { /// Creates a `Game` struct that appears as a `Playing <name>` status. /// /// **Note**: Maximum `name` length is 128. @@ -47,31 +58,136 @@ impl Game { } } -impl Presence { - #[doc(hidden)] - pub fn decode(value: Value) -> Result<Presence> { - let mut value = into_map(value)?; - let mut user_map = remove(&mut value, "user").and_then(into_map)?; +impl Deserialize for Game { + fn deserialize<D: Deserializer>(deserializer: D) -> StdResult<Self, D::Error> { + let mut map = JsonMap::deserialize(deserializer)?; + let kind = map.remove("type") + .and_then(|v| GameType::deserialize(v).ok()) + .unwrap_or(GameType::Playing); + let name = map.remove("name") + .and_then(|v| String::deserialize(v).ok()) + .unwrap_or_else(String::new); + let url = map.remove("url").and_then(|v| serde_json::from_value::<String>(v).ok()); + + Ok(Game { + kind: kind, + name: name, + url: url + }) + } +} + +enum_number!( + /// The type of activity that is being performed when playing a game. + GameType { + /// An indicator that the user is playing a game. + Playing = 0, + /// An indicator that the user is streaming to a service. + Streaming = 1, + } +); + +impl Default for GameType { + fn default() -> Self { + GameType::Playing + } +} + +/// A representation of the data retrieved from the gateway endpoint. +/// +/// For the bot-specific gateway, refer to [`BotGateway`]. +/// +/// [`BotGateway`]: struct.BotGateway.html +#[derive(Clone, Debug, Deserialize)] +pub struct Gateway { + /// The gateway to connect to. + pub url: String, +} + +/// Information detailing the current online status of a [`User`]. +/// +/// [`User`]: struct.User.html +#[derive(Clone, Debug)] +pub struct Presence { + /// The game that a [`User`] is current playing. + /// + /// [`User`]: struct.User.html + pub game: Option<Game>, + /// The date of the last presence update. + pub last_modified: Option<u64>, + /// The nickname of the member, if applicable. + pub nick: Option<String>, + /// The user's online status. + pub status: OnlineStatus, + /// The Id of the [`User`]. Can be used to calculate the user's creation + /// date. + pub user_id: UserId, + /// The associated user instance. + pub user: Option<Arc<RwLock<User>>>, +} + +impl Deserialize for Presence { + fn deserialize<D: Deserializer>(deserializer: D) -> StdResult<Presence, D::Error> { + let mut map = JsonMap::deserialize(deserializer)?; + let mut user_map = map.remove("user") + .ok_or_else(|| DeError::custom("expected presence user")) + .and_then(JsonMap::deserialize) + .map_err(DeError::custom)?; let (user_id, user) = if user_map.len() > 1 { - let user = User::decode(Value::Object(user_map))?; - (user.id, Some(user)) + let user = User::deserialize(Value::Object(user_map)).map_err(DeError::custom)?; + + (user.id, Some(Arc::new(RwLock::new(user)))) } else { - (remove(&mut user_map, "id").and_then(UserId::decode)?, None) + let user_id = user_map.remove("id") + .ok_or_else(|| DeError::custom("Missing presence user id")) + .and_then(|x| UserId::deserialize(x.clone())) + .map_err(DeError::custom)?; + + (user_id, None) }; - let game = match value.remove("game") { - None | Some(Value::Null) => None, - Some(v) => Game::decode(v)?, + let game = match map.remove("game") { + Some(v) => serde_json::from_value::<Option<Game>>(v).map_err(DeError::custom)?, + None => None, + }; + let last_modified = match map.remove("last_modified") { + Some(v) => Some(u64::deserialize(v).map_err(DeError::custom)?), + None => None, + }; + let nick = match map.remove("nick") { + Some(v) => serde_json::from_value::<Option<String>>(v).map_err(DeError::custom)?, + None => None, }; + let status = map.remove("status") + .ok_or_else(|| DeError::custom("expected presence status")) + .and_then(OnlineStatus::deserialize) + .map_err(DeError::custom)?; Ok(Presence { - user_id: user_id, - status: remove(&mut value, "status").and_then(OnlineStatus::decode_str)?, - last_modified: opt(&mut value, "last_modified", |v| Ok(req!(v.as_u64())))?, game: game, - user: user.map(RwLock::new).map(Arc::new), - nick: opt(&mut value, "nick", into_string)?, + last_modified: last_modified, + nick: nick, + status: status, + user: user, + user_id: user_id, }) } } + +/// An initial set of information given after IDENTIFYing to the gateway. +#[derive(Clone, Debug, Deserialize)] +pub struct Ready { + pub guilds: Vec<GuildStatus>, + #[serde(deserialize_with="deserialize_presences")] + pub presences: HashMap<UserId, Presence>, + #[serde(deserialize_with="deserialize_private_channels")] + pub private_channels: HashMap<ChannelId, Channel>, + pub session_id: String, + pub shard: Option<[u64; 2]>, + #[serde(default, rename="_trace")] + pub trace: Vec<String>, + pub user: CurrentUser, + #[serde(rename="v")] + pub version: u64, +} diff --git a/src/model/guild/emoji.rs b/src/model/guild/emoji.rs index 0bb0f40..54a70d3 100644 --- a/src/model/guild/emoji.rs +++ b/src/model/guild/emoji.rs @@ -1,9 +1,7 @@ use std::fmt::{Display, Formatter, Result as FmtResult, Write as FmtWrite}; -use ::model::{Emoji, EmojiId}; +use ::model::{EmojiId, RoleId}; #[cfg(feature="cache")] -use serde_json::builder::ObjectBuilder; -#[cfg(feature="cache")] use std::mem; #[cfg(feature="cache")] use ::client::{CACHE, rest}; @@ -12,6 +10,30 @@ use ::internal::prelude::*; #[cfg(feature="cache")] use ::model::GuildId; +/// Represents a custom guild emoji, which can either be created using the API, +/// or via an integration. Emojis created using the API only work within the +/// guild it was created in. +#[derive(Clone, Debug, Deserialize)] +pub struct Emoji { + /// The Id of the emoji. + pub id: EmojiId, + /// The name of the emoji. It must be at least 2 characters long and can + /// only contain alphanumeric characters and underscores. + pub name: String, + /// Whether the emoji is managed via an [`Integration`] service. + /// + /// [`Integration`]: struct.Integration.html + pub managed: bool, + /// Whether the emoji name needs to be surrounded by colons in order to be + /// used by the client. + pub require_colons: bool, + /// A list of [`Role`]s that are allowed to use the emoji. If there are no + /// roles specified, then usage is unrestricted. + /// + /// [`Role`]: struct.Role.html + pub roles: Vec<RoleId>, +} + impl Emoji { /// Deletes the emoji. /// @@ -39,9 +61,9 @@ impl Emoji { pub fn edit(&mut self, name: &str) -> Result<()> { match self.find_guild_id() { Some(guild_id) => { - let map = ObjectBuilder::new() - .insert("name", name) - .build(); + let map = json!({ + "name": name, + }); match rest::edit_emoji(guild_id.0, self.id.0, &map) { Ok(emoji) => { diff --git a/src/model/guild/feature.rs b/src/model/guild/feature.rs new file mode 100644 index 0000000..cfbcabc --- /dev/null +++ b/src/model/guild/feature.rs @@ -0,0 +1,25 @@ +/// 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 d568360..1ef9a32 100644 --- a/src/model/guild/guild_id.rs +++ b/src/model/guild/guild_id.rs @@ -1,4 +1,3 @@ -use serde_json::builder::ObjectBuilder; use std::fmt::{Display, Formatter, Result as FmtResult}; use ::client::rest; use ::internal::prelude::*; @@ -71,10 +70,10 @@ impl GuildId { /// [`rest::create_channel`]: ../client/rest/fn.create_channel.html /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html pub fn create_channel(&self, name: &str, kind: ChannelType) -> Result<GuildChannel> { - let map = ObjectBuilder::new() - .insert("name", name) - .insert("type", kind.name()) - .build(); + let map = json!({ + "name": name, + "type": kind.name(), + }); rest::create_channel(self.0, &map) } @@ -97,10 +96,10 @@ impl GuildId { /// [`utils::read_image`]: ../utils/fn.read_image.html /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html pub fn create_emoji(&self, name: &str, image: &str) -> Result<Emoji> { - let map = ObjectBuilder::new() - .insert("name", name) - .insert("image", image) - .build(); + let map = json!({ + "name": name, + "image": image, + }); rest::create_emoji(self.0, &map) } @@ -113,10 +112,10 @@ impl GuildId { pub fn create_integration<I>(&self, integration_id: I, kind: &str) -> Result<()> where I: Into<IntegrationId> { let integration_id = integration_id.into(); - let map = ObjectBuilder::new() - .insert("id", integration_id.0) - .insert("type", kind) - .build(); + let map = json!({ + "id": integration_id.0, + "type": kind, + }); rest::create_guild_integration(self.0, integration_id.0, &map) } @@ -131,7 +130,7 @@ impl GuildId { /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html #[inline] pub fn create_role<F: FnOnce(EditRole) -> EditRole>(&self, f: F) -> Result<Role> { - rest::create_role(self.0, &f(EditRole::default()).0.build()) + rest::create_role(self.0, &f(EditRole::default()).0) } /// Deletes the current guild if the current account is the owner of the @@ -194,7 +193,7 @@ impl GuildId { /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html #[inline] pub fn edit<F: FnOnce(EditGuild) -> EditGuild>(&mut self, f: F) -> Result<PartialGuild> { - rest::edit_guild(self.0, &f(EditGuild::default()).0.build()) + rest::edit_guild(self.0, &f(EditGuild::default()).0) } /// Edits an [`Emoji`]'s name in the guild. @@ -208,7 +207,9 @@ impl GuildId { /// [`Emoji::edit`]: struct.Emoji.html#method.edit /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html pub fn edit_emoji<E: Into<EmojiId>>(&self, emoji_id: E, name: &str) -> Result<Emoji> { - let map = ObjectBuilder::new().insert("name", name).build(); + let map = json!({ + "name": name, + }); rest::edit_emoji(self.0, emoji_id.into().0, &map) } @@ -229,7 +230,7 @@ impl GuildId { #[inline] pub fn edit_member<F, U>(&self, user_id: U, f: F) -> Result<()> where F: FnOnce(EditMember) -> EditMember, U: Into<UserId> { - rest::edit_member(self.0, user_id.into().0, &f(EditMember::default()).0.build()) + rest::edit_member(self.0, user_id.into().0, &f(EditMember::default()).0) } /// Edits the current user's nickname for the guild. @@ -263,7 +264,7 @@ 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> { - rest::edit_role(self.0, role_id.into().0, &f(EditRole::default()).0.build()) + rest::edit_role(self.0, role_id.into().0, &f(EditRole::default()).0) } /// Search the cache for the guild. @@ -372,7 +373,9 @@ impl GuildId { /// [`Member`]: struct.Member.html /// [Kick Members]: permissions/constant.KICK_MEMBERS.html pub fn get_prune_count(&self, days: u16) -> Result<GuildPrune> { - let map = ObjectBuilder::new().insert("days", days).build(); + let map = json!({ + "days": days, + }); rest::get_guild_prune_count(self.0, &map) } @@ -411,7 +414,8 @@ impl GuildId { /// [Move Members]: permissions/constant.MOVE_MEMBERS.html pub fn move_member<C, U>(&self, user_id: U, channel_id: C) -> Result<()> where C: Into<ChannelId>, U: Into<UserId> { - let map = ObjectBuilder::new().insert("channel_id", channel_id.into().0).build(); + let mut map = Map::new(); + map.insert("channel_id".to_owned(), Value::Number(Number::from(channel_id.into().0))); rest::edit_member(self.0, user_id.into().0, &map) } @@ -437,7 +441,11 @@ impl GuildId { /// [Kick Members]: permissions/constant.KICK_MEMBERS.html #[inline] pub fn start_prune(&self, days: u16) -> Result<GuildPrune> { - rest::start_guild_prune(self.0, &ObjectBuilder::new().insert("days", days).build()) + let map = json!({ + "days": days, + }); + + rest::start_guild_prune(self.0, &map) } /// Unbans a [`User`] from the guild. diff --git a/src/model/guild/integration.rs b/src/model/guild/integration.rs index d7f9967..b276bc3 100644 --- a/src/model/guild/integration.rs +++ b/src/model/guild/integration.rs @@ -1,4 +1,21 @@ -use ::model::{Integration, IntegrationId}; +use super::*; + +/// Various information about integrations. +#[derive(Clone, Debug, Deserialize)] +pub struct Integration { + pub id: IntegrationId, + pub account: IntegrationAccount, + pub enabled: bool, + #[serde(rename="expire_behaviour")] + pub expire_behaviour: u64, + pub expire_grace_period: u64, + pub kind: String, + pub name: String, + pub role_id: RoleId, + pub synced_at: u64, + pub syncing: bool, + pub user: User, +} impl From<Integration> for IntegrationId { /// Gets the Id of integration. @@ -6,3 +23,10 @@ impl From<Integration> for IntegrationId { integration.id } } + +/// Integration account object. +#[derive(Clone, Debug, Deserialize)] +pub struct IntegrationAccount { + pub id: String, + pub name: String, +} diff --git a/src/model/guild/member.rs b/src/model/guild/member.rs index 8fc53e1..630610f 100644 --- a/src/model/guild/member.rs +++ b/src/model/guild/member.rs @@ -1,15 +1,39 @@ use std::borrow::Cow; use std::fmt::{Display, Formatter, Result as FmtResult}; -use ::internal::prelude::*; +use super::deserialize_sync_user; use ::model::*; #[cfg(feature="cache")] use ::client::{CACHE, rest}; #[cfg(feature="cache")] +use ::internal::prelude::*; +#[cfg(feature="cache")] use ::utils::builder::EditMember; #[cfg(feature="cache")] use ::utils::Colour; +/// Information about a member of a guild. +#[derive(Clone, Debug, Deserialize)] +pub struct Member { + /// Indicator of whether the member can hear in voice channels. + pub deaf: bool, + /// The unique Id of the guild that the member is a part of. + pub guild_id: Option<GuildId>, + /// Timestamp representing the date when the member joined. + pub joined_at: String, + /// Indicator of whether the member can speak in voice channels. + pub mute: bool, + /// The member's nickname, if present. + /// + /// Can't be longer than 32 characters. + pub nick: Option<String>, + /// Vector of Ids of [`Role`]s given to the member. + pub roles: Vec<RoleId>, + /// Attached User struct. + #[serde(deserialize_with="deserialize_sync_user")] + pub user: Arc<RwLock<User>>, +} + impl Member { /// Adds a [`Role`] to the member, editing its roles in-place if the request /// was successful. @@ -50,7 +74,7 @@ impl Member { let guild_id = self.find_guild()?; self.roles.extend_from_slice(role_ids); - let map = EditMember::default().roles(&self.roles).0.build(); + let map = EditMember::default().roles(&self.roles).0; match rest::edit_member(guild_id.0, self.user.read().unwrap().id.0, &map) { Ok(()) => Ok(()), @@ -105,15 +129,6 @@ impl Member { roles.iter().find(|r| r.colour.0 != default.0).map(|r| r.colour) } - #[doc(hidden)] - pub fn decode_guild(guild_id: GuildId, mut value: Value) -> Result<Member> { - if let Some(v) = value.as_object_mut() { - v.insert("guild_id".to_owned(), Value::U64(guild_id.0)); - } - - Self::decode(value) - } - /// Calculates the member's display name. /// /// The nickname takes priority over the member's username if it exists. @@ -141,7 +156,7 @@ impl Member { #[cfg(feature="cache")] pub fn edit<F: FnOnce(EditMember) -> EditMember>(&self, f: F) -> Result<()> { let guild_id = self.find_guild()?; - let map = f(EditMember::default()).0.build(); + let map = f(EditMember::default()).0; rest::edit_member(guild_id.0, self.user.read().unwrap().id.0, &map) } @@ -210,7 +225,7 @@ impl Member { let guild_id = self.find_guild()?; self.roles.retain(|r| !role_ids.contains(r)); - let map = EditMember::default().roles(&self.roles).0.build(); + let map = EditMember::default().roles(&self.roles).0; match rest::edit_member(guild_id.0, self.user.read().unwrap().id.0, &map) { Ok(()) => Ok(()), diff --git a/src/model/guild/mod.rs b/src/model/guild/mod.rs index 8b21ac7..7299f84 100644 --- a/src/model/guild/mod.rs +++ b/src/model/guild/mod.rs @@ -1,13 +1,5 @@ -use serde_json::builder::ObjectBuilder; -use ::client::rest; -use ::constants::LARGE_THRESHOLD; -use ::model::*; -use ::utils::builder::{EditGuild, EditMember, EditRole}; - -#[cfg(feature="cache")] -use ::client::CACHE; - mod emoji; +mod feature; mod guild_id; mod integration; mod member; @@ -15,12 +7,117 @@ mod partial_guild; mod role; pub use self::emoji::*; +pub use self::feature::*; pub use self::guild_id::*; pub use self::integration::*; pub use self::member::*; pub use self::partial_guild::*; pub use self::role::*; +use serde::de::Error as DeError; +use serde_json; +use super::utils::*; +use ::client::rest; +use ::constants::LARGE_THRESHOLD; +use ::model::*; +use ::utils::builder::{EditGuild, EditMember, EditRole}; + +#[cfg(feature="cache")] +use ::client::CACHE; + +/// A representation of a banning of a user. +#[derive(Clone, Debug, Deserialize)] +pub struct Ban { + /// The reason given for this ban. + /// + /// **Note**: Until the Audit Log feature is completed by Discord, this will + /// always be `None`. + pub reason: Option<String>, + /// The user that was banned. + pub user: User, +} + +/// Information about a Discord guild, such as channels, emojis, etc. +#[derive(Clone, Debug)] +pub struct Guild { + /// Id of a voice channel that's considered the AFK channel. + pub afk_channel_id: Option<ChannelId>, + /// The amount of seconds a user can not show any activity in a voice + /// channel before being moved to an AFK channel -- if one exists. + pub afk_timeout: u64, + /// All voice and text channels contained within a guild. + /// + /// This contains all channels regardless of permissions (i.e. the ability + /// of the bot to read from or connect to them). + pub channels: HashMap<ChannelId, Arc<RwLock<GuildChannel>>>, + /// Indicator of whether notifications for all messages are enabled by + /// default in the guild. + pub default_message_notifications: u64, + /// All of the guild's custom emojis. + pub emojis: HashMap<EmojiId, Emoji>, + /// VIP features enabled for the guild. Can be obtained through the + /// [Discord Partnership] website. + /// + /// [Discord Partnership]: https://discordapp.com/partners + pub features: Vec<Feature>, + /// The hash of the icon used by the guild. + /// + /// In the client, this appears on the guild list on the left-hand side. + pub icon: Option<String>, + /// The unique Id identifying the guild. + /// + /// This is equivilant to the Id of the default role (`@everyone`) and also + /// that of the default channel (typically `#general`). + pub id: GuildId, + /// The date that the current user joined the guild. + pub joined_at: String, + /// Indicator of whether the guild is considered "large" by Discord. + pub large: bool, + /// The number of members in the guild. + pub member_count: u64, + /// Users who are members of the guild. + /// + /// Members might not all be available when the [`ReadyEvent`] is received + /// if the [`member_count`] is greater than the `LARGE_THRESHOLD` set by + /// the library. + /// + /// [`ReadyEvent`]: events/struct.ReadyEvent.html + pub members: HashMap<UserId, Member>, + /// Indicator of whether the guild requires multi-factor authentication for + /// [`Role`]s or [`User`]s with moderation permissions. + /// + /// [`Role`]: struct.Role.html + /// [`User`]: struct.User.html + pub mfa_level: u64, + /// The name of the guild. + pub name: String, + /// The Id of the [`User`] who owns the guild. + /// + /// [`User`]: struct.User.html + pub owner_id: UserId, + /// A mapping of [`User`]s' Ids to their current presences. + /// + /// [`User`]: struct.User.html + pub presences: HashMap<UserId, Presence>, + /// The region that the voice servers that the guild uses are located in. + pub region: String, + /// A mapping of the guild's roles. + pub roles: HashMap<RoleId, Role>, + /// An identifying hash of the guild's splash icon. + /// + /// If the [`InviteSplash`] feature is enabled, this can be used to generate + /// a URL to a splash image. + /// + /// [`InviteSplash`]: enum.Feature.html#variant.InviteSplash + pub splash: Option<String>, + /// Indicator of the current verification level of the guild. + pub verification_level: VerificationLevel, + /// A mapping of of [`User`]s to their current voice state. + /// + /// [`User`]: struct.User.html + pub voice_states: HashMap<UserId, VoiceState>, +} + impl Guild { #[cfg(feature="cache")] fn has_perms(&self, mut permissions: Permissions) -> Result<bool> { @@ -133,11 +230,11 @@ impl Guild { /// [US West region]: enum.Region.html#variant.UsWest /// [whitelist]: https://discordapp.com/developers/docs/resources/guild#create-guild pub fn create(name: &str, region: Region, icon: Option<&str>) -> Result<PartialGuild> { - let map = ObjectBuilder::new() - .insert("icon", icon) - .insert("name", name) - .insert("region", region.name()) - .build(); + let map = json!({ + "icon": icon, + "name": name, + "region": region.name(), + }); rest::create_guild(&map) } @@ -249,50 +346,6 @@ impl Guild { self.id.create_role(f) } - #[doc(hidden)] - pub fn decode(value: Value) -> Result<Guild> { - let mut map = into_map(value)?; - - let id = remove(&mut map, "id").and_then(GuildId::decode)?; - - let channels = { - let mut channels = HashMap::new(); - - let vals = decode_array(remove(&mut map, "channels")?, - |v| GuildChannel::decode_guild(v, id))?; - - for channel in vals { - channels.insert(channel.id, Arc::new(RwLock::new(channel))); - } - - channels - }; - - Ok(Guild { - afk_channel_id: opt(&mut map, "afk_channel_id", ChannelId::decode)?, - afk_timeout: req!(remove(&mut map, "afk_timeout")?.as_u64()), - channels: channels, - default_message_notifications: req!(remove(&mut map, "default_message_notifications")?.as_u64()), - emojis: remove(&mut map, "emojis").and_then(decode_emojis)?, - features: remove(&mut map, "features").and_then(|v| decode_array(v, Feature::decode_str))?, - icon: opt(&mut map, "icon", into_string)?, - id: id, - joined_at: remove(&mut map, "joined_at").and_then(into_string)?, - large: req!(remove(&mut map, "large")?.as_bool()), - member_count: req!(remove(&mut map, "member_count")?.as_u64()), - members: remove(&mut map, "members").and_then(decode_members)?, - mfa_level: req!(remove(&mut map, "mfa_level")?.as_u64()), - name: remove(&mut map, "name").and_then(into_string)?, - owner_id: remove(&mut map, "owner_id").and_then(UserId::decode)?, - presences: remove(&mut map, "presences").and_then(decode_presences)?, - region: remove(&mut map, "region").and_then(into_string)?, - roles: remove(&mut map, "roles").and_then(decode_roles)?, - splash: opt(&mut map, "splash", into_string)?, - verification_level: remove(&mut map, "verification_level").and_then(VerificationLevel::decode)?, - voice_states: remove(&mut map, "voice_states").and_then(decode_voice_states)?, - }) - } - /// Deletes the current guild if the current user is the owner of the /// guild. /// @@ -904,6 +957,172 @@ impl Guild { } } +impl Deserialize for Guild { + fn deserialize<D: Deserializer>(deserializer: D) -> StdResult<Self, D::Error> { + let mut map = JsonMap::deserialize(deserializer)?; + + let id = map.get("id") + .and_then(|x| x.as_str()) + .and_then(|x| x.parse::<u64>().ok()); + + if let Some(guild_id) = id { + if let Some(array) = map.get_mut("channels").and_then(|x| x.as_array_mut()) { + + for value in array { + if let Some(channel) = value.as_object_mut() { + channel.insert("guild_id".to_owned(), Value::Number(Number::from(guild_id))); + } + } + } + } + + let afk_channel_id = match map.remove("afk_channel_id") { + Some(v) => serde_json::from_value::<Option<ChannelId>>(v).map_err(DeError::custom)?, + None => None, + }; + let afk_timeout = map.remove("afk_timeout") + .ok_or_else(|| DeError::custom("expected guild afk_timeout")) + .and_then(u64::deserialize) + .map_err(DeError::custom)?; + let channels = map.remove("channels") + .ok_or_else(|| DeError::custom("expected guild channels")) + .and_then(deserialize_guild_channels) + .map_err(DeError::custom)?; + let default_message_notifications = map.remove("default_message_notifications") + .ok_or_else(|| DeError::custom("expected guild default_message_notifications")) + .and_then(u64::deserialize) + .map_err(DeError::custom)?; + let emojis = map.remove("emojis") + .ok_or_else(|| DeError::custom("expected guild emojis")) + .and_then(deserialize_emojis) + .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>>) + .map_err(DeError::custom)?; + let icon = match map.remove("icon") { + Some(v) => Option::<String>::deserialize(v).map_err(DeError::custom)?, + None => None, + }; + let id = map.remove("id") + .ok_or_else(|| DeError::custom("expected guild id")) + .and_then(GuildId::deserialize) + .map_err(DeError::custom)?; + let joined_at = map.remove("joined_at") + .ok_or_else(|| DeError::custom("expected guild joined_at")) + .and_then(String::deserialize) + .map_err(DeError::custom)?; + let large = map.remove("large") + .ok_or_else(|| DeError::custom("expected guild large")) + .and_then(bool::deserialize) + .map_err(DeError::custom)?; + let member_count = map.remove("member_count") + .ok_or_else(|| DeError::custom("expected guild member_count")) + .and_then(u64::deserialize) + .map_err(DeError::custom)?; + let members = map.remove("members") + .ok_or_else(|| DeError::custom("expected guild members")) + .and_then(deserialize_members) + .map_err(DeError::custom)?; + let mfa_level = map.remove("mfa_level") + .ok_or_else(|| DeError::custom("expected guild mfa_level")) + .and_then(u64::deserialize) + .map_err(DeError::custom)?; + let name = map.remove("name") + .ok_or_else(|| DeError::custom("expected guild name")) + .and_then(String::deserialize) + .map_err(DeError::custom)?; + let owner_id = map.remove("owner_id") + .ok_or_else(|| DeError::custom("expected guild owner_id")) + .and_then(UserId::deserialize) + .map_err(DeError::custom)?; + let presences = map.remove("presences") + .ok_or_else(|| DeError::custom("expected guild presences")) + .and_then(deserialize_presences) + .map_err(DeError::custom)?; + let region = map.remove("region") + .ok_or_else(|| DeError::custom("expected guild region")) + .and_then(String::deserialize) + .map_err(DeError::custom)?; + let roles = map.remove("roles") + .ok_or_else(|| DeError::custom("expected guild roles")) + .and_then(deserialize_roles) + .map_err(DeError::custom)?; + let splash = match map.remove("splash") { + Some(v) => Option::<String>::deserialize(v).map_err(DeError::custom)?, + None => None, + }; + let verification_level = map.remove("verification_level") + .ok_or_else(|| DeError::custom("expected guild verification_level")) + .and_then(VerificationLevel::deserialize) + .map_err(DeError::custom)?; + let voice_states = map.remove("voice_states") + .ok_or_else(|| DeError::custom("expected guild voice_states")) + .and_then(deserialize_voice_states) + .map_err(DeError::custom)?; + + Ok(Self { + afk_channel_id: afk_channel_id, + afk_timeout: afk_timeout, + channels: channels, + default_message_notifications: default_message_notifications, + emojis: emojis, + features: features, + icon: icon, + id: id, + joined_at: joined_at, + large: large, + member_count: member_count, + members: members, + mfa_level: mfa_level, + name: name, + owner_id: owner_id, + presences: presences, + region: region, + roles: roles, + splash: splash, + verification_level: verification_level, + voice_states: voice_states, + }) + } +} + +/// Information relating to a guild's widget embed. +#[derive(Clone, Debug, Deserialize)] +pub struct GuildEmbed { + /// The Id of the channel to show the embed for. + pub channel_id: ChannelId, + /// Whether the widget embed is enabled. + pub enabled: bool, +} + +/// Representation of the number of members that would be pruned by a guild +/// prune operation. +#[derive(Clone, Copy, Debug, Deserialize)] +pub struct GuildPrune { + /// The number of members that would be pruned by the operation. + pub pruned: u64, +} + +/// Basic information about a guild. +#[derive(Clone, Debug, Deserialize)] +pub struct GuildInfo { + /// The unique Id of the guild. + /// + /// Can be used to calculate creation date. + pub id: GuildId, + /// The hash of the icon of the guild. + /// + /// This can be used to generate a URL to the guild's icon image. + pub icon: Option<String>, + /// The name of the guild. + pub name: String, + /// Indicator of whether the current user is the owner. + pub owner: bool, + /// The permissions that the current user has. + pub permissions: Permissions, +} + impl GuildInfo { /// Returns the formatted URL of the guild's icon, if the guild has an icon. pub fn icon_url(&self) -> Option<String> { @@ -938,46 +1157,65 @@ impl InviteGuild { } } -impl PossibleGuild<Guild> { - #[doc(hidden)] - pub fn decode(value: Value) -> Result<Self> { - let mut value = into_map(value)?; - if remove(&mut value, "unavailable").ok().and_then(|v| v.as_bool()).unwrap_or(false) { - remove(&mut value, "id").and_then(GuildId::decode).map(PossibleGuild::Offline) - } else { - Guild::decode(Value::Object(value)).map(PossibleGuild::Online) - } - } +/// Data for an unavailable guild. +#[derive(Clone, Copy, Debug, Deserialize)] +pub struct GuildUnavailable { + /// The Id of the [`Guild`] that is unavailable. + /// + /// [`Guild`]: struct.Guild.html + pub id: GuildId, + /// Indicator of whether the guild is unavailable. + /// + /// This should always be `true`. + pub unavailable: bool, +} +#[allow(large_enum_variant)] +#[derive(Clone, Debug, Deserialize)] +#[serde(untagged)] +pub enum GuildStatus { + OnlinePartialGuild(PartialGuild), + OnlineGuild(Guild), + Offline(GuildUnavailable), +} + +impl GuildStatus { /// Retrieves the Id of the inner [`Guild`]. /// /// [`Guild`]: struct.Guild.html pub fn id(&self) -> GuildId { match *self { - PossibleGuild::Offline(guild_id) => guild_id, - PossibleGuild::Online(ref live_guild) => live_guild.id, + GuildStatus::Offline(offline) => offline.id, + GuildStatus::OnlineGuild(ref guild) => guild.id, + GuildStatus::OnlinePartialGuild(ref partial_guild) => partial_guild.id, } } } -impl PossibleGuild<PartialGuild> { - #[doc(hidden)] - pub fn decode(value: Value) -> Result<Self> { - let mut value = into_map(value)?; - if remove(&mut value, "unavailable").ok().and_then(|v| v.as_bool()).unwrap_or(false) { - remove(&mut value, "id").and_then(GuildId::decode).map(PossibleGuild::Offline) - } else { - PartialGuild::decode(Value::Object(value)).map(PossibleGuild::Online) - } +enum_number!( + #[doc="The level to set as criteria prior to a user being able to send + messages in a [`Guild`]. + + [`Guild`]: struct.Guild.html"] + VerificationLevel { + /// Does not require any verification. + None = 0, + /// Low verification level. + Low = 1, + /// Medium verification level. + Medium = 2, + /// High verification level. + High = 3, } +); - /// Retrieves the Id of the inner [`Guild`]. - /// - /// [`Guild`]: struct.Guild.html - pub fn id(&self) -> GuildId { +impl VerificationLevel { + pub fn num(&self) -> u64 { match *self { - PossibleGuild::Offline(id) => id, - PossibleGuild::Online(ref live_guild) => live_guild.id, + VerificationLevel::None => 0, + VerificationLevel::Low => 1, + VerificationLevel::Medium => 2, + VerificationLevel::High => 3, } } } diff --git a/src/model/guild/partial_guild.rs b/src/model/guild/partial_guild.rs index f5ab502..947de50 100644 --- a/src/model/guild/partial_guild.rs +++ b/src/model/guild/partial_guild.rs @@ -1,6 +1,30 @@ use ::model::*; use ::utils::builder::{EditGuild, EditMember, EditRole}; +/// Partial information about a [`Guild`]. This does not include information +/// like member data. +/// +/// [`Guild`]: struct.Guild.html +#[derive(Clone, Debug, Deserialize)] +pub struct PartialGuild { + pub id: GuildId, + pub afk_channel_id: Option<ChannelId>, + pub afk_timeout: u64, + pub default_message_notifications: u64, + pub embed_channel_id: Option<ChannelId>, + pub embed_enabled: bool, + pub emojis: HashMap<EmojiId, Emoji>, + pub features: Vec<Feature>, + pub icon: Option<String>, + pub mfa_level: u64, + pub name: String, + pub owner_id: UserId, + pub region: String, + pub roles: HashMap<RoleId, Role>, + pub splash: Option<String>, + pub verification_level: VerificationLevel, +} + impl PartialGuild { /// Ban a [`User`] from the guild. All messages by the /// user within the last given number of days given will be deleted. This diff --git a/src/model/guild/role.rs b/src/model/guild/role.rs index 77d84e1..d4e1da8 100644 --- a/src/model/guild/role.rs +++ b/src/model/guild/role.rs @@ -9,6 +9,52 @@ use ::internal::prelude::*; #[cfg(feature="cache")] use ::utils::builder::EditRole; +/// Information about a role within a guild. A role represents a set of +/// permissions, and can be attached to one or multiple users. A role has +/// various miscellaneous configurations, such as being assigned a colour. Roles +/// are unique per guild and do not cross over to other guilds in any way, and +/// can have channel-specific permission overrides in addition to guild-level +/// permissions. +#[derive(Clone, Debug, Deserialize)] +pub struct Role { + /// The Id of the role. Can be used to calculate the role's creation date. + pub id: RoleId, + /// The colour of the role. This is an ergonomic representation of the inner + /// value. + #[serde(rename="color")] + pub colour: Colour, + /// Indicator of whether the role is pinned above lesser roles. + /// + /// In the client, this causes [`Member`]s in the role to be seen above + /// those in roles with a lower [`position`]. + /// + /// [`Member`]: struct.Member.html + /// [`position`]: #structfield.position + pub hoist: bool, + /// Indicator of whether the role is managed by an integration service. + pub managed: bool, + /// Indicator of whether the role can be mentioned, similar to mentioning a + /// specific member or `@everyone`. + /// + /// Only members of the role will be notified if a role is mentioned with + /// this set to `true`. + #[serde(default)] + pub mentionable: bool, + /// The name of the role. + pub name: String, + /// A set of permissions that the role has been assigned. + /// + /// See the [`permissions`] module for more information. + /// + /// [`permissions`]: permissions/index.html + pub permissions: Permissions, + /// The role's position in the position list. Roles are considered higher in + /// hierarchy if their position is higher. + /// + /// The `@everyone` role is usually either `-1` or `0`. + pub position: i64, +} + impl Role { /// Deletes the role. /// diff --git a/src/model/invite.rs b/src/model/invite.rs index 4e33a3f..d041d55 100644 --- a/src/model/invite.rs +++ b/src/model/invite.rs @@ -1,7 +1,6 @@ -use super::{Invite, RichInvite}; +use super::*; use ::client::rest; use ::internal::prelude::*; -use ::model::ChannelId; use ::utils::builder::CreateInvite; use ::utils; @@ -10,6 +9,25 @@ use super::permissions; #[cfg(feature="cache")] use super::utils as model_utils; +/// Information about an invite code. +/// +/// Information can not be accessed for guilds the current user is banned from. +#[derive(Clone, Debug, Deserialize)] +pub struct Invite { + /// The unique code for the invite. + pub code: String, + /// A representation of the minimal amount of information needed about the + /// [`GuildChannel`] being invited to. + /// + /// [`GuildChannel`]: struct.GuildChannel.html + pub channel: InviteChannel, + /// a representation of the minimal amount of information needed about the + /// [`Guild`] being invited to. + /// + /// [`Guild`]: struct.Guild.html + pub guild: InviteGuild, +} + impl Invite { /// Creates an invite for a [`GuildChannel`], providing a builder so that /// fields may optionally be set. @@ -42,7 +60,7 @@ impl Invite { } } - rest::create_invite(channel_id.0, &f(CreateInvite::default()).0.build()) + rest::create_invite(channel_id.0, &f(CreateInvite::default()).0) } /// Deletes the invite. @@ -76,6 +94,65 @@ impl Invite { } } +/// A inimal information about the channel an invite points to. +#[derive(Clone, Debug, Deserialize)] +pub struct InviteChannel { + pub id: ChannelId, + pub name: String, + #[serde(rename="type")] + pub kind: ChannelType, +} + +/// A minimal amount of information about the guild an invite points to. +#[derive(Clone, Debug, Deserialize)] +pub struct InviteGuild { + pub id: GuildId, + pub icon: Option<String>, + pub name: String, + pub splash_hash: Option<String>, +} + +/// Detailed information about an invite. +/// This information can only be retrieved by anyone with the [Manage Guild] +/// permission. Otherwise, a minimal amount of information can be retrieved via +/// the [`Invite`] struct. +/// +/// [`Invite`]: struct.Invite.html +/// [Manage Guild]: permissions/constant.MANAGE_GUILD.html +#[derive(Clone, Debug, Deserialize)] +pub struct RichInvite { + /// A representation of the minimal amount of information needed about the + /// channel being invited to. + pub channel: InviteChannel, + /// The unique code for the invite. + pub code: String, + /// When the invite was created. + pub created_at: String, + /// A representation of the minimal amount of information needed about the + /// guild being invited to. + pub guild: InviteGuild, + /// The user that created the invite. + pub inviter: User, + /// The maximum age of the invite in seconds, from when it was created. + pub max_age: u64, + /// The maximum number of times that an invite may be used before it expires. + + /// Note that this does not supercede the [`max_age`] value, if the value of + /// [`temporary`] is `true`. If the value of `temporary` is `false`, then the + /// invite _will_ self-expire after the given number of max uses. + + /// If the value is `0`, then the invite is permanent. + /// + /// [`max_age`]: #structfield.max_age + /// [`temporary`]: #structfield.temporary + pub max_uses: u64, + /// Indicator of whether the invite self-expires after a certain amount of + /// time or uses. + pub temporary: bool, + /// The amount of times that an invite has been used. + pub uses: u64, +} + impl RichInvite { /// Deletes the invite. /// diff --git a/src/model/misc.rs b/src/model/misc.rs index 118492c..5cf3a91 100644 --- a/src/model/misc.rs +++ b/src/model/misc.rs @@ -1,18 +1,6 @@ -use super::{ - ChannelId, - Channel, - Emoji, - Member, - RoleId, - Role, - UserId, - User, - IncidentStatus, - EmojiIdentifier -}; -use ::internal::prelude::*; -use std::str::FromStr; use std::result::Result as StdResult; +use std::str::FromStr; +use super::*; use ::utils; /// Allows something - such as a channel or role - to be mentioned in a message. @@ -130,6 +118,16 @@ impl FromStr for RoleId { } } +/// A version of an emoji used only when solely the Id and name are known. +#[derive(Clone, Debug)] +pub struct EmojiIdentifier { + /// The Id of the emoji. + pub id: EmojiId, + /// The name of the emoji. It must be at least 2 characters long and can + /// only contain alphanumeric characters and underscores. + pub name: String, +} + impl EmojiIdentifier { /// Generates a URL to the emoji's image. #[inline] @@ -171,9 +169,66 @@ impl FromStr for Channel { } } -impl IncidentStatus { - #[doc(hidden)] - pub fn decode(value: Value) -> Result<Self> { - Self::decode_str(value) - } +/// A component that was affected during a service incident. +/// +/// This is pulled from the Discord status page. +#[derive(Clone, Debug, Deserialize)] +pub struct AffectedComponent { + pub name: String, +} + +/// An incident retrieved from the Discord status page. +/// +/// This is not necessarily a representation of an ongoing incident. +#[derive(Clone, Debug, Deserialize)] +pub struct Incident { + pub created_at: String, + pub id: String, + pub impact: String, + pub incident_updates: Vec<IncidentUpdate>, + pub monitoring_at: Option<String>, + pub name: String, + pub page_id: String, + pub resolved_at: Option<String>, + pub short_link: String, + pub status: String, + pub updated_at: String, +} + +/// An update to an incident from the Discord status page. +/// +/// This will typically state what new information has been discovered about an +/// incident. +#[derive(Clone, Debug, Deserialize)] +pub struct IncidentUpdate { + pub affected_components: Vec<AffectedComponent>, + pub body: String, + pub created_at: String, + pub display_at: String, + pub id: String, + pub incident_id: String, + pub status: IncidentStatus, + pub updated_at: String, +} + +/// The type of status update during a service incident. +#[derive(Copy, Clone, Debug, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize)] +#[serde(rename_all="snake_case")] +pub enum IncidentStatus { + Identified, + Investigating, + Monitoring, + Postmortem, + Resolved, +} + +/// A Discord status maintenance message. This can be either for active +/// maintenances or for scheduled maintenances. +#[derive(Clone, Debug, Deserialize)] +pub struct Maintenance { + pub description: String, + pub id: String, + pub name: String, + pub start: String, + pub stop: String, } diff --git a/src/model/mod.rs b/src/model/mod.rs index ad70076..9b15049 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -25,6 +25,7 @@ mod guild; mod invite; mod misc; mod user; +mod voice; mod webhook; pub use self::channel::*; @@ -34,6 +35,7 @@ pub use self::invite::*; pub use self::misc::*; pub use self::permissions::Permissions; pub use self::user::*; +pub use self::voice::*; pub use self::webhook::*; use self::utils::*; @@ -41,40 +43,23 @@ use std::collections::HashMap; use std::sync::{Arc, RwLock}; use time::Timespec; use ::internal::prelude::*; -use ::utils::{Colour, decode_array}; - -// All of the enums and structs are imported here. These are built from the -// build script located at `./build.rs`. -// -// These use definitions located in `./definitions`, to map to structs and -// enums, each respectively located in their own folder. -// -// For structs, this will almost always include their decode method, although -// some require their own decoding due to many special fields. -// -// For enums, this will include the variants, and will automatically generate -// the number/string decoding methods where appropriate. -// -// As only the struct/enum itself and common mappings can be built, this leaves -// unique methods on each to be implemented here. -include!(concat!(env!("OUT_DIR"), "/models/built.rs")); +use ::utils::Colour; + +fn default_true() -> bool { true } macro_rules! id { ($(#[$attr:meta] $name:ident;)*) => { $( #[$attr] - #[derive(Copy, Clone, Debug, Eq, Hash, PartialOrd, Ord)] + #[derive(Copy, Clone, Debug, Deserialize, Eq, Hash, PartialOrd, Ord, Serialize)] #[allow(derive_hash_xor_eq)] pub struct $name(pub u64); impl $name { - fn decode(value: Value) -> Result<Self> { - decode_id(value).map($name) - } - /// Retrieves the time that the Id was created at. pub fn created_at(&self) -> Timespec { let offset = (self.0 >> 22) / 1000; + Timespec::new(1420070400 + offset as i64, 0) } } @@ -119,24 +104,6 @@ id! { WebhookId; } -/// A container for any channel. -#[derive(Clone, Debug)] -pub enum Channel { - /// A group. A group comprises of only one channel. - Group(Arc<RwLock<Group>>), - /// A [text] or [voice] channel within a [`Guild`]. - /// - /// [`Guild`]: struct.Guild.html - /// [text]: enum.ChannelType.html#variant.Text - /// [voice]: enum.ChannelType.html#variant.Voice - Guild(Arc<RwLock<GuildChannel>>), - /// A private channel to another [`User`]. No other users may access the - /// channel. For multi-user "private channels", use a group. - /// - /// [`User`]: struct.User.html - Private(Arc<RwLock<PrivateChannel>>), -} - /// A container for guilds. /// /// This is used to differentiate whether a guild itself can be used or whether @@ -149,29 +116,6 @@ pub enum GuildContainer { Id(GuildId), } -/// The type of edit being made to a Channel's permissions. -/// -/// This is for use with methods such as `Context::create_permission`. -/// -/// [`Context::create_permission`]: ../client/ -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum PermissionOverwriteType { - /// A member which is having its permission overwrites edited. - Member(UserId), - /// A role which is having its permission overwrites edited. - Role(RoleId), -} - -/// A guild which may or may not currently be available. -#[derive(Clone, Debug)] -pub enum PossibleGuild<T> { - /// An indicator that a guild is currently unavailable for at least one of - /// a variety of reasons. - Offline(GuildId), - /// An indicator that a guild is currently available. - Online(T), -} - /// Denotes the target for a search. /// /// This can be either a [`Guild`] - which can search multiple [`Channel`]s - @@ -204,3 +148,145 @@ impl From<GuildId> for SearchTarget { SearchTarget::Guild(guild_id) } } + +/// Information about a user's application. An application does not necessarily +/// have an associated bot user. +#[derive(Clone, Debug, Deserialize)] +pub struct ApplicationInfo { + /// The bot user associated with the application. See [`BotApplication`] for + /// more information. + /// + /// [`BotApplication`]: struct.BotApplication.html + pub bot: Option<BotApplication>, + /// Indicator of whether the bot is public. + /// + /// If a bot is public, anyone may invite it to their [`Guild`]. While a bot + /// is private, only the owner may add it to a guild. + /// + /// [`Guild`]: struct.Guild.html + #[serde(default="default_true")] + pub bot_public: bool, + /// Indicator of whether the bot requires an OAuth2 code grant. + pub bot_require_code_grant: bool, + /// A description of the application, assigned by the application owner. + pub description: String, + /// A set of bitflags assigned to the application, which represent gated + /// feature flags that have been enabled for the application. + pub flags: Option<u64>, + /// A hash pointing to the application's icon. + /// + /// This is not necessarily equivalent to the bot user's avatar. + pub icon: Option<String>, + /// The unique numeric Id of the application. + pub id: UserId, + /// The name assigned to the application by the application owner. + pub name: String, + /// A list of redirect URIs assigned to the application. + pub redirect_uris: Vec<String>, + /// A list of RPC Origins assigned to the application. + pub rpc_origins: Vec<String>, + /// The given secret to the application. + /// + /// This is not equivalent to the application's bot user's token. + pub secret: String, +} + +/// Information about an application with an application's bot user. +#[derive(Clone, Debug, Deserialize)] +pub struct BotApplication { + /// The unique Id of the bot user. + pub id: UserId, + /// A hash of the avatar, if one is assigned. + /// + /// Can be used to generate a full URL to the avatar. + pub avatar: Option<String>, + /// Indicator of whether it is a bot. + #[serde(default)] + pub bot: bool, + /// The discriminator assigned to the bot user. + /// + /// While discriminators are not unique, the `username#discriminator` pair + /// is. + pub discriminator: u16, + /// The bot user's username. + pub name: String, + /// The token used to authenticate as the bot user. + /// + /// **Note**: Keep this information private, as untrusted sources can use it + /// to perform any action with a bot user. + pub token: String, +} + +/// Information about the current application and its owner. +#[derive(Clone, Debug, Deserialize)] +pub struct CurrentApplicationInfo { + pub description: String, + pub icon: Option<String>, + pub id: UserId, + pub name: String, + pub owner: User, + #[serde(default)] + pub rpc_origins: Vec<String>, +} + +/// The name of a region that a voice server can be located in. +#[derive(Copy, Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize)] +pub enum Region { + #[serde(rename="amsterdam")] + Amsterdam, + #[serde(rename="brazil")] + Brazil, + #[serde(rename="eu-central")] + EuCentral, + #[serde(rename="eu-west")] + EuWest, + #[serde(rename="frankfurt")] + Frankfurt, + #[serde(rename="london")] + London, + #[serde(rename="sydney")] + Sydney, + #[serde(rename="us-central")] + UsCentral, + #[serde(rename="us-east")] + UsEast, + #[serde(rename="us-south")] + UsSouth, + #[serde(rename="us-west")] + UsWest, + #[serde(rename="vip-amsterdam")] + VipAmsterdam, + #[serde(rename="vip-us-east")] + VipUsEast, + #[serde(rename="vip-us-west")] + VipUsWest, +} + +impl Region { + pub fn name(&self) -> &str { + match *self { + Region::Amsterdam => "amsterdam", + Region::Brazil => "brazil", + Region::EuCentral => "eu-central", + Region::EuWest => "eu-west", + Region::Frankfurt => "frankfurt", + Region::London => "london", + Region::Sydney => "sydney", + Region::UsCentral => "us-central", + Region::UsEast => "us-east", + Region::UsSouth => "us-south", + Region::UsWest => "us-west", + Region::VipAmsterdam => "vip-amsterdam", + Region::VipUsEast => "vip-us-east", + Region::VipUsWest => "vip-us-west", + } + } +} + +use serde::{Deserialize, Deserializer}; +use std::result::Result as StdResult; + +fn deserialize_sync_user<D: Deserializer>(deserializer: D) + -> StdResult<Arc<RwLock<User>>, D::Error> { + Ok(Arc::new(RwLock::new(User::deserialize(deserializer)?))) +} diff --git a/src/model/permissions.rs b/src/model/permissions.rs index b3345eb..09fad4a 100644 --- a/src/model/permissions.rs +++ b/src/model/permissions.rs @@ -37,7 +37,10 @@ //! [Manage Roles]: constant.MANAGE_ROLES.html //! [Manage Webhooks]: constant.MANAGE_WEBHOOKS.html -use ::internal::prelude::*; +use serde::de::{Error as DeError, Visitor}; +use serde::{Deserialize, Deserializer}; +use std::fmt::{Formatter, Result as FmtResult}; +use std::result::Result as StdResult; /// Returns a set of permissions with the original @everyone permissions set /// to true. @@ -145,6 +148,7 @@ pub fn voice() -> Permissions { CONNECT | SPEAK | USE_VAD } + bitflags! { /// A set of permissions that can be assigned to [`User`]s and [`Role`]s via /// [`PermissionOverwrite`]s, roles globally in a [`Guild`], and to @@ -253,11 +257,6 @@ bitflags! { } impl Permissions { - #[doc(hidden)] - pub fn decode(value: Value) -> Result<Permissions> { - Ok(Self::from_bits_truncate(value.as_u64().unwrap())) - } - /// Shorthand for checking that the set of permissions contains the /// [Add Reactions] permission. /// @@ -482,3 +481,35 @@ impl Permissions { self.contains(self::USE_VAD) } } + +impl Deserialize for Permissions { + fn deserialize<D: Deserializer>(deserializer: D) -> StdResult<Self, D::Error> { + Ok(Permissions::from_bits_truncate(deserializer.deserialize_u64(U64Visitor)?)) + } +} + +struct U64Visitor; + +impl Visitor for U64Visitor { + type Value = u64; + + fn expecting(&self, formatter: &mut Formatter) -> FmtResult { + formatter.write_str("an unsigned 64-bit integer") + } + + fn visit_i32<E: DeError>(self, value: i32) -> StdResult<u64, E> { + Ok(value as u64) + } + + fn visit_i64<E: DeError>(self, value: i64) -> StdResult<u64, E> { + Ok(value as u64) + } + + fn visit_u32<E: DeError>(self, value: u32) -> StdResult<u64, E> { + Ok(value as u64) + } + + fn visit_u64<E: DeError>(self, value: u64) -> StdResult<u64, E> { + Ok(value) + } +} diff --git a/src/model/user.rs b/src/model/user.rs index d77275e..b38756a 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -1,17 +1,6 @@ -use serde_json::builder::ObjectBuilder; +use serde_json; use std::{fmt, mem}; -use super::{ - CurrentUser, - GuildContainer, - GuildId, - GuildInfo, - Member, - Message, - PrivateChannel, - RoleId, - User, - UserId, -}; +use super::*; use time::Timespec; use ::client::rest::{self, GuildPagination}; use ::internal::prelude::*; @@ -23,6 +12,56 @@ use std::sync::{Arc, RwLock}; #[cfg(feature="cache")] use ::client::CACHE; +/// An override for a channel. +#[derive(Clone, Debug, Deserialize)] +pub struct ChannelOverride { + /// The channel the override is for. + pub channel_id: ChannelId, + /// The notification level to use for the channel. + pub message_notifications: NotificationLevel, + /// Indicator of whether the channel is muted. + /// + /// In the client, this will not show an unread indicator for the channel, + /// although it will continue to show when the user is mentioned in it. + pub muted: bool, +} + +/// The type of a user connection. +/// +/// Note that this is related to a [`Connection`], and has nothing to do with +/// WebSocket connections. +/// +/// [`Connection`]: struct.Connection.html +#[derive(Copy, Clone, Debug, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize)] +pub enum ConnectionType { + /// A Battle.net connection. + #[serde(rename="battlenet")] + BattleNet, + /// A Steam connection. + #[serde(rename="steam")] + Steam, + /// A Twitch.tv connection. + #[serde(rename="twitch")] + TwitchTv, + #[serde(rename="youtube")] + YouTube, +} + +/// Information about the current user. +#[derive(Clone, Debug, Deserialize)] +pub struct CurrentUser { + pub id: UserId, + pub avatar: Option<String>, + #[serde(default)] + pub bot: bool, + pub discriminator: u16, + pub email: Option<String>, + pub mfa_enabled: bool, + #[serde(rename="username")] + pub name: String, + pub verified: bool, +} + impl CurrentUser { /// Returns the formatted URL of the user's icon, if one exists. /// @@ -65,15 +104,14 @@ impl CurrentUser { /// ``` pub fn edit<F>(&mut self, f: F) -> Result<()> where F: FnOnce(EditProfile) -> EditProfile { - let mut map = ObjectBuilder::new() - .insert("avatar", Some(&self.avatar)) - .insert("username", &self.name); + let mut map = Map::new(); + map.insert("username".to_owned(), Value::String(self.name.clone())); if let Some(email) = self.email.as_ref() { - map = map.insert("email", email) + map.insert("email".to_owned(), Value::String(email.clone())); } - match rest::edit_profile(&f(EditProfile(map)).0.build()) { + match rest::edit_profile(&f(EditProfile(map)).0) { Ok(new) => { let _ = mem::replace(self, new); @@ -97,6 +135,194 @@ impl CurrentUser { } } +/// An enum that represents a default avatar. +/// +/// The default avatar is calculated via the result of `discriminator % 5`. +/// +/// The has of the avatar can be retrieved via calling [`name`] on the enum. +/// +/// [`name`]: #method.name +#[derive(Copy, Clone, Debug, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize)] +pub enum DefaultAvatar { + /// The avatar when the result is `0`. + #[serde(rename="6debd47ed13483642cf09e832ed0bc1b")] + Blurple, + /// The avatar when the result is `1`. + #[serde(rename="322c936a8c8be1b803cd94861bdfa868")] + Grey, + /// The avatar when the result is `2`. + #[serde(rename="dd4dbc0016779df1378e7812eabaa04d")] + Green, + /// The avatar when the result is `3`. + #[serde(rename="0e291f67c9274a1abdddeb3fd919cbaa")] + Orange, + /// The avatar when the result is `4`. + #[serde(rename="1cbd08c76f8af6dddce02c5138971129")] + Red, +} + +impl DefaultAvatar { + /// Retrieves the String hash of the default avatar. + pub fn name(&self) -> Result<String> { + serde_json::to_string(self).map_err(From::from) + } +} + +/// Flags about who may add the current user as a friend. +#[derive(Clone, Debug, Deserialize)] +pub struct FriendSourceFlags { + #[serde(default)] + pub all: bool, + #[serde(default)] + pub mutual_friends: bool, + #[serde(default)] + pub mutual_guilds: bool, +} + +enum_number!( + /// Identifier for the notification level of a channel. + NotificationLevel { + /// Receive notifications for everything. + All = 0, + /// Receive only mentions. + Mentions = 1, + /// Receive no notifications. + Nothing = 2, + /// Inherit the notification level from the parent setting. + Parent = 3, + } +); + +/// The representation of a user's status. +/// +/// # Examples +/// +/// - [`DoNotDisturb`]; +/// - [`Invisible`]. +/// +/// [`DoNotDisturb`]: #variant.DoNotDisturb +/// [`Invisible`]: #variant.Invisible +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize)] +pub enum OnlineStatus { + #[serde(rename="dnd")] + DoNotDisturb, + #[serde(rename="idle")] + Idle, + #[serde(rename="invisible")] + Invisible, + #[serde(rename="offline")] + Offline, + #[serde(rename="online")] + Online, +} + +impl OnlineStatus { + pub fn name(&self) -> &str { + match *self { + OnlineStatus::DoNotDisturb => "dnd", + OnlineStatus::Idle => "idle", + OnlineStatus::Invisible => "invisible", + OnlineStatus::Offline => "offline", + OnlineStatus::Online => "online", + } + } +} + +impl Default for OnlineStatus { + fn default() -> OnlineStatus { + OnlineStatus::Online + } +} + +/// A summary of messages for a channel. +/// +/// These are received within a [`ReadyEvent`]. +/// +/// [`ReadyEvent`]: event/struct.ReadyEvent.html +#[derive(Clone, Debug, Deserialize)] +pub struct ReadState { + /// The unique Id of the channel. + pub id: ChannelId, + /// The Id of the latest message sent to the channel. + pub last_message_id: Option<MessageId>, + /// The time that a message was most recently pinned to the channel. + pub last_pin_timestamp: Option<String>, + /// The amount of times that the current user has been mentioned in the + /// channel since the last message ACKed. + #[serde(default)] + pub mention_count: u64, +} + +/// Information about a relationship that a user has with another user. +#[derive(Clone, Debug, Deserialize)] +pub struct Relationship { + /// Unique Id of the other user. + pub id: UserId, + /// The type of the relationship, e.g. blocked, friends, etc. + #[serde(rename="type")] + pub kind: RelationshipType, + /// The User instance of the other user. + pub user: User, +} + +enum_number!( + /// The type of relationship between the current user and another user. + RelationshipType { + /// The current user has a friend request ignored. + Ignored = 0, + /// The current user has the other user added as a friend. + Friends = 1, + /// The current user has the other blocked. + Blocked = 2, + /// The current user has an incoming friend request from the other user. + IncomingRequest = 3, + /// The current user has a friend request outgoing. + OutgoingRequest = 4, + } +); + +/// A reason that a user was suggested to be added as a friend. +#[derive(Clone, Debug, Deserialize)] +pub struct SuggestionReason { + /// The name of the user on the platform. + pub name: String, + /// The type of reason. + pub kind: u64, + /// The platform that the current user and the other user share. + pub platform: ConnectionType, +} + +/// The current user's progress through the Discord tutorial. +/// +/// This is only applicable to selfbots. +#[derive(Clone, Debug, Deserialize)] +pub struct Tutorial { + pub indicators_confirmed: Vec<String>, + pub indicators_suppressed: bool, +} + +/// Information about a user. +#[derive(Clone, Debug, Deserialize)] +pub struct User { + /// The unique Id of the user. Can be used to calculate the account's + /// cration date. + pub id: UserId, + /// Optional avatar hash. + pub avatar: Option<String>, + /// Indicator of whether the user is a bot. + #[serde(default)] + pub bot: bool, + /// The account's discriminator to differentiate the user from others with + /// the same [`name`]. The name+discriminator pair is always unique. + /// + /// [`name`]: #structfield.name + pub discriminator: String, + /// The account's username. Changing username will trigger a discriminator + /// change if the username+discriminator pair becomes non-unique. + #[serde(rename="username")] + pub name: String, +} + impl User { /// Returns the formatted URL of the user's icon, if one exists. /// @@ -202,24 +428,24 @@ impl User { if let Some(finding) = finding { finding } else { - let map = ObjectBuilder::new() - .insert("recipient_id", self.id.0) - .build(); + let map = json!({ + "recipient_id": self.id.0, + }); rest::create_private_channel(&map)?.id } } else { - let map = ObjectBuilder::new() - .insert("recipient_id", self.id.0) - .build(); + let map = json!({ + "recipient_id": self.id.0, + }); rest::create_private_channel(&map)?.id }}; - let map = ObjectBuilder::new() - .insert("content", content) - .insert("tts", false) - .build(); + let map = json!({ + "content": content, + "tts": false, + }); rest::send_message(private_channel_id.0, &map) } @@ -319,13 +545,48 @@ impl fmt::Display for User { } } +/// A user's connection. +/// +/// **Note**: This is not in any way related to a WebSocket connection. +#[derive(Clone, Debug, Deserialize)] +pub struct UserConnection { + /// The User's Id through the connection. + pub id: String, + /// Whether the user automatically syncs friends through the connection. + pub friend_sync: bool, + /// The relevant integrations. + pub integrations: Vec<Integration>, + /// The type of connection set. + #[serde(rename="type")] + pub kind: ConnectionType, + /// The user's name through the connection. + pub name: String, + /// Indicator of whether the connection has been revoked. + pub revoked: bool, + /// The visibility level. + pub visibility: u64, +} + +/// Settings about a guild in regards to notification configuration. +#[derive(Clone, Debug, Deserialize)] +pub struct UserGuildSettings { + pub channel_overriddes: Vec<ChannelOverride>, + pub guild_id: Option<GuildId>, + pub message_notifications: NotificationLevel, + pub mobile_push: bool, + pub muted: bool, + pub suppress_everyone: bool, +} + impl UserId { /// Creates a direct message channel between the [current user] and the /// user. This can also retrieve the channel if one already exists. /// /// [current user]: struct.CurrentUser.html pub fn create_dm_channel(&self) -> Result<PrivateChannel> { - let map = ObjectBuilder::new().insert("recipient_id", self.0).build(); + let map = json!({ + "recipient_id": self.0, + }); rest::create_private_channel(&map) } diff --git a/src/model/utils.rs b/src/model/utils.rs index cbae244..ab7a8fb 100644 --- a/src/model/utils.rs +++ b/src/model/utils.rs @@ -1,69 +1,46 @@ -use std::collections::{BTreeMap, HashMap}; +use serde::de::Error as DeError; +use std::collections::HashMap; use std::sync::{Arc, RwLock}; use super::*; + +#[cfg(feature="cache")] use ::internal::prelude::*; -use ::utils::{decode_array, into_array}; #[cfg(feature="cache")] use super::permissions::{self, Permissions}; #[cfg(feature="cache")] use ::client::CACHE; -#[macro_escape] -macro_rules! req { - ($opt:expr) => { - $opt.ok_or(Error::Decode(concat!("Type mismatch in model:", - line!(), - ": ", - stringify!($opt)), - Value::Null))? - } -} - -pub fn decode_emojis(value: Value) -> Result<HashMap<EmojiId, Emoji>> { +pub fn deserialize_emojis<D: Deserializer>(deserializer: D) + -> StdResult<HashMap<EmojiId, Emoji>, D::Error> { + let vec: Vec<Emoji> = Deserialize::deserialize(deserializer)?; let mut emojis = HashMap::new(); - for emoji in decode_array(value, Emoji::decode)? { + for emoji in vec { emojis.insert(emoji.id, emoji); } Ok(emojis) } -pub fn decode_id(value: Value) -> Result<u64> { - match value { - Value::U64(num) => Ok(num), - Value::I64(num) => Ok(num as u64), - Value::String(text) => match text.parse::<u64>() { - Ok(num) => Ok(num), - Err(_) => Err(Error::Decode("Expected numeric ID", - Value::String(text))) - }, - value => Err(Error::Decode("Expected numeric ID", value)) - } -} +pub fn deserialize_guild_channels<D: Deserializer>(deserializer: D) + -> StdResult<HashMap<ChannelId, Arc<RwLock<GuildChannel>>>, D::Error> { + let vec: Vec<GuildChannel> = Deserialize::deserialize(deserializer)?; + let mut map = HashMap::new(); -pub fn decode_members(value: Value) -> Result<HashMap<UserId, Member>> { - let mut members = HashMap::new(); - - for member in decode_array(value, Member::decode)? { - let user_id = member.user.read().unwrap().id; - - members.insert(user_id, member); + for channel in vec { + map.insert(channel.id, Arc::new(RwLock::new(channel))); } - Ok(members) + Ok(map) } -pub fn decode_guild_members(guild_id: GuildId, value: Value) -> Result<HashMap<UserId, Member>> { +pub fn deserialize_members<D: Deserializer>(deserializer: D) + -> StdResult<HashMap<UserId, Member>, D::Error> { + let vec: Vec<Member> = Deserialize::deserialize(deserializer)?; let mut members = HashMap::new(); - let member_vec = into_array(value).map(|x| x - .into_iter() - .map(|v| Member::decode_guild(guild_id, v)) - .filter_map(Result::ok) - .collect::<Vec<_>>())?; - for member in member_vec { + for member in vec { let user_id = member.user.read().unwrap().id; members.insert(user_id, member); @@ -72,21 +49,24 @@ pub fn decode_guild_members(guild_id: GuildId, value: Value) -> Result<HashMap<U Ok(members) } -pub fn decode_presences(value: Value) -> Result<HashMap<UserId, Presence>> { +pub fn deserialize_presences<D: Deserializer>(deserializer: D) + -> StdResult<HashMap<UserId, Presence>, D::Error> { + let vec: Vec<Presence> = Deserialize::deserialize(deserializer)?; let mut presences = HashMap::new(); - for presence in decode_array(value, Presence::decode)? { + for presence in vec { presences.insert(presence.user_id, presence); } Ok(presences) } -pub fn decode_private_channels(value: Value) - -> Result<HashMap<ChannelId, Channel>> { +pub fn deserialize_private_channels<D: Deserializer>(deserializer: D) + -> StdResult<HashMap<ChannelId, Channel>, D::Error> { + let vec: Vec<Channel> = Deserialize::deserialize(deserializer)?; let mut private_channels = HashMap::new(); - for private_channel in decode_array(value, Channel::decode)? { + 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, @@ -99,103 +79,54 @@ pub fn decode_private_channels(value: Value) Ok(private_channels) } -pub fn decode_roles(value: Value) -> Result<HashMap<RoleId, Role>> { +pub fn deserialize_roles<D: Deserializer>(deserializer: D) + -> StdResult<HashMap<RoleId, Role>, D::Error> { + let vec: Vec<Role> = Deserialize::deserialize(deserializer)?; let mut roles = HashMap::new(); - for role in decode_array(value, Role::decode)? { + for role in vec { roles.insert(role.id, role); } Ok(roles) } -pub fn decode_shards(value: Value) -> Result<[u64; 2]> { - let array = into_array(value)?; +pub fn deserialize_single_recipient<D: Deserializer>(deserializer: D) + -> StdResult<Arc<RwLock<User>>, D::Error> { + let mut users: Vec<User> = Deserialize::deserialize(deserializer)?; + let user = if users.is_empty() { + return Err(DeError::custom("Expected a single recipient")); + } else { + users.remove(0) + }; - Ok([ - req!(array.get(0) - .ok_or(Error::Client(ClientError::InvalidShards))?.as_u64()) as u64, - req!(array.get(1) - .ok_or(Error::Client(ClientError::InvalidShards))?.as_u64()) as u64, - ]) + Ok(Arc::new(RwLock::new(user))) } -pub fn decode_users(value: Value) -> Result<HashMap<UserId, Arc<RwLock<User>>>> { +pub fn deserialize_users<D: Deserializer>(deserializer: D) + -> StdResult<HashMap<UserId, Arc<RwLock<User>>>, D::Error> { + let vec: Vec<User> = Deserialize::deserialize(deserializer)?; let mut users = HashMap::new(); - for user in decode_array(value, User::decode)? { + for user in vec { users.insert(user.id, Arc::new(RwLock::new(user))); } Ok(users) } -pub fn decode_voice_states(value: Value) - -> Result<HashMap<UserId, VoiceState>> { +pub fn deserialize_voice_states<D: Deserializer>(deserializer: D) + -> StdResult<HashMap<UserId, VoiceState>, D::Error> { + let vec: Vec<VoiceState> = Deserialize::deserialize(deserializer)?; let mut voice_states = HashMap::new(); - for voice_state in decode_array(value, VoiceState::decode)? { + for voice_state in vec { voice_states.insert(voice_state.user_id, voice_state); } Ok(voice_states) } -pub fn into_string(value: Value) -> Result<String> { - match value { - Value::String(s) => Ok(s), - Value::U64(v) => Ok(v.to_string()), - Value::I64(v) => Ok(v.to_string()), - value => Err(Error::Decode("Expected string", value)), - } -} - -pub fn into_map(value: Value) -> Result<BTreeMap<String, Value>> { - match value { - Value::Object(m) => Ok(m), - value => Err(Error::Decode("Expected object", value)), - } -} - -pub fn into_u64(value: Value) -> Result<u64> { - match value { - Value::I64(v) => Ok(v as u64), - Value::String(v) => match v.parse::<u64>() { - Ok(v) => Ok(v), - Err(_) => Err(Error::Decode("Expected valid u64", Value::String(v))), - }, - Value::U64(v) => Ok(v), - value => Err(Error::Decode("Expected u64", value)), - } -} - -pub fn opt<F, T>(map: &mut BTreeMap<String, Value>, key: &str, f: F) - -> Result<Option<T>> where F: FnOnce(Value) -> Result<T> { - match map.remove(key) { - None | Some(Value::Null) => Ok(None), - Some(val) => f(val).map(Some), - } -} - -pub fn decode_discriminator(value: Value) -> Result<u16> { - match value { - Value::I64(v) => Ok(v as u16), - Value::U64(v) => Ok(v as u16), - Value::String(s) => match s.parse::<u16>() { - Ok(v) => Ok(v), - Err(_) => Err(Error::Decode("Error parsing discriminator as u16", - Value::String(s))), - }, - value => Err(Error::Decode("Expected string or u64", value)), - } -} - -pub fn remove(map: &mut BTreeMap<String, Value>, key: &str) -> Result<Value> { - map.remove(key).ok_or_else(|| { - Error::Decode("Unexpected absent key", Value::String(key.into())) - }) -} - #[cfg(feature="cache")] pub fn user_has_perms(channel_id: ChannelId, mut permissions: Permissions) diff --git a/src/model/voice.rs b/src/model/voice.rs new file mode 100644 index 0000000..10c74b2 --- /dev/null +++ b/src/model/voice.rs @@ -0,0 +1,36 @@ +use super::*; + +/// Information about an available voice region. +#[derive(Clone, Debug, Deserialize)] +pub struct VoiceRegion { + /// Whether it is a custom voice region, which is used for events. + pub custom: bool, + /// Whether it is a deprecated voice region, which you should avoid using. + pub deprecated: bool, + /// The internal Id of the voice region. + pub id: String, + /// A recognizable name of the location of the voice region. + pub name: String, + /// Whether the voice region is optimal for use by the current user. + pub optional: bool, + /// an example hostname. + pub sample_hostname: String, + /// An example port. + pub sample_port: u64, + /// Indicator of whether the voice region is only for VIP guilds. + pub vip: bool, +} + +/// A user's state within a voice channel. +#[derive(Clone, Debug, Deserialize)] +pub struct VoiceState { + pub channel_id: Option<ChannelId>, + pub deaf: bool, + pub mute: bool, + pub self_deaf: bool, + pub self_mute: bool, + pub session_id: String, + pub suppress: bool, + pub token: Option<String>, + pub user_id: UserId, +} diff --git a/src/model/webhook.rs b/src/model/webhook.rs index 4f831d9..0a2718e 100644 --- a/src/model/webhook.rs +++ b/src/model/webhook.rs @@ -1,10 +1,42 @@ -use serde_json::builder::ObjectBuilder; use std::mem; -use super::{Message, Webhook, WebhookId}; +use super::*; use ::utils::builder::ExecuteWebhook; use ::client::rest; use ::internal::prelude::*; +/// 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 +/// use. +#[derive(Clone, Debug, Deserialize)] +pub struct Webhook { + /// The unique Id. + /// + /// Can be used to calculate the creation date of the webhook. + pub id: WebhookId, + /// The default avatar. + /// + /// This can be modified via [`ExecuteWebhook::avatar`]. + /// + /// [`ExecuteWebhook::avatar`]: ../utils/builder/struct.ExecuteWebhook.html#method.avatar + pub avatar: Option<String>, + /// The Id of the channel that owns the webhook. + pub channel_id: ChannelId, + /// The Id of the guild that owns the webhook. + pub guild_id: Option<GuildId>, + /// The default name of the webhook. + /// + /// This can be modified via [`ExecuteWebhook::username`]. + /// + /// [`ExecuteWebhook::username`]: ../utils/builder/struct.ExecuteWebhook.html#method.username + pub name: Option<String>, + /// The webhook's secure token. + pub token: String, + /// The user that created the webhook. + /// + /// **Note**: This is not received when getting a webhook by its token. + pub user: Option<User>, +} + impl Webhook { /// Deletes the webhook. /// @@ -63,16 +95,15 @@ impl Webhook { /// /// [`rest::edit_webhook`]: ../client/rest/fn.edit_webhook.html /// [`rest::edit_webhook_with_token`]: ../client/rest/fn.edit_webhook_with_token.html - pub fn edit(&mut self, name: Option<&str>, avatar: Option<&str>) - -> Result<()> { + pub fn edit(&mut self, name: Option<&str>, avatar: Option<&str>) -> Result<()> { if name.is_none() && avatar.is_none() { return Ok(()); } - let mut map = ObjectBuilder::new(); + let mut map = Map::new(); if let Some(avatar) = avatar { - map = map.insert("avatar", if avatar.is_empty() { + map.insert("avatar".to_owned(), if avatar.is_empty() { Value::Null } else { Value::String(avatar.to_owned()) @@ -80,10 +111,10 @@ impl Webhook { } if let Some(name) = name { - map = map.insert("name", name); + map.insert("name".to_owned(), Value::String(name.to_owned())); } - match rest::edit_webhook_with_token(self.id.0, &self.token, &map.build()) { + match rest::edit_webhook_with_token(self.id.0, &self.token, &map) { Ok(replacement) => { mem::replace(self, replacement); @@ -142,7 +173,7 @@ impl Webhook { /// ``` #[inline] pub fn execute<F: FnOnce(ExecuteWebhook) -> ExecuteWebhook>(&self, f: F) -> Result<Message> { - rest::execute_webhook(self.id.0, &self.token, &f(ExecuteWebhook::default()).0.build()) + rest::execute_webhook(self.id.0, &self.token, &f(ExecuteWebhook::default()).0) } /// Retrieves the latest information about the webhook, editing the diff --git a/src/utils/builder/create_embed.rs b/src/utils/builder/create_embed.rs index 86d5b11..7c1c606 100644 --- a/src/utils/builder/create_embed.rs +++ b/src/utils/builder/create_embed.rs @@ -15,11 +15,10 @@ //! [`ExecuteWebhook::embeds`]: struct.ExecuteWebhook.html#method.embeds //! [here]: https://discordapp.com/developers/docs/resources/channel#embed-object -use serde_json::builder::ObjectBuilder; use serde_json::Value; -use std::collections::BTreeMap; use std::default::Default; use time::Tm; +use ::internal::prelude::*; use ::model::Embed; use ::utils::Colour; @@ -34,7 +33,7 @@ use ::utils::Colour; /// [`Context::send_message`]: ../../client/struct.Context.html#method.send_message /// [`Embed`]: ../../model/struct.Embed.html /// [`ExecuteWebhook::embeds`]: struct.ExecuteWebhook.html#method.embeds -pub struct CreateEmbed(pub BTreeMap<String, Value>); +pub struct CreateEmbed(pub Map<String, Value>); impl CreateEmbed { /// Set the author of the embed. @@ -45,9 +44,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.build(); + let author = f(CreateEmbedAuthor::default()).0; - self.0.insert("author".to_owned(), author); + self.0.insert("author".to_owned(), Value::Object(author)); CreateEmbed(self.0) } @@ -63,7 +62,7 @@ impl CreateEmbed { /// Set the colour of the left-hand side of the embed. pub fn colour<C: Into<Colour>>(mut self, colour: C) -> Self { - self.0.insert("color".to_owned(), Value::U64(colour.into().0 as u64)); + self.0.insert("color".to_owned(), Value::Number(Number::from(colour.into().0 as u64))); CreateEmbed(self.0) } @@ -89,7 +88,7 @@ impl CreateEmbed { /// [`CreateEmbedField`]: struct.CreateEmbedField.html pub fn field<F>(mut self, f: F) -> Self where F: FnOnce(CreateEmbedField) -> CreateEmbedField { - let field = f(CreateEmbedField::default()).0.build(); + let field = f(CreateEmbedField::default()).0; { let key = "fields".to_owned(); @@ -106,7 +105,7 @@ impl CreateEmbed { return CreateEmbed(self.0); }, }; - arr.push(field); + arr.push(Value::Object(field)); self.0.insert("fields".to_owned(), Value::Array(arr)); } @@ -122,18 +121,18 @@ impl CreateEmbed { /// [`CreateEmbedFooter`]: struct.CreateEmbedFooter.html pub fn footer<F>(mut self, f: F) -> Self where F: FnOnce(CreateEmbedFooter) -> CreateEmbedFooter { - let footer = f(CreateEmbedFooter::default()).0.build(); + let footer = f(CreateEmbedFooter::default()).0; - self.0.insert("footer".to_owned(), footer); + self.0.insert("footer".to_owned(), Value::Object(footer)); CreateEmbed(self.0) } /// Set the image associated with the embed. This only supports HTTP(S). pub fn image(mut self, url: &str) -> Self { - let image = ObjectBuilder::new() - .insert("url".to_owned(), url.to_owned()) - .build(); + let image = json!({ + "url": url.to_owned() + }); self.0.insert("image".to_owned(), image); @@ -142,9 +141,9 @@ impl CreateEmbed { /// Set the thumbnail of the embed. This only supports HTTP(S). pub fn thumbnail(mut self, url: &str) -> Self { - let thumbnail = ObjectBuilder::new() - .insert("url".to_owned(), url.to_owned()) - .build(); + let thumbnail = json!({ + "url": url.to_owned(), + }); self.0.insert("thumbnail".to_owned(), thumbnail); @@ -198,7 +197,7 @@ impl CreateEmbed { impl Default for CreateEmbed { /// Creates a builder with default values, setting the `type` to `rich`. fn default() -> CreateEmbed { - let mut map = BTreeMap::new(); + let mut map = Map::new(); map.insert("type".to_owned(), Value::String("rich".to_owned())); CreateEmbed(map) @@ -233,13 +232,11 @@ impl From<Embed> for CreateEmbed { b = b.description(&description); } - if let Some(fields) = embed.fields { - for field in fields { - b = b.field(move |f| f - .inline(field.inline) - .name(&field.name) - .value(&field.value)); - } + for field in embed.fields { + b = b.field(move |f| f + .inline(field.inline) + .name(&field.name) + .value(&field.value)); } if let Some(image) = embed.image { @@ -266,7 +263,6 @@ impl From<Embed> for CreateEmbed { } } - /// A builder to create a fake [`Embed`] object's author, for use with the /// [`CreateEmbed::author`] method. /// @@ -276,22 +272,28 @@ impl From<Embed> for CreateEmbed { /// [`CreateEmbed::author`]: struct.CreateEmbed.html#method.author /// [`name`]: #method.name #[derive(Default)] -pub struct CreateEmbedAuthor(pub ObjectBuilder); +pub struct CreateEmbedAuthor(pub Map<String, Value>); impl CreateEmbedAuthor { /// Set the URL of the author's icon. - pub fn icon_url(self, icon_url: &str) -> Self { - CreateEmbedAuthor(self.0.insert("icon_url", icon_url)) + pub fn icon_url(mut self, icon_url: &str) -> Self { + self.0.insert("icon_url".to_owned(), Value::String(icon_url.to_owned())); + + self } /// Set the author's name. - pub fn name(self, name: &str) -> Self { - CreateEmbedAuthor(self.0.insert("name", name)) + pub fn name(mut self, name: &str) -> Self { + self.0.insert("name".to_owned(), Value::String(name.to_owned())); + + self } /// Set the author's URL. - pub fn url(self, url: &str) -> Self { - CreateEmbedAuthor(self.0.insert("url", url)) + pub fn url(mut self, url: &str) -> Self { + self.0.insert("url".to_owned(), Value::String(url.to_owned())); + + self } } @@ -303,22 +305,28 @@ impl CreateEmbedAuthor { /// /// [`Embed`]: ../../model/struct.Embed.html /// [`CreateEmbed::field`]: struct.CreateEmbed.html#method.field -pub struct CreateEmbedField(pub ObjectBuilder); +pub struct CreateEmbedField(pub Map<String, Value>); impl CreateEmbedField { /// Set whether the field is inlined. Set to true by default. - pub fn inline(self, inline: bool) -> Self { - CreateEmbedField(self.0.insert("inline", inline)) + pub fn inline(mut self, inline: bool) -> Self { + self.0.insert("inline".to_owned(), Value::Bool(inline)); + + self } /// Set the field's name. It can't be longer than 256 characters. - pub fn name(self, name: &str) -> Self { - CreateEmbedField(self.0.insert("name", name)) + pub fn name(mut self, name: &str) -> Self { + self.0.insert("name".to_owned(), Value::String(name.to_owned())); + + self } /// Set the field's value. It can't be longer than 1024 characters. - pub fn value(self, value: &str) -> Self { - CreateEmbedField(self.0.insert("value", value)) + pub fn value(mut self, value: &str) -> Self { + self.0.insert("value".to_owned(), Value::String(value.to_owned())); + + self } } @@ -326,7 +334,10 @@ impl Default for CreateEmbedField { /// Creates a builder with default values, setting the value of `inline` to /// `true`. fn default() -> CreateEmbedField { - CreateEmbedField(ObjectBuilder::new().insert("inline", true)) + let mut map = Map::new(); + map.insert("inline".to_owned(), Value::Bool(true)); + + CreateEmbedField(map) } } @@ -338,17 +349,21 @@ impl Default for CreateEmbedField { /// [`Embed`]: ../../model/struct.Embed.html /// [`CreateEmbed::footer`]: struct.CreateEmbed.html#method.footer #[derive(Default)] -pub struct CreateEmbedFooter(pub ObjectBuilder); +pub struct CreateEmbedFooter(pub Map<String, Value>); impl CreateEmbedFooter { /// Set the icon URL's value. This only supports HTTP(S). - pub fn icon_url(self, icon_url: &str) -> Self { - CreateEmbedFooter(self.0.insert("icon_url", icon_url)) + pub fn icon_url(mut self, icon_url: &str) -> Self { + self.0.insert("icon_url".to_owned(), Value::String(icon_url.to_owned())); + + self } /// Set the footer's text. - pub fn text(self, text: &str) -> Self { - CreateEmbedFooter(self.0.insert("text", text)) + pub fn text(mut self, text: &str) -> Self { + self.0.insert("text".to_owned(), Value::String(text.to_owned())); + + self } } diff --git a/src/utils/builder/create_invite.rs b/src/utils/builder/create_invite.rs index 99125b3..d7595cd 100644 --- a/src/utils/builder/create_invite.rs +++ b/src/utils/builder/create_invite.rs @@ -1,6 +1,6 @@ -use serde_json::builder::ObjectBuilder; use serde_json::Value; use std::default::Default; +use ::internal::prelude::*; /// A builder to create a [`RichInvite`] for use via [`Context::create_invite`]. /// @@ -28,7 +28,7 @@ use std::default::Default; /// /// [`Context::create_invite`]: ../../client/struct.Context.html#method.create_invite /// [`RichInvite`]: ../../model/struct.Invite.html -pub struct CreateInvite(pub ObjectBuilder); +pub struct CreateInvite(pub JsonMap); impl CreateInvite { /// The duration that the invite will be valid for. @@ -36,8 +36,10 @@ impl CreateInvite { /// Set to `0` for an invite which does not expire after an amount of time. /// /// Defaults to `86400`, or 24 hours. - pub fn max_age(self, max_age: u64) -> Self { - CreateInvite(self.0.insert("max_age", max_age)) + pub fn max_age(mut self, max_age: u64) -> Self { + self.0.insert("max_age".to_owned(), Value::Number(Number::from(max_age))); + + self } /// The number of uses that the invite will be valid for. @@ -45,28 +47,37 @@ impl CreateInvite { /// Set to `0` for an invite which does not expire after a number of uses. /// /// Defaults to `0`. - pub fn max_uses(self, max_uses: u64) -> Self { - CreateInvite(self.0.insert("max_uses", max_uses)) + pub fn max_uses(mut self, max_uses: u64) -> Self { + self.0.insert("max_uses".to_owned(), Value::Number(Number::from(max_uses))); + + self } /// Whether an invite grants a temporary membership. /// /// Defaults to `false`. - pub fn temporary(self, temporary: bool) -> Self { - CreateInvite(self.0.insert("temporary", temporary)) + pub fn temporary(mut self, temporary: bool) -> Self { + self.0.insert("temporary".to_owned(), Value::Bool(temporary)); + + self } /// Whether or not to try to reuse a similar invite. /// /// Defaults to `false`. - pub fn unique(self, unique: bool) -> Self { - CreateInvite(self.0.insert("unique", unique)) + pub fn unique(mut self, unique: bool) -> Self { + self.0.insert("unique".to_owned(), Value::Bool(unique)); + + self } } impl Default for CreateInvite { /// Creates a builder with default values, setting `validate` to `null`. fn default() -> CreateInvite { - CreateInvite(ObjectBuilder::new().insert("validate", Value::Null)) + let mut map = Map::new(); + map.insert("validate".to_owned(), Value::Null); + + CreateInvite(map) } } diff --git a/src/utils/builder/create_message.rs b/src/utils/builder/create_message.rs index 8aed477..2dbbd79 100644 --- a/src/utils/builder/create_message.rs +++ b/src/utils/builder/create_message.rs @@ -1,7 +1,5 @@ -use serde_json::Value; -use std::collections::BTreeMap; -use std::default::Default; use super::CreateEmbed; +use ::internal::prelude::*; /// A builder to specify the contents of an [`rest::send_message`] request, /// primarily meant for use through [`Context::send_message`]. @@ -38,7 +36,7 @@ use super::CreateEmbed; /// [`content`]: #method.content /// [`embed`]: #method.embed /// [`rest::send_message`]: ../../client/rest/fn.send_message.html -pub struct CreateMessage(pub BTreeMap<String, Value>); +pub struct CreateMessage(pub Map<String, Value>); impl CreateMessage { /// Set the content of the message. @@ -79,7 +77,7 @@ impl Default for CreateMessage { /// [`Message`]: ../../model/struct.Message.html /// [`tts`]: #method.tts fn default() -> CreateMessage { - let mut map = BTreeMap::default(); + let mut map = Map::default(); map.insert("tts".to_owned(), Value::Bool(false)); CreateMessage(map) diff --git a/src/utils/builder/edit_channel.rs b/src/utils/builder/edit_channel.rs index e136cbf..2fa61a5 100644 --- a/src/utils/builder/edit_channel.rs +++ b/src/utils/builder/edit_channel.rs @@ -1,5 +1,4 @@ -use serde_json::builder::ObjectBuilder; -use std::default::Default; +use ::internal::prelude::*; /// A builder to edit a [`GuildChannel`] for use via one of a couple methods. /// @@ -24,7 +23,8 @@ use std::default::Default; /// [`Context::edit_channel`]: ../client/struct.Context.html#method.edit_channel /// [`GuildChannel`]: ../model/struct.GuildChannel.html /// [`GuildChannel::edit`]: ../model/struct.GuildChannel.html#method.edit -pub struct EditChannel(pub ObjectBuilder); +#[derive(Default)] +pub struct EditChannel(pub JsonMap); impl EditChannel { /// The bitrate of the channel in bits. @@ -32,20 +32,26 @@ impl EditChannel { /// This is for [voice] channels only. /// /// [voice]: ../../model/enum.ChannelType.html#variant.Voice - pub fn bitrate(self, bitrate: u64) -> Self { - EditChannel(self.0.insert("bitrate", bitrate)) + pub fn bitrate(mut self, bitrate: u64) -> Self { + self.0.insert("bitrate".to_owned(), Value::Number(Number::from(bitrate))); + + self } /// The name of the channel. /// /// Must be between 2 and 100 characters long. - pub fn name(self, name: &str) -> Self { - EditChannel(self.0.insert("name", name)) + pub fn name(mut self, name: &str) -> Self { + self.0.insert("name".to_owned(), Value::String(name.to_owned())); + + self } /// The position of the channel in the channel list. - pub fn position(self, position: u64) -> Self { - EditChannel(self.0.insert("position", position)) + pub fn position(mut self, position: u64) -> Self { + self.0.insert("position".to_owned(), Value::Number(Number::from(position))); + + self } /// The topic of the channel. Can be empty. @@ -55,8 +61,10 @@ impl EditChannel { /// This is for [text] channels only. /// /// [text]: ../../model/enum.ChannelType.html#variant.Text - pub fn topic(self, topic: &str) -> Self { - EditChannel(self.0.insert("topic", topic)) + pub fn topic(mut self, topic: &str) -> Self { + self.0.insert("topic".to_owned(), Value::String(topic.to_owned())); + + self } /// The number of users that may be in the channel simultaneously. @@ -64,14 +72,9 @@ impl EditChannel { /// This is for [voice] channels only. /// /// [voice]: ../../model/enum.ChannelType.html#variant.Voice - pub fn user_limit(self, user_limit: u64) -> Self { - EditChannel(self.0.insert("user_limit", user_limit)) - } -} + pub fn user_limit(mut self, user_limit: u64) -> Self { + self.0.insert("user_limit".to_owned(), Value::Number(Number::from(user_limit))); -impl Default for EditChannel { - /// Creates a builder with no default parameters. - fn default() -> EditChannel { - EditChannel(ObjectBuilder::new()) + self } } diff --git a/src/utils/builder/edit_guild.rs b/src/utils/builder/edit_guild.rs index 83d5016..bd94efa 100644 --- a/src/utils/builder/edit_guild.rs +++ b/src/utils/builder/edit_guild.rs @@ -1,6 +1,4 @@ -use serde_json::builder::ObjectBuilder; -use serde_json::Value; -use std::default::Default; +use ::internal::prelude::*; use ::model::{ChannelId, Region, UserId, VerificationLevel}; /// A builder to optionally edit certain fields of a [`Guild`]. This is meant @@ -13,7 +11,8 @@ use ::model::{ChannelId, Region, UserId, VerificationLevel}; /// [`Guild`]: ../../model/struct.Guild.html /// [`LiveGuild::edit`]: ../../model/struct.LiveGuild.html#method.edit /// [Manage Guild]: ../../model/permissions/constant.MANAGE_GUILD.html -pub struct EditGuild(pub ObjectBuilder); +#[derive(Default)] +pub struct EditGuild(pub Map<String, Value>); impl EditGuild { /// Set the "AFK voice channel" that users are to move to if they have been @@ -24,19 +23,23 @@ impl EditGuild { /// valid. /// /// [`afk_timeout`]: #method.afk_timeout - pub fn afk_channel<C: Into<ChannelId>>(self, channel: Option<C>) -> Self { - EditGuild(self.0.insert("afk_channel_id", match channel { - Some(channel) => Value::U64(channel.into().0), + pub fn afk_channel<C: Into<ChannelId>>(mut self, channel: Option<C>) -> Self { + self.0.insert("afk_channel_id".to_owned(), match channel { + Some(channel) => Value::Number(Number::from(channel.into().0)), None => Value::Null, - })) + }); + + self } /// Set the amount of time a user is to be moved to the AFK channel - /// configured via [`afk_channel`] - after being AFK. /// /// [`afk_channel`]: #method.afk_channel - pub fn afk_timeout(self, timeout: u64) -> Self { - EditGuild(self.0.insert("afk_timeout", timeout)) + pub fn afk_timeout(mut self, timeout: u64) -> Self { + self.0.insert("afk_timeout".to_owned(), Value::Number(Number::from(timeout))); + + self } /// Set the icon of the guild. Pass `None` to remove the icon. @@ -58,25 +61,28 @@ impl EditGuild { /// ``` /// /// [`utils::read_image`]: ../../utils/fn.read_image.html - pub fn icon(self, icon: Option<&str>) -> Self { - EditGuild(self.0 - .insert("icon", - icon.map_or_else(|| Value::Null, - |x| Value::String(x.to_owned())))) + pub fn icon(mut self, icon: Option<&str>) -> Self { + self.0.insert("icon".to_owned(), icon.map_or_else(|| Value::Null, |x| Value::String(x.to_owned()))); + + self } /// Set the name of the guild. /// /// **Note**: Must be between (and including) 2-100 chracters. - pub fn name(self, name: &str) -> Self { - EditGuild(self.0.insert("name", name)) + pub fn name(mut self, name: &str) -> Self { + self.0.insert("name".to_owned(), Value::String(name.to_owned())); + + self } /// Transfers the ownership of the guild to another user by Id. /// /// **Note**: The current user must be the owner of the guild. - pub fn owner<U: Into<UserId>>(self, user_id: U) -> Self { - EditGuild(self.0.insert("owner_id", user_id.into().0)) + pub fn owner<U: Into<UserId>>(mut self, user_id: U) -> Self { + self.0.insert("owner_id".to_owned(), Value::Number(Number::from(user_id.into().0))); + + self } /// Set the voice region of the server. @@ -96,8 +102,10 @@ impl EditGuild { /// ``` /// /// [`Region::UsWest`]: ../../model/enum.Region.html#variant.UsWest - pub fn region(self, region: Region) -> Self { - EditGuild(self.0.insert("region", region.name())) + pub fn region(mut self, region: Region) -> Self { + self.0.insert("region".to_owned(), Value::String(region.name().to_owned())); + + self } /// Set the splash image of the guild on the invitation page. @@ -107,11 +115,12 @@ impl EditGuild { /// /// [`InviteSplash`]: ../../model/enum.Feature.html#variant.InviteSplash /// [`features`]: ../../model/struct.LiveGuild.html#structfield.features - pub fn splash(self, splash: Option<&str>) -> Self { - EditGuild(self.0 - .insert("splash", - splash.map_or_else(|| Value::Null, - |x| Value::String(x.to_owned())))) + pub fn splash(mut self, splash: Option<&str>) -> Self { + let splash = splash.map_or(Value::Null, |x| Value::String(x.to_owned())); + + self.0.insert("splash".to_owned(), splash); + + self } /// Set the verification level of the guild. This can restrict what a @@ -145,15 +154,12 @@ impl EditGuild { /// /// [`VerificationLevel`]: ../../model/enum.VerificationLevel.html /// [`VerificationLevel::High`]: ../../model/enum.VerificationLevel.html#variant.High - pub fn verification_level<V>(self, verification_level: V) -> Self + pub fn verification_level<V>(mut self, verification_level: V) -> Self where V: Into<VerificationLevel> { - EditGuild(self.0.insert("verification_level", - verification_level.into().num())) - } -} + let num = Value::Number(Number::from(verification_level.into().num())); + + self.0.insert("verification_level".to_owned(), num); -impl Default for EditGuild { - fn default() -> EditGuild { - EditGuild(ObjectBuilder::new()) + self } } diff --git a/src/utils/builder/edit_member.rs b/src/utils/builder/edit_member.rs index cb67214..58f260a 100644 --- a/src/utils/builder/edit_member.rs +++ b/src/utils/builder/edit_member.rs @@ -1,6 +1,5 @@ -use serde_json::builder::ObjectBuilder; -use std::default::Default; use ::model::{ChannelId, RoleId}; +use ::internal::prelude::*; /// A builder which edits the properties of a [`Member`], to be used in /// conjunction with [`Context::edit_member`] and [`Member::edit`]. @@ -8,7 +7,8 @@ use ::model::{ChannelId, RoleId}; /// [`Context::edit_member`]: ../../client/struct.Context.html#method.edit_member /// [`Member`]: ../../model/struct.Member.html /// [`Member::edit`]: ../../model/struct.Member.html#method.edit -pub struct EditMember(pub ObjectBuilder); +#[derive(Default)] +pub struct EditMember(pub JsonMap); impl EditMember { /// Whether to deafen the member. @@ -16,8 +16,10 @@ impl EditMember { /// Requires the [Deafen Members] permission. /// /// [Deafen Members]: ../../model/permissions/constant.DEAFEN_MEMBERS.html - pub fn deafen(self, deafen: bool) -> Self { - EditMember(self.0.insert("deaf", deafen)) + pub fn deafen(mut self, deafen: bool) -> Self { + self.0.insert("deaf".to_owned(), Value::Bool(deafen)); + + self } /// Whether to mute the member. @@ -25,8 +27,10 @@ impl EditMember { /// Requires the [Mute Members] permission. /// /// [Mute Members]: ../../model/permissions/constant.MUTE_MEMBERS.html - pub fn mute(self, mute: bool) -> Self { - EditMember(self.0.insert("mute", mute)) + pub fn mute(mut self, mute: bool) -> Self { + self.0.insert("mute".to_owned(), Value::Bool(mute)); + + self } /// Changes the member's nickname. Pass an empty string to reset the @@ -35,8 +39,10 @@ impl EditMember { /// Requires the [Manage Nicknames] permission. /// /// [Manage Nicknames]: ../../model/permissions/constant.MANAGE_NICKNAMES.html - pub fn nickname(self, nickname: &str) -> Self { - EditMember(self.0.insert("nick", nickname)) + pub fn nickname(mut self, nickname: &str) -> Self { + self.0.insert("nick".to_owned(), Value::String(nickname.to_owned())); + + self } /// Set the list of roles that the member should have. @@ -44,10 +50,12 @@ impl EditMember { /// Requires the [Manage Roles] permission to modify. /// /// [Manage Roles]: ../../model/permissions/constant.MANAGE_ROLES.html - pub fn roles(self, roles: &[RoleId]) -> Self { - EditMember(self.0 - .insert_array("roles", - |a| roles.iter().fold(a, |a, id| a.push(id.0)))) + pub fn roles(mut self, roles: &[RoleId]) -> Self { + let role_ids = roles.iter().map(|x| Value::Number(Number::from(x.0))).collect(); + + self.0.insert("roles".to_owned(), Value::Array(role_ids)); + + self } /// The Id of the voice channel to move the member to. @@ -55,13 +63,9 @@ impl EditMember { /// Requires the [Move Members] permission. /// /// [Move Members]: ../../model/permissions/constant.MOVE_MEMBERS.html - pub fn voice_channel<C: Into<ChannelId>>(self, channel_id: C) -> Self { - EditMember(self.0.insert("channel_id", channel_id.into().0)) - } -} + pub fn voice_channel<C: Into<ChannelId>>(mut self, channel_id: C) -> Self { + self.0.insert("channel_id".to_owned(), Value::Number(Number::from(channel_id.into().0))); -impl Default for EditMember { - fn default() -> EditMember { - EditMember(ObjectBuilder::new()) + self } } diff --git a/src/utils/builder/edit_profile.rs b/src/utils/builder/edit_profile.rs index 83f1da2..680a371 100644 --- a/src/utils/builder/edit_profile.rs +++ b/src/utils/builder/edit_profile.rs @@ -1,12 +1,11 @@ -use serde_json::builder::ObjectBuilder; -use serde_json::Value; -use std::default::Default; +use ::internal::prelude::*; /// A builder to edit the current user's settings, to be used in conjunction /// with [`Context::edit_profile`]. /// /// [`Context::edit_profile`]: ../../client/struct.Context.html#method.edit_profile -pub struct EditProfile(pub ObjectBuilder); +#[derive(Default)] +pub struct EditProfile(pub JsonMap); impl EditProfile { /// Sets the avatar of the current user. `None` can be passed to remove an @@ -33,11 +32,12 @@ impl EditProfile { /// ``` /// /// [`utils::read_image`]: ../fn.read_image.html - pub fn avatar(self, icon: Option<&str>) -> Self { - EditProfile(self.0 - .insert("avatar", - icon.map_or_else(|| Value::Null, - |x| Value::String(x.to_owned())))) + pub fn avatar(mut self, avatar: Option<&str>) -> Self { + let avatar = avatar.map_or(Value::Null, |x| Value::String(x.to_owned())); + + self.0.insert("avatar".to_owned(), avatar); + + self } /// Modifies the current user's email address. @@ -50,8 +50,10 @@ impl EditProfile { /// **Note**: This can only be used by user accounts. /// /// [provided]: #method.password - pub fn email(self, email: &str) -> Self { - EditProfile(self.0.insert("email", email)) + pub fn email(mut self, email: &str) -> Self { + self.0.insert("email".to_owned(), Value::String(email.to_owned())); + + self } /// Modifies the current user's password. @@ -60,8 +62,10 @@ impl EditProfile { /// [provided]. /// /// [provided]: #method.password - pub fn new_password(self, new_password: &str) -> Self { - EditProfile(self.0.insert("new_password", new_password)) + pub fn new_password(mut self, new_password: &str) -> Self { + self.0.insert("new_password".to_owned(), Value::String(new_password.to_owned())); + + self } /// Used for providing the current password as verification when @@ -69,8 +73,10 @@ impl EditProfile { /// /// [modifying the password]: #method.new_password /// [modifying the associated email address]: #method.email - pub fn password(self, password: &str) -> Self { - EditProfile(self.0.insert("password", password)) + pub fn password(mut self, password: &str) -> Self { + self.0.insert("password".to_owned(), Value::String(password.to_owned())); + + self } /// Modifies the current user's username. @@ -79,13 +85,9 @@ impl EditProfile { /// and current discriminator, a new unique discriminator will be assigned. /// If there are no available discriminators with the requested username, /// an error will occur. - pub fn username(self, username: &str) -> Self { - EditProfile(self.0.insert("username", username)) - } -} + pub fn username(mut self, username: &str) -> Self { + self.0.insert("username".to_owned(), Value::String(username.to_owned())); -impl Default for EditProfile { - fn default() -> EditProfile { - EditProfile(ObjectBuilder::new()) + self } } diff --git a/src/utils/builder/edit_role.rs b/src/utils/builder/edit_role.rs index e9b86e9..de8efc7 100644 --- a/src/utils/builder/edit_role.rs +++ b/src/utils/builder/edit_role.rs @@ -1,5 +1,5 @@ -use serde_json::builder::ObjectBuilder; use std::default::Default; +use ::internal::prelude::*; use ::model::{Permissions, Role, permissions}; /// A builer to create or edit a [`Role`] for use via a number of model and @@ -31,53 +31,67 @@ use ::model::{Permissions, Role, permissions}; /// [`Guild::create_role`]: ../../model/struct.Guild.html#method.create_role /// [`Role`]: ../../model/struct.Role.html /// [`Role::edit`]: ../../model/struct.Role.html#method.edit -pub struct EditRole(pub ObjectBuilder); +pub struct EditRole(pub JsonMap); impl EditRole { /// Creates a new builder with the values of the given [`Role`]. /// /// [`Role`]: ../../model/struct.Role.html pub fn new(role: &Role) -> Self { - EditRole(ObjectBuilder::new() - .insert("color", role.colour.0) - .insert("hoist", role.hoist) - .insert("managed", role.managed) - .insert("mentionable", role.mentionable) - .insert("name", &role.name) - .insert("permissions", role.permissions.bits()) - .insert("position", role.position)) + let mut map = Map::new(); + map.insert("color".to_owned(), Value::Number(Number::from(role.colour.0))); + map.insert("hoist".to_owned(), Value::Bool(role.hoist)); + map.insert("managed".to_owned(), Value::Bool(role.managed)); + map.insert("mentionable".to_owned(), Value::Bool(role.mentionable)); + map.insert("name".to_owned(), Value::String(role.name.clone())); + map.insert("permissions".to_owned(), Value::Number(Number::from(role.permissions.bits()))); + map.insert("position".to_owned(), Value::Number(Number::from(role.position))); + + EditRole(map) } /// Sets the colour of the role. - pub fn colour(self, colour: u64) -> Self { - EditRole(self.0.insert("color", colour)) + pub fn colour(mut self, colour: u64) -> Self { + self.0.insert("color".to_owned(), Value::Number(Number::from(colour))); + + self } /// Whether or not to hoist the role above lower-positioned role in the user /// list. - pub fn hoist(self, hoist: bool) -> Self { - EditRole(self.0.insert("hoist", hoist)) + pub fn hoist(mut self, hoist: bool) -> Self { + self.0.insert("hoist".to_owned(), Value::Bool(hoist)); + + self } /// Whether or not to make the role mentionable, notifying its users. - pub fn mentionable(self, mentionable: bool) -> Self { - EditRole(self.0.insert("mentionable", mentionable)) + pub fn mentionable(mut self, mentionable: bool) -> Self { + self.0.insert("mentionable".to_owned(), Value::Bool(mentionable)); + + self } /// The name of the role to set. - pub fn name(self, name: &str) -> Self { - EditRole(self.0.insert("name", name)) + pub fn name(mut self, name: &str) -> Self { + self.0.insert("name".to_owned(), Value::String(name.to_owned())); + + self } /// The set of permissions to assign the role. - pub fn permissions(self, permissions: Permissions) -> Self { - EditRole(self.0.insert("permissions", permissions.bits())) + pub fn permissions(mut self, permissions: Permissions) -> Self { + self.0.insert("permissions".to_owned(), Value::Number(Number::from(permissions.bits()))); + + self } /// The position to assign the role in the role list. This correlates to the /// role's position in the user list. - pub fn position(self, position: u8) -> Self { - EditRole(self.0.insert("position", position)) + pub fn position(mut self, position: u8) -> Self { + self.0.insert("position".to_owned(), Value::Number(Number::from(position))); + + self } } @@ -95,12 +109,16 @@ impl Default for EditRole { /// /// [general permissions set]: ../../model/permissions/fn.general.html fn default() -> EditRole { - EditRole(ObjectBuilder::new() - .insert("color", 10070709) - .insert("hoist", false) - .insert("mentionable", false) - .insert("name", "new role".to_owned()) - .insert("permissions", permissions::general().bits()) - .insert("position", 1)) + let mut map = Map::new(); + let permissions = Number::from(permissions::general().bits()); + + map.insert("color".to_owned(), Value::Number(Number::from(10070709))); + map.insert("hoist".to_owned(), Value::Bool(false)); + map.insert("mentionable".to_owned(), Value::Bool(false)); + map.insert("name".to_owned(), Value::String("new role".to_owned())); + map.insert("permissions".to_owned(), Value::Number(permissions)); + map.insert("position".to_owned(), Value::Number(Number::from(1))); + + EditRole(map) } } diff --git a/src/utils/builder/execute_webhook.rs b/src/utils/builder/execute_webhook.rs index 073dcb3..5e79f31 100644 --- a/src/utils/builder/execute_webhook.rs +++ b/src/utils/builder/execute_webhook.rs @@ -1,6 +1,6 @@ -use serde_json::builder::ObjectBuilder; use serde_json::Value; use std::default::Default; +use ::internal::prelude::*; /// A builder to create the inner content of a [`Webhook`]'s execution. /// @@ -52,12 +52,14 @@ use std::default::Default; /// [`Webhook`]: ../../model/struct.Webhook.html /// [`Webhook::execute`]: ../../model/struct.Webhook.html#method.execute /// [`execute_webhook`]: ../../client/rest/fn.execute_webhook.html -pub struct ExecuteWebhook(pub ObjectBuilder); +pub struct ExecuteWebhook(pub JsonMap); impl ExecuteWebhook { /// Override the default avatar of the webhook with an image URL. - pub fn avatar_url(self, avatar_url: &str) -> Self { - ExecuteWebhook(self.0.insert("avatar_url", avatar_url)) + pub fn avatar_url(mut self, avatar_url: &str) -> Self { + self.0.insert("avatar_url".to_owned(), Value::String(avatar_url.to_owned())); + + self } /// Set the content of the message. @@ -66,8 +68,10 @@ impl ExecuteWebhook { /// omitted. /// /// [`embeds`]: #method.embeds - pub fn content(self, content: &str) -> Self { - ExecuteWebhook(self.0.insert("content", content)) + pub fn content(mut self, content: &str) -> Self { + self.0.insert("content".to_owned(), Value::String(content.to_owned())); + + self } /// Set the embeds associated with the message. @@ -83,20 +87,26 @@ impl ExecuteWebhook { /// [`Embed::fake`]: ../../model/struct.Embed.html#method.fake /// [`Webhook::execute`]: ../../model/struct.Webhook.html#method.execute /// [struct-level documentation]: #examples - pub fn embeds(self, embeds: Vec<Value>) -> Self { - ExecuteWebhook(self.0.insert("embeds", embeds)) + pub fn embeds(mut self, embeds: Vec<Value>) -> Self { + self.0.insert("embeds".to_owned(), Value::Array(embeds)); + + self } /// Whether the message is a text-to-speech message. /// /// Think carefully before setting this to `true`. - pub fn tts(self, tts: bool) -> Self { - ExecuteWebhook(self.0.insert("tts", tts)) + pub fn tts(mut self, tts: bool) -> Self { + self.0.insert("tts".to_owned(), Value::Bool(tts)); + + self } /// Override the default username of the webhook. - pub fn username(self, username: &str) -> Self { - ExecuteWebhook(self.0.insert("username", username)) + pub fn username(mut self, username: &str) -> Self { + self.0.insert("username".to_owned(), Value::String(username.to_owned())); + + self } } @@ -110,6 +120,9 @@ impl Default for ExecuteWebhook { /// [`Webhook`]: ../../model/struct.Webhook.html /// [`tts`]: #method.tts fn default() -> ExecuteWebhook { - ExecuteWebhook(ObjectBuilder::new().insert("tts", false)) + let mut map = Map::new(); + map.insert("tts".to_owned(), Value::Bool(false)); + + ExecuteWebhook(map) } } diff --git a/src/utils/colour.rs b/src/utils/colour.rs index 69f549d..8687db0 100644 --- a/src/utils/colour.rs +++ b/src/utils/colour.rs @@ -1,6 +1,3 @@ -use std::default::Default; -use ::internal::prelude::*; - macro_rules! colour { ($(#[$attr:meta] $name:ident, $val:expr;)*) => { impl Colour { @@ -64,7 +61,7 @@ macro_rules! colour { /// [`Role`]: ../model/struct.Role.html /// [`dark_teal`]: #method.dark_teal /// [`get_g`]: #method.get_g -#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] pub struct Colour(pub u32); impl Colour { @@ -123,15 +120,6 @@ impl Colour { Colour(uint) } - #[doc(hidden)] - pub fn decode(value: Value) -> Result<Colour> { - match value { - Value::U64(v) => Ok(Colour(v as u32)), - Value::I64(v) => Ok(Colour(v as u32)), - other => Err(Error::Decode("Expected valid colour", other)), - } - } - /// Returns the red RGB component of this Colour. /// /// # Examples diff --git a/src/utils/macros.rs b/src/utils/macros.rs index 16382b1..be94046 100644 --- a/src/utils/macros.rs +++ b/src/utils/macros.rs @@ -114,3 +114,58 @@ macro_rules! feature_voice { } } } + +#[macro_export] +macro_rules! enum_number { + (#[$attr_:meta] $name:ident { $(#[$attr:meta] $variant:ident = $value:expr, )* }) => { + #[$attr_] + #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] + pub enum $name { + $( + #[$attr] + $variant = $value, + )* + } + + impl ::serde::Serialize for $name { + fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error> + where S: ::serde::Serializer + { + // Serialize the enum as a u64. + serializer.serialize_u64(*self as u64) + } + } + + impl ::serde::Deserialize for $name { + fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error> + where D: ::serde::Deserializer + { + struct Visitor; + + impl ::serde::de::Visitor for Visitor { + type Value = $name; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + formatter.write_str("positive integer") + } + + fn visit_u64<E>(self, value: u64) -> StdResult<$name, E> + where E: ::serde::de::Error + { + // Rust does not come with a simple way of converting a + // number to an enum, so use a big `match`. + match value { + $( $value => Ok($name::$variant), )* + _ => Err(E::custom( + format!("unknown {} value: {}", + stringify!($name), value))), + } + } + } + + // Deserialize the enum from a u64. + deserializer.deserialize_u64(Visitor) + } + } + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index ca20b57..235709e 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -22,19 +22,6 @@ use ::model::{EmojiIdentifier, EmojiId}; pub use self::message_builder::MessageBuilder; -#[doc(hidden)] -pub fn decode_array<T, F: Fn(Value) -> Result<T>>(value: Value, f: F) -> Result<Vec<T>> { - into_array(value).and_then(|x| x.into_iter().map(f).collect()) -} - -#[doc(hidden)] -pub fn into_array(value: Value) -> Result<Vec<Value>> { - match value { - Value::Array(v) => Ok(v), - value => Err(Error::Decode("Expected array", value)), - } -} - /// Retrieves the "code" part of an [invite][`RichInvite`] out of a URL. /// /// # Examples |