* Add API-level support for Rich Presences
* Add library-level support for Game presences
* Add model conversions for outgoing+incoming rich presences
* Refactored Game into Activities
* Integrated Activities with user entities
rebase hell from 5f3cb947a9
* Fix JSON converters for Activities
* Finish rebase, activity should be set on BaseSocketClient
* Use ApplicationId to define a rich presence
* Added SetActivityAsync to Base and Sharded Socket clients
* Remove public parameterless Game constructor
* Remove GameAssets, refactored to GameAsset
* Hide constructors for types that should be read-only
* Revert changes to Discord.Net.sln
got damned visual studio caching
* Refactor GameParty to use dedicated current/capacity values
Per feedback from @khionu
tags/2.0.0-beta
| @@ -22,6 +22,12 @@ namespace Discord | |||||
| public static string GetEmojiUrl(ulong emojiId) | public static string GetEmojiUrl(ulong emojiId) | ||||
| => $"{DiscordConfig.CDNUrl}emojis/{emojiId}.png"; | => $"{DiscordConfig.CDNUrl}emojis/{emojiId}.png"; | ||||
| public static string GetRichAssetUrl(ulong appId, string assetId, ushort size, ImageFormat format) | |||||
| { | |||||
| string extension = FormatToExtension(format, ""); | |||||
| return $"{DiscordConfig.CDNUrl}app-assets/{appId}/{assetId}.{extension}?size={size}"; | |||||
| } | |||||
| private static string FormatToExtension(ImageFormat format, string imageId) | private static string FormatToExtension(ImageFormat format, string imageId) | ||||
| { | { | ||||
| if (format == ImageFormat.Auto) | if (format == ImageFormat.Auto) | ||||
| @@ -0,0 +1,19 @@ | |||||
| using System.Diagnostics; | |||||
| namespace Discord | |||||
| { | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||||
| public class Game : IActivity | |||||
| { | |||||
| public string Name { get; internal set; } | |||||
| internal Game() { } | |||||
| public Game(string name) | |||||
| { | |||||
| Name = name; | |||||
| } | |||||
| public override string ToString() => Name; | |||||
| private string DebuggerDisplay => Name; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,15 @@ | |||||
| namespace Discord | |||||
| { | |||||
| public class GameAsset | |||||
| { | |||||
| internal GameAsset() { } | |||||
| internal ulong ApplicationId { get; set; } | |||||
| public string Text { get; internal set; } | |||||
| public string ImageId { get; internal set; } | |||||
| public string GetImageUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) | |||||
| => CDN.GetRichAssetUrl(ApplicationId, ImageId, size, format); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,11 @@ | |||||
| namespace Discord | |||||
| { | |||||
| public class GameParty | |||||
| { | |||||
| internal GameParty() { } | |||||
| public string Id { get; internal set; } | |||||
| public int Members { get; internal set; } | |||||
| public int Capacity { get; internal set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,16 @@ | |||||
| namespace Discord | |||||
| { | |||||
| public class GameSecrets | |||||
| { | |||||
| public string Match { get; } | |||||
| public string Join { get; } | |||||
| public string Spectate { get; } | |||||
| internal GameSecrets(string match, string join, string spectate) | |||||
| { | |||||
| Match = match; | |||||
| Join = join; | |||||
| Spectate = spectate; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,16 @@ | |||||
| using System; | |||||
| namespace Discord | |||||
| { | |||||
| public class GameTimestamps | |||||
| { | |||||
| public DateTimeOffset? Start { get; } | |||||
| public DateTimeOffset? End { get; } | |||||
| internal GameTimestamps(DateTimeOffset? start, DateTimeOffset? end) | |||||
| { | |||||
| Start = start; | |||||
| End = end; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,13 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| public interface IActivity | |||||
| { | |||||
| string Name { get; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,22 @@ | |||||
| using System.Diagnostics; | |||||
| namespace Discord | |||||
| { | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||||
| public class RichGame : Game | |||||
| { | |||||
| internal RichGame() { } | |||||
| public string Details { get; internal set;} | |||||
| public string State { get; internal set;} | |||||
| public ulong ApplicationId { get; internal set; } | |||||
| public GameAsset SmallAsset { get; internal set; } | |||||
| public GameAsset LargeAsset { get; internal set; } | |||||
| public GameParty Party { get; internal set; } | |||||
| public GameSecrets Secrets { get; internal set; } | |||||
| public GameTimestamps Timestamps { get; internal set; } | |||||
| public override string ToString() => Name; | |||||
| private string DebuggerDisplay => $"{Name} (Rich)"; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,21 @@ | |||||
| using System.Diagnostics; | |||||
| namespace Discord | |||||
| { | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||||
| public class StreamingGame : Game | |||||
| { | |||||
| public string Url { get; internal set; } | |||||
| public StreamType StreamType { get; internal set; } | |||||
| public StreamingGame(string name, string url, StreamType streamType) | |||||
| { | |||||
| Name = name; | |||||
| Url = url; | |||||
| StreamType = streamType; | |||||
| } | |||||
| public override string ToString() => Name; | |||||
| private string DebuggerDisplay => $"{Name} ({Url})"; | |||||
| } | |||||
| } | |||||
| @@ -1,24 +0,0 @@ | |||||
| using System.Diagnostics; | |||||
| namespace Discord | |||||
| { | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||||
| public struct Game | |||||
| { | |||||
| public string Name { get; } | |||||
| public string StreamUrl { get; } | |||||
| public StreamType StreamType { get; } | |||||
| public Game(string name, string streamUrl, StreamType type) | |||||
| { | |||||
| Name = name; | |||||
| StreamUrl = streamUrl; | |||||
| StreamType = type; | |||||
| } | |||||
| private Game(string name) | |||||
| : this(name, null, StreamType.NotStreaming) { } | |||||
| public override string ToString() => Name; | |||||
| private string DebuggerDisplay => StreamUrl != null ? $"{Name} ({StreamUrl})" : Name; | |||||
| } | |||||
| } | |||||
| @@ -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; } | ||||
| } | } | ||||
| @@ -13,6 +13,22 @@ namespace Discord.API | |||||
| public Optional<string> StreamUrl { get; set; } | public Optional<string> StreamUrl { get; set; } | ||||
| [JsonProperty("type")] | [JsonProperty("type")] | ||||
| public Optional<StreamType?> StreamType { get; set; } | public Optional<StreamType?> StreamType { get; set; } | ||||
| [JsonProperty("details")] | |||||
| public Optional<string> Details { get; set; } | |||||
| [JsonProperty("state")] | |||||
| public Optional<string> State { get; set; } | |||||
| [JsonProperty("application_id")] | |||||
| public Optional<ulong> ApplicationId { get; set; } | |||||
| [JsonProperty("assets")] | |||||
| public Optional<API.GameAssets> Assets { get; set; } | |||||
| [JsonProperty("party")] | |||||
| public Optional<API.GameParty> Party { get; set; } | |||||
| [JsonProperty("secrets")] | |||||
| public Optional<API.GameSecrets> Secrets { get; set; } | |||||
| [JsonProperty("timestamps")] | |||||
| public Optional<API.GameTimestamps> Timestamps { get; set; } | |||||
| [JsonProperty("instance")] | |||||
| public Optional<bool> Instance { get; set; } | |||||
| [OnError] | [OnError] | ||||
| internal void OnError(StreamingContext context, ErrorContext errorContext) | internal void OnError(StreamingContext context, ErrorContext errorContext) | ||||
| @@ -0,0 +1,16 @@ | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API | |||||
| { | |||||
| internal class GameAssets | |||||
| { | |||||
| [JsonProperty("small_text")] | |||||
| public Optional<string> SmallText { get; set; } | |||||
| [JsonProperty("small_image")] | |||||
| public Optional<string> SmallImage { get; set; } | |||||
| [JsonProperty("large_image")] | |||||
| public Optional<string> LargeText { get; set; } | |||||
| [JsonProperty("large_text")] | |||||
| public Optional<string> LargeImage { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,12 @@ | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API | |||||
| { | |||||
| internal class GameParty | |||||
| { | |||||
| [JsonProperty("id")] | |||||
| public string Id { get; set; } | |||||
| [JsonProperty("size")] | |||||
| public int[] Size { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,14 @@ | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API | |||||
| { | |||||
| internal class GameSecrets | |||||
| { | |||||
| [JsonProperty("match")] | |||||
| public string Match { get; set; } | |||||
| [JsonProperty("join")] | |||||
| public string Join { get; set; } | |||||
| [JsonProperty("spectate")] | |||||
| public string Spectate { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,15 @@ | |||||
| using System; | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API | |||||
| { | |||||
| internal class GameTimestamps | |||||
| { | |||||
| [JsonProperty("start")] | |||||
| [UnixTimestamp] | |||||
| public Optional<DateTimeOffset> Start { get; set; } | |||||
| [JsonProperty("end")] | |||||
| [UnixTimestamp] | |||||
| public Optional<DateTimeOffset> End { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,7 @@ | |||||
| using System; | |||||
| namespace Discord.API | |||||
| { | |||||
| [AttributeUsage(AttributeTargets.Property)] | |||||
| internal class UnixTimestampAttribute : Attribute { } | |||||
| } | |||||
| @@ -10,7 +10,8 @@ | |||||
| <ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | <ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | ||||
| </ItemGroup> | </ItemGroup> | ||||
| <ItemGroup Condition=" '$(TargetFramework)' != 'net45' "> | <ItemGroup Condition=" '$(TargetFramework)' != 'net45' "> | ||||
| <PackageReference Include="System.Net.Http" Version="4.3.2" /> <!-- https://github.com/dotnet/corefx/issues/19535 --> | |||||
| <PackageReference Include="System.Net.Http" Version="4.3.2" /> | |||||
| <!-- https://github.com/dotnet/corefx/issues/19535 --> | |||||
| </ItemGroup> | </ItemGroup> | ||||
| <ItemGroup Condition=" '$(TargetFramework)' == 'net45' "> | <ItemGroup Condition=" '$(TargetFramework)' == 'net45' "> | ||||
| <Reference Include="System.Net.Http" /> | <Reference Include="System.Net.Http" /> | ||||
| @@ -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; | ||||
| @@ -66,6 +66,12 @@ namespace Discord.Net.Converters | |||||
| if (type == typeof(ulong)) | if (type == typeof(ulong)) | ||||
| return UInt64Converter.Instance; | return UInt64Converter.Instance; | ||||
| } | } | ||||
| bool hasUnixStamp = propInfo.GetCustomAttribute<UnixTimestampAttribute>() != null; | |||||
| if (hasUnixStamp) | |||||
| { | |||||
| if (type == typeof(DateTimeOffset)) | |||||
| return UnixTimestampConverter.Instance; | |||||
| } | |||||
| //Enums | //Enums | ||||
| if (type == typeof(PermissionTarget)) | if (type == typeof(PermissionTarget)) | ||||
| @@ -0,0 +1,28 @@ | |||||
| using System; | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.Net.Converters | |||||
| { | |||||
| public class UnixTimestampConverter : JsonConverter | |||||
| { | |||||
| public static readonly UnixTimestampConverter Instance = new UnixTimestampConverter(); | |||||
| public override bool CanConvert(Type objectType) => true; | |||||
| public override bool CanRead => true; | |||||
| public override bool CanWrite => true; | |||||
| public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | |||||
| { | |||||
| // Discord doesn't validate if timestamps contain decimals or not | |||||
| if (reader.Value is double d) | |||||
| return new DateTimeOffset(1970, 1, 1, 0, 0, 0, 0, TimeSpan.Zero).AddMilliseconds(d); | |||||
| long offset = (long)reader.Value; | |||||
| return new DateTimeOffset(1970, 1, 1, 0, 0, 0, 0, TimeSpan.Zero).AddMilliseconds(offset); | |||||
| } | |||||
| public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) | |||||
| { | |||||
| throw new NotImplementedException(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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) | ||||
| @@ -13,7 +13,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 abstract int Latency { get; protected set; } | public abstract int Latency { get; protected set; } | ||||
| public abstract UserStatus Status { get; protected set; } | public abstract UserStatus Status { get; protected set; } | ||||
| public abstract Game? Game { get; protected set; } | |||||
| public abstract IActivity Activity { get; protected set; } | |||||
| internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; | internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; | ||||
| @@ -45,6 +45,7 @@ namespace Discord.WebSocket | |||||
| public abstract Task StopAsync(); | public abstract Task StopAsync(); | ||||
| public abstract Task SetStatusAsync(UserStatus status); | public abstract Task SetStatusAsync(UserStatus status); | ||||
| public abstract Task SetGameAsync(string name, string streamUrl = null, StreamType streamType = StreamType.NotStreaming); | public abstract Task SetGameAsync(string name, string streamUrl = null, StreamType streamType = StreamType.NotStreaming); | ||||
| public abstract Task SetActivityAsync(IActivity activity); | |||||
| public abstract Task DownloadUsersAsync(IEnumerable<IGuild> guilds); | public abstract Task DownloadUsersAsync(IEnumerable<IGuild> guilds); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -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 override 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()); | ||||
| @@ -239,9 +239,18 @@ namespace Discord.WebSocket | |||||
| await _shards[i].SetStatusAsync(status).ConfigureAwait(false); | await _shards[i].SetStatusAsync(status).ConfigureAwait(false); | ||||
| } | } | ||||
| 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) | ||||
| { | |||||
| IActivity activity = null; | |||||
| if (streamUrl != null) | |||||
| activity = new StreamingGame(name, streamUrl, streamType); | |||||
| else if (name != null) | |||||
| activity = new Game(name); | |||||
| await SetActivityAsync(activity).ConfigureAwait(false); | |||||
| } | |||||
| public override async Task SetActivityAsync(IActivity activity) | |||||
| { | { | ||||
| for (int i = 0; i < _shards.Length; i++) | for (int i = 0; i < _shards.Length; i++) | ||||
| await _shards[i].SetGameAsync(name, streamUrl, streamType).ConfigureAwait(false); | |||||
| await _shards[i].SetActivityAsync(activity).ConfigureAwait(false); | |||||
| } | } | ||||
| private void RegisterEvents(DiscordSocketClient client, bool isPrimary) | private void RegisterEvents(DiscordSocketClient client, bool isPrimary) | ||||
| @@ -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; } | |||||
| public override IActivity Activity { get; protected set; } | |||||
| //From DiscordSocketConfig | //From DiscordSocketConfig | ||||
| internal int TotalShards { get; private set; } | internal int TotalShards { get; private set; } | ||||
| @@ -328,33 +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 (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 override async Task SetActivityAsync(IActivity activity) | |||||
| { | |||||
| Activity = activity; | |||||
| 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) | |||||
| { | { | ||||
| gameModel = new API.Game | |||||
| { | |||||
| Name = game.Value.Name, | |||||
| StreamType = game.Value.StreamType, | |||||
| StreamUrl = game.Value.StreamUrl | |||||
| }; | |||||
| 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,11 +2,83 @@ | |||||
| { | { | ||||
| 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); | |||||
| // Rich Game | |||||
| if (model.ApplicationId.IsSpecified) | |||||
| { | |||||
| ulong appId = model.ApplicationId.Value; | |||||
| var assets = model.Assets.GetValueOrDefault()?.ToEntity(appId); | |||||
| return new RichGame | |||||
| { | |||||
| ApplicationId = appId, | |||||
| Name = model.Name, | |||||
| Details = model.Details.GetValueOrDefault(), | |||||
| State = model.State.GetValueOrDefault(), | |||||
| SmallAsset = assets?[0], | |||||
| LargeAsset = assets?[1], | |||||
| 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); | |||||
| } | |||||
| // (Small, Large) | |||||
| public static GameAsset[] ToEntity(this API.GameAssets model, ulong appId) | |||||
| { | |||||
| return new GameAsset[] | |||||
| { | |||||
| model.SmallImage.IsSpecified ? new GameAsset | |||||
| { | |||||
| ApplicationId = appId, | |||||
| ImageId = model.SmallImage.GetValueOrDefault(), | |||||
| Text = model.SmallText.GetValueOrDefault() | |||||
| } : null, | |||||
| model.LargeImage.IsSpecified ? new GameAsset | |||||
| { | |||||
| ApplicationId = appId, | |||||
| ImageId = model.LargeImage.GetValueOrDefault(), | |||||
| Text = model.LargeText.GetValueOrDefault() | |||||
| } : null, | |||||
| }; | |||||
| } | |||||
| public static GameParty ToEntity(this API.GameParty model) | |||||
| { | |||||
| // Discord will probably send bad data since they don't validate anything | |||||
| int current = 0, cap = 0; | |||||
| if (model.Size.Length == 2) | |||||
| { | |||||
| current = model.Size[0]; | |||||
| cap = model.Size[1]; | |||||
| } | |||||
| return new GameParty | |||||
| { | |||||
| Id = model.Id, | |||||
| Members = current, | |||||
| Capacity = cap, | |||||
| }; | |||||
| } | |||||
| public static GameSecrets ToEntity(this API.GameSecrets model) | |||||
| { | |||||
| return new GameSecrets(model.Match, model.Join, model.Spectate); | |||||
| } | |||||
| public static GameTimestamps ToEntity(this API.GameTimestamps model) | |||||
| { | |||||
| return new GameTimestamps(model.Start.ToNullable(), model.End.ToNullable()); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||