Refactor Games, support reading Rich Presences (#877)
* 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 5f3cb947a92f4fd01cc4df47ca548180036b47f3 * 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
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
19
src/Discord.Net.Core/Entities/Activities/Game.cs
Normal file
19
src/Discord.Net.Core/Entities/Activities/Game.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/Discord.Net.Core/Entities/Activities/GameAsset.cs
Normal file
15
src/Discord.Net.Core/Entities/Activities/GameAsset.cs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/Discord.Net.Core/Entities/Activities/GameParty.cs
Normal file
11
src/Discord.Net.Core/Entities/Activities/GameParty.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/Discord.Net.Core/Entities/Activities/GameSecrets.cs
Normal file
16
src/Discord.Net.Core/Entities/Activities/GameSecrets.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/Discord.Net.Core/Entities/Activities/GameTimestamps.cs
Normal file
16
src/Discord.Net.Core/Entities/Activities/GameTimestamps.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/Discord.Net.Core/Entities/Activities/IActivity.cs
Normal file
13
src/Discord.Net.Core/Entities/Activities/IActivity.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/Discord.Net.Core/Entities/Activities/RichGame.cs
Normal file
22
src/Discord.Net.Core/Entities/Activities/RichGame.cs
Normal file
@@ -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)";
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/Discord.Net.Core/Entities/Activities/StreamingGame.cs
Normal file
21
src/Discord.Net.Core/Entities/Activities/StreamingGame.cs
Normal file
@@ -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>
|
/// <summary> Gets the activity this user is currently doing. </summary>
|
||||||
Game? Game { get; }
|
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)
|
||||||
|
|||||||
16
src/Discord.Net.Rest/API/Common/GameAssets.cs
Normal file
16
src/Discord.Net.Rest/API/Common/GameAssets.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/Discord.Net.Rest/API/Common/GameParty.cs
Normal file
12
src/Discord.Net.Rest/API/Common/GameParty.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/Discord.Net.Rest/API/Common/GameSecrets.cs
Normal file
14
src/Discord.Net.Rest/API/Common/GameSecrets.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/Discord.Net.Rest/API/Common/GameTimestamps.cs
Normal file
15
src/Discord.Net.Rest/API/Common/GameTimestamps.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/Discord.Net.Rest/API/UnixTimestampAttribute.cs
Normal file
7
src/Discord.Net.Rest/API/UnixTimestampAttribute.cs
Normal file
@@ -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)
|
if (streamUrl != null)
|
||||||
Game = new Game(name, streamUrl, streamType);
|
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;
|
var gameModel = new GameModel();
|
||||||
if (game != null)
|
// 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
|
gameModel.StreamUrl = stream.Url;
|
||||||
{
|
gameModel.StreamType = stream.StreamType;
|
||||||
Name = game.Value.Name,
|
|
||||||
StreamType = game.Value.StreamType,
|
|
||||||
StreamUrl = game.Value.StreamUrl
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
else
|
else if (activity != null)
|
||||||
gameModel = 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,
|
// Rich Game
|
||||||
model.StreamUrl.GetValueOrDefault(null),
|
if (model.ApplicationId.IsSpecified)
|
||||||
model.StreamType.GetValueOrDefault(null) ?? StreamType.NotStreaming);
|
{
|
||||||
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user