* 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
pull/915/head
@@ -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()); | ||||
@@ -240,8 +240,17 @@ 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) | ||||
{ | { | ||||
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()); | |||||
} | } | ||||
} | } | ||||
} | } |