@@ -1,6 +1,6 @@ | |||
namespace Discord | |||
{ | |||
public struct GameSecrets | |||
public class GameSecrets | |||
{ | |||
public string Match { get; } | |||
public string Join { get; } | |||
@@ -2,7 +2,7 @@ | |||
namespace Discord | |||
{ | |||
public struct GameTimestamps | |||
public class GameTimestamps | |||
{ | |||
public DateTimeOffset Start { get; } | |||
public DateTimeOffset End { get; } | |||
@@ -2,8 +2,8 @@ | |||
{ | |||
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> | |||
UserStatus Status { get; } | |||
} |
@@ -16,7 +16,7 @@ namespace Discord.Rest | |||
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | |||
public string Discriminator => DiscriminatorValue.ToString("D4"); | |||
public string Mention => MentionUtils.MentionUser(Id); | |||
public virtual Game? Game => null; | |||
public virtual IActivity Activity => null; | |||
public virtual UserStatus Status => UserStatus.Offline; | |||
public virtual bool IsWebhook => false; | |||
@@ -18,7 +18,7 @@ namespace Discord.Rpc | |||
public string Discriminator => DiscriminatorValue.ToString("D4"); | |||
public string Mention => MentionUtils.MentionUser(Id); | |||
public virtual bool IsWebhook => false; | |||
public virtual Game? Game => null; | |||
public virtual IActivity Activity => null; | |||
public virtual UserStatus Status => UserStatus.Offline; | |||
internal RpcUser(DiscordRpcClient discord, ulong id) | |||
@@ -22,7 +22,7 @@ namespace Discord.WebSocket | |||
/// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | |||
public override int Latency { get => GetLatency(); protected set { } } | |||
public override UserStatus Status { get => _shards[0].Status; protected set { } } | |||
public override Game? Game { get => _shards[0].Game; protected set { } } | |||
public IActivity Activity { get => _shards[0].Activity; protected set { } } | |||
internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; | |||
public override IReadOnlyCollection<SocketGuild> Guilds => GetGuilds().ToReadOnlyCollection(() => GetGuildCount()); | |||
@@ -48,7 +48,7 @@ namespace Discord.WebSocket | |||
/// <inheritdoc /> | |||
public override int Latency { get; protected set; } | |||
public override UserStatus Status { get; protected set; } = UserStatus.Online; | |||
public override Game? Game { get; protected set; } | |||
internal IActivity Activity { get; protected set; } | |||
//From DiscordSocketConfig | |||
internal int TotalShards { get; private set; } | |||
@@ -328,77 +328,39 @@ namespace Discord.WebSocket | |||
} | |||
public override async Task SetGameAsync(string name, string streamUrl = null, StreamType streamType = StreamType.NotStreaming) | |||
{ | |||
if (name != null) | |||
Game = new Game(name, streamUrl, streamType); | |||
if (name != null && streamUrl != null) | |||
Activity = new StreamingGame(name, streamUrl, streamType); | |||
else if (name != null) | |||
Activity = new Game(name); | |||
else | |||
Game = null; | |||
Activity = null; | |||
await SendStatusAsync().ConfigureAwait(false); | |||
} | |||
public async Task SetGameAsync(Game game) | |||
public async Task SetActivityAsync(IActivity activity) | |||
{ | |||
Game = game; | |||
Activity = activity; | |||
await SendStatusAsync().ConfigureAwait(false); | |||
} | |||
private async Task SendStatusAsync() | |||
{ | |||
if (CurrentUser == null) | |||
return; | |||
var game = Game; | |||
var activity = Activity; | |||
var status = Status; | |||
var statusSince = _statusSince; | |||
CurrentUser.Presence = new SocketPresence(status, game); | |||
CurrentUser.Presence = new SocketPresence(status, activity); | |||
GameModel gameModel; | |||
if (game != null) | |||
var gameModel = new GameModel(); | |||
// Discord only accepts rich presence over RPC, don't even bother building a payload | |||
if (activity is RichGame game) throw new NotSupportedException("Outgoing Rich Presences are not supported"); | |||
if (activity is StreamingGame stream) | |||
{ | |||
var assets = game.Value.Assets.HasValue | |||
? new API.GameAssets() | |||
{ | |||
SmallText = game.Value.Assets.Value.SmallText, | |||
SmallImage = game.Value.Assets.Value.SmallImage, | |||
LargeText = game.Value.Assets.Value.LargeText, | |||
LargeImage = game.Value.Assets.Value.LargeImage, | |||
} | |||
: Optional.Create<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( | |||
status, | |||
@@ -8,20 +8,20 @@ namespace Discord.WebSocket | |||
public struct SocketPresence : IPresence | |||
{ | |||
public UserStatus Status { get; } | |||
public Game? Game { get; } | |||
public IActivity Activity { get; } | |||
internal SocketPresence(UserStatus status, Game? game) | |||
internal SocketPresence(UserStatus status, IActivity activity) | |||
{ | |||
Status = status; | |||
Game = game; | |||
Activity= activity; | |||
} | |||
internal static SocketPresence Create(Model model) | |||
{ | |||
return new SocketPresence(model.Status, model.Game != null ? model.Game.ToEntity() : (Game?)null); | |||
return new SocketPresence(model.Status, model.Game?.ToEntity()); | |||
} | |||
public override string ToString() => Status.ToString(); | |||
private string DebuggerDisplay => $"{Status}{(Game != null ? $", {Game.Value.Name} ({Game.Value.StreamType})" : "")}"; | |||
private string DebuggerDisplay => $"{Status}{(Activity != null ? $", {Activity.Name}": "")}"; | |||
internal SocketPresence Clone() => this; | |||
} | |||
@@ -18,7 +18,7 @@ namespace Discord.WebSocket | |||
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | |||
public string Discriminator => DiscriminatorValue.ToString("D4"); | |||
public string Mention => MentionUtils.MentionUser(Id); | |||
public Game? Game => Presence.Game; | |||
public IActivity Activity => Presence.Activity; | |||
public UserStatus Status => Presence.Status; | |||
internal SocketUser(DiscordSocketClient discord, ulong id) | |||
@@ -2,32 +2,64 @@ | |||
{ | |||
internal static class EntityExtensions | |||
{ | |||
public static Game ToEntity(this API.Game model) | |||
public static IActivity ToEntity(this API.Game model) | |||
{ | |||
return new Game(model.Name, | |||
model.StreamUrl.GetValueOrDefault(null), | |||
model.StreamType.GetValueOrDefault(null) ?? StreamType.NotStreaming, | |||
model.Details.GetValueOrDefault(), | |||
model.State.GetValueOrDefault(), | |||
model.ApplicationId.ToNullable(), | |||
model.Assets.GetValueOrDefault(null)?.ToEntity(), | |||
model.Party.GetValueOrDefault(null)?.ToEntity(), | |||
model.Secrets.GetValueOrDefault(null)?.ToEntity(), | |||
model.Timestamps.GetValueOrDefault(null)?.ToEntity() | |||
); | |||
// Rich Game | |||
if (model.Details.IsSpecified) | |||
{ | |||
var appId = model.ApplicationId.ToNullable(); | |||
return new RichGame | |||
{ | |||
ApplicationId = appId, | |||
Name = model.Name, | |||
Details = model.Details.GetValueOrDefault(), | |||
State = model.State.GetValueOrDefault(), | |||
Assets = model.Assets.GetValueOrDefault()?.ToEntity(appId ?? 0), | |||
Party = model.Party.GetValueOrDefault()?.ToEntity(), | |||
Secrets = model.Secrets.GetValueOrDefault()?.ToEntity(), | |||
Timestamps = model.Timestamps.GetValueOrDefault()?.ToEntity() | |||
}; | |||
} | |||
// Stream Game | |||
if (model.StreamUrl.IsSpecified) | |||
{ | |||
return new StreamingGame( | |||
model.Name, | |||
model.StreamUrl.Value, | |||
model.StreamType.Value.GetValueOrDefault()); | |||
} | |||
// Normal Game | |||
return new Game(model.Name); | |||
} | |||
public static GameAssets ToEntity(this API.GameAssets model) | |||
public static GameAssets ToEntity(this API.GameAssets model, ulong appId) | |||
{ | |||
return new GameAssets(model.SmallText.GetValueOrDefault(), | |||
model.SmallImage.GetValueOrDefault(), | |||
model.LargeText.GetValueOrDefault(), | |||
model.LargeImage.GetValueOrDefault()); | |||
return new GameAssets | |||
{ | |||
Large = new GameAsset | |||
{ | |||
ApplicationId = appId, | |||
ImageId = model.LargeImage.GetValueOrDefault(), | |||
Text = model.LargeText.GetValueOrDefault() | |||
}, | |||
Small = new GameAsset | |||
{ | |||
ApplicationId = appId, | |||
ImageId = model.LargeImage.GetValueOrDefault(), | |||
Text = model.LargeText.GetValueOrDefault() | |||
}, | |||
}; | |||
} | |||
public static GameParty ToEntity(this API.GameParty model) | |||
{ | |||
return new GameParty(model.Size, model.Id); | |||
return new GameParty | |||
{ | |||
Id = model.Id, | |||
Size = model.Size | |||
}; | |||
} | |||
public static GameSecrets ToEntity(this API.GameSecrets model) | |||