diff --git a/src/Discord.Net.Core/Entities/Users/IPresence.cs b/src/Discord.Net.Core/Entities/Users/IPresence.cs
index 6972037f0..45babf481 100644
--- a/src/Discord.Net.Core/Entities/Users/IPresence.cs
+++ b/src/Discord.Net.Core/Entities/Users/IPresence.cs
@@ -1,4 +1,4 @@
-using System.Collections.Immutable;
+using System.Collections.Generic;
namespace Discord
{
@@ -14,10 +14,10 @@ namespace Discord
///
/// Gets the set of clients where this user is currently active.
///
- IImmutableSet ActiveClients { get; }
+ IReadOnlyCollection ActiveClients { get; }
///
/// Gets the list of activities that this user currently has available.
///
- IImmutableList Activities { get; }
+ IReadOnlyCollection Activities { get; }
}
}
diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs
index 9cf42814c..70f990fe7 100644
--- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs
+++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs
@@ -5,6 +5,7 @@ using System.Globalization;
using System.Threading.Tasks;
using Model = Discord.API.User;
using EventUserModel = Discord.API.GuildScheduledEventUser;
+using System.Collections.Generic;
namespace Discord.Rest
{
@@ -41,9 +42,9 @@ namespace Discord.Rest
///
public virtual UserStatus Status => UserStatus.Offline;
///
- public virtual IImmutableSet ActiveClients => ImmutableHashSet.Empty;
+ public virtual IReadOnlyCollection ActiveClients => ImmutableHashSet.Empty;
///
- public virtual IImmutableList Activities => ImmutableList.Empty;
+ public virtual IReadOnlyCollection Activities => ImmutableList.Empty;
///
public virtual bool IsWebhook => false;
diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
index 4ad25d4d5..91fb24021 100644
--- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
+++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
@@ -502,6 +502,18 @@ namespace Discord.WebSocket
internal readonly AsyncEvent> _recipientRemovedEvent = new AsyncEvent>();
#endregion
+ #region Presence
+
+ /// Fired when a users presence is updated.
+ public event Func PresenceUpdated
+ {
+ add { _presenceUpdated.Add(value); }
+ remove { _presenceUpdated.Remove(value); }
+ }
+ internal readonly AsyncEvent> _presenceUpdated = new AsyncEvent>();
+
+ #endregion
+
#region Invites
///
/// Fired when an invite is created.
diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
index 03c85ffc7..444e69a26 100644
--- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
@@ -1858,6 +1858,8 @@ namespace Discord.WebSocket
var data = (payload as JToken).ToObject(_serializer);
+ SocketUser user = null;
+
if (data.GuildId.IsSpecified)
{
var guild = State.GetGuild(data.GuildId.Value);
@@ -1872,7 +1874,7 @@ namespace Discord.WebSocket
return;
}
- var user = guild.GetUser(data.User.Id);
+ user = guild.GetUser(data.User.Id);
if (user == null)
{
if (data.Status == UserStatus.Offline)
@@ -1890,26 +1892,21 @@ namespace Discord.WebSocket
await TimedInvokeAsync(_userUpdatedEvent, nameof(UserUpdated), globalBefore, user).ConfigureAwait(false);
}
}
-
- var before = user.Clone();
- user.Update(State, data, true);
- var cacheableBefore = new Cacheable(before, user.Id, true, () => Task.FromResult(user));
- await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), cacheableBefore, user).ConfigureAwait(false);
}
else
{
- var globalUser = State.GetUser(data.User.Id);
- if (globalUser == null)
+ user = State.GetUser(data.User.Id);
+ if (user == null)
{
await UnknownGlobalUserAsync(type, data.User.Id).ConfigureAwait(false);
return;
}
-
- var before = globalUser.Clone();
- globalUser.Update(State, data.User);
- globalUser.Update(State, data);
- await TimedInvokeAsync(_userUpdatedEvent, nameof(UserUpdated), before, globalUser).ConfigureAwait(false);
}
+
+ var before = user.Presence.Clone();
+ user.Update(State, data.User);
+ user.Update(data);
+ await TimedInvokeAsync(_presenceUpdated, nameof(PresenceUpdated), user, before, user.Presence).ConfigureAwait(false);
}
break;
case "TYPING_START":
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs
index 3a1ad23b6..525ae0b34 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs
@@ -1,7 +1,6 @@
using System.Diagnostics;
using System.Linq;
using Model = Discord.API.User;
-using PresenceModel = Discord.API.Presence;
namespace Discord.WebSocket
{
@@ -48,11 +47,6 @@ namespace Discord.WebSocket
}
}
- internal void Update(ClientState state, PresenceModel model)
- {
- Presence = SocketPresence.Create(model);
- }
-
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Global)";
internal new SocketGlobalUser Clone() => MemberwiseClone() as SocketGlobalUser;
}
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs
index 147456cb0..ae3319227 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs
@@ -164,8 +164,7 @@ namespace Discord.WebSocket
{
if (updatePresence)
{
- Presence = SocketPresence.Create(model);
- GlobalUser.Update(state, model);
+ Update(model);
}
if (model.Nick.IsSpecified)
Nickname = model.Nick.Value;
@@ -174,6 +173,13 @@ namespace Discord.WebSocket
if (model.PremiumSince.IsSpecified)
_premiumSinceTicks = model.PremiumSince.Value?.UtcTicks;
}
+
+ internal override void Update(PresenceModel model)
+ {
+ Presence.Update(model);
+ GlobalUser.Update(model);
+ }
+
private void UpdateRoles(ulong[] roleIds)
{
var roles = ImmutableArray.CreateBuilder(roleIds.Length + 1);
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs
index fe672a4d6..5250e15ad 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs
@@ -11,26 +11,37 @@ namespace Discord.WebSocket
/// Represents the WebSocket user's presence status. This may include their online status and their activity.
///
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
- public struct SocketPresence : IPresence
+ public class SocketPresence : IPresence
{
///
- public UserStatus Status { get; }
+ public UserStatus Status { get; private set; }
///
- public IImmutableSet ActiveClients { get; }
+ public IReadOnlyCollection ActiveClients { get; private set; }
///
- public IImmutableList Activities { get; }
+ public IReadOnlyCollection Activities { get; private set; }
+
+ internal SocketPresence() { }
internal SocketPresence(UserStatus status, IImmutableSet activeClients, IImmutableList activities)
{
Status = status;
ActiveClients = activeClients ?? ImmutableHashSet.Empty;
Activities = activities ?? ImmutableList.Empty;
}
+
internal static SocketPresence Create(Model model)
{
- var clients = ConvertClientTypesDict(model.ClientStatus.GetValueOrDefault());
- var activities = ConvertActivitiesList(model.Activities);
- return new SocketPresence(model.Status, clients, activities);
+ var entity = new SocketPresence();
+ entity.Update(model);
+ return entity;
+ }
+
+ internal void Update(Model model)
+ {
+ Status = model.Status;
+ ActiveClients = ConvertClientTypesDict(model.ClientStatus.GetValueOrDefault()) ?? ImmutableArray.Empty;
+ Activities = ConvertActivitiesList(model.Activities) ?? ImmutableArray.Empty;
}
+
///
/// Creates a new containing all of the client types
/// where a user is active from the data supplied in the Presence update frame.
@@ -42,7 +53,7 @@ namespace Discord.WebSocket
///
/// A collection of all s that this user is active.
///
- private static IImmutableSet ConvertClientTypesDict(IDictionary clientTypesDict)
+ private static IReadOnlyCollection ConvertClientTypesDict(IDictionary clientTypesDict)
{
if (clientTypesDict == null || clientTypesDict.Count == 0)
return ImmutableHashSet.Empty;
@@ -84,6 +95,6 @@ namespace Discord.WebSocket
public override string ToString() => Status.ToString();
private string DebuggerDisplay => $"{Status}{(Activities?.FirstOrDefault()?.Name ?? "")}";
- internal SocketPresence Clone() => this;
+ internal SocketPresence Clone() => MemberwiseClone() as SocketPresence;
}
}
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs
index 5e5e5cf0c..b38bd8a4a 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Threading.Tasks;
using Discord.Rest;
using Model = Discord.API.User;
+using PresenceModel = Discord.API.Presence;
namespace Discord.WebSocket
{
@@ -40,9 +41,9 @@ namespace Discord.WebSocket
///
public UserStatus Status => Presence.Status;
///
- public IImmutableSet ActiveClients => Presence.ActiveClients ?? ImmutableHashSet.Empty;
+ public IReadOnlyCollection ActiveClients => Presence.ActiveClients ?? ImmutableHashSet.Empty;
///
- public IImmutableList Activities => Presence.Activities ?? ImmutableList.Empty;
+ public IReadOnlyCollection Activities => Presence.Activities ?? ImmutableList.Empty;
///
/// Gets mutual guilds shared with this user.
///
@@ -91,6 +92,11 @@ namespace Discord.WebSocket
return hasChanges;
}
+ internal virtual void Update(PresenceModel model)
+ {
+ Presence.Update(model);
+ }
+
///
public async Task CreateDMChannelAsync(RequestOptions options = null)
=> await UserHelper.CreateDMChannelAsync(this, Discord, options).ConfigureAwait(false);