From 9da11b418488890e57d1b59b06c8c67c378745d2 Mon Sep 17 00:00:00 2001 From: Chris Johnston Date: Sat, 22 Jun 2019 20:46:20 -0700 Subject: [PATCH] [ifcbrk] feature: Implement Client Status Support (#1247) * Implement Client Status Support Adds support for using the client_status as sent as part of the Presence model. This value can be used to determine if a user is active on the native desktop app, the mobile app, or the website. * lint: whitespace in IPresence * Remove breaking change to IPresence interface with a note for 2.1 * update comment to not reference 2.1 * re-add interface break to IPresence * add example payload for client_status * use inline declaration for Enum.TryParse --- .../Entities/Users/ClientType.cs | 21 +++++++++++ .../Entities/Users/IPresence.cs | 6 ++++ src/Discord.Net.Rest/API/Common/Presence.cs | 10 +++++- .../Entities/Users/RestUser.cs | 3 ++ .../DiscordSocketClient.cs | 4 +-- .../Entities/Users/SocketPresence.cs | 36 +++++++++++++++++-- .../Entities/Users/SocketUnknownUser.cs | 4 +-- .../Entities/Users/SocketUser.cs | 2 ++ .../Entities/Users/SocketWebhookUser.cs | 4 +-- 9 files changed, 80 insertions(+), 10 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/Users/ClientType.cs diff --git a/src/Discord.Net.Core/Entities/Users/ClientType.cs b/src/Discord.Net.Core/Entities/Users/ClientType.cs new file mode 100644 index 000000000..d4afe39f3 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Users/ClientType.cs @@ -0,0 +1,21 @@ +namespace Discord +{ + /// + /// Defines the types of clients a user can be active on. + /// + public enum ClientType + { + /// + /// The user is active using the mobile application. + /// + Mobile, + /// + /// The user is active using the desktop application. + /// + Desktop, + /// + /// The user is active using the web application. + /// + Web + } +} diff --git a/src/Discord.Net.Core/Entities/Users/IPresence.cs b/src/Discord.Net.Core/Entities/Users/IPresence.cs index b300115f8..620eb907c 100644 --- a/src/Discord.Net.Core/Entities/Users/IPresence.cs +++ b/src/Discord.Net.Core/Entities/Users/IPresence.cs @@ -1,3 +1,5 @@ +using System.Collections.Immutable; + namespace Discord { /// @@ -13,5 +15,9 @@ namespace Discord /// Gets the current status of this user. /// UserStatus Status { get; } + /// + /// Gets the set of clients where this user is currently active. + /// + IImmutableSet ActiveClients { get; } } } diff --git a/src/Discord.Net.Rest/API/Common/Presence.cs b/src/Discord.Net.Rest/API/Common/Presence.cs index 2902b7ce3..22526e8ac 100644 --- a/src/Discord.Net.Rest/API/Common/Presence.cs +++ b/src/Discord.Net.Rest/API/Common/Presence.cs @@ -1,5 +1,6 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using Newtonsoft.Json; +using System.Collections.Generic; namespace Discord.API { @@ -18,5 +19,12 @@ namespace Discord.API public Optional Roles { get; set; } [JsonProperty("nick")] public Optional Nick { get; set; } + // This property is a Dictionary where each key is the ClientType + // and the values are the current client status. + // The client status values are all the same. + // Example: + // "client_status": { "desktop": "dnd", "mobile": "dnd" } + [JsonProperty("client_status")] + public Optional> ClientStatus { get; set; } } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs index 6af5b5c95..37385fb7e 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Immutable; using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.User; @@ -31,6 +32,8 @@ namespace Discord.Rest /// public virtual UserStatus Status => UserStatus.Offline; /// + public virtual IImmutableSet ActiveClients => ImmutableHashSet.Empty; + /// public virtual bool IsWebhook => false; internal RestUser(BaseDiscordClient discord, ulong id) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 48ce34d83..70a135c01 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -325,7 +325,7 @@ namespace Discord.WebSocket { var user = SocketGlobalUser.Create(this, state, model); user.GlobalUser.AddRef(); - user.Presence = new SocketPresence(UserStatus.Online, null); + user.Presence = new SocketPresence(UserStatus.Online, null, null); return user; }); } @@ -433,7 +433,7 @@ namespace Discord.WebSocket return; var status = Status; var statusSince = _statusSince; - CurrentUser.Presence = new SocketPresence(status, Activity); + CurrentUser.Presence = new SocketPresence(status, Activity, null); var gameModel = new GameModel(); // Discord only accepts rich presence over RPC, don't even bother building a payload diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs index 4112fd273..52f111303 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs @@ -1,3 +1,6 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using Model = Discord.API.Presence; @@ -13,15 +16,42 @@ namespace Discord.WebSocket public UserStatus Status { get; } /// public IActivity Activity { get; } - - internal SocketPresence(UserStatus status, IActivity activity) + /// + public IImmutableSet ActiveClients { get; } + internal SocketPresence(UserStatus status, IActivity activity, IImmutableSet activeClients) { Status = status; Activity= activity; + ActiveClients = activeClients; } internal static SocketPresence Create(Model model) { - return new SocketPresence(model.Status, model.Game?.ToEntity()); + var clients = ConvertClientTypesDict(model.ClientStatus.GetValueOrDefault()); + return new SocketPresence(model.Status, model.Game?.ToEntity(), clients); + } + /// + /// Creates a new containing all of the client types + /// where a user is active from the data supplied in the Presence update frame. + /// + /// + /// A dictionary keyed by the + /// and where the value is the . + /// + /// + /// A collection of all s that this user is active. + /// + private static IImmutableSet ConvertClientTypesDict(IDictionary clientTypesDict) + { + if (clientTypesDict == null || clientTypesDict.Count == 0) + return ImmutableHashSet.Empty; + var set = new HashSet(); + foreach (var key in clientTypesDict.Keys) + { + if (Enum.TryParse(key, true, out ClientType type)) + set.Add(type); + // quietly discard ClientTypes that do not match + } + return set.ToImmutableHashSet(); } /// diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs index e6ce0137e..840a1c30b 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs @@ -24,8 +24,8 @@ namespace Discord.WebSocket /// public override bool IsWebhook => false; - - internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null); } set { } } + /// + internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null, null); } set { } } /// /// This field is not supported for an unknown user. internal override SocketGlobalUser GlobalUser => diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs index 4832e7311..eceb071eb 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs @@ -38,6 +38,8 @@ namespace Discord.WebSocket public IActivity Activity => Presence.Activity; /// public UserStatus Status => Presence.Status; + /// + public IImmutableSet ActiveClients => Presence.ActiveClients; /// /// Gets mutual guilds shared with this user. /// diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs index 2d701ef64..8ff4ee48c 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs @@ -29,8 +29,8 @@ namespace Discord.WebSocket /// public override bool IsWebhook => true; - - internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null); } set { } } + /// + internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null, null); } set { } } internal override SocketGlobalUser GlobalUser => throw new NotSupportedException();