From 64c3409a959871167cbd921f0f4be6b1e0eac08e Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 15 Sep 2015 12:41:14 -0300 Subject: [PATCH] Converted DataWebSocket events, added state timestamp, added voiceactivity config --- .../Collections/AsyncCollection.cs | 31 +- src/Discord.Net/DiscordClient.API.cs | 9 +- src/Discord.Net/DiscordClient.Events.cs | 105 +++-- src/Discord.Net/DiscordClient.cs | 375 +++++++++++++++++- src/Discord.Net/DiscordClientConfig.cs | 7 +- src/Discord.Net/Models/Member.cs | 7 +- src/Discord.Net/Models/Server.cs | 4 +- .../Net/WebSockets/DataWebSocket.cs | 11 +- .../Net/WebSockets/DataWebSockets.Events.cs | 8 +- .../Net/WebSockets/VoiceWebSocket.cs | 27 +- 10 files changed, 502 insertions(+), 82 deletions(-) diff --git a/src/Discord.Net/Collections/AsyncCollection.cs b/src/Discord.Net/Collections/AsyncCollection.cs index 21c5be04d..3449c14eb 100644 --- a/src/Discord.Net/Collections/AsyncCollection.cs +++ b/src/Discord.Net/Collections/AsyncCollection.cs @@ -16,6 +16,13 @@ namespace Discord.Collections public TValue Item { get; } public CollectionItemEventArgs(TValue item) { Item = item; } } + internal class CollectionItemRemappedEventArgs : EventArgs + { + public TValue Item { get; } + public string OldId { get; } + public string NewId { get; } + public CollectionItemRemappedEventArgs(TValue item, string oldId, string newId) { Item = item; OldId = oldId; NewId = newId; } + } internal EventHandler ItemCreated; private void RaiseItemCreated(TValue item) @@ -23,18 +30,25 @@ namespace Discord.Collections if (ItemCreated != null) ItemCreated(this, new CollectionItemEventArgs(item)); } - internal EventHandler ItemUpdated; - protected void RaiseItemUpdated(TValue item) - { - if (ItemUpdated != null) - ItemUpdated(this, new CollectionItemEventArgs(item)); - } internal EventHandler ItemDestroyed; private void RaiseItemDestroyed(TValue item) { if (ItemDestroyed != null) ItemDestroyed(this, new CollectionItemEventArgs(item)); } + internal EventHandler ItemRemapped; + private void RaiseItemRemapped(TValue item, string oldId, string newId) + { + if (ItemRemapped != null) + ItemRemapped(this, new CollectionItemRemappedEventArgs(item, oldId, newId)); + } + + internal EventHandler Cleared; + private void RaiseCleared() + { + if (Cleared != null) + Cleared(this, EventArgs.Empty); + } protected readonly DiscordClient _client; protected readonly ConcurrentDictionary _dictionary; @@ -100,8 +114,11 @@ namespace Discord.Collections protected internal void Clear() { lock (_writerLock) + { _dictionary.Clear(); - } + RaiseCleared(); + } + } protected abstract void OnCreated(TValue item); protected abstract void OnRemoved(TValue item); diff --git a/src/Discord.Net/DiscordClient.API.cs b/src/Discord.Net/DiscordClient.API.cs index 9a3a5b8d4..464889a1f 100644 --- a/src/Discord.Net/DiscordClient.API.cs +++ b/src/Discord.Net/DiscordClient.API.cs @@ -404,9 +404,12 @@ namespace Discord { var msg = _messages.GetOrAdd(x.Id, x.ChannelId); msg.Update(x); - var user = msg.User; - if (user != null) - user.UpdateActivity(x.Timestamp); + if (_config.TrackActivity) + { + var user = msg.User; + if (user != null) + user.UpdateActivity(x.Timestamp); + } return msg; }) .ToArray(); diff --git a/src/Discord.Net/DiscordClient.Events.cs b/src/Discord.Net/DiscordClient.Events.cs index 17fc476f0..31edb5f8e 100644 --- a/src/Discord.Net/DiscordClient.Events.cs +++ b/src/Discord.Net/DiscordClient.Events.cs @@ -15,6 +15,7 @@ namespace Discord Unknown = 0, Authentication, Cache, + Client, DataWebSocket, MessageQueue, Rest, @@ -26,68 +27,106 @@ namespace Discord public readonly LogMessageSeverity Severity; public readonly LogMessageSource Source; public readonly string Message; + internal LogMessageEventArgs(LogMessageSeverity severity, LogMessageSource source, string msg) { Severity = severity; Source = source; Message = msg; } } public sealed class ServerEventArgs : EventArgs { public readonly Server Server; + public string ServerId => Server.Id; + internal ServerEventArgs(Server server) { Server = server; } } public sealed class ChannelEventArgs : EventArgs { public readonly Channel Channel; + public string ChannelId => Channel.Id; + public Server Server => Channel.Server; + public string ServerId => Channel.ServerId; + internal ChannelEventArgs(Channel channel) { Channel = channel; } } public sealed class UserEventArgs : EventArgs { public readonly User User; + public string UserId => User.Id; + internal UserEventArgs(User user) { User = user; } } public sealed class MessageEventArgs : EventArgs { public readonly Message Message; + public string MessageId => Message.Id; + public Member Member => Message.Member; + public Channel Channel => Message.Channel; + public string ChannelId => Message.ChannelId; + public Server Server => Message.Server; + public string ServerId => Message.ServerId; + public User User => Member.User; + public string UserId => Message.UserId; + internal MessageEventArgs(Message msg) { Message = msg; } } public sealed class RoleEventArgs : EventArgs { public readonly Role Role; + public string RoleId => Role.Id; + public Server Server => Role.Server; + public string ServerId => Role.ServerId; + internal RoleEventArgs(Role role) { Role = role; } } public sealed class BanEventArgs : EventArgs { public readonly User User; + public readonly string UserId; public readonly Server Server; - internal BanEventArgs(User user, Server server) + public string ServerId => Server.Id; + + internal BanEventArgs(User user, string userId, Server server) { User = user; + UserId = userId; Server = server; } } public sealed class MemberEventArgs : EventArgs { public readonly Member Member; + public User User => Member.User; + public string UserId => Member.UserId; + public Server Server => Member.Server; + public string ServerId => Member.ServerId; + internal MemberEventArgs(Member member) { Member = member; } } public sealed class UserTypingEventArgs : EventArgs { - public readonly User User; + public readonly Member Member; public readonly Channel Channel; - internal UserTypingEventArgs(User user, Channel channel) + public string ChannelId => Channel.Id; + public Server Server => Channel.Server; + public string ServerId => Channel.ServerId; + public User User => Member.User; + public string UserId => Member.UserId; + + internal UserTypingEventArgs(Member member, Channel channel) { - User = user; + Member = member; Channel = channel; - } + } } - public sealed class VoiceServerUpdatedEventArgs : EventArgs + /*public sealed class VoiceServerUpdatedEventArgs : EventArgs { public readonly Server Server; + public string ServerId => Server.Id; public readonly string Endpoint; internal VoiceServerUpdatedEventArgs(Server server, string endpoint) { Server = server; Endpoint = endpoint; } - } + }*/ public partial class DiscordClient { @@ -178,11 +217,11 @@ namespace Discord if (MessageUpdated != null) MessageUpdated(this, new MessageEventArgs(msg)); } - public event EventHandler MessageRead; - private void RaiseMessageRead(Message msg) + public event EventHandler MessageReadRemotely; + private void RaiseMessageReadRemotely(Message msg) { - if (MessageRead != null) - MessageRead(this, new MessageEventArgs(msg)); + if (MessageReadRemotely != null) + MessageReadRemotely(this, new MessageEventArgs(msg)); } public event EventHandler MessageSent; private void RaiseMessageSent(Message msg) @@ -213,16 +252,16 @@ namespace Discord //Ban public event EventHandler BanAdded; - private void RaiseBanAdded(User user, Server server) + private void RaiseBanAdded(string userId, Server server) { if (BanAdded != null) - BanAdded(this, new BanEventArgs(user, server)); + BanAdded(this, new BanEventArgs(_users[userId], userId, server)); } public event EventHandler BanRemoved; - private void RaiseBanRemoved(User user, Server server) + private void RaiseBanRemoved(string userId, Server server) { if (BanRemoved != null) - BanRemoved(this, new BanEventArgs(user, server)); + BanRemoved(this, new BanEventArgs(_users[userId], userId, server)); } //Member @@ -244,26 +283,24 @@ namespace Discord if (MemberUpdated != null) MemberUpdated(this, new MemberEventArgs(member)); } - - //Status - public event EventHandler PresenceUpdated; - private void RaisePresenceUpdated(Member member) + public event EventHandler MemberPresenceUpdated; + private void RaiseMemberPresenceUpdated(Member member) { - if (PresenceUpdated != null) - PresenceUpdated(this, new MemberEventArgs(member)); + if (MemberPresenceUpdated != null) + MemberPresenceUpdated(this, new MemberEventArgs(member)); } - public event EventHandler VoiceStateUpdated; - private void RaiseVoiceStateUpdated(Member member) + public event EventHandler MemberVoiceStateUpdated; + private void RaiseMemberVoiceStateUpdated(Member member) { - if (VoiceStateUpdated != null) - VoiceStateUpdated(this, new MemberEventArgs(member)); + if (MemberVoiceStateUpdated != null) + MemberVoiceStateUpdated(this, new MemberEventArgs(member)); } - public event EventHandler UserTyping; - private void RaiseUserTyping(User user, Channel channel) + public event EventHandler MemberIsTyping; + private void RaiseMemberIsTyping(Member member, Channel channel) { - if (UserTyping != null) - UserTyping(this, new UserTypingEventArgs(user, channel)); - } + if (MemberIsTyping != null) + MemberIsTyping(this, new UserTypingEventArgs(member, channel)); + } //Voice public event EventHandler VoiceConnected; @@ -278,11 +315,11 @@ namespace Discord if (VoiceDisconnected != null) VoiceDisconnected(this, EventArgs.Empty); } - public event EventHandler VoiceServerUpdated; + /*public event EventHandler VoiceServerChanged; private void RaiseVoiceServerUpdated(Server server, string endpoint) { - if (VoiceServerUpdated != null) - VoiceServerUpdated(this, new VoiceServerUpdatedEventArgs(server, endpoint)); - } + if (VoiceServerChanged != null) + VoiceServerChanged(this, new VoiceServerUpdatedEventArgs(server, endpoint)); + }*/ } } diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index 5c94667be..96490ecbc 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -3,6 +3,7 @@ using Discord.Helpers; using Discord.Net; using Discord.Net.API; using Discord.Net.WebSockets; +using Newtonsoft.Json; using System; using System.Collections.Concurrent; using System.Net; @@ -30,6 +31,7 @@ namespace Discord private readonly ConcurrentQueue _pendingMessages; private readonly ManualResetEvent _disconnectedEvent; private readonly ManualResetEventSlim _connectedEvent; + private readonly JsonSerializer _serializer; private Task _runTask; protected ExceptionDispatchInfo _disconnectReason; private bool _wasDisconnectUnexpected; @@ -78,7 +80,7 @@ namespace Discord _dataSocket = new DataWebSocket(this); _dataSocket.Connected += (s, e) => { if (_state == (int)DiscordClientState.Connecting) CompleteConnect(); }; _voiceSocket = new VoiceWebSocket(this); - + _channels = new Channels(this); _members = new Members(this); _messages = new Messages(this); @@ -87,47 +89,380 @@ namespace Discord _users = new Users(this); _dataSocket.LogMessage += (s, e) => RaiseOnLog(e.Severity, LogMessageSource.DataWebSocket, e.Message); - _voiceSocket.LogMessage += (s, e) => RaiseOnLog(e.Severity, LogMessageSource.DataWebSocket, e.Message); + _voiceSocket.LogMessage += (s, e) => RaiseOnLog(e.Severity, LogMessageSource.VoiceWebSocket, e.Message); if (_config.LogLevel >= LogMessageSeverity.Info) { _dataSocket.Connected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.DataWebSocket, "Connected"); _dataSocket.Disconnected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.DataWebSocket, "Disconnected"); + _dataSocket.ReceievedEvent += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.DataWebSocket, $"Receieved {e.Type}"); _voiceSocket.Connected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.VoiceWebSocket, "Connected"); _voiceSocket.Disconnected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.VoiceWebSocket, "Disconnected"); } if (_config.LogLevel >= LogMessageSeverity.Verbose) { + Connected += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, "Connected"); + Disconnected += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, "Disconnected"); + ServerCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Created Server: {e.Server.Name} ({e.Server.Id})"); + ServerDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Destroyed Server: {e.Server.Name} ({e.Server.Id})"); + ServerUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Updated Server: {e.Server.Name} ({e.Server.Id})"); + UserUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Updated User: {e.User.Name} ({e.UserId})"); + ChannelCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Created Channel: {e.Server.Name}/{e.Channel.Name} ({e.ServerId}/{e.ChannelId})"); + ChannelDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Destroyed Channel: {e.Server.Name}/{e.Channel.Name} ({e.ServerId}/{e.ChannelId})"); + ChannelUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Updated Channel: {e.Server.Name}/{e.Channel.Name} ({e.ServerId}/{e.ChannelId})"); + MessageCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Created Message: {e.Server.Name}/{e.Channel.Name}/{e.MessageId} ({e.ServerId}/{e.ChannelId}/{e.MessageId})"); + MessageDeleted += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Deleted Message: {e.Server.Name}/{e.Channel.Name}/{e.MessageId} ({e.ServerId}/{e.ChannelId}/{e.MessageId})"); + MessageUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Updated Message: {e.Server.Name}/{e.Channel.Name}/{e.MessageId} ({e.ServerId}/{e.ChannelId}/{e.MessageId})"); + MessageReadRemotely += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Read Message (Remotely): {e.Server.Name}/{e.Channel.Name}/{e.MessageId} ({e.ServerId}/{e.ChannelId}/{e.MessageId})"); + MessageSent += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Sent Message: {e.Server.Name}/{e.Channel.Name}/{e.MessageId} ({e.ServerId}/{e.ChannelId}/{e.MessageId})"); + RoleCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Created Role: {e.Server.Name}/{e.Role.Name} ({e.ServerId}/{e.RoleId})."); + RoleUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Update Role: {e.Server.Name}/{e.Role.Name} ({e.ServerId}/{e.RoleId})."); + RoleDeleted += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Deleted Role: {e.Server.Name}/{e.Role.Name} ({e.ServerId}/{e.RoleId})."); + BanAdded += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Added Ban: {e.Server.Name}/{e.User?.Name ?? "Unknown"} ({e.ServerId}/{e.UserId})."); + BanRemoved += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Removed Ban: {e.Server.Name}/{e.User?.Name ?? "Unknown"} ({e.ServerId}/{e.UserId})."); + MemberAdded += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Added Member: {e.Server.Name}/{e.User.Name} ({e.ServerId}/{e.UserId})."); + MemberRemoved += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Removed Member: {e.Server.Name}/{e.User.Name} ({e.ServerId}/{e.UserId})."); + MemberUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Updated Member: {e.Server.Name}/{e.User.Name} ({e.ServerId}/{e.UserId})."); + MemberPresenceUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Updated Member (Presence): {e.Server.Name}/{e.User.Name} ({e.ServerId}/{e.UserId})"); + MemberVoiceStateUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Updated Member (Voice State): {e.Server.Name}/{e.User.Name} ({e.ServerId}/{e.UserId})"); + MemberIsTyping += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Updated Member (Is Typing): {e.Server.Name}/{e.Channel.Name}/{e.User.Name} ({e.ServerId}/{e.ChannelId}/{e.UserId})"); + VoiceConnected += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Voice Connected"); + VoiceDisconnected += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Voice Disconnected"); + _channels.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Created Channel {e.Item.ServerId}/{e.Item.Id}"); - _channels.ItemUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Updated Channel {e.Item.ServerId}/{e.Item.Id}"); _channels.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Destroyed Channel {e.Item.ServerId}/{e.Item.Id}"); + _channels.Cleared += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Cleared Channels"); _members.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Created Member {e.Item.ServerId}/{e.Item.UserId}"); - _members.ItemUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Updated Member {e.Item.ServerId}/{e.Item.UserId}"); _members.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Destroyed Member {e.Item.ServerId}/{e.Item.UserId}"); + _members.Cleared += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Cleared Members"); _messages.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Created Message {e.Item.ServerId}/{e.Item.ChannelId}/{e.Item.Id}"); - _messages.ItemUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Updated Message {e.Item.ServerId}/{e.Item.ChannelId}/{e.Item.Id}"); _messages.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Destroyed Message {e.Item.ServerId}/{e.Item.ChannelId}/{e.Item.Id}"); + _messages.ItemRemapped += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Remapped Message {e.Item.ServerId}/{e.Item.ChannelId}/[{e.OldId} -> {e.NewId}]"); + _messages.Cleared += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Cleared Members"); _roles.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Created Role {e.Item.ServerId}/{e.Item.Id}"); - _roles.ItemUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Updated Role {e.Item.ServerId}/{e.Item.Id}"); _roles.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Destroyed Role {e.Item.ServerId}/{e.Item.Id}"); + _roles.Cleared += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Cleared Members"); _servers.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Created Server {e.Item.Id}"); - _servers.ItemUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Updated Server {e.Item.Id}"); _servers.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Destroyed Server {e.Item.Id}"); + _servers.Cleared += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Cleared Members"); _users.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Created User {e.Item.Id}"); - _users.ItemUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Updated User {e.Item.Id}"); _users.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Destroyed User {e.Item.Id}"); + _users.Cleared += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Cache, $"Cleared Members"); _api.RestClient.OnRequest += (s, e) => { if (e.Payload != null) - RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Rest, $"{e.Method.Method} {e.Path}: {Math.Round(e.ElapsedMilliseconds, 2)} ({e.Payload})"); + RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Rest, $"{e.Method.Method} {e.Path}: {Math.Round(e.ElapsedMilliseconds, 2)} ms ({e.Payload})"); else - RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Rest, $"{e.Method.Method} {e.Path}: {Math.Round(e.ElapsedMilliseconds, 2)}"); + RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Rest, $"{e.Method.Method} {e.Path}: {Math.Round(e.ElapsedMilliseconds, 2)} ms"); }; } if (_config.UseMessageQueue) _pendingMessages = new ConcurrentQueue(); - } + + _serializer = new JsonSerializer(); +#if TEST_RESPONSES + _serializer.CheckAdditionalContent = true; + _serializer.MissingMemberHandling = MissingMemberHandling.Error; +#endif + + _dataSocket.ReceievedEvent += async (s, e) => + { + switch (e.Type) + { + //Global + case "READY": //Resync + { + var data = e.Payload.ToObject(_serializer); + + _servers.Clear(); + _channels.Clear(); + _users.Clear(); + + _currentUserId = data.User.Id; + _currentUser = _users.GetOrAdd(data.User.Id); + _currentUser.Update(data.User); + foreach (var model in data.Guilds) + { + var server = _servers.GetOrAdd(model.Id); + server.Update(model); + } + foreach (var model in data.PrivateChannels) + { + var channel = _channels.GetOrAdd(model.Id, null, model.Recipient?.Id); + channel.Update(model); + } + } + break; + + //Servers + case "GUILD_CREATE": + { + var model = e.Payload.ToObject(_serializer); + var server = _servers.GetOrAdd(model.Id); + server.Update(model); + RaiseEvent(nameof(ServerCreated), () => RaiseServerCreated(server)); + } + break; + case "GUILD_UPDATE": + { + var model = e.Payload.ToObject(_serializer); + var server = _servers.GetOrAdd(model.Id); + server.Update(model); + RaiseEvent(nameof(ServerUpdated), () => RaiseServerUpdated(server)); + } + break; + case "GUILD_DELETE": + { + var data = e.Payload.ToObject(_serializer); + var server = _servers.TryRemove(data.Id); + if (server != null) + RaiseEvent(nameof(ServerDestroyed), () => RaiseServerDestroyed(server)); + } + break; + + //Channels + case "CHANNEL_CREATE": + { + var data = e.Payload.ToObject(_serializer); + var channel = _channels.GetOrAdd(data.Id, data.GuildId, data.Recipient?.Id); + channel.Update(data); + RaiseEvent(nameof(ChannelCreated), () => RaiseChannelCreated(channel)); + } + break; + case "CHANNEL_UPDATE": + { + var data = e.Payload.ToObject(_serializer); + var channel = _channels.GetOrAdd(data.Id, data.GuildId, data.Recipient?.Id); + channel.Update(data); + RaiseEvent(nameof(ChannelUpdated), () => RaiseChannelUpdated(channel)); + } + break; + case "CHANNEL_DELETE": + { + var data = e.Payload.ToObject(_serializer); + var channel = _channels.TryRemove(data.Id); + if (channel != null) + RaiseEvent(nameof(ChannelDestroyed), () => RaiseChannelDestroyed(channel)); + } + break; + + //Members + case "GUILD_MEMBER_ADD": + { + var data = e.Payload.ToObject(_serializer); + var member = _members.GetOrAdd(data.UserId, data.GuildId); + member.Update(data); + RaiseEvent(nameof(MemberAdded), () => RaiseMemberAdded(member)); + } + break; + case "GUILD_MEMBER_UPDATE": + { + var data = e.Payload.ToObject(_serializer); + var member = _members.GetOrAdd(data.UserId, data.GuildId); + member.Update(data); + RaiseEvent(nameof(MemberUpdated), () => RaiseMemberUpdated(member)); + } + break; + case "GUILD_MEMBER_REMOVE": + { + var data = e.Payload.ToObject(_serializer); + var member = _members.TryRemove(data.UserId, data.GuildId); + if (member != null) + try { RaiseMemberRemoved(member); } catch { } + } + break; + + //Roles + case "GUILD_ROLE_CREATE": + { + var data = e.Payload.ToObject(_serializer); + var role = _roles.GetOrAdd(data.Data.Id, data.GuildId); + role.Update(data.Data); + RaiseEvent(nameof(RoleUpdated), () => RaiseRoleUpdated(role)); + } + break; + case "GUILD_ROLE_UPDATE": + { + var data = e.Payload.ToObject(_serializer); + var role = _roles.GetOrAdd(data.Data.Id, data.GuildId); + role.Update(data.Data); + RaiseEvent(nameof(RoleUpdated), () => RaiseRoleUpdated(role)); + } + break; + case "GUILD_ROLE_DELETE": + { + var data = e.Payload.ToObject(_serializer); + var role = _roles.TryRemove(data.RoleId); + if (role != null) + RaiseEvent(nameof(RoleDeleted), () => RaiseRoleDeleted(role)); + } + break; + + //Bans + case "GUILD_BAN_ADD": + { + var data = e.Payload.ToObject(_serializer); + var server = _servers[data.GuildId]; + if (server != null) + { + server.AddBan(data.UserId); + RaiseEvent(nameof(BanAdded), () => RaiseBanAdded(data.UserId, server)); + } + } + break; + case "GUILD_BAN_REMOVE": + { + var data = e.Payload.ToObject(_serializer); + var server = _servers[data.GuildId]; + if (server != null && server.RemoveBan(data.UserId)) + RaiseEvent(nameof(BanRemoved), () => RaiseBanRemoved(data.UserId, server)); + } + break; + + //Messages + case "MESSAGE_CREATE": + { + var data = e.Payload.ToObject(_serializer); + Message msg = null; + + bool wasLocal = _config.UseMessageQueue && data.Author.Id == _currentUserId && data.Nonce != null; + if (wasLocal) + { + msg = _messages.Remap("nonce" + data.Nonce, data.Id); + if (msg != null) + { + msg.IsQueued = false; + msg.Id = data.Id; + } + } + + if (msg == null) + msg = _messages.GetOrAdd(data.Id, data.ChannelId); + msg.Update(data); + if (_config.TrackActivity) + msg.User.UpdateActivity(data.Timestamp); + if (wasLocal) + RaiseEvent(nameof(MessageSent), () => RaiseMessageSent(msg)); + RaiseEvent(nameof(MessageCreated), () => RaiseMessageCreated(msg)); + } + break; + case "MESSAGE_UPDATE": + { + var data = e.Payload.ToObject(_serializer); + var msg = _messages.GetOrAdd(data.Id, data.ChannelId); + msg.Update(data); + RaiseEvent(nameof(MessageUpdated), () => RaiseMessageUpdated(msg)); + } + break; + case "MESSAGE_DELETE": + { + var data = e.Payload.ToObject(_serializer); + var msg = _messages.TryRemove(data.Id); + if (msg != null) + RaiseEvent(nameof(MessageDeleted), () => RaiseMessageDeleted(msg)); + } + break; + case "MESSAGE_ACK": + { + var data = e.Payload.ToObject(_serializer); + var msg = GetMessage(data.MessageId); + if (msg != null) + RaiseEvent(nameof(MessageReadRemotely), () => RaiseMessageReadRemotely(msg)); + } + break; + + //Statuses + case "PRESENCE_UPDATE": + { + var data = e.Payload.ToObject(_serializer); + var member = _members[data.UserId, data.GuildId]; + /*if (_config.TrackActivity) + { + var user = _users[data.User.Id]; + if (user != null) + user.UpdateActivity(DateTime.UtcNow); + }*/ + if (member != null) + { + member.Update(data); + RaiseEvent(nameof(MemberPresenceUpdated), () => RaiseMemberPresenceUpdated(member)); + } + } + break; + case "VOICE_STATE_UPDATE": + { + var data = e.Payload.ToObject(_serializer); + var member = _members[data.UserId, data.GuildId]; + /*if (_config.TrackActivity) + { + var user = _users[data.User.Id]; + if (user != null) + user.UpdateActivity(DateTime.UtcNow); + }*/ + if (member != null) + { + member.Update(data); + RaiseEvent(nameof(MemberVoiceStateUpdated), () => RaiseMemberVoiceStateUpdated(member)); + } + } + break; + case "TYPING_START": + { + var data = e.Payload.ToObject(_serializer); + var channel = _channels[data.ChannelId]; + if (_config.TrackActivity) + { + var user = _users[data.UserId]; + user.UpdateActivity(DateTime.UtcNow); + } + if (channel != null) + { + var member = _members[data.UserId, channel.ServerId]; + if (member != null) + RaiseEvent(nameof(MemberIsTyping), () => RaiseMemberIsTyping(member, channel)); + } + } + break; + + //Voice + case "VOICE_SERVER_UPDATE": + { + var data = e.Payload.ToObject(_serializer); + var server = _servers[data.GuildId]; + if (_config.EnableVoice) + { + string host = "wss://" + data.Endpoint.Split(':')[0]; + await _voiceSocket.Login(host, data.GuildId, _currentUserId, _dataSocket.SessionId, data.Token).ConfigureAwait(false); + } + } + break; + + //Settings + case "USER_UPDATE": + { + var data = e.Payload.ToObject(_serializer); + var user = _users[data.Id]; + if (user != null) + { + user.Update(data); + try { RaiseUserUpdated(user); } catch { } + } + } + break; + case "USER_SETTINGS_UPDATE": + { + //TODO: Process this + } + break; + + //Others + default: + RaiseOnLog(LogMessageSeverity.Warning, LogMessageSource.DataWebSocket, $"Unknown message type: {e.Type}"); + break; + } + }; + } private void _dataSocket_Connected(object sender, EventArgs e) { @@ -262,10 +597,8 @@ namespace Discord _disconnectedEvent.Set(); await _dataSocket.Disconnect().ConfigureAwait(false); -#if !DNXCORE50 if (_config.EnableVoice) await _voiceSocket.Disconnect().ConfigureAwait(false); -#endif Message ignored; while (_pendingMessages.TryDequeue(out ignored)) { } @@ -282,6 +615,12 @@ namespace Discord } //Helpers + /// Blocking call that will not return until client has been stopped. This is mainly intended for use in console applications. + public void Block() + { + _disconnectedEvent.WaitOne(); + } + private void CheckReady(bool checkVoice = false) { switch (_state) @@ -301,10 +640,14 @@ namespace Discord #endif throw new InvalidOperationException("Voice is not enabled for this client."); } - /// Blocking call that will not return until client has been stopped. This is mainly intended for use in console applications. - public void Block() + private void RaiseEvent(string name, Action action) { - _disconnectedEvent.WaitOne(); + try { action(); } + catch (Exception ex) + { + RaiseOnLog(LogMessageSeverity.Error, LogMessageSource.Client, + $"{name} event handler raised an exception: ${ex.GetBaseException().Message}"); + } } //Experimental diff --git a/src/Discord.Net/DiscordClientConfig.cs b/src/Discord.Net/DiscordClientConfig.cs index 900ee4b4a..20c6b9816 100644 --- a/src/Discord.Net/DiscordClientConfig.cs +++ b/src/Discord.Net/DiscordClientConfig.cs @@ -29,14 +29,15 @@ namespace Discord private int _voiceBufferLength = 3000; //Experimental Features -#if !DNXCORE50 - /// (Experimental) Enables the voice websocket and UDP client (Experimental!). This option requires the opus .dll or .so be in the local lib/ folder. + /// (Experimental) Enables the voice websocket and UDP client. This option requires the opus .dll or .so be in the local lib/ folder. public bool EnableVoice { get { return _enableVoice; } set { SetValue(ref _enableVoice, value); } } private bool _enableVoice = false; -#endif /// (Experimental) Enables or disables the internal message queue. This will allow SendMessage to return immediately and handle messages internally. Messages will set the IsQueued and HasFailed properties to show their progress. public bool UseMessageQueue { get { return _useMessageQueue; } set { SetValue(ref _useMessageQueue, value); } } private bool _useMessageQueue = false; + /// (Experimental) Maintains the LastActivity property for users, showing when they last made an action (sent message, joined server, typed, etc). + public bool TrackActivity { get { return _trackActivity; } set { SetValue(ref _trackActivity, value); } } + private bool _trackActivity = false; //Lock private bool _isLocked; diff --git a/src/Discord.Net/Models/Member.cs b/src/Discord.Net/Models/Member.cs index ce1777915..f5555b2c1 100644 --- a/src/Discord.Net/Models/Member.cs +++ b/src/Discord.Net/Models/Member.cs @@ -23,6 +23,7 @@ namespace Discord public string GameId { get; internal set; } /// Returns the current status for this user. public string Status { get; internal set; } + public DateTime StatusSince { get; internal set; } public string UserId { get; } [JsonIgnore] @@ -66,7 +67,11 @@ namespace Discord } internal void Update(Net.API.PresenceMemberInfo model) { - Status = model.Status; + if (Status != model.Status) + { + Status = model.Status; + StatusSince = DateTime.UtcNow; + } GameId = model.GameId; } internal void Update(Net.API.VoiceMemberInfo model) diff --git a/src/Discord.Net/Models/Server.cs b/src/Discord.Net/Models/Server.cs index 16c1b3671..b94a17199 100644 --- a/src/Discord.Net/Models/Server.cs +++ b/src/Discord.Net/Models/Server.cs @@ -23,8 +23,8 @@ namespace Discord public DateTime JoinedAt { get; internal set; } /// Returns the region for this server (see Regions). public string Region { get; internal set; } - /// Returns the endpoint for this server's voice server. - internal string VoiceServer { get; set; } + /*/// Returns the endpoint for this server's voice server. + internal string VoiceServer { get; set; }*/ /// Returns true if the current user created this server. public bool IsOwner => _client.CurrentUserId == OwnerId; diff --git a/src/Discord.Net/Net/WebSockets/DataWebSocket.cs b/src/Discord.Net/Net/WebSockets/DataWebSocket.cs index 0b5a7d89b..cf7855e0c 100644 --- a/src/Discord.Net/Net/WebSockets/DataWebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/DataWebSocket.cs @@ -7,9 +7,12 @@ namespace Discord.Net.WebSockets { internal partial class DataWebSocket : WebSocket { - private string _lastSession, _redirectServer; + private string _redirectServer; private int _lastSeq; + public string SessionId => _sessionId; + private string _sessionId; + public DataWebSocket(DiscordClient client) : base(client) { @@ -35,7 +38,7 @@ namespace Discord.Net.WebSockets if (_redirectServer != null) { var resumeMsg = new Commands.Resume(); - resumeMsg.Payload.SessionId = _lastSession; + resumeMsg.Payload.SessionId = _sessionId; resumeMsg.Payload.Sequence = _lastSeq; QueueMessage(resumeMsg); _redirectServer = null; @@ -57,11 +60,11 @@ namespace Discord.Net.WebSockets if (msg.Type == "READY") { var payload = token.ToObject(); - _lastSession = payload.SessionId; + _sessionId = payload.SessionId; _heartbeatInterval = payload.HeartbeatInterval; QueueMessage(new Commands.UpdateStatus()); } - RaiseOnEvent(msg.Type, token); + RaiseReceievedEvent(msg.Type, token); if (msg.Type == "READY") CompleteConnect(); if (_logLevel >= LogMessageSeverity.Info) diff --git a/src/Discord.Net/Net/WebSockets/DataWebSockets.Events.cs b/src/Discord.Net/Net/WebSockets/DataWebSockets.Events.cs index 657559aea..95de31c73 100644 --- a/src/Discord.Net/Net/WebSockets/DataWebSockets.Events.cs +++ b/src/Discord.Net/Net/WebSockets/DataWebSockets.Events.cs @@ -16,11 +16,11 @@ namespace Discord.Net.WebSockets internal partial class DataWebSocket { - public event EventHandler OnEvent; - private void RaiseOnEvent(string type, JToken payload) + public event EventHandler ReceievedEvent; + private void RaiseReceievedEvent(string type, JToken payload) { - if (OnEvent != null) - OnEvent(this, new WebSocketEventEventArgs(type, payload)); + if (ReceievedEvent != null) + ReceievedEvent(this, new WebSocketEventEventArgs(type, payload)); } } } diff --git a/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs b/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs index 68a81c14d..7244a2d26 100644 --- a/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs @@ -52,6 +52,25 @@ namespace Discord.Net.WebSockets _targetAudioBufferLength = client.Config.VoiceBufferLength / 20; //20 ms frames } + public async Task Login(string host, string serverId, string userId, string sessionId, string token) + { + _serverId = serverId; + _userId = userId; + _sessionId = sessionId; + _token = token; + + await base.Connect(host); + + Commands.Login msg = new Commands.Login(); + msg.Payload.Token = token; + //msg.Payload.Properties["$os"] = ""; + //msg.Payload.Properties["$browser"] = ""; + msg.Payload.Properties["$device"] = "Discord.Net"; + //msg.Payload.Properties["$referrer"] = ""; + //msg.Payload.Properties["$referring_domain"] = ""; + QueueMessage(msg); + } + protected override Task[] Run() { _udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); @@ -98,14 +117,6 @@ namespace Discord.Net.WebSockets return base.Cleanup(); } - public void SetSessionData(string serverId, string userId, string sessionId, string token) - { - _serverId = serverId; - _userId = userId; - _sessionId = sessionId; - _token = token; - } - private async Task ReceiveVoiceAsync() { var cancelSource = _cancelToken;