diff --git a/src/Discord.Net.Core/Entities/Activities/GameSecrets.cs b/src/Discord.Net.Core/Entities/Activities/GameSecrets.cs index e919d40a0..3a6cbc9b2 100644 --- a/src/Discord.Net.Core/Entities/Activities/GameSecrets.cs +++ b/src/Discord.Net.Core/Entities/Activities/GameSecrets.cs @@ -1,6 +1,6 @@ namespace Discord { - public struct GameSecrets + public class GameSecrets { public string Match { get; } public string Join { get; } diff --git a/src/Discord.Net.Core/Entities/Activities/GameTimestamps.cs b/src/Discord.Net.Core/Entities/Activities/GameTimestamps.cs index cf2dbb281..106f8ea49 100644 --- a/src/Discord.Net.Core/Entities/Activities/GameTimestamps.cs +++ b/src/Discord.Net.Core/Entities/Activities/GameTimestamps.cs @@ -2,7 +2,7 @@ namespace Discord { - public struct GameTimestamps + public class GameTimestamps { public DateTimeOffset Start { get; } public DateTimeOffset End { get; } diff --git a/src/Discord.Net.Core/Entities/Users/IPresence.cs b/src/Discord.Net.Core/Entities/Users/IPresence.cs index 7f182241b..25adcc9c4 100644 --- a/src/Discord.Net.Core/Entities/Users/IPresence.cs +++ b/src/Discord.Net.Core/Entities/Users/IPresence.cs @@ -2,8 +2,8 @@ { public interface IPresence { - /// Gets the game this user is currently playing, if any. - Game? Game { get; } + /// Gets the activity this user is currently doing. + IActivity Activity { get; } /// Gets the current status of this user. UserStatus Status { get; } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs index d8ade3a6b..c6cf6103a 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -16,7 +16,7 @@ namespace Discord.Rest public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); public string Discriminator => DiscriminatorValue.ToString("D4"); public string Mention => MentionUtils.MentionUser(Id); - public virtual Game? Game => null; + public virtual IActivity Activity => null; public virtual UserStatus Status => UserStatus.Offline; public virtual bool IsWebhook => false; diff --git a/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs b/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs index c6b0b2fd8..f55c83b75 100644 --- a/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs +++ b/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs @@ -18,7 +18,7 @@ namespace Discord.Rpc public string Discriminator => DiscriminatorValue.ToString("D4"); public string Mention => MentionUtils.MentionUser(Id); public virtual bool IsWebhook => false; - public virtual Game? Game => null; + public virtual IActivity Activity => null; public virtual UserStatus Status => UserStatus.Offline; internal RpcUser(DiscordRpcClient discord, ulong id) diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index 6c2a0f3b9..af7f38f6f 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -22,7 +22,7 @@ namespace Discord.WebSocket /// Gets the estimated round-trip latency, in milliseconds, to the gateway server. public override int Latency { get => GetLatency(); protected set { } } public override UserStatus Status { get => _shards[0].Status; protected set { } } - public override Game? Game { get => _shards[0].Game; protected set { } } + public IActivity Activity { get => _shards[0].Activity; protected set { } } internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; public override IReadOnlyCollection Guilds => GetGuilds().ToReadOnlyCollection(() => GetGuildCount()); diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index e5564ba3f..55a943ca1 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -48,7 +48,7 @@ namespace Discord.WebSocket /// public override int Latency { get; protected set; } public override UserStatus Status { get; protected set; } = UserStatus.Online; - public override Game? Game { get; protected set; } + internal IActivity Activity { get; protected set; } //From DiscordSocketConfig internal int TotalShards { get; private set; } @@ -328,77 +328,39 @@ namespace Discord.WebSocket } public override async Task SetGameAsync(string name, string streamUrl = null, StreamType streamType = StreamType.NotStreaming) { - if (name != null) - Game = new Game(name, streamUrl, streamType); + if (name != null && streamUrl != null) + Activity = new StreamingGame(name, streamUrl, streamType); + else if (name != null) + Activity = new Game(name); else - Game = null; + Activity = null; await SendStatusAsync().ConfigureAwait(false); } - - public async Task SetGameAsync(Game game) + public async Task SetActivityAsync(IActivity activity) { - Game = game; + Activity = activity; await SendStatusAsync().ConfigureAwait(false); } + private async Task SendStatusAsync() { if (CurrentUser == null) return; - var game = Game; + var activity = Activity; var status = Status; var statusSince = _statusSince; - CurrentUser.Presence = new SocketPresence(status, game); + CurrentUser.Presence = new SocketPresence(status, activity); - GameModel gameModel; - if (game != null) + var gameModel = new GameModel(); + // Discord only accepts rich presence over RPC, don't even bother building a payload + if (activity is RichGame game) throw new NotSupportedException("Outgoing Rich Presences are not supported"); + if (activity is StreamingGame stream) { - var assets = game.Value.Assets.HasValue - ? new API.GameAssets() - { - SmallText = game.Value.Assets.Value.SmallText, - SmallImage = game.Value.Assets.Value.SmallImage, - LargeText = game.Value.Assets.Value.LargeText, - LargeImage = game.Value.Assets.Value.LargeImage, - } - : Optional.Create(); - var party = game.Value.Party.HasValue - ? new API.GameParty - { - Id = game.Value.Party.Value.Id, - Size = game.Value.Party.Value.Size - } - : Optional.Create(); - var secrets = game.Value.Secrets.HasValue - ? new API.GameSecrets() - { - Join = game.Value.Secrets.Value.Join, - Match = game.Value.Secrets.Value.Match, - Spectate = game.Value.Secrets.Value.Spectate - } - : Optional.Create(); - var timestamps = game.Value.Timestamps.HasValue - ? new API.GameTimestamps - { - Start = game.Value.Timestamps.Value.Start, - End = game.Value.Timestamps.Value.End - } - : Optional.Create(); - gameModel = new API.Game - { - Name = game.Value.Name, - StreamType = game.Value.StreamType, - StreamUrl = game.Value.StreamUrl, - Details = game.Value.Details, - State = game.Value.State, - ApplicationId = game.Value.ApplicationId ?? Optional.Create(), - Assets = assets, - Party = party, - Secrets = secrets, - Timestamps = timestamps, - }; + gameModel.StreamUrl = stream.Url; + gameModel.StreamType = stream.StreamType; } - else - gameModel = null; + else if (activity != null) + gameModel.Name = activity.Name; await ApiClient.SendStatusUpdateAsync( status, diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs index 00d4b4bbc..7d7ba16ce 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs @@ -8,20 +8,20 @@ namespace Discord.WebSocket public struct SocketPresence : IPresence { public UserStatus Status { get; } - public Game? Game { get; } + public IActivity Activity { get; } - internal SocketPresence(UserStatus status, Game? game) + internal SocketPresence(UserStatus status, IActivity activity) { Status = status; - Game = game; + Activity= activity; } internal static SocketPresence Create(Model model) { - return new SocketPresence(model.Status, model.Game != null ? model.Game.ToEntity() : (Game?)null); + return new SocketPresence(model.Status, model.Game?.ToEntity()); } public override string ToString() => Status.ToString(); - private string DebuggerDisplay => $"{Status}{(Game != null ? $", {Game.Value.Name} ({Game.Value.StreamType})" : "")}"; + private string DebuggerDisplay => $"{Status}{(Activity != null ? $", {Activity.Name}": "")}"; internal SocketPresence Clone() => this; } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs index a0c78b93f..58d5c62a1 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs @@ -18,7 +18,7 @@ namespace Discord.WebSocket public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); public string Discriminator => DiscriminatorValue.ToString("D4"); public string Mention => MentionUtils.MentionUser(Id); - public Game? Game => Presence.Game; + public IActivity Activity => Presence.Activity; public UserStatus Status => Presence.Status; internal SocketUser(DiscordSocketClient discord, ulong id) diff --git a/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs b/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs index be62fec5a..35efef269 100644 --- a/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs +++ b/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs @@ -2,32 +2,64 @@ { internal static class EntityExtensions { - public static Game ToEntity(this API.Game model) + public static IActivity ToEntity(this API.Game model) { - return new Game(model.Name, - model.StreamUrl.GetValueOrDefault(null), - model.StreamType.GetValueOrDefault(null) ?? StreamType.NotStreaming, - model.Details.GetValueOrDefault(), - model.State.GetValueOrDefault(), - model.ApplicationId.ToNullable(), - model.Assets.GetValueOrDefault(null)?.ToEntity(), - model.Party.GetValueOrDefault(null)?.ToEntity(), - model.Secrets.GetValueOrDefault(null)?.ToEntity(), - model.Timestamps.GetValueOrDefault(null)?.ToEntity() - ); + // Rich Game + if (model.Details.IsSpecified) + { + var appId = model.ApplicationId.ToNullable(); + return new RichGame + { + ApplicationId = appId, + Name = model.Name, + Details = model.Details.GetValueOrDefault(), + State = model.State.GetValueOrDefault(), + + Assets = model.Assets.GetValueOrDefault()?.ToEntity(appId ?? 0), + Party = model.Party.GetValueOrDefault()?.ToEntity(), + Secrets = model.Secrets.GetValueOrDefault()?.ToEntity(), + Timestamps = model.Timestamps.GetValueOrDefault()?.ToEntity() + + }; + } + // Stream Game + if (model.StreamUrl.IsSpecified) + { + return new StreamingGame( + model.Name, + model.StreamUrl.Value, + model.StreamType.Value.GetValueOrDefault()); + } + // Normal Game + return new Game(model.Name); } - public static GameAssets ToEntity(this API.GameAssets model) + public static GameAssets ToEntity(this API.GameAssets model, ulong appId) { - return new GameAssets(model.SmallText.GetValueOrDefault(), - model.SmallImage.GetValueOrDefault(), - model.LargeText.GetValueOrDefault(), - model.LargeImage.GetValueOrDefault()); + return new GameAssets + { + Large = new GameAsset + { + ApplicationId = appId, + ImageId = model.LargeImage.GetValueOrDefault(), + Text = model.LargeText.GetValueOrDefault() + }, + Small = new GameAsset + { + ApplicationId = appId, + ImageId = model.LargeImage.GetValueOrDefault(), + Text = model.LargeText.GetValueOrDefault() + }, + }; } public static GameParty ToEntity(this API.GameParty model) { - return new GameParty(model.Size, model.Id); + return new GameParty + { + Id = model.Id, + Size = model.Size + }; } public static GameSecrets ToEntity(this API.GameSecrets model)