| @@ -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 | ||||
| }, | }, | ||||