| @@ -1,6 +1,6 @@ | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| public struct GameSecrets | |||||
| public class GameSecrets | |||||
| { | { | ||||
| public string Match { get; } | public string Match { get; } | ||||
| public string Join { get; } | public string Join { get; } | ||||
| @@ -2,7 +2,7 @@ | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| public struct GameTimestamps | |||||
| public class GameTimestamps | |||||
| { | { | ||||
| public DateTimeOffset Start { get; } | public DateTimeOffset Start { get; } | ||||
| public DateTimeOffset End { get; } | public DateTimeOffset End { get; } | ||||
| @@ -2,8 +2,8 @@ | |||||
| { | { | ||||
| public interface IPresence | public interface IPresence | ||||
| { | { | ||||
| /// <summary> Gets the game this user is currently playing, if any. </summary> | |||||
| Game? Game { get; } | |||||
| /// <summary> Gets the activity this user is currently doing. </summary> | |||||
| IActivity Activity { get; } | |||||
| /// <summary> Gets the current status of this user. </summary> | /// <summary> Gets the current status of this user. </summary> | ||||
| UserStatus Status { get; } | UserStatus Status { get; } | ||||
| } | } | ||||
| @@ -16,7 +16,7 @@ namespace Discord.Rest | |||||
| public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | ||||
| public string Discriminator => DiscriminatorValue.ToString("D4"); | public string Discriminator => DiscriminatorValue.ToString("D4"); | ||||
| public string Mention => MentionUtils.MentionUser(Id); | public string Mention => MentionUtils.MentionUser(Id); | ||||
| public virtual Game? Game => null; | |||||
| public virtual IActivity Activity => null; | |||||
| public virtual UserStatus Status => UserStatus.Offline; | public virtual UserStatus Status => UserStatus.Offline; | ||||
| public virtual bool IsWebhook => false; | public virtual bool IsWebhook => false; | ||||
| @@ -18,7 +18,7 @@ namespace Discord.Rpc | |||||
| public string Discriminator => DiscriminatorValue.ToString("D4"); | public string Discriminator => DiscriminatorValue.ToString("D4"); | ||||
| public string Mention => MentionUtils.MentionUser(Id); | public string Mention => MentionUtils.MentionUser(Id); | ||||
| public virtual bool IsWebhook => false; | public virtual bool IsWebhook => false; | ||||
| public virtual Game? Game => null; | |||||
| public virtual IActivity Activity => null; | |||||
| public virtual UserStatus Status => UserStatus.Offline; | public virtual UserStatus Status => UserStatus.Offline; | ||||
| internal RpcUser(DiscordRpcClient discord, ulong id) | internal RpcUser(DiscordRpcClient discord, ulong id) | ||||
| @@ -22,7 +22,7 @@ namespace Discord.WebSocket | |||||
| /// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | /// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | ||||
| public override int Latency { get => GetLatency(); protected set { } } | public override int Latency { get => GetLatency(); protected set { } } | ||||
| public override UserStatus Status { get => _shards[0].Status; 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; | internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; | ||||
| public override IReadOnlyCollection<SocketGuild> Guilds => GetGuilds().ToReadOnlyCollection(() => GetGuildCount()); | public override IReadOnlyCollection<SocketGuild> Guilds => GetGuilds().ToReadOnlyCollection(() => GetGuildCount()); | ||||
| @@ -48,7 +48,7 @@ namespace Discord.WebSocket | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public override int Latency { get; protected set; } | public override int Latency { get; protected set; } | ||||
| public override UserStatus Status { get; protected set; } = UserStatus.Online; | public override UserStatus Status { get; protected set; } = UserStatus.Online; | ||||
| public override Game? Game { get; protected set; } | |||||
| internal IActivity Activity { get; protected set; } | |||||
| //From DiscordSocketConfig | //From DiscordSocketConfig | ||||
| internal int TotalShards { get; private set; } | 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) | 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 | else | ||||
| Game = null; | |||||
| Activity = null; | |||||
| await SendStatusAsync().ConfigureAwait(false); | await SendStatusAsync().ConfigureAwait(false); | ||||
| } | } | ||||
| public async Task SetGameAsync(Game game) | |||||
| public async Task SetActivityAsync(IActivity activity) | |||||
| { | { | ||||
| Game = game; | |||||
| Activity = activity; | |||||
| await SendStatusAsync().ConfigureAwait(false); | await SendStatusAsync().ConfigureAwait(false); | ||||
| } | } | ||||
| private async Task SendStatusAsync() | private async Task SendStatusAsync() | ||||
| { | { | ||||
| if (CurrentUser == null) | if (CurrentUser == null) | ||||
| return; | return; | ||||
| var game = Game; | |||||
| var activity = Activity; | |||||
| var status = Status; | var status = Status; | ||||
| var statusSince = _statusSince; | 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<API.GameAssets>(); | |||||
| var party = game.Value.Party.HasValue | |||||
| ? new API.GameParty | |||||
| { | |||||
| Id = game.Value.Party.Value.Id, | |||||
| Size = game.Value.Party.Value.Size | |||||
| } | |||||
| : Optional.Create<API.GameParty>(); | |||||
| 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<API.GameSecrets>(); | |||||
| var timestamps = game.Value.Timestamps.HasValue | |||||
| ? new API.GameTimestamps | |||||
| { | |||||
| Start = game.Value.Timestamps.Value.Start, | |||||
| End = game.Value.Timestamps.Value.End | |||||
| } | |||||
| : Optional.Create<API.GameTimestamps>(); | |||||
| 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<ulong>(), | |||||
| 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( | await ApiClient.SendStatusUpdateAsync( | ||||
| status, | status, | ||||
| @@ -8,20 +8,20 @@ namespace Discord.WebSocket | |||||
| public struct SocketPresence : IPresence | public struct SocketPresence : IPresence | ||||
| { | { | ||||
| public UserStatus Status { get; } | 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; | Status = status; | ||||
| Game = game; | |||||
| Activity= activity; | |||||
| } | } | ||||
| internal static SocketPresence Create(Model model) | 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(); | 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; | internal SocketPresence Clone() => this; | ||||
| } | } | ||||
| @@ -18,7 +18,7 @@ namespace Discord.WebSocket | |||||
| public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | ||||
| public string Discriminator => DiscriminatorValue.ToString("D4"); | public string Discriminator => DiscriminatorValue.ToString("D4"); | ||||
| public string Mention => MentionUtils.MentionUser(Id); | public string Mention => MentionUtils.MentionUser(Id); | ||||
| public Game? Game => Presence.Game; | |||||
| public IActivity Activity => Presence.Activity; | |||||
| public UserStatus Status => Presence.Status; | public UserStatus Status => Presence.Status; | ||||
| internal SocketUser(DiscordSocketClient discord, ulong id) | internal SocketUser(DiscordSocketClient discord, ulong id) | ||||
| @@ -2,32 +2,64 @@ | |||||
| { | { | ||||
| internal static class EntityExtensions | 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) | 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) | public static GameSecrets ToEntity(this API.GameSecrets model) | ||||