@@ -14,8 +14,8 @@ namespace Discord.API | |||||
[JsonProperty("joined_at")] | [JsonProperty("joined_at")] | ||||
public DateTime?JoinedAt { get; set; } | public DateTime?JoinedAt { get; set; } | ||||
[JsonProperty("deaf")] | [JsonProperty("deaf")] | ||||
public bool Deaf { get; set; } | |||||
public bool? Deaf { get; set; } | |||||
[JsonProperty("mute")] | [JsonProperty("mute")] | ||||
public bool Mute { get; set; } | |||||
public bool? Mute { get; set; } | |||||
} | } | ||||
} | } |
@@ -27,7 +27,7 @@ namespace Discord.API | |||||
public Attachment[] Attachments { get; set; } | public Attachment[] Attachments { get; set; } | ||||
[JsonProperty("embeds")] | [JsonProperty("embeds")] | ||||
public Embed[] Embeds { get; set; } | public Embed[] Embeds { get; set; } | ||||
[JsonProperty("nonce")] | |||||
public uint? Nonce { get; set; } | |||||
/*[JsonProperty("nonce")] | |||||
public object Nonce { get; set; }*/ | |||||
} | } | ||||
} | } |
@@ -6,6 +6,8 @@ namespace Discord.API | |||||
{ | { | ||||
[JsonProperty("user")] | [JsonProperty("user")] | ||||
public User User { get; set; } | public User User { get; set; } | ||||
[JsonProperty("guild_id")] | |||||
public ulong? GuildId { get; set; } | |||||
[JsonProperty("status")] | [JsonProperty("status")] | ||||
public UserStatus Status { get; set; } | public UserStatus Status { get; set; } | ||||
[JsonProperty("game")] | [JsonProperty("game")] | ||||
@@ -4,8 +4,10 @@ namespace Discord.API | |||||
{ | { | ||||
public class VoiceState | public class VoiceState | ||||
{ | { | ||||
[JsonProperty("guild_id")] | |||||
public ulong? GuildId { get; set; } | |||||
[JsonProperty("channel_id")] | [JsonProperty("channel_id")] | ||||
public ulong ChannelId { get; set; } | |||||
public ulong? ChannelId { get; set; } | |||||
[JsonProperty("user_id")] | [JsonProperty("user_id")] | ||||
public ulong UserId { get; set; } | public ulong UserId { get; set; } | ||||
[JsonProperty("session_id")] | [JsonProperty("session_id")] | ||||
@@ -0,0 +1,10 @@ | |||||
using Newtonsoft.Json; | |||||
namespace Discord.API.Gateway | |||||
{ | |||||
public class GuildMemberAddEvent : GuildMember | |||||
{ | |||||
[JsonProperty("guild_id")] | |||||
public ulong GuildId { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,12 @@ | |||||
using Newtonsoft.Json; | |||||
namespace Discord.API.Gateway | |||||
{ | |||||
public class GuildMemberRemoveEvent | |||||
{ | |||||
[JsonProperty("guild_id")] | |||||
public ulong GuildId { get; set; } | |||||
[JsonProperty("user")] | |||||
public User User { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,10 @@ | |||||
using Newtonsoft.Json; | |||||
namespace Discord.API.Gateway | |||||
{ | |||||
public class GuildMemberUpdateEvent : GuildMember | |||||
{ | |||||
[JsonProperty("guild_id")] | |||||
public ulong GuildId { get; set; } | |||||
} | |||||
} |
@@ -7,6 +7,6 @@ namespace Discord.API.Gateway | |||||
[JsonProperty("guild_id")] | [JsonProperty("guild_id")] | ||||
public ulong GuildId { get; set; } | public ulong GuildId { get; set; } | ||||
[JsonProperty("role")] | [JsonProperty("role")] | ||||
public Role Data { get; set; } | |||||
public Role Role { get; set; } | |||||
} | } | ||||
} | } |
@@ -7,6 +7,6 @@ namespace Discord.API.Gateway | |||||
[JsonProperty("guild_id")] | [JsonProperty("guild_id")] | ||||
public ulong GuildId { get; set; } | public ulong GuildId { get; set; } | ||||
[JsonProperty("role")] | [JsonProperty("role")] | ||||
public Role Data { get; set; } | |||||
public Role Role { get; set; } | |||||
} | } | ||||
} | } |
@@ -41,6 +41,9 @@ namespace Discord | |||||
private readonly ConcurrentQueue<ulong> _largeGuilds; | private readonly ConcurrentQueue<ulong> _largeGuilds; | ||||
private readonly Logger _gatewayLogger; | private readonly Logger _gatewayLogger; | ||||
#if BENCHMARK | |||||
private readonly Logger _benchmarkLogger; | |||||
#endif | |||||
private readonly DataStoreProvider _dataStoreProvider; | private readonly DataStoreProvider _dataStoreProvider; | ||||
private readonly JsonSerializer _serializer; | private readonly JsonSerializer _serializer; | ||||
private readonly int _connectionTimeout, _reconnectDelay, _failedReconnectDelay; | private readonly int _connectionTimeout, _reconnectDelay, _failedReconnectDelay; | ||||
@@ -106,7 +109,10 @@ namespace Discord | |||||
_largeThreshold = config.LargeThreshold; | _largeThreshold = config.LargeThreshold; | ||||
_gatewayLogger = _log.CreateLogger("Gateway"); | _gatewayLogger = _log.CreateLogger("Gateway"); | ||||
#if BENCHMARK | |||||
_benchmarkLogger = _log.CreateLogger("Benchmark"); | |||||
#endif | |||||
_serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | ||||
ApiClient.SentGatewayMessage += async opCode => await _gatewayLogger.Debug($"Sent {(GatewayOpCode)opCode}"); | ApiClient.SentGatewayMessage += async opCode => await _gatewayLogger.Debug($"Sent {(GatewayOpCode)opCode}"); | ||||
@@ -207,7 +213,7 @@ namespace Discord | |||||
{ | { | ||||
dataStore = dataStore ?? DataStore; | dataStore = dataStore ?? DataStore; | ||||
var guild = new CachedGuild(this, model); | |||||
var guild = new CachedGuild(this, model, dataStore); | |||||
if (model.Unavailable != true) | if (model.Unavailable != true) | ||||
{ | { | ||||
for (int i = 0; i < model.Channels.Length; i++) | for (int i = 0; i < model.Channels.Length; i++) | ||||
@@ -247,7 +253,7 @@ namespace Discord | |||||
{ | { | ||||
dataStore = dataStore ?? DataStore; | dataStore = dataStore ?? DataStore; | ||||
var recipient = AddCachedUser(model.Recipient, dataStore); | |||||
var recipient = GetOrAddCachedUser(model.Recipient, dataStore); | |||||
var channel = recipient.AddDMChannel(model); | var channel = recipient.AddDMChannel(model); | ||||
dataStore.AddChannel(channel); | dataStore.AddChannel(channel); | ||||
return channel; | return channel; | ||||
@@ -287,7 +293,7 @@ namespace Discord | |||||
{ | { | ||||
return Task.FromResult<IUser>(DataStore.Users.Where(x => x.Discriminator == discriminator && x.Username == username).FirstOrDefault()); | return Task.FromResult<IUser>(DataStore.Users.Where(x => x.Discriminator == discriminator && x.Username == username).FirstOrDefault()); | ||||
} | } | ||||
internal CachedPublicUser AddCachedUser(API.User model, DataStore dataStore = null) | |||||
internal CachedPublicUser GetOrAddCachedUser(API.User model, DataStore dataStore = null) | |||||
{ | { | ||||
dataStore = dataStore ?? DataStore; | dataStore = dataStore ?? DataStore; | ||||
@@ -299,8 +305,7 @@ namespace Discord | |||||
{ | { | ||||
dataStore = dataStore ?? DataStore; | dataStore = dataStore ?? DataStore; | ||||
var user = dataStore.GetUser(id); | |||||
user.RemoveRef(); | |||||
var user = dataStore.RemoveUser(id); | |||||
return user; | return user; | ||||
} | } | ||||
@@ -336,7 +341,7 @@ namespace Discord | |||||
batchTasks[j] = guild.DownloaderPromise; | batchTasks[j] = guild.DownloaderPromise; | ||||
} | } | ||||
ApiClient.SendRequestMembers(batchIds); | |||||
await ApiClient.SendRequestMembers(batchIds).ConfigureAwait(false); | |||||
if (isLast && batchCount > 1) | if (isLast && batchCount > 1) | ||||
await Task.WhenAll(batchTasks.Take(count)).ConfigureAwait(false); | await Task.WhenAll(batchTasks.Take(count)).ConfigureAwait(false); | ||||
@@ -347,474 +352,511 @@ namespace Discord | |||||
private async Task ProcessMessage(GatewayOpCode opCode, int? seq, string type, object payload) | private async Task ProcessMessage(GatewayOpCode opCode, int? seq, string type, object payload) | ||||
{ | { | ||||
if (seq != null) | |||||
_lastSeq = seq.Value; | |||||
#if BENCHMARK | |||||
Stopwatch stopwatch = Stopwatch.StartNew(); | |||||
try | try | ||||
{ | { | ||||
switch (opCode) | |||||
#endif | |||||
if (seq != null) | |||||
_lastSeq = seq.Value; | |||||
try | |||||
{ | { | ||||
case GatewayOpCode.Hello: | |||||
{ | |||||
var data = (payload as JToken).ToObject<HelloEvent>(_serializer); | |||||
await ApiClient.SendIdentify().ConfigureAwait(false); | |||||
_heartbeatTask = RunHeartbeat(data.HeartbeatInterval, _heartbeatCancelToken.Token); | |||||
} | |||||
break; | |||||
case GatewayOpCode.HeartbeatAck: | |||||
{ | |||||
var latency = (int)(Environment.TickCount - _heartbeatTime); | |||||
await _gatewayLogger.Debug($"Latency: {latency} ms").ConfigureAwait(false); | |||||
Latency = latency; | |||||
await LatencyUpdated.Raise(latency).ConfigureAwait(false); | |||||
} | |||||
break; | |||||
case GatewayOpCode.Dispatch: | |||||
switch (type) | |||||
{ | |||||
//Global | |||||
case "READY": | |||||
{ | |||||
//TODO: Make downloading large guilds optional | |||||
var data = (payload as JToken).ToObject<ReadyEvent>(_serializer); | |||||
var dataStore = _dataStoreProvider(ShardId, _totalShards, data.Guilds.Length, data.PrivateChannels.Length); | |||||
_currentUser = new CachedSelfUser(this, data.User); | |||||
for (int i = 0; i < data.Guilds.Length; i++) | |||||
AddCachedGuild(data.Guilds[i], dataStore); | |||||
for (int i = 0; i < data.PrivateChannels.Length; i++) | |||||
AddCachedDMChannel(data.PrivateChannels[i], dataStore); | |||||
_sessionId = data.SessionId; | |||||
DataStore = dataStore; | |||||
await Ready.Raise().ConfigureAwait(false); | |||||
_connectTask.TrySetResult(true); //Signal the .Connect() call to complete | |||||
} | |||||
break; | |||||
//Guilds | |||||
case "GUILD_CREATE": | |||||
{ | |||||
var data = (payload as JToken).ToObject<ExtendedGuild>(_serializer); | |||||
var guild = new CachedGuild(this, data); | |||||
DataStore.AddGuild(guild); | |||||
if (data.Unavailable == false) | |||||
type = "GUILD_AVAILABLE"; | |||||
else | |||||
await JoinedGuild.Raise(guild).ConfigureAwait(false); | |||||
await GuildAvailable.Raise(guild); | |||||
} | |||||
break; | |||||
case "GUILD_UPDATE": | |||||
{ | |||||
var data = (payload as JToken).ToObject<API.Guild>(_serializer); | |||||
var guild = DataStore.GetGuild(data.Id); | |||||
if (guild != null) | |||||
switch (opCode) | |||||
{ | |||||
case GatewayOpCode.Hello: | |||||
{ | |||||
await _gatewayLogger.Debug($"Received Hello").ConfigureAwait(false); | |||||
var data = (payload as JToken).ToObject<HelloEvent>(_serializer); | |||||
await ApiClient.SendIdentify().ConfigureAwait(false); | |||||
_heartbeatTask = RunHeartbeat(data.HeartbeatInterval, _heartbeatCancelToken.Token); | |||||
} | |||||
break; | |||||
case GatewayOpCode.HeartbeatAck: | |||||
{ | |||||
await _gatewayLogger.Debug($"Received HeartbeatAck").ConfigureAwait(false); | |||||
var latency = (int)(Environment.TickCount - _heartbeatTime); | |||||
await _gatewayLogger.Debug($"Latency = {latency} ms").ConfigureAwait(false); | |||||
Latency = latency; | |||||
await LatencyUpdated.Raise(latency).ConfigureAwait(false); | |||||
} | |||||
break; | |||||
case GatewayOpCode.Dispatch: | |||||
switch (type) | |||||
{ | |||||
//Global | |||||
case "READY": | |||||
{ | { | ||||
var before = _enablePreUpdateEvents ? guild.Clone() : null; | |||||
guild.Update(data, UpdateSource.WebSocket); | |||||
await GuildUpdated.Raise(before, guild); | |||||
await _gatewayLogger.Debug($"Received Dispatch (READY)").ConfigureAwait(false); | |||||
//TODO: Make downloading large guilds optional | |||||
var data = (payload as JToken).ToObject<ReadyEvent>(_serializer); | |||||
var dataStore = _dataStoreProvider(ShardId, _totalShards, data.Guilds.Length, data.PrivateChannels.Length); | |||||
_currentUser = new CachedSelfUser(this, data.User); | |||||
for (int i = 0; i < data.Guilds.Length; i++) | |||||
AddCachedGuild(data.Guilds[i], dataStore); | |||||
for (int i = 0; i < data.PrivateChannels.Length; i++) | |||||
AddCachedDMChannel(data.PrivateChannels[i], dataStore); | |||||
_sessionId = data.SessionId; | |||||
DataStore = dataStore; | |||||
await Ready.Raise().ConfigureAwait(false); | |||||
_connectTask.TrySetResult(true); //Signal the .Connect() call to complete | |||||
} | } | ||||
else | |||||
await _gatewayLogger.Warning("GUILD_UPDATE referenced an unknown guild."); | |||||
} | |||||
break; | |||||
case "GUILD_DELETE": | |||||
{ | |||||
var data = (payload as JToken).ToObject<ExtendedGuild>(_serializer); | |||||
var guild = DataStore.RemoveGuild(data.Id); | |||||
if (guild != null) | |||||
break; | |||||
//Guilds | |||||
case "GUILD_CREATE": | |||||
{ | { | ||||
if (data.Unavailable == true) | |||||
type = "GUILD_UNAVAILABLE"; | |||||
var data = (payload as JToken).ToObject<ExtendedGuild>(_serializer); | |||||
var guild = new CachedGuild(this, data, DataStore); | |||||
DataStore.AddGuild(guild); | |||||
if (data.Unavailable == false) | |||||
type = "GUILD_AVAILABLE"; | |||||
await _gatewayLogger.Debug($"Received Dispatch ({type})").ConfigureAwait(false); | |||||
await GuildUnavailable.Raise(guild); | |||||
if (data.Unavailable != true) | |||||
await LeftGuild.Raise(guild); | |||||
if (data.Unavailable != false) | |||||
await JoinedGuild.Raise(guild).ConfigureAwait(false); | |||||
await GuildAvailable.Raise(guild).ConfigureAwait(false); | |||||
} | } | ||||
else | |||||
await _gatewayLogger.Warning("GUILD_DELETE referenced an unknown guild."); | |||||
} | |||||
break; | |||||
//Channels | |||||
case "CHANNEL_CREATE": | |||||
{ | |||||
var data = (payload as JToken).ToObject<API.Channel>(_serializer); | |||||
ICachedChannel channel = null; | |||||
if (data.GuildId != null) | |||||
break; | |||||
case "GUILD_UPDATE": | |||||
{ | { | ||||
var guild = DataStore.GetGuild(data.GuildId.Value); | |||||
await _gatewayLogger.Debug($"Received Dispatch (GUILD_UPDATE)").ConfigureAwait(false); | |||||
var data = (payload as JToken).ToObject<API.Guild>(_serializer); | |||||
var guild = DataStore.GetGuild(data.Id); | |||||
if (guild != null) | if (guild != null) | ||||
{ | { | ||||
channel = guild.AddCachedChannel(data); | |||||
DataStore.AddChannel(channel); | |||||
var before = _enablePreUpdateEvents ? guild.Clone() : null; | |||||
guild.Update(data, UpdateSource.WebSocket); | |||||
await GuildUpdated.Raise(before, guild).ConfigureAwait(false); | |||||
} | } | ||||
else | else | ||||
await _gatewayLogger.Warning("CHANNEL_CREATE referenced an unknown guild."); | |||||
await _gatewayLogger.Warning("GUILD_UPDATE referenced an unknown guild."); | |||||
} | } | ||||
else | |||||
channel = AddCachedDMChannel(data); | |||||
if (channel != null) | |||||
await ChannelCreated.Raise(channel); | |||||
} | |||||
break; | |||||
case "CHANNEL_UPDATE": | |||||
{ | |||||
var data = (payload as JToken).ToObject<API.Channel>(_serializer); | |||||
var channel = DataStore.GetChannel(data.Id); | |||||
if (channel != null) | |||||
break; | |||||
case "GUILD_DELETE": | |||||
{ | { | ||||
var before = _enablePreUpdateEvents ? channel.Clone() : null; | |||||
channel.Update(data, UpdateSource.WebSocket); | |||||
await ChannelUpdated.Raise(before, channel); | |||||
var data = (payload as JToken).ToObject<ExtendedGuild>(_serializer); | |||||
if (data.Unavailable == true) | |||||
type = "GUILD_UNAVAILABLE"; | |||||
await _gatewayLogger.Debug($"Received Dispatch ({type})").ConfigureAwait(false); | |||||
var guild = DataStore.RemoveGuild(data.Id); | |||||
if (guild != null) | |||||
{ | |||||
await GuildUnavailable.Raise(guild).ConfigureAwait(false); | |||||
if (data.Unavailable != true) | |||||
await LeftGuild.Raise(guild).ConfigureAwait(false); | |||||
foreach (var member in guild.Members) | |||||
member.User.RemoveRef(); | |||||
} | |||||
else | |||||
await _gatewayLogger.Warning($"{type} referenced an unknown guild.").ConfigureAwait(false); | |||||
} | } | ||||
else | |||||
await _gatewayLogger.Warning("CHANNEL_UPDATE referenced an unknown channel."); | |||||
} | |||||
break; | |||||
case "CHANNEL_DELETE": | |||||
{ | |||||
var data = (payload as JToken).ToObject<API.Channel>(_serializer); | |||||
var channel = RemoveCachedChannel(data.Id); | |||||
if (channel != null) | |||||
await ChannelDestroyed.Raise(channel); | |||||
else | |||||
await _gatewayLogger.Warning("CHANNEL_DELETE referenced an unknown channel."); | |||||
} | |||||
break; | |||||
//Members | |||||
/*case "GUILD_MEMBER_ADD": | |||||
{ | |||||
var data = (payload as JToken).ToObject<API.GuildMember>(_serializer); | |||||
var guild = GetGuild(data.GuildId.Value); | |||||
if (guild != null) | |||||
break; | |||||
//Channels | |||||
case "CHANNEL_CREATE": | |||||
{ | { | ||||
var user = guild.AddCachedUser(data.User.Id, true, true); | |||||
user.Update(data); | |||||
user.UpdateActivity(); | |||||
UserJoined.Raise(user); | |||||
await _gatewayLogger.Debug($"Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false); | |||||
var data = (payload as JToken).ToObject<API.Channel>(_serializer); | |||||
ICachedChannel channel = null; | |||||
if (data.GuildId != null) | |||||
{ | |||||
var guild = DataStore.GetGuild(data.GuildId.Value); | |||||
if (guild != null) | |||||
{ | |||||
channel = guild.AddCachedChannel(data); | |||||
DataStore.AddChannel(channel); | |||||
} | |||||
else | |||||
await _gatewayLogger.Warning("CHANNEL_CREATE referenced an unknown guild."); | |||||
} | |||||
else | |||||
channel = AddCachedDMChannel(data); | |||||
if (channel != null) | |||||
await ChannelCreated.Raise(channel); | |||||
} | } | ||||
else | |||||
await _gatewayLogger.Warning("GUILD_MEMBER_ADD referenced an unknown guild."); | |||||
} | |||||
break; | |||||
case "GUILD_MEMBER_UPDATE": | |||||
{ | |||||
var data = (payload as JToken).ToObject<API.GuildMember>(_serializer); | |||||
var guild = GetGuild(data.GuildId.Value); | |||||
if (guild != null) | |||||
break; | |||||
case "CHANNEL_UPDATE": | |||||
{ | { | ||||
var user = guild.GetCachedUser(data.User.Id); | |||||
if (user != null) | |||||
await _gatewayLogger.Debug($"Received Dispatch (CHANNEL_UPDATE)").ConfigureAwait(false); | |||||
var data = (payload as JToken).ToObject<API.Channel>(_serializer); | |||||
var channel = DataStore.GetChannel(data.Id); | |||||
if (channel != null) | |||||
{ | { | ||||
var before = _enablePreUpdateEvents ? user.Clone() : null; | |||||
user.Update(data); | |||||
await UserUpdated.Raise(before, user); | |||||
var before = _enablePreUpdateEvents ? channel.Clone() : null; | |||||
channel.Update(data, UpdateSource.WebSocket); | |||||
await ChannelUpdated.Raise(before, channel); | |||||
} | } | ||||
else | else | ||||
await _gatewayLogger.Warning("GUILD_MEMBER_UPDATE referenced an unknown user."); | |||||
await _gatewayLogger.Warning("CHANNEL_UPDATE referenced an unknown channel."); | |||||
} | |||||
break; | |||||
case "CHANNEL_DELETE": | |||||
{ | |||||
await _gatewayLogger.Debug($"Received Dispatch (CHANNEL_DELETE)").ConfigureAwait(false); | |||||
var data = (payload as JToken).ToObject<API.Channel>(_serializer); | |||||
var channel = RemoveCachedChannel(data.Id); | |||||
if (channel != null) | |||||
await ChannelDestroyed.Raise(channel); | |||||
else | |||||
await _gatewayLogger.Warning("CHANNEL_DELETE referenced an unknown channel."); | |||||
} | } | ||||
else | |||||
await _gatewayLogger.Warning("GUILD_MEMBER_UPDATE referenced an unknown guild."); | |||||
} | |||||
break; | |||||
case "GUILD_MEMBER_REMOVE": | |||||
{ | |||||
var data = (payload as JToken).ToObject<API.GuildMember>(_serializer); | |||||
var guild = GetGuild(data.GuildId.Value); | |||||
if (guild != null) | |||||
break; | |||||
//Members | |||||
case "GUILD_MEMBER_ADD": | |||||
{ | { | ||||
var user = guild.RemoveCachedUser(data.User.Id); | |||||
if (user != null) | |||||
await _gatewayLogger.Debug($"Received Dispatch (GUILD_MEMBER_ADD)").ConfigureAwait(false); | |||||
var data = (payload as JToken).ToObject<GuildMemberAddEvent>(_serializer); | |||||
var guild = DataStore.GetGuild(data.GuildId); | |||||
if (guild != null) | |||||
{ | { | ||||
user.GlobalUser.RemoveGuild(); | |||||
if (user.GuildCount == 0 && user.DMChannel == null) | |||||
DataStore.RemoveUser(user.Id); | |||||
await UserLeft.Raise(user); | |||||
var user = guild.AddCachedUser(data); | |||||
await UserJoined.Raise(user).ConfigureAwait(false); | |||||
} | } | ||||
else | else | ||||
await _gatewayLogger.Warning("GUILD_MEMBER_REMOVE referenced an unknown user."); | |||||
await _gatewayLogger.Warning("GUILD_MEMBER_ADD referenced an unknown guild."); | |||||
} | } | ||||
else | |||||
await _gatewayLogger.Warning("GUILD_MEMBER_REMOVE referenced an unknown guild."); | |||||
} | |||||
break; | |||||
case "GUILD_MEMBERS_CHUNK": | |||||
{ | |||||
var data = (payload as JToken).ToObject<GuildMembersChunkEvent>(_serializer); | |||||
var guild = GetCachedGuild(data.GuildId); | |||||
if (guild != null) | |||||
break; | |||||
case "GUILD_MEMBER_UPDATE": | |||||
{ | { | ||||
foreach (var memberData in data.Members) | |||||
await _gatewayLogger.Debug($"Received Dispatch (GUILD_MEMBER_UPDATE)").ConfigureAwait(false); | |||||
var data = (payload as JToken).ToObject<GuildMemberUpdateEvent>(_serializer); | |||||
var guild = DataStore.GetGuild(data.GuildId); | |||||
if (guild != null) | |||||
{ | { | ||||
var user = guild.AddCachedUser(memberData.User.Id, true, false); | |||||
user.Update(memberData); | |||||
var user = guild.GetCachedUser(data.User.Id); | |||||
if (user != null) | |||||
{ | |||||
var before = _enablePreUpdateEvents ? user.Clone() : null; | |||||
user.Update(data, UpdateSource.WebSocket); | |||||
await UserUpdated.Raise(before, user); | |||||
} | |||||
else | |||||
await _gatewayLogger.Warning("GUILD_MEMBER_UPDATE referenced an unknown user."); | |||||
} | } | ||||
if (guild.CurrentUserCount >= guild.UserCount) //Finished downloading for there | |||||
await GuildAvailable.Raise(guild); | |||||
else | |||||
await _gatewayLogger.Warning("GUILD_MEMBER_UPDATE referenced an unknown guild."); | |||||
} | } | ||||
else | |||||
await _gatewayLogger.Warning("GUILD_MEMBERS_CHUNK referenced an unknown guild."); | |||||
} | |||||
break; | |||||
//Roles | |||||
/*case "GUILD_ROLE_CREATE": | |||||
{ | |||||
var data = (payload as JToken).ToObject<GuildRoleCreateEvent>(_serializer); | |||||
var guild = GetCachedGuild(data.GuildId); | |||||
if (guild != null) | |||||
break; | |||||
case "GUILD_MEMBER_REMOVE": | |||||
{ | { | ||||
var role = guild.AddCachedRole(data.Data.Id); | |||||
role.Update(data.Data, false); | |||||
RoleCreated.Raise(role); | |||||
await _gatewayLogger.Debug($"Received Dispatch (GUILD_MEMBER_REMOVE)").ConfigureAwait(false); | |||||
var data = (payload as JToken).ToObject<GuildMemberRemoveEvent>(_serializer); | |||||
var guild = DataStore.GetGuild(data.GuildId); | |||||
if (guild != null) | |||||
{ | |||||
var user = guild.RemoveCachedUser(data.User.Id); | |||||
if (user != null) | |||||
{ | |||||
user.User.RemoveRef(); | |||||
await UserLeft.Raise(user); | |||||
} | |||||
else | |||||
await _gatewayLogger.Warning("GUILD_MEMBER_REMOVE referenced an unknown user."); | |||||
} | |||||
else | |||||
await _gatewayLogger.Warning("GUILD_MEMBER_REMOVE referenced an unknown guild."); | |||||
} | } | ||||
else | |||||
await _gatewayLogger.Warning("GUILD_ROLE_CREATE referenced an unknown guild."); | |||||
} | |||||
break; | |||||
case "GUILD_ROLE_UPDATE": | |||||
{ | |||||
var data = (payload as JToken).ToObject<GuildRoleUpdateEvent>(_serializer); | |||||
var guild = GetCachedGuild(data.GuildId); | |||||
if (guild != null) | |||||
break; | |||||
case "GUILD_MEMBERS_CHUNK": | |||||
{ | { | ||||
var role = guild.GetRole(data.Data.Id); | |||||
if (role != null) | |||||
await _gatewayLogger.Debug($"Received Dispatch (GUILD_MEMBERS_CHUNK)").ConfigureAwait(false); | |||||
var data = (payload as JToken).ToObject<GuildMembersChunkEvent>(_serializer); | |||||
var guild = DataStore.GetGuild(data.GuildId); | |||||
if (guild != null) | |||||
{ | { | ||||
var before = _enablePreUpdateEvents ? role.Clone() : null; | |||||
role.Update(data.Data, true); | |||||
RoleUpdated.Raise(before, role); | |||||
foreach (var memberModel in data.Members) | |||||
guild.AddCachedUser(memberModel); | |||||
if (guild.DownloadedMemberCount >= guild.MemberCount) //Finished downloading for there | |||||
{ | |||||
guild.CompleteDownloadMembers(); | |||||
await GuildDownloadedMembers.Raise(guild).ConfigureAwait(false); | |||||
} | |||||
} | } | ||||
else | else | ||||
await _gatewayLogger.Warning("GUILD_ROLE_UPDATE referenced an unknown role."); | |||||
await _gatewayLogger.Warning("GUILD_MEMBERS_CHUNK referenced an unknown guild."); | |||||
} | } | ||||
else | |||||
await _gatewayLogger.Warning("GUILD_ROLE_UPDATE referenced an unknown guild."); | |||||
} | |||||
break; | |||||
case "GUILD_ROLE_DELETE": | |||||
{ | |||||
var data = (payload as JToken).ToObject<GuildRoleDeleteEvent>(_serializer); | |||||
var guild = DataStore.GetGuild(data.GuildId); | |||||
if (guild != null) | |||||
break; | |||||
//Roles | |||||
case "GUILD_ROLE_CREATE": | |||||
{ | { | ||||
var role = guild.RemoveRole(data.RoleId); | |||||
if (role != null) | |||||
RoleDeleted.Raise(role); | |||||
await _gatewayLogger.Debug($"Received Dispatch (GUILD_ROLE_CREATE)").ConfigureAwait(false); | |||||
var data = (payload as JToken).ToObject<GuildRoleCreateEvent>(_serializer); | |||||
var guild = DataStore.GetGuild(data.GuildId); | |||||
if (guild != null) | |||||
{ | |||||
var role = guild.AddCachedRole(data.Role); | |||||
await RoleCreated.Raise(role).ConfigureAwait(false); | |||||
} | |||||
else | else | ||||
await _gatewayLogger.Warning("GUILD_ROLE_DELETE referenced an unknown role."); | |||||
await _gatewayLogger.Warning("GUILD_ROLE_CREATE referenced an unknown guild."); | |||||
} | } | ||||
else | |||||
await _gatewayLogger.Warning("GUILD_ROLE_DELETE referenced an unknown guild."); | |||||
} | |||||
break; | |||||
//Bans | |||||
case "GUILD_BAN_ADD": | |||||
{ | |||||
var data = (payload as JToken).ToObject<GuildBanEvent>(_serializer); | |||||
var guild = GetCachedGuild(data.GuildId); | |||||
if (guild != null) | |||||
await UserBanned.Raise(new User(this, data)); | |||||
else | |||||
await _gatewayLogger.Warning("GUILD_BAN_ADD referenced an unknown guild."); | |||||
} | |||||
break; | |||||
case "GUILD_BAN_REMOVE": | |||||
{ | |||||
var data = payload.ToObject<GuildBanEvent>(_serializer); | |||||
var guild = GetCachedGuild(data.GuildId); | |||||
if (guild != null) | |||||
await UserUnbanned.Raise(new User(this, data)); | |||||
else | |||||
await _gatewayLogger.Warning("GUILD_BAN_REMOVE referenced an unknown guild."); | |||||
} | |||||
break; | |||||
//Messages | |||||
case "MESSAGE_CREATE": | |||||
{ | |||||
var data = (payload as JToken).ToObject<API.Message>(_serializer); | |||||
var channel = DataStore.GetChannel(data.ChannelId); | |||||
if (channel != null) | |||||
break; | |||||
case "GUILD_ROLE_UPDATE": | |||||
{ | { | ||||
var user = channel.GetUser(data.Author.Id); | |||||
await _gatewayLogger.Debug($"Received Dispatch (GUILD_ROLE_UPDATE)").ConfigureAwait(false); | |||||
if (user != null) | |||||
var data = (payload as JToken).ToObject<GuildRoleUpdateEvent>(_serializer); | |||||
var guild = DataStore.GetGuild(data.GuildId); | |||||
if (guild != null) | |||||
{ | { | ||||
bool isAuthor = data.Author.Id == CurrentUser.Id; | |||||
var msg = channel.AddMessage(data.Id, user, data.Timestamp.Value); | |||||
msg.Update(data); | |||||
MessageReceived.Raise(msg); | |||||
var role = guild.GetRole(data.Role.Id); | |||||
if (role != null) | |||||
{ | |||||
var before = _enablePreUpdateEvents ? role.Clone() : null; | |||||
role.Update(data.Role, UpdateSource.WebSocket); | |||||
await RoleUpdated.Raise(before, role).ConfigureAwait(false); | |||||
} | |||||
else | |||||
await _gatewayLogger.Warning("GUILD_ROLE_UPDATE referenced an unknown role."); | |||||
} | } | ||||
else | else | ||||
await _gatewayLogger.Warning("MESSAGE_CREATE referenced an unknown user."); | |||||
await _gatewayLogger.Warning("GUILD_ROLE_UPDATE referenced an unknown guild."); | |||||
} | } | ||||
else | |||||
await _gatewayLogger.Warning("MESSAGE_CREATE referenced an unknown channel."); | |||||
} | |||||
break; | |||||
case "MESSAGE_UPDATE": | |||||
{ | |||||
var data = (payload as JToken).ToObject<API.Message>(_serializer); | |||||
var channel = GetCachedChannel(data.ChannelId); | |||||
if (channel != null) | |||||
break; | |||||
case "GUILD_ROLE_DELETE": | |||||
{ | { | ||||
var msg = channel.GetMessage(data.Id, data.Author?.Id); | |||||
var before = _enablePreUpdateEvents ? msg.Clone() : null; | |||||
msg.Update(data); | |||||
MessageUpdated.Raise(before, msg); | |||||
await _gatewayLogger.Debug($"Received Dispatch (GUILD_ROLE_DELETE)").ConfigureAwait(false); | |||||
var data = (payload as JToken).ToObject<GuildRoleDeleteEvent>(_serializer); | |||||
var guild = DataStore.GetGuild(data.GuildId); | |||||
if (guild != null) | |||||
{ | |||||
var role = guild.RemoveCachedRole(data.RoleId); | |||||
if (role != null) | |||||
await RoleDeleted.Raise(role).ConfigureAwait(false); | |||||
else | |||||
await _gatewayLogger.Warning("GUILD_ROLE_DELETE referenced an unknown role."); | |||||
} | |||||
else | |||||
await _gatewayLogger.Warning("GUILD_ROLE_DELETE referenced an unknown guild."); | |||||
} | } | ||||
else | |||||
await _gatewayLogger.Warning("MESSAGE_UPDATE referenced an unknown channel."); | |||||
} | |||||
break; | |||||
case "MESSAGE_DELETE": | |||||
{ | |||||
var data = (payload as JToken).ToObject<API.Message>(_serializer); | |||||
var channel = GetCachedChannel(data.ChannelId); | |||||
if (channel != null) | |||||
break; | |||||
//Bans | |||||
case "GUILD_BAN_ADD": | |||||
{ | { | ||||
var msg = channel.RemoveMessage(data.Id); | |||||
MessageDeleted.Raise(msg); | |||||
await _gatewayLogger.Debug($"Received Dispatch (GUILD_BAN_ADD)").ConfigureAwait(false); | |||||
var data = (payload as JToken).ToObject<GuildBanEvent>(_serializer); | |||||
var guild = DataStore.GetGuild(data.GuildId); | |||||
if (guild != null) | |||||
await UserBanned.Raise(new User(this, data)); | |||||
else | |||||
await _gatewayLogger.Warning("GUILD_BAN_ADD referenced an unknown guild."); | |||||
} | } | ||||
else | |||||
await _gatewayLogger.Warning("MESSAGE_DELETE referenced an unknown channel."); | |||||
} | |||||
break; | |||||
//Statuses | |||||
case "PRESENCE_UPDATE": | |||||
{ | |||||
var data = (payload as JToken).ToObject<API.Presence>(_serializer); | |||||
User user; | |||||
Guild guild; | |||||
if (data.GuildId == null) | |||||
break; | |||||
case "GUILD_BAN_REMOVE": | |||||
{ | { | ||||
guild = null; | |||||
user = GetPrivateChannel(data.User.Id)?.Recipient; | |||||
await _gatewayLogger.Debug($"Received Dispatch (GUILD_BAN_REMOVE)").ConfigureAwait(false); | |||||
var data = (payload as JToken).ToObject<GuildBanEvent>(_serializer); | |||||
var guild = DataStore.GetGuild(data.GuildId); | |||||
if (guild != null) | |||||
await UserUnbanned.Raise(new User(this, data)); | |||||
else | |||||
await _gatewayLogger.Warning("GUILD_BAN_REMOVE referenced an unknown guild."); | |||||
} | } | ||||
else | |||||
break; | |||||
//Messages | |||||
case "MESSAGE_CREATE": | |||||
{ | { | ||||
guild = GetGuild(data.GuildId.Value); | |||||
if (guild == null) | |||||
await _gatewayLogger.Debug($"Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); | |||||
var data = (payload as JToken).ToObject<API.Message>(_serializer); | |||||
var channel = DataStore.GetChannel(data.ChannelId) as ICachedMessageChannel; | |||||
if (channel != null) | |||||
{ | { | ||||
await _gatewayLogger.Warning("PRESENCE_UPDATE referenced an unknown guild."); | |||||
break; | |||||
var author = channel.GetCachedUser(data.Author.Id); | |||||
if (author != null) | |||||
{ | |||||
var msg = channel.AddCachedMessage(author, data); | |||||
await MessageReceived.Raise(msg).ConfigureAwait(false); | |||||
} | |||||
else | |||||
await _gatewayLogger.Warning("MESSAGE_CREATE referenced an unknown user."); | |||||
} | } | ||||
else | else | ||||
user = guild.GetUser(data.User.Id); | |||||
await _gatewayLogger.Warning("MESSAGE_CREATE referenced an unknown channel."); | |||||
} | } | ||||
if (user != null) | |||||
break; | |||||
case "MESSAGE_UPDATE": | |||||
{ | { | ||||
var before = _enablePreUpdateEvents ? user.Clone() : null; | |||||
user.Update(data); | |||||
UserUpdated.Raise(before, user); | |||||
await _gatewayLogger.Debug($"Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); | |||||
var data = (payload as JToken).ToObject<API.Message>(_serializer); | |||||
var channel = DataStore.GetChannel(data.ChannelId) as ICachedMessageChannel; | |||||
if (channel != null) | |||||
{ | |||||
var msg = channel.GetCachedMessage(data.Id); | |||||
var before = _enablePreUpdateEvents ? msg.Clone() : null; | |||||
msg.Update(data, UpdateSource.WebSocket); | |||||
await MessageUpdated.Raise(before, msg).ConfigureAwait(false); | |||||
} | |||||
else | |||||
await _gatewayLogger.Warning("MESSAGE_UPDATE referenced an unknown channel."); | |||||
} | } | ||||
else | |||||
break; | |||||
case "MESSAGE_DELETE": | |||||
{ | { | ||||
//Occurs when a user leaves a guild | |||||
//await _gatewayLogger.Warning("PRESENCE_UPDATE referenced an unknown user."); | |||||
await _gatewayLogger.Debug($"Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); | |||||
var data = (payload as JToken).ToObject<API.Message>(_serializer); | |||||
var channel = DataStore.GetChannel(data.ChannelId) as ICachedMessageChannel; | |||||
if (channel != null) | |||||
{ | |||||
var msg = channel.RemoveCachedMessage(data.Id); | |||||
await MessageDeleted.Raise(msg).ConfigureAwait(false); | |||||
} | |||||
else | |||||
await _gatewayLogger.Warning("MESSAGE_DELETE referenced an unknown channel."); | |||||
} | } | ||||
} | |||||
break; | |||||
case "TYPING_START": | |||||
{ | |||||
var data = (payload as JToken).ToObject<TypingStartEvent>(_serializer); | |||||
var channel = GetCachedChannel(data.ChannelId); | |||||
if (channel != null) | |||||
break; | |||||
//Statuses | |||||
case "PRESENCE_UPDATE": | |||||
{ | { | ||||
var user = channel.GetUser(data.UserId); | |||||
if (user != null) | |||||
await _gatewayLogger.Debug($"Received Dispatch (PRESENCE_UPDATE)").ConfigureAwait(false); | |||||
var data = (payload as JToken).ToObject<API.Presence>(_serializer); | |||||
if (data.GuildId == null) | |||||
{ | |||||
var user = DataStore.GetUser(data.User.Id); | |||||
if (user == null) | |||||
user.Update(data, UpdateSource.WebSocket); | |||||
} | |||||
else | |||||
{ | { | ||||
await UserIsTyping.Raise(channel, user); | |||||
user.UpdateActivity(); | |||||
var guild = DataStore.GetGuild(data.GuildId.Value); | |||||
if (guild == null) | |||||
{ | |||||
await _gatewayLogger.Warning("PRESENCE_UPDATE referenced an unknown guild."); | |||||
break; | |||||
} | |||||
if (data.Status == UserStatus.Offline) | |||||
guild.RemoveCachedPresence(data.User.Id); | |||||
else | |||||
guild.AddOrUpdateCachedPresence(data); | |||||
} | } | ||||
} | } | ||||
else | |||||
await _gatewayLogger.Warning("TYPING_START referenced an unknown channel."); | |||||
} | |||||
break; | |||||
//Voice | |||||
case "VOICE_STATE_UPDATE": | |||||
{ | |||||
var data = (payload as JToken).ToObject<API.VoiceState>(_serializer); | |||||
var guild = GetGuild(data.GuildId); | |||||
if (guild != null) | |||||
break; | |||||
case "TYPING_START": | |||||
{ | { | ||||
var user = guild.GetUser(data.UserId); | |||||
if (user != null) | |||||
await _gatewayLogger.Debug($"Received Dispatch (TYPING_START)").ConfigureAwait(false); | |||||
var data = (payload as JToken).ToObject<TypingStartEvent>(_serializer); | |||||
var channel = DataStore.GetChannel(data.ChannelId) as ICachedMessageChannel; | |||||
if (channel != null) | |||||
{ | { | ||||
var before = _enablePreUpdateEvents ? user.Clone() : null; | |||||
user.Update(data); | |||||
UserUpdated.Raise(before, user); | |||||
var user = channel.GetCachedUser(data.UserId); | |||||
if (user != null) | |||||
await UserIsTyping.Raise(channel, user).ConfigureAwait(false); | |||||
} | } | ||||
else | else | ||||
await _gatewayLogger.Warning("TYPING_START referenced an unknown channel.").ConfigureAwait(false); | |||||
} | |||||
break; | |||||
//Voice | |||||
case "VOICE_STATE_UPDATE": | |||||
{ | |||||
await _gatewayLogger.Debug($"Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false); | |||||
var data = (payload as JToken).ToObject<API.VoiceState>(_serializer); | |||||
if (data.GuildId.HasValue) | |||||
{ | { | ||||
//Occurs when a user leaves a guild | |||||
//await _gatewayLogger.Warning("VOICE_STATE_UPDATE referenced an unknown user."); | |||||
var guild = DataStore.GetGuild(data.GuildId.Value); | |||||
if (guild != null) | |||||
{ | |||||
if (data.ChannelId == null) | |||||
guild.RemoveCachedVoiceState(data.UserId); | |||||
else | |||||
guild.AddOrUpdateCachedVoiceState(data); | |||||
var user = guild.GetCachedUser(data.UserId); | |||||
user.Update(data, UpdateSource.WebSocket); | |||||
} | |||||
else | |||||
await _gatewayLogger.Warning("VOICE_STATE_UPDATE referenced an unknown guild.").ConfigureAwait(false); | |||||
} | } | ||||
} | } | ||||
else | |||||
await _gatewayLogger.Warning("VOICE_STATE_UPDATE referenced an unknown guild."); | |||||
} | |||||
break; | |||||
//Settings | |||||
case "USER_UPDATE": | |||||
{ | |||||
var data = (payload as JToken).ToObject<SelfUser>(_serializer); | |||||
if (data.Id == CurrentUser.Id) | |||||
break; | |||||
//Settings | |||||
case "USER_UPDATE": | |||||
{ | { | ||||
var before = _enablePreUpdateEvents ? CurrentUser.Clone() : null; | |||||
CurrentUser.Update(data); | |||||
await CurrentUserUpdated.Raise(before, CurrentUser).ConfigureAwait(false); | |||||
await _gatewayLogger.Debug($"Received Dispatch (USER_UPDATE)").ConfigureAwait(false); | |||||
var data = (payload as JToken).ToObject<API.User>(_serializer); | |||||
if (data.Id == CurrentUser.Id) | |||||
{ | |||||
var before = _enablePreUpdateEvents ? CurrentUser.Clone() : null; | |||||
CurrentUser.Update(data, UpdateSource.WebSocket); | |||||
await CurrentUserUpdated.Raise(before, CurrentUser).ConfigureAwait(false); | |||||
} | |||||
} | } | ||||
} | |||||
break;*/ | |||||
//Ignored | |||||
case "USER_SETTINGS_UPDATE": | |||||
case "MESSAGE_ACK": //TODO: Add (User only) | |||||
case "GUILD_EMOJIS_UPDATE": //TODO: Add | |||||
case "GUILD_INTEGRATIONS_UPDATE": //TODO: Add | |||||
case "VOICE_SERVER_UPDATE": //TODO: Add | |||||
case "RESUMED": //TODO: Add | |||||
await _gatewayLogger.Debug($"Ignored Dispatch ({type})").ConfigureAwait(false); | |||||
return; | |||||
//Others | |||||
default: | |||||
await _gatewayLogger.Warning($"Unknown Dispatch ({type})").ConfigureAwait(false); | |||||
return; | |||||
} | |||||
break; | |||||
default: | |||||
await _gatewayLogger.Warning($"Unknown OpCode ({opCode})").ConfigureAwait(false); | |||||
return; | |||||
break; | |||||
//Ignored | |||||
case "USER_SETTINGS_UPDATE": | |||||
case "MESSAGE_ACK": //TODO: Add (User only) | |||||
case "GUILD_EMOJIS_UPDATE": //TODO: Add | |||||
case "GUILD_INTEGRATIONS_UPDATE": //TODO: Add | |||||
case "VOICE_SERVER_UPDATE": //TODO: Add | |||||
case "RESUMED": //TODO: Add | |||||
await _gatewayLogger.Debug($"Ignored Dispatch ({type})").ConfigureAwait(false); | |||||
return; | |||||
//Others | |||||
default: | |||||
await _gatewayLogger.Warning($"Unknown Dispatch ({type})").ConfigureAwait(false); | |||||
return; | |||||
} | |||||
break; | |||||
default: | |||||
await _gatewayLogger.Warning($"Unknown OpCode ({opCode})").ConfigureAwait(false); | |||||
return; | |||||
} | |||||
} | } | ||||
catch (Exception ex) | |||||
{ | |||||
await _gatewayLogger.Error($"Error handling {opCode}{(type != null ? $" ({type})" : "")}", ex).ConfigureAwait(false); | |||||
return; | |||||
} | |||||
#if BENCHMARK | |||||
} | } | ||||
catch (Exception ex) | |||||
finally | |||||
{ | { | ||||
await _gatewayLogger.Error($"Error handling {opCode}{(type != null ? $" ({type})" : "")}", ex).ConfigureAwait(false); | |||||
return; | |||||
stopwatch.Stop(); | |||||
double millis = Math.Round(stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2); | |||||
await _benchmarkLogger.Debug($"{millis} ms").ConfigureAwait(false); | |||||
} | } | ||||
await _gatewayLogger.Debug($"Received {opCode}{(type != null ? $" ({type})" : "")}").ConfigureAwait(false); | |||||
#endif | |||||
} | } | ||||
private async Task RunHeartbeat(int intervalMillis, CancellationToken cancelToken) | private async Task RunHeartbeat(int intervalMillis, CancellationToken cancelToken) | ||||
{ | { | ||||
@@ -57,6 +57,8 @@ namespace Discord | |||||
{ | { | ||||
await Discord.ApiClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false); | await Discord.ApiClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false); | ||||
} | } | ||||
public Role Clone() => MemberwiseClone() as Role; | |||||
public override string ToString() => Name; | public override string ToString() => Name; | ||||
private string DebuggerDisplay => $"{Name} ({Id})"; | private string DebuggerDisplay => $"{Name} ({Id})"; | ||||
@@ -5,6 +5,7 @@ using System.Collections.Immutable; | |||||
using System.Linq; | using System.Linq; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using Model = Discord.API.GuildMember; | using Model = Discord.API.GuildMember; | ||||
using VoiceStateModel = Discord.API.VoiceState; | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
@@ -24,12 +25,12 @@ namespace Discord | |||||
public string AvatarUrl => User.AvatarUrl; | public string AvatarUrl => User.AvatarUrl; | ||||
public DateTime CreatedAt => User.CreatedAt; | public DateTime CreatedAt => User.CreatedAt; | ||||
public string Discriminator => User.Discriminator; | public string Discriminator => User.Discriminator; | ||||
public Game? Game => User.Game; | |||||
public bool IsAttached => User.IsAttached; | public bool IsAttached => User.IsAttached; | ||||
public bool IsBot => User.IsBot; | public bool IsBot => User.IsBot; | ||||
public string Mention => User.Mention; | public string Mention => User.Mention; | ||||
public UserStatus Status => User.Status; | |||||
public string Username => User.Username; | public string Username => User.Username; | ||||
public virtual UserStatus Status => User.Status; | |||||
public virtual Game? Game => User.Game; | |||||
public DiscordClient Discord => Guild.Discord; | public DiscordClient Discord => Guild.Discord; | ||||
@@ -43,8 +44,10 @@ namespace Discord | |||||
{ | { | ||||
if (source == UpdateSource.Rest && IsAttached) return; | if (source == UpdateSource.Rest && IsAttached) return; | ||||
IsDeaf = model.Deaf; | |||||
IsMute = model.Mute; | |||||
if (model.Deaf.HasValue) | |||||
IsDeaf = model.Deaf.Value; | |||||
if (model.Mute.HasValue) | |||||
IsMute = model.Mute.Value; | |||||
JoinedAt = model.JoinedAt.Value; | JoinedAt = model.JoinedAt.Value; | ||||
Nickname = model.Nick; | Nickname = model.Nick; | ||||
@@ -56,6 +59,13 @@ namespace Discord | |||||
GuildPermissions = new GuildPermissions(Permissions.ResolveGuild(this)); | GuildPermissions = new GuildPermissions(Permissions.ResolveGuild(this)); | ||||
} | } | ||||
public void Update(VoiceStateModel model, UpdateSource source) | |||||
{ | |||||
if (source == UpdateSource.Rest && IsAttached) return; | |||||
IsDeaf = model.Deaf; | |||||
IsMute = model.Mute; | |||||
} | |||||
public async Task Update() | public async Task Update() | ||||
{ | { | ||||
@@ -107,6 +117,10 @@ namespace Discord | |||||
IGuild IGuildUser.Guild => Guild; | IGuild IGuildUser.Guild => Guild; | ||||
IReadOnlyCollection<IRole> IGuildUser.Roles => Roles; | IReadOnlyCollection<IRole> IGuildUser.Roles => Roles; | ||||
IVoiceChannel IGuildUser.VoiceChannel => null; | |||||
bool IVoiceState.IsSelfDeafened => false; | |||||
bool IVoiceState.IsSelfMuted => false; | |||||
bool IVoiceState.IsSuppressed => false; | |||||
IVoiceChannel IVoiceState.VoiceChannel => null; | |||||
string IVoiceState.VoiceSessionId => null; | |||||
} | } | ||||
} | } |
@@ -6,7 +6,7 @@ using Discord.API.Rest; | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
/// <summary> A Guild-User pairing. </summary> | /// <summary> A Guild-User pairing. </summary> | ||||
public interface IGuildUser : IUpdateable, IUser | |||||
public interface IGuildUser : IUpdateable, IUser, IVoiceState | |||||
{ | { | ||||
/// <summary> Returns true if the guild has deafened this user. </summary> | /// <summary> Returns true if the guild has deafened this user. </summary> | ||||
bool IsDeaf { get; } | bool IsDeaf { get; } | ||||
@@ -23,8 +23,6 @@ namespace Discord | |||||
IGuild Guild { get; } | IGuild Guild { get; } | ||||
/// <summary> Returns a collection of the roles this user is a member of in this guild, including the guild's @everyone role. </summary> | /// <summary> Returns a collection of the roles this user is a member of in this guild, including the guild's @everyone role. </summary> | ||||
IReadOnlyCollection<IRole> Roles { get; } | IReadOnlyCollection<IRole> Roles { get; } | ||||
/// <summary> Gets the voice channel this user is currently in, if any. </summary> | |||||
IVoiceChannel VoiceChannel { get; } | |||||
/// <summary> Gets the channel-level permissions granted to this user for a given channel. </summary> | /// <summary> Gets the channel-level permissions granted to this user for a given channel. </summary> | ||||
ChannelPermissions GetPermissions(IGuildChannel channel); | ChannelPermissions GetPermissions(IGuildChannel channel); | ||||
@@ -0,0 +1,16 @@ | |||||
namespace Discord | |||||
{ | |||||
public interface IVoiceState | |||||
{ | |||||
/// <summary> Returns true if this user has marked themselves as deafened. </summary> | |||||
bool IsSelfDeafened { get; } | |||||
/// <summary> Returns true if this user has marked themselves as muted. </summary> | |||||
bool IsSelfMuted { get; } | |||||
/// <summary> Returns true if the guild is temporarily blocking audio to/from this user. </summary> | |||||
bool IsSuppressed { get; } | |||||
/// <summary> Gets the voice channel this user is currently in, if any. </summary> | |||||
IVoiceChannel VoiceChannel { get; } | |||||
/// <summary> Gets the unique identifier for this user's voice session. </summary> | |||||
string VoiceSessionId { get; } | |||||
} | |||||
} |
@@ -30,7 +30,7 @@ namespace Discord | |||||
var model = await Discord.ApiClient.GetCurrentUser().ConfigureAwait(false); | var model = await Discord.ApiClient.GetCurrentUser().ConfigureAwait(false); | ||||
Update(model, UpdateSource.Rest); | Update(model, UpdateSource.Rest); | ||||
} | |||||
} | |||||
public async Task Modify(Action<ModifyCurrentUserParams> func) | public async Task Modify(Action<ModifyCurrentUserParams> func) | ||||
{ | { | ||||
if (func != null) throw new NullReferenceException(nameof(func)); | if (func != null) throw new NullReferenceException(nameof(func)); | ||||
@@ -17,9 +17,9 @@ namespace Discord | |||||
public override DiscordClient Discord { get; } | public override DiscordClient Discord { get; } | ||||
public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, _avatarId); | public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, _avatarId); | ||||
public virtual Game? Game => null; | |||||
public string Mention => MentionUtils.Mention(this, false); | public string Mention => MentionUtils.Mention(this, false); | ||||
public string NicknameMention => MentionUtils.Mention(this, true); | public string NicknameMention => MentionUtils.Mention(this, true); | ||||
public virtual Game? Game => null; | |||||
public virtual UserStatus Status => UserStatus.Unknown; | public virtual UserStatus Status => UserStatus.Unknown; | ||||
public User(DiscordClient discord, Model model) | public User(DiscordClient discord, Model model) | ||||
@@ -13,7 +13,7 @@ namespace Discord | |||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | ||||
public new CachedPublicUser Recipient => base.Recipient as CachedPublicUser; | public new CachedPublicUser Recipient => base.Recipient as CachedPublicUser; | ||||
public IReadOnlyCollection<IUser> Members => ImmutableArray.Create<IUser>(Discord.CurrentUser, Recipient); | |||||
public IReadOnlyCollection<ICachedUser> Members => ImmutableArray.Create<ICachedUser>(Discord.CurrentUser, Recipient); | |||||
public CachedDMChannel(DiscordSocketClient discord, CachedPublicUser recipient, Model model) | public CachedDMChannel(DiscordSocketClient discord, CachedPublicUser recipient, Model model) | ||||
: base(discord, recipient, model) | : base(discord, recipient, model) | ||||
@@ -21,11 +21,11 @@ namespace Discord | |||||
_messages = new MessageCache(Discord, this); | _messages = new MessageCache(Discord, this); | ||||
} | } | ||||
public override Task<IUser> GetUser(ulong id) => Task.FromResult(GetCachedUser(id)); | |||||
public override Task<IReadOnlyCollection<IUser>> GetUsers() => Task.FromResult(Members); | |||||
public override Task<IUser> GetUser(ulong id) => Task.FromResult<IUser>(GetCachedUser(id)); | |||||
public override Task<IReadOnlyCollection<IUser>> GetUsers() => Task.FromResult<IReadOnlyCollection<IUser>>(Members); | |||||
public override Task<IReadOnlyCollection<IUser>> GetUsers(int limit, int offset) | public override Task<IReadOnlyCollection<IUser>> GetUsers(int limit, int offset) | ||||
=> Task.FromResult<IReadOnlyCollection<IUser>>(Members.Skip(offset).Take(limit).ToImmutableArray()); | => Task.FromResult<IReadOnlyCollection<IUser>>(Members.Skip(offset).Take(limit).ToImmutableArray()); | ||||
public IUser GetCachedUser(ulong id) | |||||
public ICachedUser GetCachedUser(ulong id) | |||||
{ | { | ||||
var currentUser = Discord.CurrentUser; | var currentUser = Discord.CurrentUser; | ||||
if (id == Recipient.Id) | if (id == Recipient.Id) | ||||
@@ -48,7 +48,7 @@ namespace Discord | |||||
{ | { | ||||
return await _messages.Download(fromMessageId, dir, limit).ConfigureAwait(false); | return await _messages.Download(fromMessageId, dir, limit).ConfigureAwait(false); | ||||
} | } | ||||
public CachedMessage AddCachedMessage(IUser author, MessageModel model) | |||||
public CachedMessage AddCachedMessage(ICachedUser author, MessageModel model) | |||||
{ | { | ||||
var msg = new CachedMessage(this, author, model); | var msg = new CachedMessage(this, author, model); | ||||
_messages.Add(msg); | _messages.Add(msg); | ||||
@@ -11,6 +11,8 @@ using ExtendedModel = Discord.API.Gateway.ExtendedGuild; | |||||
using MemberModel = Discord.API.GuildMember; | using MemberModel = Discord.API.GuildMember; | ||||
using Model = Discord.API.Guild; | using Model = Discord.API.Guild; | ||||
using PresenceModel = Discord.API.Presence; | using PresenceModel = Discord.API.Presence; | ||||
using RoleModel = Discord.API.Role; | |||||
using VoiceStateModel = Discord.API.VoiceState; | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
@@ -20,9 +22,11 @@ namespace Discord | |||||
private ConcurrentHashSet<ulong> _channels; | private ConcurrentHashSet<ulong> _channels; | ||||
private ConcurrentDictionary<ulong, CachedGuildUser> _members; | private ConcurrentDictionary<ulong, CachedGuildUser> _members; | ||||
private ConcurrentDictionary<ulong, Presence> _presences; | private ConcurrentDictionary<ulong, Presence> _presences; | ||||
private int _userCount; | |||||
private ConcurrentDictionary<ulong, VoiceState> _voiceStates; | |||||
public bool Available { get; private set; } //TODO: Add to IGuild | public bool Available { get; private set; } //TODO: Add to IGuild | ||||
public int MemberCount { get; private set; } | |||||
public int DownloadedMemberCount { get; private set; } | |||||
public bool HasAllMembers => _downloaderPromise.Task.IsCompleted; | public bool HasAllMembers => _downloaderPromise.Task.IsCompleted; | ||||
public Task DownloaderPromise => _downloaderPromise.Task; | public Task DownloaderPromise => _downloaderPromise.Task; | ||||
@@ -32,9 +36,10 @@ namespace Discord | |||||
public IReadOnlyCollection<ICachedGuildChannel> Channels => _channels.Select(x => GetCachedChannel(x)).ToReadOnlyCollection(_channels); | public IReadOnlyCollection<ICachedGuildChannel> Channels => _channels.Select(x => GetCachedChannel(x)).ToReadOnlyCollection(_channels); | ||||
public IReadOnlyCollection<CachedGuildUser> Members => _members.ToReadOnlyCollection(); | public IReadOnlyCollection<CachedGuildUser> Members => _members.ToReadOnlyCollection(); | ||||
public CachedGuild(DiscordSocketClient discord, Model model) : base(discord, model) | |||||
public CachedGuild(DiscordSocketClient discord, ExtendedModel model, DataStore dataStore) : base(discord, model) | |||||
{ | { | ||||
_downloaderPromise = new TaskCompletionSource<bool>(); | _downloaderPromise = new TaskCompletionSource<bool>(); | ||||
Update(model, UpdateSource.Creation, dataStore); | |||||
} | } | ||||
public void Update(ExtendedModel model, UpdateSource source, DataStore dataStore) | public void Update(ExtendedModel model, UpdateSource source, DataStore dataStore) | ||||
@@ -52,6 +57,8 @@ namespace Discord | |||||
_presences = new ConcurrentDictionary<ulong, Presence>(); | _presences = new ConcurrentDictionary<ulong, Presence>(); | ||||
if (_roles == null) | if (_roles == null) | ||||
_roles = new ConcurrentDictionary<ulong, Role>(); | _roles = new ConcurrentDictionary<ulong, Role>(); | ||||
if (_voiceStates == null) | |||||
_voiceStates = new ConcurrentDictionary<ulong, VoiceState>(); | |||||
if (Emojis == null) | if (Emojis == null) | ||||
Emojis = ImmutableArray.Create<Emoji>(); | Emojis = ImmutableArray.Create<Emoji>(); | ||||
if (Features == null) | if (Features == null) | ||||
@@ -61,7 +68,7 @@ namespace Discord | |||||
base.Update(model as Model, source); | base.Update(model as Model, source); | ||||
_userCount = model.MemberCount; | |||||
MemberCount = model.MemberCount; | |||||
var channels = new ConcurrentHashSet<ulong>(); | var channels = new ConcurrentHashSet<ulong>(); | ||||
if (model.Channels != null) | if (model.Channels != null) | ||||
@@ -75,7 +82,7 @@ namespace Discord | |||||
if (model.Presences != null) | if (model.Presences != null) | ||||
{ | { | ||||
for (int i = 0; i < model.Presences.Length; i++) | for (int i = 0; i < model.Presences.Length; i++) | ||||
AddCachedPresence(model.Presences[i], presences); | |||||
AddOrUpdateCachedPresence(model.Presences[i], presences); | |||||
} | } | ||||
_presences = presences; | _presences = presences; | ||||
@@ -85,10 +92,19 @@ namespace Discord | |||||
for (int i = 0; i < model.Members.Length; i++) | for (int i = 0; i < model.Members.Length; i++) | ||||
AddCachedUser(model.Members[i], members, dataStore); | AddCachedUser(model.Members[i], members, dataStore); | ||||
_downloaderPromise = new TaskCompletionSource<bool>(); | _downloaderPromise = new TaskCompletionSource<bool>(); | ||||
DownloadedMemberCount = model.Members.Length; | |||||
if (!model.Large) | if (!model.Large) | ||||
_downloaderPromise.SetResult(true); | _downloaderPromise.SetResult(true); | ||||
} | } | ||||
_members = members; | _members = members; | ||||
var voiceStates = new ConcurrentDictionary<ulong, VoiceState>(); | |||||
if (model.VoiceStates != null) | |||||
{ | |||||
for (int i = 0; i < model.VoiceStates.Length; i++) | |||||
AddOrUpdateCachedVoiceState(model.VoiceStates[i], _voiceStates); | |||||
} | |||||
_voiceStates = voiceStates; | |||||
} | } | ||||
public override Task<IGuildChannel> GetChannel(ulong id) => Task.FromResult<IGuildChannel>(GetCachedChannel(id)); | public override Task<IGuildChannel> GetChannel(ulong id) => Task.FromResult<IGuildChannel>(GetCachedChannel(id)); | ||||
@@ -108,7 +124,7 @@ namespace Discord | |||||
(channels ?? _channels).TryRemove(id); | (channels ?? _channels).TryRemove(id); | ||||
} | } | ||||
public Presence AddCachedPresence(PresenceModel model, ConcurrentDictionary<ulong, Presence> presences = null) | |||||
public Presence AddOrUpdateCachedPresence(PresenceModel model, ConcurrentDictionary<ulong, Presence> presences = null) | |||||
{ | { | ||||
var game = model.Game != null ? new Game(model.Game) : (Game?)null; | var game = model.Game != null ? new Game(model.Game) : (Game?)null; | ||||
var presence = new Presence(model.Status, game); | var presence = new Presence(model.Status, game); | ||||
@@ -130,6 +146,42 @@ namespace Discord | |||||
return null; | return null; | ||||
} | } | ||||
public Role AddCachedRole(RoleModel model, ConcurrentDictionary<ulong, Role> roles = null) | |||||
{ | |||||
var role = new Role(this, model); | |||||
(roles ?? _roles)[model.Id] = role; | |||||
return role; | |||||
} | |||||
public Role RemoveCachedRole(ulong id) | |||||
{ | |||||
Role role; | |||||
if (_roles.TryRemove(id, out role)) | |||||
return role; | |||||
return null; | |||||
} | |||||
public VoiceState AddOrUpdateCachedVoiceState(VoiceStateModel model, ConcurrentDictionary<ulong, VoiceState> voiceStates = null) | |||||
{ | |||||
var voiceChannel = GetCachedChannel(model.ChannelId.Value) as CachedVoiceChannel; | |||||
var voiceState = new VoiceState(voiceChannel, model.SessionId, model.SelfMute, model.SelfDeaf, model.Suppress); | |||||
(voiceStates ?? _voiceStates)[model.UserId] = voiceState; | |||||
return voiceState; | |||||
} | |||||
public VoiceState? GetCachedVoiceState(ulong id) | |||||
{ | |||||
VoiceState voiceState; | |||||
if (_voiceStates.TryGetValue(id, out voiceState)) | |||||
return voiceState; | |||||
return null; | |||||
} | |||||
public VoiceState? RemoveCachedVoiceState(ulong id) | |||||
{ | |||||
VoiceState voiceState; | |||||
if (_voiceStates.TryRemove(id, out voiceState)) | |||||
return voiceState; | |||||
return null; | |||||
} | |||||
public override Task<IGuildUser> GetUser(ulong id) => Task.FromResult<IGuildUser>(GetCachedUser(id)); | public override Task<IGuildUser> GetUser(ulong id) => Task.FromResult<IGuildUser>(GetCachedUser(id)); | ||||
public override Task<IGuildUser> GetCurrentUser() | public override Task<IGuildUser> GetCurrentUser() | ||||
=> Task.FromResult<IGuildUser>(CurrentUser); | => Task.FromResult<IGuildUser>(CurrentUser); | ||||
@@ -140,10 +192,11 @@ namespace Discord | |||||
=> Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members.OrderBy(x => x.Id).Skip(offset).Take(limit).ToImmutableArray()); | => Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members.OrderBy(x => x.Id).Skip(offset).Take(limit).ToImmutableArray()); | ||||
public CachedGuildUser AddCachedUser(MemberModel model, ConcurrentDictionary<ulong, CachedGuildUser> members = null, DataStore dataStore = null) | public CachedGuildUser AddCachedUser(MemberModel model, ConcurrentDictionary<ulong, CachedGuildUser> members = null, DataStore dataStore = null) | ||||
{ | { | ||||
var user = Discord.AddCachedUser(model.User); | |||||
var user = Discord.GetOrAddCachedUser(model.User); | |||||
var member = new CachedGuildUser(this, user, model); | var member = new CachedGuildUser(this, user, model); | ||||
(members ?? _members)[user.Id] = member; | (members ?? _members)[user.Id] = member; | ||||
user.AddRef(); | user.AddRef(); | ||||
DownloadedMemberCount++; | |||||
return member; | return member; | ||||
} | } | ||||
public CachedGuildUser GetCachedUser(ulong id) | public CachedGuildUser GetCachedUser(ulong id) | ||||
@@ -160,7 +213,6 @@ namespace Discord | |||||
return member; | return member; | ||||
return null; | return null; | ||||
} | } | ||||
public async Task DownloadMembers() | public async Task DownloadMembers() | ||||
{ | { | ||||
if (!HasAllMembers) | if (!HasAllMembers) | ||||
@@ -169,7 +221,7 @@ namespace Discord | |||||
} | } | ||||
public void CompleteDownloadMembers() | public void CompleteDownloadMembers() | ||||
{ | { | ||||
_downloaderPromise.SetResult(true); | |||||
_downloaderPromise.TrySetResult(true); | |||||
} | } | ||||
public CachedGuild Clone() => MemberwiseClone() as CachedGuild; | public CachedGuild Clone() => MemberwiseClone() as CachedGuild; | ||||
@@ -2,11 +2,21 @@ | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
internal class CachedGuildUser : GuildUser, ICachedEntity<ulong> | |||||
internal class CachedGuildUser : GuildUser, ICachedUser | |||||
{ | { | ||||
public VoiceChannel VoiceChannel { get; private set; } | |||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | ||||
public new CachedGuild Guild => base.Guild as CachedGuild; | |||||
public new CachedPublicUser User => base.User as CachedPublicUser; | |||||
public Presence? Presence => Guild.GetCachedPresence(Id); | |||||
public override Game? Game => Presence?.Game; | |||||
public override UserStatus Status => Presence?.Status ?? UserStatus.Offline; | |||||
public VoiceState? VoiceState => Guild.GetCachedVoiceState(Id); | |||||
public bool IsSelfDeafened => VoiceState?.IsSelfDeafened ?? false; | |||||
public bool IsSelfMuted => VoiceState?.IsSelfMuted ?? false; | |||||
public bool IsSuppressed => VoiceState?.IsSuppressed ?? false; | |||||
public CachedVoiceChannel VoiceChannel => VoiceState?.VoiceChannel; | |||||
public CachedGuildUser(CachedGuild guild, CachedPublicUser user, Model model) | public CachedGuildUser(CachedGuild guild, CachedPublicUser user, Model model) | ||||
: base(guild, user, model) | : base(guild, user, model) | ||||
@@ -14,5 +24,6 @@ namespace Discord | |||||
} | } | ||||
public CachedGuildUser Clone() => MemberwiseClone() as CachedGuildUser; | public CachedGuildUser Clone() => MemberwiseClone() as CachedGuildUser; | ||||
ICachedUser ICachedUser.Clone() => Clone(); | |||||
} | } | ||||
} | } |
@@ -1,15 +1,20 @@ | |||||
using ChannelModel = Discord.API.Channel; | using ChannelModel = Discord.API.Channel; | ||||
using Model = Discord.API.User; | using Model = Discord.API.User; | ||||
using PresenceModel = Discord.API.Presence; | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
internal class CachedPublicUser : User, ICachedEntity<ulong> | |||||
internal class CachedPublicUser : User, ICachedUser | |||||
{ | { | ||||
private int _references; | private int _references; | ||||
private Game? _game; | |||||
private UserStatus _status; | |||||
public CachedDMChannel DMChannel { get; private set; } | public CachedDMChannel DMChannel { get; private set; } | ||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | ||||
public override UserStatus Status => _status; | |||||
public override Game? Game => _game; | |||||
public CachedPublicUser(DiscordSocketClient discord, Model model) | public CachedPublicUser(DiscordSocketClient discord, Model model) | ||||
: base(discord, model) | : base(discord, model) | ||||
@@ -39,6 +44,16 @@ namespace Discord | |||||
} | } | ||||
} | } | ||||
public void Update(PresenceModel model, UpdateSource source) | |||||
{ | |||||
if (source == UpdateSource.Rest) return; | |||||
var game = model.Game != null ? new Game(model.Game) : (Game?)null; | |||||
_status = model.Status; | |||||
_game = game; | |||||
} | |||||
public void AddRef() | public void AddRef() | ||||
{ | { | ||||
lock (this) | lock (this) | ||||
@@ -54,5 +69,6 @@ namespace Discord | |||||
} | } | ||||
public CachedPublicUser Clone() => MemberwiseClone() as CachedPublicUser; | public CachedPublicUser Clone() => MemberwiseClone() as CachedPublicUser; | ||||
ICachedUser ICachedUser.Clone() => Clone(); | |||||
} | } | ||||
} | } |
@@ -2,7 +2,7 @@ | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
internal class CachedSelfUser : SelfUser, ICachedEntity<ulong> | |||||
internal class CachedSelfUser : SelfUser, ICachedUser | |||||
{ | { | ||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | ||||
@@ -12,5 +12,6 @@ namespace Discord | |||||
} | } | ||||
public CachedSelfUser Clone() => MemberwiseClone() as CachedSelfUser; | public CachedSelfUser Clone() => MemberwiseClone() as CachedSelfUser; | ||||
ICachedUser ICachedUser.Clone() => Clone(); | |||||
} | } | ||||
} | } |
@@ -14,7 +14,7 @@ namespace Discord | |||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | ||||
public new CachedGuild Guild => base.Guild as CachedGuild; | public new CachedGuild Guild => base.Guild as CachedGuild; | ||||
public IReadOnlyCollection<IGuildUser> Members | |||||
public IReadOnlyCollection<CachedGuildUser> Members | |||||
=> Guild.Members.Where(x => Permissions.GetValue(Permissions.ResolveChannel(x, this, x.GuildPermissions.RawValue), ChannelPermission.ReadMessages)).ToImmutableArray(); | => Guild.Members.Where(x => Permissions.GetValue(Permissions.ResolveChannel(x, this, x.GuildPermissions.RawValue), ChannelPermission.ReadMessages)).ToImmutableArray(); | ||||
public CachedTextChannel(CachedGuild guild, Model model) | public CachedTextChannel(CachedGuild guild, Model model) | ||||
@@ -23,11 +23,11 @@ namespace Discord | |||||
_messages = new MessageCache(Discord, this); | _messages = new MessageCache(Discord, this); | ||||
} | } | ||||
public override Task<IGuildUser> GetUser(ulong id) => Task.FromResult(GetCachedUser(id)); | |||||
public override Task<IReadOnlyCollection<IGuildUser>> GetUsers() => Task.FromResult(Members); | |||||
public override Task<IGuildUser> GetUser(ulong id) => Task.FromResult<IGuildUser>(GetCachedUser(id)); | |||||
public override Task<IReadOnlyCollection<IGuildUser>> GetUsers() => Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members); | |||||
public override Task<IReadOnlyCollection<IGuildUser>> GetUsers(int limit, int offset) | public override Task<IReadOnlyCollection<IGuildUser>> GetUsers(int limit, int offset) | ||||
=> Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members.Skip(offset).Take(limit).ToImmutableArray()); | => Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members.Skip(offset).Take(limit).ToImmutableArray()); | ||||
public IGuildUser GetCachedUser(ulong id) | |||||
public CachedGuildUser GetCachedUser(ulong id) | |||||
{ | { | ||||
var user = Guild.GetCachedUser(id); | var user = Guild.GetCachedUser(id); | ||||
if (user != null && Permissions.GetValue(Permissions.ResolveChannel(user, this, user.GuildPermissions.RawValue), ChannelPermission.ReadMessages)) | if (user != null && Permissions.GetValue(Permissions.ResolveChannel(user, this, user.GuildPermissions.RawValue), ChannelPermission.ReadMessages)) | ||||
@@ -48,7 +48,7 @@ namespace Discord | |||||
return await _messages.Download(fromMessageId, dir, limit).ConfigureAwait(false); | return await _messages.Download(fromMessageId, dir, limit).ConfigureAwait(false); | ||||
} | } | ||||
public CachedMessage AddCachedMessage(IUser author, MessageModel model) | |||||
public CachedMessage AddCachedMessage(ICachedUser author, MessageModel model) | |||||
{ | { | ||||
var msg = new CachedMessage(this, author, model); | var msg = new CachedMessage(this, author, model); | ||||
_messages.Add(msg); | _messages.Add(msg); | ||||
@@ -65,10 +65,10 @@ namespace Discord | |||||
public CachedTextChannel Clone() => MemberwiseClone() as CachedTextChannel; | public CachedTextChannel Clone() => MemberwiseClone() as CachedTextChannel; | ||||
IReadOnlyCollection<IUser> ICachedMessageChannel.Members => Members; | |||||
IReadOnlyCollection<ICachedUser> ICachedMessageChannel.Members => Members; | |||||
IMessage IMessageChannel.GetCachedMessage(ulong id) => GetCachedMessage(id); | IMessage IMessageChannel.GetCachedMessage(ulong id) => GetCachedMessage(id); | ||||
IUser ICachedMessageChannel.GetCachedUser(ulong id) => GetCachedUser(id); | |||||
ICachedUser ICachedMessageChannel.GetCachedUser(ulong id) => GetCachedUser(id); | |||||
ICachedChannel ICachedChannel.Clone() => Clone(); | ICachedChannel ICachedChannel.Clone() => Clone(); | ||||
} | } | ||||
} | } |
@@ -5,12 +5,12 @@ namespace Discord | |||||
{ | { | ||||
internal interface ICachedMessageChannel : ICachedChannel, IMessageChannel | internal interface ICachedMessageChannel : ICachedChannel, IMessageChannel | ||||
{ | { | ||||
IReadOnlyCollection<IUser> Members { get; } | |||||
IReadOnlyCollection<ICachedUser> Members { get; } | |||||
CachedMessage AddCachedMessage(IUser author, MessageModel model); | |||||
CachedMessage AddCachedMessage(ICachedUser author, MessageModel model); | |||||
new CachedMessage GetCachedMessage(ulong id); | new CachedMessage GetCachedMessage(ulong id); | ||||
CachedMessage RemoveCachedMessage(ulong id); | CachedMessage RemoveCachedMessage(ulong id); | ||||
IUser GetCachedUser(ulong id); | |||||
ICachedUser GetCachedUser(ulong id); | |||||
} | } | ||||
} | } |
@@ -0,0 +1,7 @@ | |||||
namespace Discord | |||||
{ | |||||
internal interface ICachedUser : IUser, ICachedEntity<ulong> | |||||
{ | |||||
ICachedUser Clone(); | |||||
} | |||||
} |
@@ -1,62 +0,0 @@ | |||||
/*using System; | |||||
using Model = Discord.API.MemberVoiceState; | |||||
namespace Discord.WebSocket | |||||
{ | |||||
internal class VoiceState : IVoiceState | |||||
{ | |||||
[Flags] | |||||
private enum VoiceStates : byte | |||||
{ | |||||
None = 0x0, | |||||
Muted = 0x01, | |||||
Deafened = 0x02, | |||||
Suppressed = 0x4, | |||||
SelfMuted = 0x10, | |||||
SelfDeafened = 0x20, | |||||
} | |||||
private VoiceStates _voiceStates; | |||||
public Guild Guild { get; } | |||||
public ulong UserId { get; } | |||||
/// <summary> Gets this user's current voice channel. </summary> | |||||
public VoiceChannel VoiceChannel { get; set; } | |||||
/// <summary> Returns true if this user has marked themselves as muted. </summary> | |||||
public bool IsSelfMuted => (_voiceStates & VoiceStates.SelfMuted) != 0; | |||||
/// <summary> Returns true if this user has marked themselves as deafened. </summary> | |||||
public bool IsSelfDeafened => (_voiceStates & VoiceStates.SelfDeafened) != 0; | |||||
/// <summary> Returns true if the guild is blocking audio from this user. </summary> | |||||
public bool IsMuted => (_voiceStates & VoiceStates.Muted) != 0; | |||||
/// <summary> Returns true if the guild is blocking audio to this user. </summary> | |||||
public bool IsDeafened => (_voiceStates & VoiceStates.Deafened) != 0; | |||||
/// <summary> Returns true if the guild is temporarily blocking audio to/from this user. </summary> | |||||
public bool IsSuppressed => (_voiceStates & VoiceStates.Suppressed) != 0; | |||||
public VoiceState(ulong userId, Guild guild) | |||||
{ | |||||
UserId = userId; | |||||
Guild = guild; | |||||
} | |||||
private void Update(Model model, UpdateSource source) | |||||
{ | |||||
if (model.IsMuted == true) | |||||
_voiceStates |= VoiceStates.Muted; | |||||
else if (model.IsMuted == false) | |||||
_voiceStates &= ~VoiceStates.Muted; | |||||
if (model.IsDeafened == true) | |||||
_voiceStates |= VoiceStates.Deafened; | |||||
else if (model.IsDeafened == false) | |||||
_voiceStates &= ~VoiceStates.Deafened; | |||||
if (model.IsSuppressed == true) | |||||
_voiceStates |= VoiceStates.Suppressed; | |||||
else if (model.IsSuppressed == false) | |||||
_voiceStates &= ~VoiceStates.Suppressed; | |||||
} | |||||
} | |||||
}*/ |
@@ -0,0 +1,42 @@ | |||||
using System; | |||||
namespace Discord | |||||
{ | |||||
internal struct VoiceState : IVoiceState | |||||
{ | |||||
[Flags] | |||||
private enum Flags : byte | |||||
{ | |||||
None = 0x0, | |||||
Suppressed = 0x1, | |||||
SelfMuted = 0x2, | |||||
SelfDeafened = 0x4, | |||||
} | |||||
private readonly Flags _voiceStates; | |||||
public CachedVoiceChannel VoiceChannel { get; } | |||||
public string VoiceSessionId { get; } | |||||
public bool IsSelfMuted => (_voiceStates & Flags.SelfMuted) != 0; | |||||
public bool IsSelfDeafened => (_voiceStates & Flags.SelfDeafened) != 0; | |||||
public bool IsSuppressed => (_voiceStates & Flags.Suppressed) != 0; | |||||
public VoiceState(CachedVoiceChannel voiceChannel, string sessionId, bool isSelfMuted, bool isSelfDeafened, bool isSuppressed) | |||||
{ | |||||
VoiceChannel = voiceChannel; | |||||
VoiceSessionId = sessionId; | |||||
Flags voiceStates = Flags.None; | |||||
if (isSelfMuted) | |||||
voiceStates |= Flags.SelfMuted; | |||||
if (isSelfDeafened) | |||||
voiceStates |= Flags.SelfDeafened; | |||||
if (isSuppressed) | |||||
voiceStates |= Flags.Suppressed; | |||||
_voiceStates = voiceStates; | |||||
} | |||||
IVoiceChannel IVoiceState.VoiceChannel => VoiceChannel; | |||||
} | |||||
} |
@@ -15,6 +15,7 @@ | |||||
"buildOptions": { | "buildOptions": { | ||||
"allowUnsafe": true, | "allowUnsafe": true, | ||||
"define": [ "BENCHMARK" ], | |||||
"warningsAsErrors": false | "warningsAsErrors": false | ||||
}, | }, | ||||