@@ -15,6 +15,8 @@ namespace Discord.API.Gateway | |||||
public int LargeThreshold { get; set; } | public int LargeThreshold { get; set; } | ||||
[JsonProperty("shard")] | [JsonProperty("shard")] | ||||
public Optional<int[]> ShardingParams { get; set; } | public Optional<int[]> ShardingParams { get; set; } | ||||
[JsonProperty("presence")] | |||||
public Optional<StatusUpdateParams> Presence { get; set; } | |||||
[JsonProperty("guild_subscriptions")] | [JsonProperty("guild_subscriptions")] | ||||
public Optional<bool> GuildSubscriptions { get; set; } | public Optional<bool> GuildSubscriptions { get; set; } | ||||
[JsonProperty("intents")] | [JsonProperty("intents")] | ||||
@@ -1,4 +1,4 @@ | |||||
#pragma warning disable CS1591 | |||||
#pragma warning disable CS1591 | |||||
using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
namespace Discord.API.Gateway | namespace Discord.API.Gateway | ||||
@@ -12,7 +12,7 @@ namespace Discord.API.Gateway | |||||
public long? IdleSince { get; set; } | public long? IdleSince { get; set; } | ||||
[JsonProperty("afk")] | [JsonProperty("afk")] | ||||
public bool IsAFK { get; set; } | public bool IsAFK { get; set; } | ||||
[JsonProperty("game")] | |||||
public Game Game { get; set; } | |||||
[JsonProperty("activities")] | |||||
public Game[] Activities { get; set; } | |||||
} | } | ||||
} | } |
@@ -12,6 +12,7 @@ using System.IO.Compression; | |||||
using System.Text; | using System.Text; | ||||
using System.Threading; | using System.Threading; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using GameModel = Discord.API.Game; | |||||
namespace Discord.API | namespace Discord.API | ||||
{ | { | ||||
@@ -215,7 +216,7 @@ namespace Discord.API | |||||
await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); | await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); | ||||
} | } | ||||
public async Task SendIdentifyAsync(int largeThreshold = 100, int shardID = 0, int totalShards = 1, bool guildSubscriptions = true, GatewayIntents? gatewayIntents = null, RequestOptions options = null) | |||||
public async Task SendIdentifyAsync(int largeThreshold = 100, int shardID = 0, int totalShards = 1, bool guildSubscriptions = true, GatewayIntents? gatewayIntents = null, (UserStatus, bool, long?, GameModel[])? presence = null, RequestOptions options = null) | |||||
{ | { | ||||
options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
var props = new Dictionary<string, string> | var props = new Dictionary<string, string> | ||||
@@ -238,6 +239,17 @@ namespace Discord.API | |||||
else | else | ||||
msg.GuildSubscriptions = guildSubscriptions; | msg.GuildSubscriptions = guildSubscriptions; | ||||
if (presence.HasValue) | |||||
{ | |||||
msg.Presence = new StatusUpdateParams | |||||
{ | |||||
Status = presence.Value.Item1, | |||||
IsAFK = presence.Value.Item2, | |||||
IdleSince = presence.Value.Item3, | |||||
Activities = presence.Value.Item4 | |||||
}; | |||||
} | |||||
await SendGatewayAsync(GatewayOpCode.Identify, msg, options: options).ConfigureAwait(false); | await SendGatewayAsync(GatewayOpCode.Identify, msg, options: options).ConfigureAwait(false); | ||||
} | } | ||||
public async Task SendResumeAsync(string sessionId, int lastSeq, RequestOptions options = null) | public async Task SendResumeAsync(string sessionId, int lastSeq, RequestOptions options = null) | ||||
@@ -256,7 +268,7 @@ namespace Discord.API | |||||
options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
await SendGatewayAsync(GatewayOpCode.Heartbeat, lastSeq, options: options).ConfigureAwait(false); | await SendGatewayAsync(GatewayOpCode.Heartbeat, lastSeq, options: options).ConfigureAwait(false); | ||||
} | } | ||||
public async Task SendStatusUpdateAsync(UserStatus status, bool isAFK, long? since, Game game, RequestOptions options = null) | |||||
public async Task SendStatusUpdateAsync(UserStatus status, bool isAFK, long? since, GameModel[] game, RequestOptions options = null) | |||||
{ | { | ||||
options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
var args = new StatusUpdateParams | var args = new StatusUpdateParams | ||||
@@ -264,7 +276,7 @@ namespace Discord.API | |||||
Status = status, | Status = status, | ||||
IdleSince = since, | IdleSince = since, | ||||
IsAFK = isAFK, | IsAFK = isAFK, | ||||
Game = game | |||||
Activities = game | |||||
}; | }; | ||||
options.BucketId = GatewayBucket.Get(GatewayBucketType.PresenceUpdate).Id; | options.BucketId = GatewayBucket.Get(GatewayBucketType.PresenceUpdate).Id; | ||||
await SendGatewayAsync(GatewayOpCode.StatusUpdate, args, options: options).ConfigureAwait(false); | await SendGatewayAsync(GatewayOpCode.StatusUpdate, args, options: options).ConfigureAwait(false); | ||||
@@ -59,7 +59,8 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public override UserStatus Status { get; protected set; } = UserStatus.Online; | public override UserStatus Status { get; protected set; } = UserStatus.Online; | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public override IActivity Activity { get; protected set; } | |||||
public override IActivity Activity { get => _activity.GetValueOrDefault(); protected set => _activity = Optional.Create(value); } | |||||
private Optional<IActivity> _activity; | |||||
//From DiscordSocketConfig | //From DiscordSocketConfig | ||||
internal int TotalShards { get; private set; } | internal int TotalShards { get; private set; } | ||||
@@ -248,14 +249,11 @@ namespace Discord.WebSocket | |||||
else | else | ||||
{ | { | ||||
await _gatewayLogger.DebugAsync("Identifying").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Identifying").ConfigureAwait(false); | ||||
await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, guildSubscriptions: _guildSubscriptions, gatewayIntents: _gatewayIntents).ConfigureAwait(false); | |||||
await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, guildSubscriptions: _guildSubscriptions, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); | |||||
} | } | ||||
//Wait for READY | //Wait for READY | ||||
await _connection.WaitAsync().ConfigureAwait(false); | await _connection.WaitAsync().ConfigureAwait(false); | ||||
await _gatewayLogger.DebugAsync("Sending Status").ConfigureAwait(false); | |||||
await SendStatusAsync().ConfigureAwait(false); | |||||
} | } | ||||
finally | finally | ||||
{ | { | ||||
@@ -449,28 +447,44 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
if (CurrentUser == null) | if (CurrentUser == null) | ||||
return; | return; | ||||
CurrentUser.Presence = new SocketPresence(Status, Activity, null, null); | |||||
var presence = BuildCurrentStatus(); | |||||
await ApiClient.SendStatusUpdateAsync( | |||||
presence.Item1, | |||||
presence.Item2, | |||||
presence.Item3, | |||||
presence.Item4).ConfigureAwait(false); | |||||
} | |||||
private (UserStatus, bool, long?, GameModel[]) BuildCurrentStatus() | |||||
{ | |||||
var status = Status; | var status = Status; | ||||
var statusSince = _statusSince; | var statusSince = _statusSince; | ||||
CurrentUser.Presence = new SocketPresence(status, Activity, null, null); | |||||
var activity = _activity; | |||||
var gameModel = new GameModel(); | |||||
GameModel[] gameModels = null; | |||||
// Discord only accepts rich presence over RPC, don't even bother building a payload | // Discord only accepts rich presence over RPC, don't even bother building a payload | ||||
if (Activity is RichGame) | |||||
throw new NotSupportedException("Outgoing Rich Presences are not supported via WebSocket."); | |||||
if (Activity != null) | |||||
if (activity.GetValueOrDefault() != null) | |||||
{ | { | ||||
var gameModel = new GameModel(); | |||||
if (activity.Value is RichGame) | |||||
throw new NotSupportedException("Outgoing Rich Presences are not supported via WebSocket."); | |||||
gameModel.Name = Activity.Name; | gameModel.Name = Activity.Name; | ||||
gameModel.Type = Activity.Type; | gameModel.Type = Activity.Type; | ||||
if (Activity is StreamingGame streamGame) | if (Activity is StreamingGame streamGame) | ||||
gameModel.StreamUrl = streamGame.Url; | gameModel.StreamUrl = streamGame.Url; | ||||
gameModels = new[] { gameModel }; | |||||
} | } | ||||
else if (activity.IsSpecified) | |||||
gameModels = new GameModel[0]; | |||||
await ApiClient.SendStatusUpdateAsync( | |||||
status, | |||||
status == UserStatus.AFK, | |||||
statusSince != null ? _statusSince.Value.ToUnixTimeMilliseconds() : (long?)null, | |||||
gameModel).ConfigureAwait(false); | |||||
return (status, | |||||
status == UserStatus.AFK, | |||||
statusSince != null ? _statusSince.Value.ToUnixTimeMilliseconds() : (long?)null, | |||||
gameModels); | |||||
} | } | ||||
private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string type, object payload) | private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string type, object payload) | ||||
@@ -523,7 +537,7 @@ namespace Discord.WebSocket | |||||
await _shardedClient.AcquireIdentifyLockAsync(ShardId, _connection.CancelToken).ConfigureAwait(false); | await _shardedClient.AcquireIdentifyLockAsync(ShardId, _connection.CancelToken).ConfigureAwait(false); | ||||
try | try | ||||
{ | { | ||||
await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, guildSubscriptions: _guildSubscriptions, gatewayIntents: _gatewayIntents).ConfigureAwait(false); | |||||
await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, guildSubscriptions: _guildSubscriptions, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); | |||||
} | } | ||||
finally | finally | ||||
{ | { | ||||