@@ -1,4 +1,4 @@ | |||
using System.Collections.Immutable; | |||
using System.Collections.Generic; | |||
namespace Discord | |||
{ | |||
@@ -14,10 +14,10 @@ namespace Discord | |||
/// <summary> | |||
/// Gets the set of clients where this user is currently active. | |||
/// </summary> | |||
IImmutableSet<ClientType> ActiveClients { get; } | |||
IReadOnlyCollection<ClientType> ActiveClients { get; } | |||
/// <summary> | |||
/// Gets the list of activities that this user currently has available. | |||
/// </summary> | |||
IImmutableList<IActivity> Activities { get; } | |||
IReadOnlyCollection<IActivity> Activities { get; } | |||
} | |||
} |
@@ -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 | |||
/// <inheritdoc /> | |||
public virtual UserStatus Status => UserStatus.Offline; | |||
/// <inheritdoc /> | |||
public virtual IImmutableSet<ClientType> ActiveClients => ImmutableHashSet<ClientType>.Empty; | |||
public virtual IReadOnlyCollection<ClientType> ActiveClients => ImmutableHashSet<ClientType>.Empty; | |||
/// <inheritdoc /> | |||
public virtual IImmutableList<IActivity> Activities => ImmutableList<IActivity>.Empty; | |||
public virtual IReadOnlyCollection<IActivity> Activities => ImmutableList<IActivity>.Empty; | |||
/// <inheritdoc /> | |||
public virtual bool IsWebhook => false; | |||
@@ -502,6 +502,18 @@ namespace Discord.WebSocket | |||
internal readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientRemovedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>(); | |||
#endregion | |||
#region Presence | |||
/// <summary> Fired when a users presence is updated. </summary> | |||
public event Func<SocketUser, SocketPresence, SocketPresence, Task> PresenceUpdated | |||
{ | |||
add { _presenceUpdated.Add(value); } | |||
remove { _presenceUpdated.Remove(value); } | |||
} | |||
internal readonly AsyncEvent<Func<SocketUser, SocketPresence, SocketPresence, Task>> _presenceUpdated = new AsyncEvent<Func<SocketUser, SocketPresence, SocketPresence, Task>>(); | |||
#endregion | |||
#region Invites | |||
/// <summary> | |||
/// Fired when an invite is created. | |||
@@ -1858,6 +1858,8 @@ namespace Discord.WebSocket | |||
var data = (payload as JToken).ToObject<API.Presence>(_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<SocketGuildUser, ulong>(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": | |||
@@ -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; | |||
} | |||
@@ -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<ulong>(roleIds.Length + 1); | |||
@@ -11,26 +11,37 @@ namespace Discord.WebSocket | |||
/// Represents the WebSocket user's presence status. This may include their online status and their activity. | |||
/// </summary> | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public struct SocketPresence : IPresence | |||
public class SocketPresence : IPresence | |||
{ | |||
/// <inheritdoc /> | |||
public UserStatus Status { get; } | |||
public UserStatus Status { get; private set; } | |||
/// <inheritdoc /> | |||
public IImmutableSet<ClientType> ActiveClients { get; } | |||
public IReadOnlyCollection<ClientType> ActiveClients { get; private set; } | |||
/// <inheritdoc /> | |||
public IImmutableList<IActivity> Activities { get; } | |||
public IReadOnlyCollection<IActivity> Activities { get; private set; } | |||
internal SocketPresence() { } | |||
internal SocketPresence(UserStatus status, IImmutableSet<ClientType> activeClients, IImmutableList<IActivity> activities) | |||
{ | |||
Status = status; | |||
ActiveClients = activeClients ?? ImmutableHashSet<ClientType>.Empty; | |||
Activities = activities ?? ImmutableList<IActivity>.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<ClientType>.Empty; | |||
Activities = ConvertActivitiesList(model.Activities) ?? ImmutableArray<IActivity>.Empty; | |||
} | |||
/// <summary> | |||
/// Creates a new <see cref="IReadOnlyCollection{T}"/> 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 | |||
/// <returns> | |||
/// A collection of all <see cref="ClientType"/>s that this user is active. | |||
/// </returns> | |||
private static IImmutableSet<ClientType> ConvertClientTypesDict(IDictionary<string, string> clientTypesDict) | |||
private static IReadOnlyCollection<ClientType> ConvertClientTypesDict(IDictionary<string, string> clientTypesDict) | |||
{ | |||
if (clientTypesDict == null || clientTypesDict.Count == 0) | |||
return ImmutableHashSet<ClientType>.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; | |||
} | |||
} |
@@ -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 | |||
/// <inheritdoc /> | |||
public UserStatus Status => Presence.Status; | |||
/// <inheritdoc /> | |||
public IImmutableSet<ClientType> ActiveClients => Presence.ActiveClients ?? ImmutableHashSet<ClientType>.Empty; | |||
public IReadOnlyCollection<ClientType> ActiveClients => Presence.ActiveClients ?? ImmutableHashSet<ClientType>.Empty; | |||
/// <inheritdoc /> | |||
public IImmutableList<IActivity> Activities => Presence.Activities ?? ImmutableList<IActivity>.Empty; | |||
public IReadOnlyCollection<IActivity> Activities => Presence.Activities ?? ImmutableList<IActivity>.Empty; | |||
/// <summary> | |||
/// Gets mutual guilds shared with this user. | |||
/// </summary> | |||
@@ -91,6 +92,11 @@ namespace Discord.WebSocket | |||
return hasChanges; | |||
} | |||
internal virtual void Update(PresenceModel model) | |||
{ | |||
Presence.Update(model); | |||
} | |||
/// <inheritdoc /> | |||
public async Task<IDMChannel> CreateDMChannelAsync(RequestOptions options = null) | |||
=> await UserHelper.CreateDMChannelAsync(this, Discord, options).ConfigureAwait(false); | |||