//Constructor /// <summary> Initializes a new instance of the DiscordClient class. </summary> public DiscordClient(DiscordClientConfig config = null) { _blockEvent = new ManualResetEventSlim(true); _config = config ?? new DiscordClientConfig(); _isDebugMode = config.EnableDebug; _rand = new Random(); _serializer = new JsonSerializer(); #if TEST_RESPONSES _serializer.CheckAdditionalContent = true; _serializer.MissingMemberHandling = MissingMemberHandling.Error; #endif _userRegex = new Regex(@"<@\d+?>", RegexOptions.Compiled); _channelRegex = new Regex(@"<#\d+?>", RegexOptions.Compiled); _userRegexEvaluator = new MatchEvaluator(e => { string id = e.Value.Substring(2, e.Value.Length - 3); var user = _users[id]; if (user != null) return '@' + user.Name; else //User not found return e.Value; }); _channelRegexEvaluator = new MatchEvaluator(e => { string id = e.Value.Substring(2, e.Value.Length - 3); var channel = _channels[id]; if (channel != null) return channel.Name; else //Channel not found return e.Value; }); if (_config.UseMessageQueue) _pendingMessages = new ConcurrentQueue<Message>(); _http = new JsonHttpClient(config.EnableDebug); _api = new DiscordAPI(_http); if (_isDebugMode) _http.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Type, e.Message); CreateCaches(); _webSocket = new DiscordDataSocket(this, config.ConnectionTimeout, config.WebSocketInterval, config.EnableDebug); _webSocket.Connected += (s, e) => RaiseConnected(); _webSocket.Disconnected += async (s, e) => { RaiseDisconnected(); //Reconnect if we didn't cause the disconnect if (e.WasUnexpected) { while (!_disconnectToken.IsCancellationRequested) { try { await Task.Delay(_config.ReconnectDelay); await _webSocket.ReconnectAsync(); if (_http.Token != null) await _webSocket.Login(_http.Token); break; } catch (Exception ex) { 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); } } } }; if (_isDebugMode) _webSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Type, $"DataSocket: {e.Message}"); #if !DNXCORE50 if (_config.EnableVoice) { _voiceWebSocket = new DiscordVoiceSocket(this, _config.VoiceConnectionTimeout, _config.WebSocketInterval, config.EnableDebug); _voiceWebSocket.Connected += (s, e) => RaiseVoiceConnected(); _voiceWebSocket.Disconnected += async (s, e) => { RaiseVoiceDisconnected(); //Reconnect if we didn't cause the disconnect if (e.WasUnexpected) { while (!_disconnectToken.IsCancellationRequested) { try { await Task.Delay(_config.ReconnectDelay); await _voiceWebSocket.ReconnectAsync(); await _voiceWebSocket.Login(_currentVoiceServerId, _myId, _sessionId, _currentVoiceToken); break; } catch (Exception ex) { if (_isDebugMode) 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); } } } }; if (_isDebugMode) _voiceWebSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Type, $"VoiceSocket: {e.Message}"); } #endif #if !DNXCORE50 _webSocket.GotEvent += async (s, e) => #else _webSocket.GotEvent += (s, e) => #endif { switch (e.Type) { //Global case "READY": //Resync { var data = e.Event.ToObject<TextWebSocketEvents.Ready>(_serializer); _servers.Clear(); _channels.Clear(); _users.Clear(); _myId = data.User.Id; #if !DNXCORE50 _sessionId = data.SessionId; #endif _user = _users.Update(data.User.Id, data.User); foreach (var server in data.Guilds) _servers.Update(server.Id, server); foreach (var channel in data.PrivateChannels) _channels.Update(channel.Id, null, channel); } break; //Servers case "GUILD_CREATE": { var data = e.Event.ToObject<TextWebSocketEvents.GuildCreate>(_serializer); var server = _servers.Update(data.Id, data); try { RaiseServerCreated(server); } catch { } } break; case "GUILD_UPDATE": { var data = e.Event.ToObject<TextWebSocketEvents.GuildUpdate>(_serializer); var server = _servers.Update(data.Id, data); try { RaiseServerUpdated(server); } catch { } } break; case "GUILD_DELETE": { var data = e.Event.ToObject<TextWebSocketEvents.GuildDelete>(_serializer); var server = _servers.Remove(data.Id); if (server != null) try { RaiseServerDestroyed(server); } catch { } } break; //Channels case "CHANNEL_CREATE": { var data = e.Event.ToObject<TextWebSocketEvents.ChannelCreate>(_serializer); var channel = _channels.Update(data.Id, data.GuildId, data); try { RaiseChannelCreated(channel); } catch { } } break; case "CHANNEL_UPDATE": { var data = e.Event.ToObject<TextWebSocketEvents.ChannelUpdate>(_serializer); var channel = _channels.Update(data.Id, data.GuildId, data); try { RaiseChannelUpdated(channel); } catch { } } break; case "CHANNEL_DELETE": { var data = e.Event.ToObject<TextWebSocketEvents.ChannelDelete>(_serializer); var channel = _channels.Remove(data.Id); if (channel != null) try { RaiseChannelDestroyed(channel); } catch { } } break; //Members case "GUILD_MEMBER_ADD": { var data = e.Event.ToObject<TextWebSocketEvents.GuildMemberAdd>(_serializer); var user = _users.Update(data.User.Id, data.User); var server = _servers[data.ServerId]; if (server != null) { var member = server.UpdateMember(data); try { RaiseMemberAdded(member); } catch { } } } break; case "GUILD_MEMBER_UPDATE": { var data = e.Event.ToObject<TextWebSocketEvents.GuildMemberUpdate>(_serializer); var user = _users.Update(data.User.Id, data.User); var server = _servers[data.ServerId]; if (server != null) { var member = server.UpdateMember(data); try { RaiseMemberUpdated(member); } catch { } } } break; case "GUILD_MEMBER_REMOVE": { var data = e.Event.ToObject<TextWebSocketEvents.GuildMemberRemove>(_serializer); var server = _servers[data.ServerId]; if (server != null) { var member = server.RemoveMember(data.User.Id); if (member != null) try { RaiseMemberRemoved(member); } catch { } } } break; //Roles case "GUILD_ROLE_CREATE": { var data = e.Event.ToObject<TextWebSocketEvents.GuildRoleCreateUpdate>(_serializer); var role = _roles.Update(data.Role.Id, data.ServerId, data.Role); try { RaiseRoleCreated(role); } catch { } } break; case "GUILD_ROLE_UPDATE": { var data = e.Event.ToObject<TextWebSocketEvents.GuildRoleCreateUpdate>(_serializer); var role = _roles.Update(data.Role.Id, data.ServerId, data.Role); try { RaiseRoleUpdated(role); } catch { } } break; case "GUILD_ROLE_DELETE": { var data = e.Event.ToObject<TextWebSocketEvents.GuildRoleDelete>(_serializer); var role = _roles.Remove(data.RoleId); if (role != null) try { RaiseRoleDeleted(role); } catch { } } break; //Bans case "GUILD_BAN_ADD": { var data = e.Event.ToObject<TextWebSocketEvents.GuildBanAddRemove>(_serializer); var user = _users.Update(data.User.Id, data.User); var server = _servers[data.ServerId]; try { RaiseBanAdded(user, server); } catch { } } break; case "GUILD_BAN_REMOVE": { var data = e.Event.ToObject<TextWebSocketEvents.GuildBanAddRemove>(_serializer); var user = _users.Update(data.User.Id, data.User); var server = _servers[data.ServerId]; if (server != null && server.RemoveBan(user.Id)) { try { RaiseBanRemoved(user, server); } catch { } } } break; //Messages case "MESSAGE_CREATE": { var data = e.Event.ToObject<TextWebSocketEvents.MessageCreate>(_serializer); Message msg = null; bool wasLocal = _config.UseMessageQueue && data.Author.Id == _myId && data.Nonce != null; if (wasLocal) { msg = _messages.Remap("nonce" + data.Nonce, data.Id); if (msg != null) { msg.IsQueued = false; msg.Id = data.Id; } } msg = _messages.Update(data.Id, data.ChannelId, data); msg.User.UpdateActivity(data.Timestamp); if (wasLocal) { try { RaiseMessageSent(msg); } catch { } } try { RaiseMessageCreated(msg); } catch { } } break; case "MESSAGE_UPDATE": { var data = e.Event.ToObject<TextWebSocketEvents.MessageUpdate>(_serializer); var msg = _messages.Update(data.Id, data.ChannelId, data); try { RaiseMessageUpdated(msg); } catch { } } break; case "MESSAGE_DELETE": { var data = e.Event.ToObject<TextWebSocketEvents.MessageDelete>(_serializer); var msg = GetMessage(data.MessageId); if (msg != null) { _messages.Remove(msg.Id); try { RaiseMessageDeleted(msg); } catch { } } } break; case "MESSAGE_ACK": { var data = e.Event.ToObject<TextWebSocketEvents.MessageAck>(_serializer); var msg = GetMessage(data.MessageId); if (msg != null) try { RaiseMessageRead(msg); } catch { } } break; //Statuses case "PRESENCE_UPDATE": { var data = e.Event.ToObject<TextWebSocketEvents.PresenceUpdate>(_serializer); var user = _users.Update(data.User.Id, data.User); var server = _servers[data.ServerId]; if (server != null) { var member = server.UpdateMember(data); try { RaisePresenceUpdated(member); } catch { } } } break; case "VOICE_STATE_UPDATE": { var data = e.Event.ToObject<TextWebSocketEvents.VoiceStateUpdate>(_serializer); var server = _servers[data.ServerId]; if (server != null) { var member = server.UpdateMember(data); if (member != null) try { RaiseVoiceStateUpdated(member); } catch { } } } break; case "TYPING_START": { var data = e.Event.ToObject<TextWebSocketEvents.TypingStart>(_serializer); var channel = _channels[data.ChannelId]; var user = _users[data.UserId]; if (user != null) { user.UpdateActivity(DateTime.UtcNow); if (channel != null) try { RaiseUserTyping(user, channel); } catch { } } } break; //Voice case "VOICE_SERVER_UPDATE": { var data = e.Event.ToObject<TextWebSocketEvents.VoiceServerUpdate>(_serializer); var server = _servers[data.ServerId]; server.VoiceServer = data.Endpoint; try { RaiseVoiceServerUpdated(server, data.Endpoint); } catch { } #if !DNXCORE50 if (_config.EnableVoice && data.ServerId == _currentVoiceServerId) { _currentVoiceToken = data.Token; await _voiceWebSocket.ConnectAsync("wss://" + data.Endpoint.Split(':')[0]); await _voiceWebSocket.Login(_currentVoiceServerId, _myId, _myId, data.Token); } #endif } break; //Settings case "USER_UPDATE": { var data = e.Event.ToObject<TextWebSocketEvents.UserUpdate>(_serializer); var user = _users.Update(data.Id, data); try { RaiseUserUpdated(user); } catch { } } break; case "USER_SETTINGS_UPDATE": { //TODO: Process this } break; //Others default: RaiseOnDebugMessage(DebugMessageType.WebSocketUnknownEvent, "Unknown WebSocket message type: " + e.Type); break; } }; }
//Constructor /// <summary> Initializes a new instance of the DiscordClient class. </summary> public DiscordClient(DiscordClientConfig config = null) { _blockEvent = new ManualResetEventSlim(true); _config = config ?? new DiscordClientConfig(); _isDebugMode = config.EnableDebug; _rand = new Random(); _serializer = new JsonSerializer(); #if TEST_RESPONSES _serializer.CheckAdditionalContent = true; _serializer.MissingMemberHandling = MissingMemberHandling.Error; #endif _userRegex = new Regex(@"<@\d+?>", RegexOptions.Compiled); _channelRegex = new Regex(@"<#\d+?>", RegexOptions.Compiled); _userRegexEvaluator = new MatchEvaluator(e => { string id = e.Value.Substring(2, e.Value.Length - 3); var user = _users[id]; if (user != null) { return('@' + user.Name); } else //User not found { return(e.Value); } }); _channelRegexEvaluator = new MatchEvaluator(e => { string id = e.Value.Substring(2, e.Value.Length - 3); var channel = _channels[id]; if (channel != null) { return(channel.Name); } else //Channel not found { return(e.Value); } }); if (_config.UseMessageQueue) { _pendingMessages = new ConcurrentQueue <Message>(); } _http = new JsonHttpClient(config.EnableDebug); _api = new DiscordAPI(_http); if (_isDebugMode) { _http.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Type, e.Message); } CreateCaches(); _webSocket = new DiscordDataSocket(this, config.ConnectionTimeout, config.WebSocketInterval, config.EnableDebug); _webSocket.Connected += (s, e) => RaiseConnected(); _webSocket.Disconnected += async(s, e) => { RaiseDisconnected(); //Reconnect if we didn't cause the disconnect if (e.WasUnexpected) { while (!_disconnectToken.IsCancellationRequested) { try { await Task.Delay(_config.ReconnectDelay); await _webSocket.ReconnectAsync(); if (_http.Token != null) { await _webSocket.Login(_http.Token); } break; } catch (Exception ex) { 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); } } } }; if (_isDebugMode) { _webSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Type, $"DataSocket: {e.Message}"); } #if !DNXCORE50 if (_config.EnableVoice) { _voiceWebSocket = new DiscordVoiceSocket(this, _config.VoiceConnectionTimeout, _config.WebSocketInterval, config.EnableDebug); _voiceWebSocket.Connected += (s, e) => RaiseVoiceConnected(); _voiceWebSocket.Disconnected += async(s, e) => { RaiseVoiceDisconnected(); //Reconnect if we didn't cause the disconnect if (e.WasUnexpected) { while (!_disconnectToken.IsCancellationRequested) { try { await Task.Delay(_config.ReconnectDelay); await _voiceWebSocket.ReconnectAsync(); await _voiceWebSocket.Login(_currentVoiceServerId, _myId, _sessionId, _currentVoiceToken); break; } catch (Exception ex) { if (_isDebugMode) { 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); } } } }; if (_isDebugMode) { _voiceWebSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Type, $"VoiceSocket: {e.Message}"); } } #endif #if !DNXCORE50 _webSocket.GotEvent += async(s, e) => #else _webSocket.GotEvent += (s, e) => #endif { switch (e.Type) { //Global case "READY": //Resync { var data = e.Event.ToObject <TextWebSocketEvents.Ready>(_serializer); _servers.Clear(); _channels.Clear(); _users.Clear(); _myId = data.User.Id; #if !DNXCORE50 _sessionId = data.SessionId; #endif _user = _users.Update(data.User.Id, data.User); foreach (var server in data.Guilds) { _servers.Update(server.Id, server); } foreach (var channel in data.PrivateChannels) { _channels.Update(channel.Id, null, channel); } } break; //Servers case "GUILD_CREATE": { var data = e.Event.ToObject <TextWebSocketEvents.GuildCreate>(_serializer); var server = _servers.Update(data.Id, data); try { RaiseServerCreated(server); } catch { } } break; case "GUILD_UPDATE": { var data = e.Event.ToObject <TextWebSocketEvents.GuildUpdate>(_serializer); var server = _servers.Update(data.Id, data); try { RaiseServerUpdated(server); } catch { } } break; case "GUILD_DELETE": { var data = e.Event.ToObject <TextWebSocketEvents.GuildDelete>(_serializer); var server = _servers.Remove(data.Id); if (server != null) { try { RaiseServerDestroyed(server); } catch { } } } break; //Channels case "CHANNEL_CREATE": { var data = e.Event.ToObject <TextWebSocketEvents.ChannelCreate>(_serializer); var channel = _channels.Update(data.Id, data.GuildId, data); try { RaiseChannelCreated(channel); } catch { } } break; case "CHANNEL_UPDATE": { var data = e.Event.ToObject <TextWebSocketEvents.ChannelUpdate>(_serializer); var channel = _channels.Update(data.Id, data.GuildId, data); try { RaiseChannelUpdated(channel); } catch { } } break; case "CHANNEL_DELETE": { var data = e.Event.ToObject <TextWebSocketEvents.ChannelDelete>(_serializer); var channel = _channels.Remove(data.Id); if (channel != null) { try { RaiseChannelDestroyed(channel); } catch { } } } break; //Members case "GUILD_MEMBER_ADD": { var data = e.Event.ToObject <TextWebSocketEvents.GuildMemberAdd>(_serializer); var user = _users.Update(data.User.Id, data.User); var server = _servers[data.ServerId]; if (server != null) { var member = server.UpdateMember(data); try { RaiseMemberAdded(member); } catch { } } } break; case "GUILD_MEMBER_UPDATE": { var data = e.Event.ToObject <TextWebSocketEvents.GuildMemberUpdate>(_serializer); var user = _users.Update(data.User.Id, data.User); var server = _servers[data.ServerId]; if (server != null) { var member = server.UpdateMember(data); try { RaiseMemberUpdated(member); } catch { } } } break; case "GUILD_MEMBER_REMOVE": { var data = e.Event.ToObject <TextWebSocketEvents.GuildMemberRemove>(_serializer); var server = _servers[data.ServerId]; if (server != null) { var member = server.RemoveMember(data.User.Id); if (member != null) { try { RaiseMemberRemoved(member); } catch { } } } } break; //Roles case "GUILD_ROLE_CREATE": { var data = e.Event.ToObject <TextWebSocketEvents.GuildRoleCreateUpdate>(_serializer); var role = _roles.Update(data.Role.Id, data.ServerId, data.Role); try { RaiseRoleCreated(role); } catch { } } break; case "GUILD_ROLE_UPDATE": { var data = e.Event.ToObject <TextWebSocketEvents.GuildRoleCreateUpdate>(_serializer); var role = _roles.Update(data.Role.Id, data.ServerId, data.Role); try { RaiseRoleUpdated(role); } catch { } } break; case "GUILD_ROLE_DELETE": { var data = e.Event.ToObject <TextWebSocketEvents.GuildRoleDelete>(_serializer); var role = _roles.Remove(data.RoleId); if (role != null) { try { RaiseRoleDeleted(role); } catch { } } } break; //Bans case "GUILD_BAN_ADD": { var data = e.Event.ToObject <TextWebSocketEvents.GuildBanAddRemove>(_serializer); var user = _users.Update(data.User.Id, data.User); var server = _servers[data.ServerId]; try { RaiseBanAdded(user, server); } catch { } } break; case "GUILD_BAN_REMOVE": { var data = e.Event.ToObject <TextWebSocketEvents.GuildBanAddRemove>(_serializer); var user = _users.Update(data.User.Id, data.User); var server = _servers[data.ServerId]; if (server != null && server.RemoveBan(user.Id)) { try { RaiseBanRemoved(user, server); } catch { } } } break; //Messages case "MESSAGE_CREATE": { var data = e.Event.ToObject <TextWebSocketEvents.MessageCreate>(_serializer); Message msg = null; bool wasLocal = _config.UseMessageQueue && data.Author.Id == _myId && data.Nonce != null; if (wasLocal) { msg = _messages.Remap("nonce" + data.Nonce, data.Id); if (msg != null) { msg.IsQueued = false; msg.Id = data.Id; } } msg = _messages.Update(data.Id, data.ChannelId, data); msg.User.UpdateActivity(data.Timestamp); if (wasLocal) { try { RaiseMessageSent(msg); } catch { } } try { RaiseMessageCreated(msg); } catch { } } break; case "MESSAGE_UPDATE": { var data = e.Event.ToObject <TextWebSocketEvents.MessageUpdate>(_serializer); var msg = _messages.Update(data.Id, data.ChannelId, data); try { RaiseMessageUpdated(msg); } catch { } } break; case "MESSAGE_DELETE": { var data = e.Event.ToObject <TextWebSocketEvents.MessageDelete>(_serializer); var msg = GetMessage(data.MessageId); if (msg != null) { _messages.Remove(msg.Id); try { RaiseMessageDeleted(msg); } catch { } } } break; case "MESSAGE_ACK": { var data = e.Event.ToObject <TextWebSocketEvents.MessageAck>(_serializer); var msg = GetMessage(data.MessageId); if (msg != null) { try { RaiseMessageRead(msg); } catch { } } } break; //Statuses case "PRESENCE_UPDATE": { var data = e.Event.ToObject <TextWebSocketEvents.PresenceUpdate>(_serializer); var user = _users.Update(data.User.Id, data.User); var server = _servers[data.ServerId]; if (server != null) { var member = server.UpdateMember(data); try { RaisePresenceUpdated(member); } catch { } } } break; case "VOICE_STATE_UPDATE": { var data = e.Event.ToObject <TextWebSocketEvents.VoiceStateUpdate>(_serializer); var server = _servers[data.ServerId]; if (server != null) { var member = server.UpdateMember(data); if (member != null) { try { RaiseVoiceStateUpdated(member); } catch { } } } } break; case "TYPING_START": { var data = e.Event.ToObject <TextWebSocketEvents.TypingStart>(_serializer); var channel = _channels[data.ChannelId]; var user = _users[data.UserId]; if (user != null) { user.UpdateActivity(DateTime.UtcNow); if (channel != null) { try { RaiseUserTyping(user, channel); } catch { } } } } break; //Voice case "VOICE_SERVER_UPDATE": { var data = e.Event.ToObject <TextWebSocketEvents.VoiceServerUpdate>(_serializer); var server = _servers[data.ServerId]; server.VoiceServer = data.Endpoint; try { RaiseVoiceServerUpdated(server, data.Endpoint); } catch { } #if !DNXCORE50 if (_config.EnableVoice && data.ServerId == _currentVoiceServerId) { _currentVoiceToken = data.Token; await _voiceWebSocket.ConnectAsync("wss://" + data.Endpoint.Split(':')[0]); await _voiceWebSocket.Login(_currentVoiceServerId, _myId, _myId, data.Token); } #endif } break; //Settings case "USER_UPDATE": { var data = e.Event.ToObject <TextWebSocketEvents.UserUpdate>(_serializer); var user = _users.Update(data.Id, data); try { RaiseUserUpdated(user); } catch { } } break; case "USER_SETTINGS_UPDATE": { //TODO: Process this } break; //Others default: RaiseOnDebugMessage(DebugMessageType.WebSocketUnknownEvent, "Unknown WebSocket message type: " + e.Type); break; } }; }