diff options
| author | Zeyla Hellyer <[email protected]> | 2017-04-11 08:15:37 -0700 |
|---|---|---|
| committer | Zeyla Hellyer <[email protected]> | 2017-04-11 10:52:43 -0700 |
| commit | f6b27eb39c042e6779edc2d5d4b6e6c27d133eaf (patch) | |
| tree | a6169fee3bf9ea75391101577dcb2982e3daa388 /src | |
| parent | Clippy lints + permission byte literals (diff) | |
| download | serenity-f6b27eb39c042e6779edc2d5d4b6e6c27d133eaf.tar.xz serenity-f6b27eb39c042e6779edc2d5d4b6e6c27d133eaf.zip | |
Switch to using serde for deserialization
The current build system is rudimentary, incomplete, and rigid, offering
little in the way of customizing decoding options.
To solve this, switch to using serde-derive with custom Deserialization
implementations. This allows very simple deserialization when special
logic does not need to be applied, yet allows us to implement our own
deserialization logic when required.
The problem with the build system was that it built enums and structs
from YAML files. This is not so good, because it requires creating a
custom build system (which was rudimentary), creating "special struct
configs" when logic needed to be ever so slightly extended (rigid), and
if special logic needed to be applied, a custom deserialization method
would have been needed to be made anyway (incomplete).
To solve this, switch to serde-derive and implementing Deserialize
ourselves where required. This reduces YAML definitions that might
look like:
```yaml
---
name: Group
description: >
A group channel, potentially including other users, separate from a [`Guild`].
[`Guild`]: struct.Guild.html
fields:
- name: channel_id
description: The Id of the group channel.
from: id
type: ChannelId
- name: icon
description: The optional icon of the group channel.
optional: true
type: string
- name: last_message_id
description: The Id of the last message sent.
optional: true
type: MessageId
- name: last_pin_timestamp
description: Timestamp of the latest pinned message.
optional: true
type: string
- name: name
description: The name of the group channel.
optional: true
type: string
- name: owner_id
description: The Id of the group channel creator.
type: UserId
- name: recipients
description: Group channel's members.
custom: decode_users
t: UserId, Arc<RwLock<User>>
type: hashmap
```
to:
```rs
/// A group channel - potentially including other [`User`]s - separate from a
/// [`Guild`].
///
/// [`Guild`]: struct.Guild.html
/// [`User`]: struct.User.html
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>>>,
}
```
This is much simpler and does not have as much boilerplate.
There should not be any backwards incompatible changes other than the
old, public - yet undocumented (and hidden from documentation) - decode
methods being removed. Due to the nature of this commit, field names may
be incorrect, and will need to be corrected as deserialization errors
are found.
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 |