| @@ -89,11 +89,11 @@ | |||
| <Compile Include="..\Discord.Net\DiscordClientConfig.cs"> | |||
| <Link>DiscordClientConfig.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\DiscordTextWebSocket.cs"> | |||
| <Link>DiscordTextWebSocket.cs</Link> | |||
| <Compile Include="..\Discord.Net\DiscordDataSocket.cs"> | |||
| <Link>DiscordDataSocket.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\DiscordTextWebSocket.Events.cs"> | |||
| <Link>DiscordTextWebSocket.Events.cs</Link> | |||
| <Compile Include="..\Discord.Net\DiscordDataSocket.Events.cs"> | |||
| <Link>DiscordDataSocket.Events.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\DiscordVoiceSocket.cs"> | |||
| <Link>DiscordVoiceSocket.cs</Link> | |||
| @@ -2,248 +2,310 @@ | |||
| namespace Discord | |||
| { | |||
| public partial class DiscordClient | |||
| public enum DebugMessageType : byte | |||
| { | |||
| //Debug | |||
| public sealed class LogMessageEventArgs : EventArgs | |||
| Connection, | |||
| Event, | |||
| Cache, | |||
| WebSocketRawInput, | |||
| WebSocketUnknownInput, | |||
| WebSocketEvent, | |||
| WebSocketUnknownEvent, | |||
| VoiceOutput | |||
| } | |||
| public sealed class LogMessageEventArgs : EventArgs | |||
| { | |||
| public readonly DebugMessageType Type; | |||
| public readonly string Message; | |||
| internal LogMessageEventArgs(DebugMessageType type, string msg) { Type = type; Message = msg; } | |||
| } | |||
| public sealed class ServerEventArgs : EventArgs | |||
| { | |||
| public readonly Server Server; | |||
| internal ServerEventArgs(Server server) { Server = server; } | |||
| } | |||
| public sealed class ChannelEventArgs : EventArgs | |||
| { | |||
| public readonly Channel Channel; | |||
| internal ChannelEventArgs(Channel channel) { Channel = channel; } | |||
| } | |||
| public sealed class UserEventArgs : EventArgs | |||
| { | |||
| public readonly User User; | |||
| internal UserEventArgs(User user) { User = user; } | |||
| } | |||
| public sealed class MessageEventArgs : EventArgs | |||
| { | |||
| public readonly Message Message; | |||
| internal MessageEventArgs(Message msg) { Message = msg; } | |||
| } | |||
| public sealed class RoleEventArgs : EventArgs | |||
| { | |||
| public readonly Role Role; | |||
| internal RoleEventArgs(Role role) { Role = role; } | |||
| } | |||
| public sealed class BanEventArgs : EventArgs | |||
| { | |||
| public readonly User User; | |||
| public readonly Server Server; | |||
| internal BanEventArgs(User user, Server server) | |||
| { | |||
| public readonly string Message; | |||
| internal LogMessageEventArgs(string msg) { Message = msg; } | |||
| User = user; | |||
| Server = server; | |||
| } | |||
| public event EventHandler<LogMessageEventArgs> DebugMessage; | |||
| private void RaiseOnDebugMessage(string message) | |||
| } | |||
| public sealed class MemberEventArgs : EventArgs | |||
| { | |||
| public readonly Membership Member; | |||
| internal MemberEventArgs(Membership member) { Member = member; } | |||
| } | |||
| public sealed class UserTypingEventArgs : EventArgs | |||
| { | |||
| public readonly User User; | |||
| public readonly Channel Channel; | |||
| internal UserTypingEventArgs(User user, Channel channel) | |||
| { | |||
| if (DebugMessage != null) | |||
| DebugMessage(this, new LogMessageEventArgs(message)); | |||
| User = user; | |||
| Channel = channel; | |||
| } | |||
| public event EventHandler<LogMessageEventArgs> VoiceDebugMessage; | |||
| private void RaiseOnVoiceDebugMessage(string message) | |||
| } | |||
| public sealed class VoiceServerUpdatedEventArgs : EventArgs | |||
| { | |||
| public readonly Server Server; | |||
| public readonly string Endpoint; | |||
| internal VoiceServerUpdatedEventArgs(Server server, string endpoint) | |||
| { | |||
| if (VoiceDebugMessage != null) | |||
| VoiceDebugMessage(this, new LogMessageEventArgs(message)); | |||
| Server = server; | |||
| Endpoint = endpoint; | |||
| } | |||
| } | |||
| public partial class DiscordClient | |||
| { | |||
| //Debug | |||
| public event EventHandler<LogMessageEventArgs> DebugMessage; | |||
| internal void RaiseOnDebugMessage(DebugMessageType type, string message) | |||
| { | |||
| if (DebugMessage != null) | |||
| DebugMessage(this, new LogMessageEventArgs(type, message)); | |||
| } | |||
| //General | |||
| public event EventHandler Connected; | |||
| private void RaiseConnected() | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"Connected"); | |||
| if (Connected != null) | |||
| Connected(this, EventArgs.Empty); | |||
| } | |||
| public event EventHandler Disconnected; | |||
| private void RaiseDisconnected() | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"Disconnected"); | |||
| if (Disconnected != null) | |||
| Disconnected(this, EventArgs.Empty); | |||
| } | |||
| //Server | |||
| public sealed class ServerEventArgs : EventArgs | |||
| { | |||
| public readonly Server Server; | |||
| internal ServerEventArgs(Server server) { Server = server; } | |||
| } | |||
| public event EventHandler<ServerEventArgs> ServerCreated; | |||
| private void RaiseServerCreated(Server server) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"ServerCreated {server.Name} ({server.Id})"); | |||
| if (ServerCreated != null) | |||
| ServerCreated(this, new ServerEventArgs(server)); | |||
| } | |||
| public event EventHandler<ServerEventArgs> ServerDestroyed; | |||
| private void RaiseServerDestroyed(Server server) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"ServerDestroyed {server.Name} ({server.Id})"); | |||
| if (ServerDestroyed != null) | |||
| ServerDestroyed(this, new ServerEventArgs(server)); | |||
| } | |||
| public event EventHandler<ServerEventArgs> ServerUpdated; | |||
| private void RaiseServerUpdated(Server server) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"ServerUpdated {server.Name} ({server.Id})"); | |||
| if (ServerUpdated != null) | |||
| ServerUpdated(this, new ServerEventArgs(server)); | |||
| } | |||
| //Channel | |||
| public sealed class ChannelEventArgs : EventArgs | |||
| { | |||
| public readonly Channel Channel; | |||
| internal ChannelEventArgs(Channel channel) { Channel = channel; } | |||
| } | |||
| public event EventHandler<ChannelEventArgs> ChannelCreated; | |||
| private void RaiseChannelCreated(Channel channel) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"ChannelCreated {channel.Name} ({channel.Id})"); | |||
| if (ChannelCreated != null) | |||
| ChannelCreated(this, new ChannelEventArgs(channel)); | |||
| } | |||
| public event EventHandler<ChannelEventArgs> ChannelDestroyed; | |||
| private void RaiseChannelDestroyed(Channel channel) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"ChannelDestroyed {channel.Name} ({channel.Id})"); | |||
| if (ChannelDestroyed != null) | |||
| ChannelDestroyed(this, new ChannelEventArgs(channel)); | |||
| } | |||
| public event EventHandler<ChannelEventArgs> ChannelUpdated; | |||
| private void RaiseChannelUpdated(Channel channel) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"ChannelUpdated {channel.Name} ({channel.Id})"); | |||
| if (ChannelUpdated != null) | |||
| ChannelUpdated(this, new ChannelEventArgs(channel)); | |||
| } | |||
| //User | |||
| public sealed class UserEventArgs : EventArgs | |||
| { | |||
| public readonly User User; | |||
| internal UserEventArgs(User user) { User = user; } | |||
| } | |||
| public event EventHandler<UserEventArgs> UserUpdated; | |||
| private void RaiseUserUpdated(User user) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"UserUpdated {user.Name} ({user.Id})"); | |||
| if (UserUpdated != null) | |||
| UserUpdated(this, new UserEventArgs(user)); | |||
| } | |||
| //Message | |||
| public sealed class MessageEventArgs : EventArgs | |||
| { | |||
| public readonly Message Message; | |||
| internal MessageEventArgs(Message msg) { Message = msg; } | |||
| } | |||
| public event EventHandler<MessageEventArgs> MessageCreated; | |||
| private void RaiseMessageCreated(Message msg) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"MessageCreated {msg.Id}"); | |||
| if (MessageCreated != null) | |||
| MessageCreated(this, new MessageEventArgs(msg)); | |||
| } | |||
| public event EventHandler<MessageEventArgs> MessageDeleted; | |||
| private void RaiseMessageDeleted(Message msg) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"MessageDeleted {msg.Id}"); | |||
| if (MessageDeleted != null) | |||
| MessageDeleted(this, new MessageEventArgs(msg)); | |||
| } | |||
| public event EventHandler<MessageEventArgs> MessageUpdated; | |||
| private void RaiseMessageUpdated(Message msg) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"MessageUpdated {msg.Id}"); | |||
| if (MessageUpdated != null) | |||
| MessageUpdated(this, new MessageEventArgs(msg)); | |||
| } | |||
| public event EventHandler<MessageEventArgs> MessageRead; | |||
| private void RaiseMessageRead(Message msg) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"MessageRead {msg.Id}"); | |||
| if (MessageRead != null) | |||
| MessageRead(this, new MessageEventArgs(msg)); | |||
| } | |||
| public event EventHandler<MessageEventArgs> MessageSent; | |||
| private void RaiseMessageSent(Message msg) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"MessageSent {msg.Id}"); | |||
| if (MessageSent != null) | |||
| MessageSent(this, new MessageEventArgs(msg)); | |||
| } | |||
| //Role | |||
| public sealed class RoleEventArgs : EventArgs | |||
| { | |||
| public readonly Role Role; | |||
| internal RoleEventArgs(Role role) { Role = role; } | |||
| } | |||
| public event EventHandler<RoleEventArgs> RoleCreated; | |||
| private void RaiseRoleCreated(Role role) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"RoleCreated {role.Name} ({role.Id})"); | |||
| if (RoleCreated != null) | |||
| RoleCreated(this, new RoleEventArgs(role)); | |||
| } | |||
| public event EventHandler<RoleEventArgs> RoleUpdated; | |||
| private void RaiseRoleDeleted(Role role) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"RoleDeleted {role.Name} ({role.Id})"); | |||
| if (RoleDeleted != null) | |||
| RoleDeleted(this, new RoleEventArgs(role)); | |||
| } | |||
| public event EventHandler<RoleEventArgs> RoleDeleted; | |||
| private void RaiseRoleUpdated(Role role) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"RoleUpdated {role.Name} ({role.Id})"); | |||
| if (RoleUpdated != null) | |||
| RoleUpdated(this, new RoleEventArgs(role)); | |||
| } | |||
| //Ban | |||
| public sealed class BanEventArgs : EventArgs | |||
| { | |||
| public readonly User User; | |||
| public readonly Server Server; | |||
| internal BanEventArgs(User user, Server server) | |||
| { | |||
| User = user; | |||
| Server = server; | |||
| } | |||
| } | |||
| public event EventHandler<BanEventArgs> BanAdded; | |||
| private void RaiseBanAdded(User user, Server server) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"BanAdded {user.Name} ({user.Id}) on {server.Name} ({server.Id})"); | |||
| if (BanAdded != null) | |||
| BanAdded(this, new BanEventArgs(user, server)); | |||
| } | |||
| public event EventHandler<BanEventArgs> BanRemoved; | |||
| private void RaiseBanRemoved(User user, Server server) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"BanRemoved {user.Name} ({user.Id}) on {server.Name} ({server.Id})"); | |||
| if (BanRemoved != null) | |||
| BanRemoved(this, new BanEventArgs(user, server)); | |||
| } | |||
| //Member | |||
| public sealed class MemberEventArgs : EventArgs | |||
| { | |||
| public readonly Membership Membership; | |||
| internal MemberEventArgs(Membership membership) { Membership = membership; } | |||
| } | |||
| public event EventHandler<MemberEventArgs> MemberAdded; | |||
| private void RaiseMemberAdded(Membership membership) | |||
| private void RaiseMemberAdded(Membership member) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"MemberAdded {member.User.Name} ({member.UserId}) on {member.Server.Name} ({member.ServerId})"); | |||
| if (MemberAdded != null) | |||
| MemberAdded(this, new MemberEventArgs(membership)); | |||
| MemberAdded(this, new MemberEventArgs(member)); | |||
| } | |||
| public event EventHandler<MemberEventArgs> MemberRemoved; | |||
| private void RaiseMemberRemoved(Membership membership) | |||
| private void RaiseMemberRemoved(Membership member) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"MemberRemoved {member.User.Name} ({member.UserId}) on {member.Server.Name} ({member.ServerId})"); | |||
| if (MemberRemoved != null) | |||
| MemberRemoved(this, new MemberEventArgs(membership)); | |||
| MemberRemoved(this, new MemberEventArgs(member)); | |||
| } | |||
| public event EventHandler<MemberEventArgs> MemberUpdated; | |||
| private void RaiseMemberUpdated(Membership membership) | |||
| private void RaiseMemberUpdated(Membership member) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"MemberUpdated {member.User.Name} ({member.UserId}) on {member.Server.Name} ({member.ServerId})"); | |||
| if (MemberUpdated != null) | |||
| MemberUpdated(this, new MemberEventArgs(membership)); | |||
| MemberUpdated(this, new MemberEventArgs(member)); | |||
| } | |||
| //Status | |||
| public sealed class UserTypingEventArgs : EventArgs | |||
| { | |||
| public readonly User User; | |||
| public readonly Channel Channel; | |||
| internal UserTypingEventArgs(User user, Channel channel) | |||
| { | |||
| User = user; | |||
| Channel = channel; | |||
| } | |||
| } | |||
| public event EventHandler<MemberEventArgs> PresenceUpdated; | |||
| private void RaisePresenceUpdated(Membership member) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"PresenceUpdated {member.User.Name} ({member.UserId}) on {member.Server.Name} ({member.ServerId})"); | |||
| if (PresenceUpdated != null) | |||
| PresenceUpdated(this, new MemberEventArgs(member)); | |||
| } | |||
| public event EventHandler<MemberEventArgs> VoiceStateUpdated; | |||
| private void RaiseVoiceStateUpdated(Membership member) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"VoiceStateUpdated {member.User.Name} ({member.UserId}) on {member.Server.Name} ({member.ServerId})"); | |||
| if (VoiceStateUpdated != null) | |||
| VoiceStateUpdated(this, new MemberEventArgs(member)); | |||
| } | |||
| public event EventHandler<UserTypingEventArgs> UserTyping; | |||
| private void RaiseUserTyping(User user, Channel channel) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"VoiceStateUpdated {user.Name} ({user.Id}) on {channel.Name} ({channel.Id})"); | |||
| if (UserTyping != null) | |||
| UserTyping(this, new UserTypingEventArgs(user, channel)); | |||
| } | |||
| @@ -252,29 +314,24 @@ namespace Discord | |||
| public event EventHandler VoiceConnected; | |||
| private void RaiseVoiceConnected() | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"VoiceConnected"); | |||
| if (VoiceConnected != null) | |||
| VoiceConnected(this, EventArgs.Empty); | |||
| } | |||
| public event EventHandler VoiceDisconnected; | |||
| private void RaiseVoiceDisconnected() | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"VoiceDisconnected"); | |||
| if (VoiceDisconnected != null) | |||
| VoiceDisconnected(this, EventArgs.Empty); | |||
| } | |||
| public sealed class VoiceServerUpdatedEventArgs : EventArgs | |||
| { | |||
| public readonly Server Server; | |||
| public readonly string Endpoint; | |||
| internal VoiceServerUpdatedEventArgs(Server server, string endpoint) | |||
| { | |||
| Server = server; | |||
| Endpoint = endpoint; | |||
| } | |||
| } | |||
| public event EventHandler<VoiceServerUpdatedEventArgs> VoiceServerUpdated; | |||
| private void RaiseVoiceServerUpdated(Server server, string endpoint) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Event, $"VoiceServerUpdated {server.Name} ({server.Id})"); | |||
| if (VoiceServerUpdated != null) | |||
| VoiceServerUpdated(this, new VoiceServerUpdatedEventArgs(server, endpoint)); | |||
| } | |||
| @@ -17,8 +17,7 @@ namespace Discord | |||
| /// <summary> Provides a connection to the DiscordApp service. </summary> | |||
| public partial class DiscordClient | |||
| { | |||
| private readonly DiscordClientConfig _config; | |||
| private readonly DiscordTextWebSocket _webSocket; | |||
| private readonly DiscordDataSocket _webSocket; | |||
| #if !DNXCORE50 | |||
| private readonly DiscordVoiceSocket _voiceWebSocket; | |||
| #endif | |||
| @@ -36,6 +35,9 @@ namespace Discord | |||
| public string UserId { get; private set; } | |||
| public string SessionId { get; private set; } | |||
| public DiscordClientConfig Config => _config; | |||
| private readonly DiscordClientConfig _config; | |||
| /// <summary> Returns a collection of all users the client can see across all servers. </summary> | |||
| /// <remarks> This collection does not guarantee any ordering. </remarks> | |||
| public IEnumerable<User> Users => _users; | |||
| @@ -113,7 +115,12 @@ namespace Discord | |||
| }); | |||
| _servers = new AsyncCache<Server, API.Models.ServerReference>( | |||
| (key, parentKey) => new Server(key, this), | |||
| (key, parentKey) => | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Cache, $"Created server {key}."); | |||
| return new Server(key, this); | |||
| }, | |||
| (server, model) => | |||
| { | |||
| server.Name = model.Name; | |||
| @@ -141,12 +148,28 @@ namespace Discord | |||
| foreach (var membership in extendedModel.Presences) | |||
| server.UpdateMember(membership); | |||
| } | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Cache, $"Updated server {server.Name} ({server.Id})."); | |||
| }, | |||
| server => { } | |||
| server => | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Cache, $"Destroyed server {server.Name} ({server.Id})."); | |||
| } | |||
| ); | |||
| _channels = new AsyncCache<Channel, API.Models.ChannelReference>( | |||
| (key, parentKey) => new Channel(key, parentKey, this), | |||
| (key, parentKey) => | |||
| { | |||
| if (_config.EnableDebug) | |||
| { | |||
| if (parentKey != null) | |||
| RaiseOnDebugMessage(DebugMessageType.Cache, $"Created channel {key} in server {parentKey}."); | |||
| else | |||
| RaiseOnDebugMessage(DebugMessageType.Cache, $"Created private channel {key}."); | |||
| } | |||
| return new Channel(key, parentKey, this); | |||
| }, | |||
| (channel, model) => | |||
| { | |||
| channel.Name = model.Name; | |||
| @@ -176,6 +199,13 @@ namespace Discord | |||
| else | |||
| channel.PermissionOverwrites = null; | |||
| } | |||
| if (_config.EnableDebug) | |||
| { | |||
| if (channel.IsPrivate) | |||
| RaiseOnDebugMessage(DebugMessageType.Cache, $"Updated private channel {channel.Name} ({channel.Id})."); | |||
| else | |||
| RaiseOnDebugMessage(DebugMessageType.Cache, $"Updated channel {channel.Name} ({channel.Id}) in server {channel.Server?.Name} ({channel.ServerId})."); | |||
| } | |||
| }, | |||
| channel => | |||
| { | |||
| @@ -184,10 +214,22 @@ namespace Discord | |||
| var user = channel.Recipient; | |||
| if (user.PrivateChannelId == channel.Id) | |||
| user.PrivateChannelId = null; | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Cache, $"Destroyed private channel {channel.Name} ({channel.Id})."); | |||
| } | |||
| else | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Cache, $"Destroyed channel {channel.Name} ({channel.Id}) in server {channel.Server?.Name} ({channel.ServerId})."); | |||
| } | |||
| }); | |||
| _messages = new AsyncCache<Message, API.Models.MessageReference>( | |||
| (key, parentKey) => new Message(key, parentKey, this), | |||
| (key, parentKey) => | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Cache, $"Created message {key} in channel {parentKey}."); | |||
| return new Message(key, parentKey, this); | |||
| }, | |||
| (message, model) => | |||
| { | |||
| if (model is API.Models.Message) | |||
| @@ -260,21 +302,44 @@ namespace Discord | |||
| if (extendedModel.Author != null) | |||
| message.UserId = extendedModel.Author.Id; | |||
| } | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Cache, $"Updated message {message.Id} in channel {message.Channel?.Name} ({message.ChannelId})."); | |||
| }, | |||
| message => { } | |||
| message => | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Cache, $"Destroyed message {message.Id} in channel {message.Channel?.Name} ({message.ChannelId})."); | |||
| } | |||
| ); | |||
| _pendingMessages = new ConcurrentQueue<Message>(); | |||
| if (_config.UseMessageQueue) | |||
| _pendingMessages = new ConcurrentQueue<Message>(); | |||
| _roles = new AsyncCache<Role, API.Models.Role>( | |||
| (key, parentKey) => new Role(key, parentKey, this), | |||
| (key, parentKey) => | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Cache, $"Created role {key} in server {parentKey}."); | |||
| return new Role(key, parentKey, this); | |||
| }, | |||
| (role, model) => | |||
| { | |||
| role.Name = model.Name; | |||
| role.Permissions.RawValue = (uint)model.Permissions; | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Cache, $"Updated role {role.Name} ({role.Id}) in server {role.Server?.Name} ({role.ServerId})."); | |||
| }, | |||
| role => { } | |||
| role => | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Cache, $"Destroyed role {role.Name} ({role.Id}) in server {role.Server?.Name} ({role.ServerId})."); | |||
| } | |||
| ); | |||
| _users = new AsyncCache<User, API.Models.UserReference>( | |||
| (key, parentKey) => new User(key, this), | |||
| (key, parentKey) => | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Cache, $"Created user {key}."); | |||
| return new User(key, this); | |||
| }, | |||
| (user, model) => | |||
| { | |||
| user.AvatarId = model.Avatar; | |||
| @@ -286,53 +351,94 @@ namespace Discord | |||
| user.Email = extendedModel.Email; | |||
| user.IsVerified = extendedModel.IsVerified; | |||
| } | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Cache, $"Updated user {user?.Name} ({user.Id})."); | |||
| }, | |||
| user => { } | |||
| user => | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Cache, $"Destroyed user {user?.Name} ({user.Id})."); | |||
| } | |||
| ); | |||
| _webSocket = new DiscordTextWebSocket(this, _config.ConnectionTimeout, _config.WebSocketInterval); | |||
| _webSocket = new DiscordDataSocket(this, _config.ConnectionTimeout, _config.WebSocketInterval); | |||
| _webSocket.Connected += (s, e) => RaiseConnected(); | |||
| _webSocket.Disconnected += async (s, e) => | |||
| _webSocket.Disconnected += async (s, e) => | |||
| { | |||
| //Reconnect if we didn't cause the disconnect | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Connection, $"DataSocket disconnected."); | |||
| RaiseDisconnected(); | |||
| //Reconnect if we didn't cause the disconnect | |||
| while (!_disconnectToken.IsCancellationRequested) | |||
| { | |||
| try | |||
| { | |||
| await Task.Delay(_config.ReconnectDelay); | |||
| await _webSocket.ReconnectAsync(); | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Connection, $"DataSocket connected."); | |||
| await _webSocket.Login(); | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Connection, $"DataSocket logged in."); | |||
| break; | |||
| } | |||
| catch (Exception ex) | |||
| { | |||
| RaiseOnDebugMessage($"Reconnect Failed: {ex.Message}"); | |||
| RaiseOnDebugMessage(DebugMessageType.Connection, $"DataSocket reconnect failed: {ex.Message}"); | |||
| //Net is down? We can keep trying to reconnect until the user runs Disconnect() | |||
| await Task.Delay(_config.FailedReconnectDelay); | |||
| } | |||
| } | |||
| }; | |||
| _webSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Message); | |||
| if (_config.EnableDebug) | |||
| _webSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Type, e.Message); | |||
| #if !DNXCORE50 | |||
| if (_config.EnableVoice) | |||
| { | |||
| _voiceWebSocket = new DiscordVoiceSocket(this, _config.VoiceConnectionTimeout, _config.WebSocketInterval); | |||
| _voiceWebSocket.Connected += (s, e) => RaiseVoiceConnected(); | |||
| _voiceWebSocket.Disconnected += (s, e) => | |||
| _voiceWebSocket.Disconnected += async (s, e) => | |||
| { | |||
| //TODO: Reconnect if we didn't cause the disconnect | |||
| RaiseVoiceDisconnected(); | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Connection, $"VoiceSocket disconnected."); | |||
| RaiseVoiceDisconnected(); | |||
| //Reconnect if we didn't cause the disconnect | |||
| while (!_disconnectToken.IsCancellationRequested) | |||
| { | |||
| try | |||
| { | |||
| await Task.Delay(_config.ReconnectDelay); | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Connection, $"VoiceSocket connected."); | |||
| await _voiceWebSocket.ReconnectAsync(); | |||
| break; | |||
| } | |||
| catch (Exception ex) | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Connection, $"VoiceSocket reconnect failed: {ex.Message}"); | |||
| //Net is down? We can keep trying to reconnect until the user runs Disconnect() | |||
| await Task.Delay(_config.FailedReconnectDelay); | |||
| } | |||
| } | |||
| }; | |||
| _voiceWebSocket.OnDebugMessage += (s, e) => RaiseOnVoiceDebugMessage(e.Message); | |||
| if (_config.EnableDebug) | |||
| _voiceWebSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Type, e.Message); | |||
| } | |||
| #endif | |||
| #pragma warning disable CS1998 //Disable unused async keyword warning | |||
| #if !DNXCORE50 | |||
| _webSocket.GotEvent += async (s, e) => | |||
| #else | |||
| _webSocket.GotEvent += (s, e) => | |||
| #endif | |||
| { | |||
| switch (e.Type) | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.WebSocketEvent, $"{e.Type}: {e.Event}"); | |||
| switch (e.Type) | |||
| { | |||
| //Global | |||
| case "READY": //Resync | |||
| @@ -610,12 +716,11 @@ namespace Discord | |||
| //Others | |||
| default: | |||
| RaiseOnDebugMessage("Unknown WebSocket message type: " + e.Type); | |||
| RaiseOnDebugMessage(DebugMessageType.WebSocketUnknownEvent, "Unknown WebSocket message type: " + e.Type); | |||
| break; | |||
| } | |||
| }; | |||
| } | |||
| #pragma warning restore CS1998 //Restore unused async keyword warning | |||
| private async Task SendAsync() | |||
| { | |||
| @@ -658,7 +763,7 @@ namespace Discord | |||
| { | |||
| await Task.Delay(-1, cancelToken); | |||
| } | |||
| catch { } | |||
| catch (OperationCanceledException) { } | |||
| } | |||
| /// <summary> Returns the user with the specified id, or null if none was found. </summary> | |||
| @@ -867,12 +972,19 @@ namespace Discord | |||
| try | |||
| { | |||
| await _webSocket.ConnectAsync(url); | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Connection, $"DataSocket connected."); | |||
| Http.Token = token; | |||
| await _webSocket.Login(); | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Connection, $"DataSocket got token."); | |||
| success = true; | |||
| } | |||
| catch (InvalidOperationException) //Bad Token | |||
| { | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Connection, $"DataSocket had a bad token."); | |||
| if (password == null) //If we don't have an alternate login, throw this error | |||
| throw; | |||
| } | |||
| @@ -881,35 +993,51 @@ namespace Discord | |||
| { | |||
| //Open websocket while we wait for login response | |||
| Task socketTask = _webSocket.ConnectAsync(url); | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Connection, $"DataSocket connected."); | |||
| var response = await DiscordAPI.Login(emailOrUsername, password); | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Connection, $"DataSocket got token."); | |||
| await socketTask; | |||
| //Wait for websocket to finish connecting, then send token | |||
| token = response.Token; | |||
| Http.Token = token; | |||
| await _webSocket.Login(); | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Connection, $"DataSocket logged in."); | |||
| success = true; | |||
| } | |||
| if (!success && password == null) //Anonymous login | |||
| { | |||
| //Open websocket while we wait for login response | |||
| Task socketTask = _webSocket.ConnectAsync(url); | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Connection, $"DataSocket connected."); | |||
| var response = await DiscordAPI.LoginAnonymous(emailOrUsername); | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Connection, $"DataSocket generated anonymous token."); | |||
| await socketTask; | |||
| //Wait for websocket to finish connecting, then send token | |||
| token = response.Token; | |||
| Http.Token = token; | |||
| await _webSocket.Login(); | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.Connection, $"DataSocket logged in."); | |||
| success = true; | |||
| } | |||
| if (success) | |||
| { | |||
| var cancelToken = _disconnectToken.Token; | |||
| if (_config.UseMessageQueue) | |||
| _tasks = Task.WhenAll(await Task.Factory.StartNew(SendAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default)); | |||
| _tasks = SendAsync(); | |||
| else | |||
| _tasks = Task.WhenAll(await Task.Factory.StartNew(EmptyAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default)); | |||
| _tasks = EmptyAsync(); | |||
| _tasks = _tasks.ContinueWith(async x => | |||
| { | |||
| await _webSocket.DisconnectAsync(); | |||
| @@ -1347,10 +1475,13 @@ namespace Discord | |||
| /// <param name="count">Number of bytes in this frame. </param> | |||
| public void SendVoicePCM(byte[] data, int count) | |||
| { | |||
| if (!_config.EnableVoice) | |||
| if (!_config.EnableVoice) | |||
| throw new InvalidOperationException("Voice is not enabled for this client."); | |||
| if (count == 0) return; | |||
| _voiceWebSocket.SendPCMFrame(data, count); | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.VoiceOutput, $"Queued {count} bytes for voice output."); | |||
| _voiceWebSocket.SendPCMFrame(data, count); | |||
| } | |||
| /// <summary> Clears the PCM buffer. </summary> | |||
| @@ -1359,6 +1490,8 @@ namespace Discord | |||
| if (!_config.EnableVoice) | |||
| throw new InvalidOperationException("Voice is not enabled for this client."); | |||
| if (_config.EnableDebug) | |||
| RaiseOnDebugMessage(DebugMessageType.VoiceOutput, $"Cleared the voice buffer."); | |||
| _voiceWebSocket.ClearPCMFrames(); | |||
| } | |||
| #endif | |||
| @@ -7,6 +7,9 @@ | |||
| /// <remarks> This option requires the opus .dll or .so be in the local lib/ folder. </remarks> | |||
| public bool EnableVoice { get; set; } = false; | |||
| #endif | |||
| /// <summary> Enables the verbose DebugMessage event handler. May hinder performance but should help debug any issues. </summary> | |||
| public bool EnableDebug { get; set; } = false; | |||
| /// <summary> Max time in milliseconds to wait for the web socket to connect. </summary> | |||
| public int ConnectionTimeout { get; set; } = 5000; | |||
| /// <summary> Max time in milliseconds to wait for the voice web socket to connect. </summary> | |||
| @@ -3,7 +3,7 @@ using System; | |||
| namespace Discord | |||
| { | |||
| internal partial class DiscordTextWebSocket | |||
| internal partial class DiscordDataSocket | |||
| { | |||
| public event EventHandler<MessageEventArgs> GotEvent; | |||
| public sealed class MessageEventArgs : EventArgs | |||
| @@ -9,11 +9,11 @@ using WebSocketMessage = Discord.API.Models.TextWebSocketCommands.WebSocketMessa | |||
| namespace Discord | |||
| { | |||
| internal sealed partial class DiscordTextWebSocket : DiscordWebSocket | |||
| internal sealed partial class DiscordDataSocket : DiscordWebSocket | |||
| { | |||
| private ManualResetEventSlim _connectWaitOnLogin, _connectWaitOnLogin2; | |||
| public DiscordTextWebSocket(DiscordClient client, int timeout, int interval) | |||
| public DiscordDataSocket(DiscordClient client, int timeout, int interval) | |||
| : base(client, timeout, interval) | |||
| { | |||
| _connectWaitOnLogin = new ManualResetEventSlim(false); | |||
| @@ -72,7 +72,7 @@ namespace Discord | |||
| } | |||
| break; | |||
| default: | |||
| RaiseOnDebugMessage("Unknown WebSocket operation ID: " + msg.Operation); | |||
| RaiseOnDebugMessage(DebugMessageType.WebSocketUnknownInput, "Unknown DataSocket op: " + msg.Operation); | |||
| break; | |||
| } | |||
| #if DNXCORE | |||
| @@ -287,7 +287,7 @@ namespace Discord | |||
| break; | |||
| #endif | |||
| default: | |||
| RaiseOnDebugMessage("Unknown WebSocket operation ID: " + msg.Operation); | |||
| RaiseOnDebugMessage(DebugMessageType.WebSocketUnknownInput, "Unknown VoiceSocket op: " + msg.Operation); | |||
| break; | |||
| } | |||
| #if DNXCORE50 | |||
| @@ -4,25 +4,26 @@ namespace Discord | |||
| { | |||
| internal abstract partial class DiscordWebSocket | |||
| { | |||
| //Debug | |||
| public event EventHandler<LogMessageEventArgs> OnDebugMessage; | |||
| protected void RaiseOnDebugMessage(DebugMessageType type, string message) | |||
| { | |||
| if (OnDebugMessage != null) | |||
| OnDebugMessage(this, new LogMessageEventArgs(type, message)); | |||
| } | |||
| //Connection | |||
| public event EventHandler Connected; | |||
| private void RaiseConnected() | |||
| { | |||
| if (Connected != null) | |||
| Connected(this, EventArgs.Empty); | |||
| } | |||
| public event EventHandler Disconnected; | |||
| private void RaiseDisconnected() | |||
| { | |||
| if (Disconnected != null) | |||
| Disconnected(this, EventArgs.Empty); | |||
| } | |||
| public event EventHandler<DiscordClient.LogMessageEventArgs> OnDebugMessage; | |||
| protected void RaiseOnDebugMessage(string message) | |||
| { | |||
| if (OnDebugMessage != null) | |||
| OnDebugMessage(this, new DiscordClient.LogMessageEventArgs(message)); | |||
| } | |||
| } | |||
| } | |||
| @@ -67,7 +67,12 @@ namespace Discord | |||
| _client = client; | |||
| _bans = new ConcurrentDictionary<string, bool>(); | |||
| _members = new AsyncCache<Membership, API.Models.MemberInfo>( | |||
| (key, parentKey) => new Membership(parentKey, key, _client), | |||
| (key, parentKey) => | |||
| { | |||
| if (_client.Config.EnableDebug) | |||
| _client.RaiseOnDebugMessage(DebugMessageType.Cache, $"Created user {key} in server {parentKey}."); | |||
| return new Membership(parentKey, key, _client); | |||
| }, | |||
| (member, model) => | |||
| { | |||
| if (model is API.Models.PresenceMemberInfo) | |||
| @@ -103,7 +108,14 @@ namespace Discord | |||
| member.IsDeafened = extendedModel.IsDeafened; | |||
| member.IsMuted = extendedModel.IsMuted; | |||
| } | |||
| } | |||
| if (_client.Config.EnableDebug) | |||
| _client.RaiseOnDebugMessage(DebugMessageType.Cache, $"Updated user {member.User?.Name} ({member.UserId}) in server {member.Server?.Name} ({member.ServerId})."); | |||
| }, | |||
| (member) => | |||
| { | |||
| if (_client.Config.EnableDebug) | |||
| _client.RaiseOnDebugMessage(DebugMessageType.Cache, $"Destroyed user {member.User?.Name} ({member.UserId}) in server {member.Server?.Name} ({member.ServerId})."); | |||
| } | |||
| ); | |||
| } | |||