diff options
| author | Zeyla Hellyer <[email protected]> | 2018-04-27 08:05:56 -0700 |
|---|---|---|
| committer | Ken Swenson <[email protected]> | 2018-11-06 20:35:51 -0500 |
| commit | 3e54f4c524ad609b31c061286eab971f0084a4c5 (patch) | |
| tree | 907334e8b8a2823696f2b96456aca062f3d00a40 /src | |
| parent | Make builders mutably borrowed (diff) | |
| download | serenity-3e54f4c524ad609b31c061286eab971f0084a4c5.tar.xz serenity-3e54f4c524ad609b31c061286eab971f0084a4c5.zip | |
Add Rich Presence parsing support
Adds support for parsing Rich Presences.
This can not be used for setting activities with bots.
Upgrade path:
Basically change your import and usage from
`serenity::model::gateway::Game` to
`serenity::model::gateway::Activity`.
Diffstat (limited to 'src')
| -rw-r--r-- | src/client/bridge/gateway/shard_messenger.rs | 50 | ||||
| -rw-r--r-- | src/client/bridge/gateway/shard_runner.rs | 16 | ||||
| -rw-r--r-- | src/client/bridge/gateway/shard_runner_message.rs | 12 | ||||
| -rw-r--r-- | src/client/context.rs | 63 | ||||
| -rw-r--r-- | src/gateway/mod.rs | 6 | ||||
| -rw-r--r-- | src/gateway/shard.rs | 18 | ||||
| -rw-r--r-- | src/gateway/ws_client_ext.rs | 4 | ||||
| -rw-r--r-- | src/internal/ws_impl.rs | 20 | ||||
| -rw-r--r-- | src/model/gateway.rs | 319 |
9 files changed, 278 insertions, 230 deletions
diff --git a/src/client/bridge/gateway/shard_messenger.rs b/src/client/bridge/gateway/shard_messenger.rs index 11e75ce..21e586a 100644 --- a/src/client/bridge/gateway/shard_messenger.rs +++ b/src/client/bridge/gateway/shard_messenger.rs @@ -7,11 +7,11 @@ use websocket::message::OwnedMessage; /// A lightweight wrapper around an mpsc sender. /// /// This is used to cleanly communicate with a shard's respective -/// [`ShardRunner`]. This can be used for actions such as setting the game via -/// [`set_game`] or shutting down via [`shutdown`]. +/// [`ShardRunner`]. This can be used for actions such as setting the activity +/// via [`set_activity`] or shutting down via [`shutdown`]. /// /// [`ShardRunner`]: struct.ShardRunner.html -/// [`set_game`]: #method.set_game +/// [`set_activity`]: #method.set_activity /// [`shutdown`]: #method.shutdown #[derive(Clone, Debug)] pub struct ShardMessenger { @@ -124,13 +124,13 @@ impl ShardMessenger { }); } - /// Sets the user's current game, if any. + /// Sets the user's current activity, if any. /// /// Other presence settings are maintained. /// /// # Examples /// - /// Setting the current game to playing `"Heroes of the Storm"`: + /// Setting the current activity to playing `"Heroes of the Storm"`: /// /// ```rust,no_run /// # extern crate parking_lot; @@ -146,19 +146,9 @@ impl ShardMessenger { /// # /// # let mut shard = Shard::new(mutex.clone(), mutex, [0, 1]).unwrap(); /// # - /// # #[cfg(feature = "model")] - /// use serenity::model::gateway::Game; - /// # #[cfg(not(feature = "model"))] - /// use serenity::model::gateway::{Game, GameType}; - /// - /// # #[cfg(feature = "model")] - /// shard.set_game(Some(Game::playing("Heroes of the Storm"))); - /// # #[cfg(not(feature = "model"))] - /// shard.set_game(Some(Game { - /// kind: GameType::Playing, - /// name: "Heroes of the Storm".to_owned(), - /// url: None, - /// })); + /// use serenity::model::gateway::Activity; + /// + /// shard.set_activity(Some(Activity::playing("Heroes of the Storm"))); /// # Ok(()) /// # } /// # @@ -166,12 +156,8 @@ impl ShardMessenger { /// # try_main().unwrap(); /// # } /// ``` - pub fn set_game<T: Into<Game>>(&self, game: Option<T>) { - self._set_game(game.map(Into::into)) - } - - fn _set_game(&self, game: Option<Game>) { - let _ = self.send(ShardRunnerMessage::SetGame(game)); + pub fn set_activity(&self, activity: Option<Activity>) { + let _ = self.send(ShardRunnerMessage::SetActivity(activity)); } /// Sets the user's full presence information. @@ -198,9 +184,9 @@ impl ShardMessenger { /// # /// # let mut shard = Shard::new(mutex.clone(), mutex, [0, 1]).unwrap(); /// # - /// use serenity::model::{Game, OnlineStatus}; + /// use serenity::model::{Activity, OnlineStatus}; /// - /// shard.set_presence(Some(Game::playing("Heroes of the Storm")), OnlineStatus::Online); + /// shard.set_presence(Some(Activity::playing("Heroes of the Storm")), OnlineStatus::Online); /// # Ok(()) /// # } /// # @@ -208,20 +194,12 @@ impl ShardMessenger { /// # try_main().unwrap(); /// # } /// ``` - pub fn set_presence<T: Into<Game>>( - &self, - game: Option<T>, - status: OnlineStatus, - ) { - self._set_presence(game.map(Into::into), status) - } - - fn _set_presence(&self, game: Option<Game>, mut status: OnlineStatus) { + pub fn set_presence(&self, activity: Option<Activity>, mut status: OnlineStatus) { if status == OnlineStatus::Offline { status = OnlineStatus::Invisible; } - let _ = self.send(ShardRunnerMessage::SetPresence(status, game)); + let _ = self.send(ShardRunnerMessage::SetPresence(status, activity)); } /// Sets the user's current online status. diff --git a/src/client/bridge/gateway/shard_runner.rs b/src/client/bridge/gateway/shard_runner.rs index ea1fad6..0f244bd 100644 --- a/src/client/bridge/gateway/shard_runner.rs +++ b/src/client/bridge/gateway/shard_runner.rs @@ -258,26 +258,26 @@ impl<H: EventHandler + Send + Sync + 'static> ShardRunner<H> { ShardRunnerMessage::Message(msg) => { self.shard.client.send_message(&msg).is_ok() }, - ShardRunnerMessage::SetGame(game) => { - // To avoid a clone of `game`, we do a little bit of + ShardRunnerMessage::SetActivity(activity) => { + // To avoid a clone of `activity`, we do a little bit of // trickery here: // // First, we obtain a reference to the current presence of // the shard, and create a new presence tuple of the new - // game we received over the channel as well as the online - // status that the shard already had. + // activity we received over the channel as well as the + // online status that the shard already had. // // We then (attempt to) send the websocket message with the // status update, expressively returning: // // - whether the message successfully sent - // - the original game we received over the channel - self.shard.set_game(game); + // - the original activity we received over the channel + self.shard.set_activity(activity); self.shard.update_presence().is_ok() }, - ShardRunnerMessage::SetPresence(status, game) => { - self.shard.set_presence(status, game); + ShardRunnerMessage::SetPresence(status, activity) => { + self.shard.set_presence(status, activity); self.shard.update_presence().is_ok() }, diff --git a/src/client/bridge/gateway/shard_runner_message.rs b/src/client/bridge/gateway/shard_runner_message.rs index edb9b8a..0fac329 100644 --- a/src/client/bridge/gateway/shard_runner_message.rs +++ b/src/client/bridge/gateway/shard_runner_message.rs @@ -1,7 +1,7 @@ use model::{ - gateway::Game, + gateway::Activity, + id::GuildId, user::OnlineStatus, - id::GuildId }; use websocket::message::OwnedMessage; @@ -38,11 +38,11 @@ pub enum ShardRunnerMessage { Close(u16, Option<String>), /// Indicates that the client is to send a custom WebSocket message. Message(OwnedMessage), - /// Indicates that the client is to update the shard's presence's game. - SetGame(Option<Game>), + /// Indicates that the client is to update the shard's presence's activity. + SetActivity(Option<Activity>), /// Indicates that the client is to update the shard's presence in its - /// entirety. - SetPresence(OnlineStatus, Option<Game>), + /// entirity. + SetPresence(OnlineStatus, Option<Activity>), /// Indicates that the client is to update the shard's presence's status. SetStatus(OnlineStatus), } diff --git a/src/client/context.rs b/src/client/context.rs index f6ffbef..ff5a788 100644 --- a/src/client/context.rs +++ b/src/client/context.rs @@ -25,7 +25,7 @@ use utils::{self, VecMap}; /// [`Shard`] which received the event, or the low-level [`http`] module. /// /// The context contains "shortcuts", like for interacting with the shard. -/// Methods like [`set_game`] will unlock the shard and perform an update for +/// Methods like [`set_activity`] will unlock the shard and perform an update for /// you to save a bit of work. /// /// A context will only live for the event it was dispatched for. After the @@ -33,7 +33,7 @@ use utils::{self, VecMap}; /// /// [`Shard`]: ../gateway/struct.Shard.html /// [`http`]: ../http/index.html -/// [`set_game`]: #method.set_game +/// [`set_activity`]: #method.set_activity #[derive(Clone)] pub struct Context { /// A clone of [`Client::data`]. Refer to its documentation for more @@ -122,7 +122,7 @@ impl Context { /// Sets the current user as being [`Online`]. This maintains the current - /// game. + /// activity. /// /// # Examples /// @@ -154,7 +154,7 @@ impl Context { } /// Sets the current user as being [`Idle`]. This maintains the current - /// game. + /// activity. /// /// # Examples /// @@ -185,7 +185,7 @@ impl Context { } /// Sets the current user as being [`DoNotDisturb`]. This maintains the - /// current game. + /// current activity. /// /// # Examples /// @@ -216,7 +216,7 @@ impl Context { } /// Sets the current user as being [`Invisible`]. This maintains the current - /// game. + /// activity. /// /// # Examples /// @@ -247,8 +247,8 @@ impl Context { self.shard.set_status(OnlineStatus::Invisible); } - /// "Resets" the current user's presence, by setting the game to `None` and - /// the online status to [`Online`]. + /// "Resets" the current user's presence, by setting the activity to `None` + /// and the online status to [`Online`]. /// /// Use [`set_presence`] for fine-grained control over individual details. /// @@ -281,7 +281,7 @@ impl Context { self.shard.set_presence(None::<Game>, OnlineStatus::Online); } - /// Sets the current game, defaulting to an online status of [`Online`]. + /// Sets the current activity, defaulting to an online status of [`Online`]. /// /// # Examples /// @@ -294,7 +294,7 @@ impl Context { /// # use serenity::prelude::*; /// # use serenity::model::channel::Message; /// # - /// use serenity::model::gateway::Game; + /// use serenity::model::gateway::Activity; /// /// struct Handler; /// @@ -306,7 +306,7 @@ impl Context { /// return; /// } /// - /// ctx.set_game(Game::playing(*unsafe { args.get_unchecked(1) })); + /// ctx.set_activity(Activity::playing(*unsafe { args.get_unchecked(1) })); /// } /// } /// @@ -321,26 +321,22 @@ impl Context { /// /// [`Online`]: ../model/user/enum.OnlineStatus.html#variant.Online #[inline] - pub fn set_game<T: Into<Game>>(&self, game: T) { - self._set_game(game.into()) + pub fn set_activity(&self, activity: Activity) { + self.shard.set_presence(Some(activity), OnlineStatus::Online); } - fn _set_game(&self, game: Game) { - self.shard.set_presence(Some(game), OnlineStatus::Online); - } - - /// Sets the current game, passing in only its name. This will automatically - /// set the current user's [`OnlineStatus`] to [`Online`], and its - /// [`GameType`] as [`Playing`]. + /// Sets the current activity, passing in only its name. This will + /// automatically set the current user's [`OnlineStatus`] to [`Online`], and + /// its [`ActivityType`] as [`Playing`]. /// - /// Use [`reset_presence`] to clear the current game, or [`set_presence`] - /// for more fine-grained control. + /// Use [`reset_presence`] to clear the current activity, or + /// [`set_presence`] for more fine-grained control. /// /// **Note**: Maximum length is 128. /// /// # Examples /// - /// When an [`Event::Ready`] is received, set the game name to `"test"`: + /// When an [`Event::Ready`] is received, set the activity name to `"test"`: /// /// ```rust,no_run /// # use serenity::prelude::*; @@ -359,23 +355,24 @@ impl Context { /// ``` /// /// [`Event::Ready`]: ../model/event/enum.Event.html#variant.Ready - /// [`GameType`]: ../model/gateway/enum.GameType.html + /// [`ActivityType`]: ../model/gateway/enum.ActivityType.html /// [`Online`]: ../model/user/enum.OnlineStatus.html#variant.Online /// [`OnlineStatus`]: ../model/user/enum.OnlineStatus.html - /// [`Playing`]: ../model/gateway/enum.GameType.html#variant.Playing + /// [`Playing`]: ../model/gateway/enum.ActivityType.html#variant.Playing /// [`reset_presence`]: #method.reset_presence /// [`set_presence`]: #method.set_presence #[deprecated(since = "0.5.5", note = "Use Context::set_game")] #[inline] - pub fn set_game_name<T: Into<String>>(&self, game_name: T) { - self.set_game(game_name.into()) + pub fn set_game_name(&self, game_name: &str) { + let activity = Activity::playing(game_name); + self.shard.set_presence(Some(activity), OnlineStatus::Online); } /// Sets the current user's presence, providing all fields to be passed. /// /// # Examples /// - /// Setting the current user as having no game and being [`Idle`]: + /// Setting the current user as having no activity and being [`Idle`]: /// /// ```rust,no_run /// # use serenity::prelude::*; @@ -406,13 +403,13 @@ impl Context { /// /// impl EventHandler for Handler { /// fn ready(&self, context: Context, _: Ready) { - /// use serenity::model::gateway::Game; + /// use serenity::model::gateway::Activity; /// use serenity::model::user::OnlineStatus; /// - /// let game = Game::playing("Heroes of the Storm"); + /// let activity = Activity::playing("Heroes of the Storm"); /// let status = OnlineStatus::DoNotDisturb; /// - /// context.set_presence(Some(game), status); + /// context.set_presence(Some(activity), status); /// } /// } /// @@ -424,8 +421,8 @@ impl Context { /// [`DoNotDisturb`]: ../model/user/enum.OnlineStatus.html#variant.DoNotDisturb /// [`Idle`]: ../model/user/enum.OnlineStatus.html#variant.Idle #[inline] - pub fn set_presence(&self, game: Option<Game>, status: OnlineStatus) { - self.shard.set_presence(game, status); + pub fn set_presence(&self, activity: Option<Activity>, status: OnlineStatus) { + self.shard.set_presence(activity, status); } /// Disconnects the shard from the websocket, essentially "quiting" it. diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 07d882c..a5f3a5e 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -57,8 +57,8 @@ pub use self::{ }; use model::{ - gateway::Game, - user::OnlineStatus + gateway::Activity, + user::OnlineStatus, }; use serde_json::Value; use std::fmt::{Display, Formatter, Result as FmtResult}; @@ -70,7 +70,7 @@ use websocket::sync::{ #[cfg(feature = "client")] use client::bridge::gateway::ShardClientMessage; -pub type CurrentPresence = (Option<Game>, OnlineStatus); +pub type CurrentPresence = (Option<Activity>, OnlineStatus); pub type WsClient = Client<TlsStream<TcpStream>>; /// Indicates the current connection stage of a [`Shard`]. diff --git a/src/gateway/shard.rs b/src/gateway/shard.rs index 09a3b69..b629361 100644 --- a/src/gateway/shard.rs +++ b/src/gateway/shard.rs @@ -2,7 +2,7 @@ use constants::{self, close_codes}; use internal::prelude::*; use model::{ event::{Event, GatewayEvent}, - gateway::Game, + gateway::Activity, id::GuildId, user::OnlineStatus }; @@ -29,8 +29,8 @@ use websocket::{ /// A Shard is a higher-level handler for a websocket connection to Discord's /// gateway. The shard allows for sending and receiving messages over the -/// websocket, such as setting the active game, reconnecting, syncing guilds, -/// and more. +/// websocket, such as setting the active activity, reconnecting, syncing +/// guilds, and more. /// /// Refer to the [module-level documentation][module docs] for information on /// effectively using multiple shards, if you need to. @@ -272,22 +272,22 @@ impl Shard { /// # /// # let mut shard = Shard::new(mutex.clone(), mutex, [0, 1]).unwrap(); /// # - /// use serenity::model::gateway::Game; + /// use serenity::model::gateway::Activity; /// - /// shard.set_game(Some(Game::playing("Heroes of the Storm"))); + /// shard.set_activity(Some(Activity::playing("Heroes of the Storm"))); /// # } /// # /// # #[cfg(not(feature = "model"))] /// # fn main() { } /// ``` #[inline] - pub fn set_game(&mut self, game: Option<Game>) { - self.current_presence.0 = game; + pub fn set_activity(&mut self, activity: Option<Activity>) { + self.current_presence.0 = activity; } #[inline] - pub fn set_presence(&mut self, status: OnlineStatus, game: Option<Game>) { - self.set_game(game); + pub fn set_presence(&mut self, status: OnlineStatus, activity: Option<Activity>) { + self.set_activity(activity); self.set_status(status); } diff --git a/src/gateway/ws_client_ext.rs b/src/gateway/ws_client_ext.rs index 934383c..27f11ee 100644 --- a/src/gateway/ws_client_ext.rs +++ b/src/gateway/ws_client_ext.rs @@ -92,7 +92,7 @@ impl WebSocketGatewayClientExt for WsClient { shard_info: &[u64; 2], current_presence: &CurrentPresence, ) -> Result<()> { - let &(ref game, ref status) = current_presence; + let &(ref activity, ref status) = current_presence; let now = Utc::now().timestamp() as u64; debug!("[Shard {:?}] Sending presence update", shard_info); @@ -103,7 +103,7 @@ impl WebSocketGatewayClientExt for WsClient { "afk": false, "since": now, "status": status.name(), - "game": game.as_ref().map(|x| json!({ + "game": activity.as_ref().map(|x| json!({ "name": x.name, "type": x.kind, "url": x.url, diff --git a/src/internal/ws_impl.rs b/src/internal/ws_impl.rs index 5d8969b..fbf6ae4 100644 --- a/src/internal/ws_impl.rs +++ b/src/internal/ws_impl.rs @@ -20,25 +20,13 @@ impl ReceiverExt for WsClient<TlsStream<TcpStream>> { fn recv_json(&mut self) -> Result<Option<Value>> { Ok(match self.recv_message()? { OwnedMessage::Binary(bytes) => { - serde_json::from_reader(ZlibDecoder::new(&bytes[..])) - .map(Some) - .map_err(|why| { - warn!("Err deserializing bytes: {:?}; bytes: {:?}", why, bytes); - - why - })? + println!("{:?}", bytes); + serde_json::from_reader(ZlibDecoder::new(&bytes[..])).map(Some)? }, OwnedMessage::Close(data) => return Err(Error::Gateway(GatewayError::Closed(data))), OwnedMessage::Text(payload) => { - serde_json::from_str(&payload).map(Some).map_err(|why| { - warn!( - "Err deserializing text: {:?}; text: {}", - why, - payload, - ); - - why - })? + println!("{}", payload); + serde_json::from_str(&payload).map(Some)? }, OwnedMessage::Ping(x) => { self.send_message(&OwnedMessage::Pong(x)) diff --git a/src/model/gateway.rs b/src/model/gateway.rs index 68668cf..bbff7e4 100644 --- a/src/model/gateway.rs +++ b/src/model/gateway.rs @@ -1,5 +1,6 @@ //! Models pertaining to the gateway. +use chrono::{DateTime, Utc}; use parking_lot::RwLock; use serde::de::Error as DeError; use serde::ser::{SerializeStruct, Serialize, Serializer}; @@ -26,82 +27,116 @@ pub struct BotGateway { pub url: String, } -/// Representation of a game that a [`User`] is playing -- or streaming in the -/// case that a stream URL is provided. -/// -/// [`User`]: ../user/struct.User.html +/// Representation of an activity that a [`User`] is performing. #[derive(Clone, Debug, Serialize)] -pub struct Game { - /// The type of game status. - #[serde(default, rename = "type")] - pub kind: GameType, - /// The name of the game being played. +pub struct Activity { + /// The ID of the application for the activity. + pub application_id: Option<ApplicationId>, + /// Images for the presence and their texts. + pub assets: Option<ActivityAssets>, + /// What the user is doing. + pub details: Option<String>, + /// Activity flags describing what the payload includes. + pub flags: Option<ActivityFlags>, + /// Whether or not the activity is an instanced game session. + pub instance: Option<bool>, + /// The type of activity being performed + #[serde(default = "ActivityType::default", rename = "type")] + pub kind: ActivityType, + /// The name of the activity. pub name: String, - /// The Stream URL if [`kind`] is [`GameType::Streaming`]. + /// Information about the user's current party. + pub party: Option<ActivityParty>, + /// Secrets for Rich Presence joining and spectating. + pub secrets: Option<ActivitySecrets>, + /// The user's current party status. + pub state: Option<String>, + /// Unix timestamps for the start and/or end times of the activity. + pub timestamps: Option<ActivityTimestamps>, + /// The Stream URL if [`kind`] is [`ActivityType::Streaming`]. /// - /// [`GameType::Streaming`]: enum.GameType.html#variant.Streaming + /// [`ActivityType::Streaming`]: enum.ActivityType.html#variant.Streaming /// [`kind`]: #structfield.kind pub url: Option<String>, } #[cfg(feature = "model")] -impl Game { +impl Activity { /// Creates a `Game` struct that appears as a `Playing <name>` status. /// /// **Note**: Maximum `name` length is 128. /// /// # Examples /// - /// Create a command that sets the current game being played: + /// Create a command that sets the current activity: /// /// ```rust,no_run /// # #[macro_use] extern crate serenity; /// # /// use serenity::framework::standard::Args; - /// use serenity::model::gateway::Game; + /// use serenity::model::gateway::Activity; /// - /// command!(game(ctx, _msg, args) { + /// command!(activity(ctx, _msg, args) { /// let name = args.full(); - /// ctx.set_game(Game::playing(&name)); + /// ctx.set_activity(Activity::playing(&name)); /// }); /// # /// # fn main() {} /// ``` - pub fn playing(name: &str) -> Game { - Game { - kind: GameType::Playing, + pub fn playing(name: &str) -> Activity { + Activity { + application_id: None, + assets: None, + details: None, + flags: None, + instance: None, + kind: ActivityType::Playing, name: name.to_string(), + party: None, + secrets: None, + state: None, + timestamps: None, url: None, } } - /// Creates a `Game` struct that appears as a `Streaming <name>` status. + /// Creates an `Activity` struct that appears as a `Streaming <name>` + /// status. /// /// **Note**: Maximum `name` length is 128. /// /// # Examples /// - /// Create a command that sets the current game and stream: + /// Create a command that sets the current streaming status: /// /// ```rust,no_run /// # #[macro_use] extern crate serenity; /// # /// use serenity::framework::standard::Args; - /// use serenity::model::gateway::Game; + /// use serenity::model::gateway::Activity; /// /// // Assumes command has min_args set to 2. /// command!(stream(ctx, _msg, args) { /// # let stream_url = String::from(""); /// let name = args.full(); - /// ctx.set_game(Game::streaming(&name, &stream_url)); + /// ctx.set_activity(Activity::streaming(&name, &stream_url)); /// }); /// # /// # fn main() {} /// ``` - pub fn streaming(name: &str, url: &str) -> Game { - Game { - kind: GameType::Streaming, + pub fn streaming(name: &str, url: &str) -> Activity { + Activity { + application_id: None, + assets: None, + details: None, + flags: None, + instance: None, + kind: ActivityType::Streaming, name: name.to_string(), + party: None, + secrets: None, + state: None, + timestamps: None, url: Some(url.to_string()), } } @@ -112,115 +147,159 @@ impl Game { /// /// # Examples /// - /// Create a command that sets the current game being played: + /// Create a command that sets the current listening status: /// /// ```rust,no_run /// # #[macro_use] extern crate serenity; /// # /// use serenity::framework::standard::Args; - /// use serenity::model::gateway::Game; + /// use serenity::model::gateway::Activity; /// /// command!(listen(ctx, _msg, args) { /// let name = args.full(); - /// ctx.set_game(Game::listening(&name)); + /// ctx.set_activity(Activity::listening(&name)); /// }); /// # /// # fn main() {} /// ``` - pub fn listening(name: &str) -> Game { - Game { - kind: GameType::Listening, + pub fn listening(name: &str) -> Activity { + Activity { + application_id: None, + assets: None, + details: None, + flags: None, + instance: None, + kind: ActivityType::Listening, name: name.to_string(), + party: None, + secrets: None, + state: None, + timestamps: None, url: None, } } } -impl<'a> From<&'a str> for Game { - fn from(name: &'a str) -> Self { - Game { - kind: GameType::Playing, - name: name.to_owned(), - url: None, - } - } -} - -impl From<String> for Game { - fn from(name: String) -> Self { - Game { - kind: GameType::Playing, - url: None, - name, - } - } -} - -impl<'a> From<(String, GameType)> for Game { - fn from((name, kind): (String, GameType)) -> Self { - Self { - url: None, - kind, - name, - } - } -} - -impl<'a> From<(&'a str, &'a str)> for Game { - fn from((name, url): (&'a str, &'a str)) -> Self { - Self { - kind: GameType::Streaming, - name: name.to_owned(), - url: Some(url.to_owned()), - } - } -} - -impl From<(String, String)> for Game { - fn from((name, url): (String, String)) -> Self { - Self { - kind: GameType::Streaming, - url: Some(url), - name, - } - } -} - -impl From<(String, GameType, String)> for Game { - fn from((name, kind, url): (String, GameType, String)) -> Self { - Self { - url: Some(url), - kind, - name, - } - } -} - -impl<'de> Deserialize<'de> for Game { +impl<'de> Deserialize<'de> for Activity { fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> { let mut map = JsonMap::deserialize(deserializer)?; + let application_id = match map.remove("application_id") { + Some(v) => serde_json::from_value::<Option<_>>(v).map_err(DeError::custom)?, + None => None, + }; + let assets = match map.remove("assets") { + Some(v) => serde_json::from_value::<Option<_>>(v).map_err(DeError::custom)?, + None => None, + }; + let details = match map.remove("details") { + Some(v) => serde_json::from_value::<Option<_>>(v).map_err(DeError::custom)?, + None => None, + }; + let flags = match map.remove("flags") { + Some(v) => serde_json::from_value::<Option<_>>(v).map_err(DeError::custom)?, + None => None, + }; + let instance = match map.remove("instance") { + Some(v) => serde_json::from_value::<Option<_>>(v).map_err(DeError::custom)?, + None => None, + }; let kind = map.remove("type") - .and_then(|v| GameType::deserialize(v).ok()) - .unwrap_or(GameType::Playing); + .and_then(|v| ActivityType::deserialize(v).ok()) + .unwrap_or(ActivityType::Playing); let name = map.remove("name") .and_then(|v| String::deserialize(v).ok()) .unwrap_or_else(String::new); + let party = match map.remove("party") { + Some(v) => serde_json::from_value::<Option<_>>(v).map_err(DeError::custom)?, + None => None, + }; + let secrets = match map.remove("secrets") { + Some(v) => serde_json::from_value::<Option<_>>(v).map_err(DeError::custom)?, + None => None, + }; + let state = match map.remove("state") { + Some(v) => serde_json::from_value::<Option<_>>(v).map_err(DeError::custom)?, + None => None, + }; + let timestamps = match map.remove("timestamps") { + Some(v) => serde_json::from_value::<Option<_>>(v).map_err(DeError::custom)?, + None => None, + }; let url = map.remove("url") .and_then(|v| serde_json::from_value::<String>(v).ok()); - Ok(Game { + Ok(Activity { + application_id, + assets, + details, + flags, + instance, kind, name, + party, + secrets, + state, + timestamps, url, }) } } +/// The assets for an activity. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ActivityAssets { + /// The ID for a large asset of the activity, usually a snowflake. + pub large_image: Option<String>, + /// Text displayed when hovering over the large image of the activity. + pub large_text: Option<String>, + /// The ID for a small asset of the activity, usually a snowflake. + pub small_image: Option<String>, + /// Text displayed when hovering over the small image of the activity. + pub small_text: Option<String>, +} + +bitflags! { + /// A set of flags defining what is in an activity's payload. + #[derive(Deserialize, Serialize)] + pub struct ActivityFlags: u64 { + /// Whether the activity is an instance activity. + const INSTANCE = 0b001; + /// Whether the activity is joinable. + const JOIN = 0b010; + /// Whether the activity can be spectated. + const SPECTATE = 0b011; + /// Whether a request can be sent to join the user's party. + const JOIN_REQUEST = 0b100; + /// Whether the activity can be synced. + const SYNC = 0b101; + /// Whether the activity can be played. + const PLAY = 0b110; + } +} + +/// Information about an activity's party. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ActivityParty { + /// The ID of the party. + pub id: Option<String>, + /// Used to show the party's current and maximum size. + pub size: [u64; 2], +} +/// Secrets for an activity. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ActivitySecrets { + /// The secret for joining a party. + pub join: Option<String>, + /// The secret for a specific instanced match. + #[serde(rename = "match")] + pub match_: Option<String>, + /// The secret for spectating an activity. + pub spectate: Option<String>, +} -/// The type of activity that is being performed when playing a game. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub enum GameType { +#[derive(Clone, Copy, Debug)] +pub enum ActivityType { /// An indicator that the user is playing a game. Playing = 0, /// An indicator that the user is streaming to a service. @@ -230,16 +309,16 @@ pub enum GameType { } enum_number!( - GameType { + ActivityType { Playing, Streaming, Listening, } ); -impl GameType { +impl ActivityType { pub fn num(&self) -> u64 { - use self::GameType::*; + use self::ActivityType::*; match *self { Playing => 0, @@ -249,8 +328,8 @@ impl GameType { } } -impl Default for GameType { - fn default() -> Self { GameType::Playing } +impl Default for ActivityType { + fn default() -> Self { ActivityType::Playing } } /// A representation of the data retrieved from the gateway endpoint. @@ -269,10 +348,10 @@ pub struct Gateway { /// [`User`]: ../user/struct.User.html #[derive(Clone, Debug)] pub struct Presence { - /// The game that a [`User`] is current playing. + /// The activity that a [`User`] is performing. /// - /// [`User`]: ../user/struct.User.html - pub game: Option<Game>, + /// [`User`]: struct.User.html + pub activity: Option<Activity>, /// The date of the last presence update. pub last_modified: Option<u64>, /// The nickname of the member, if applicable. @@ -309,8 +388,8 @@ impl<'de> Deserialize<'de> for Presence { (user_id, None) }; - let game = match map.remove("game") { - Some(v) => serde_json::from_value::<Option<Game>>(v) + let activity = match map.remove("game") { + Some(v) => serde_json::from_value::<Option<Activity>>(v) .map_err(DeError::custom)?, None => None, }; @@ -329,12 +408,12 @@ impl<'de> Deserialize<'de> for Presence { .map_err(DeError::custom)?; Ok(Presence { - game, - last_modified, - nick, - status, - user, - user_id, + activity: activity, + last_modified: last_modified, + nick: nick, + status: status, + user: user, + user_id: user_id, }) } } @@ -348,7 +427,7 @@ impl Serialize for Presence { } let mut state = serializer.serialize_struct("Presence", 5)?; - state.serialize_field("game", &self.game)?; + state.serialize_field("game", &self.activity)?; state.serialize_field("last_modified", &self.last_modified)?; state.serialize_field("nick", &self.nick)?; state.serialize_field("status", &self.status)?; @@ -393,4 +472,10 @@ pub struct SessionStartLimit { pub reset_after: u64, /// The total number of session starts within the ratelimit period allowed. pub total: u64, -}
\ No newline at end of file +} +/// Timestamps of when a user started and/or is ending their activity. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ActivityTimestamps { + pub end: Option<DateTime<Utc>>, + pub start: Option<DateTime<Utc>>, +} |