From 2d42a03c57db4ffd48a6e67457bdd62b00e6c930 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 29 May 2017 23:37:08 +0300 Subject: [PATCH] Delete DiscordClient.cs --- src/Discord.Net/DiscordClient.cs | 1188 ------------------------------ 1 file changed, 1188 deletions(-) delete mode 100644 src/Discord.Net/DiscordClient.cs diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs deleted file mode 100644 index 4bb076ba1..000000000 --- a/src/Discord.Net/DiscordClient.cs +++ /dev/null @@ -1,1188 +0,0 @@ -using Discord.API.Client.GatewaySocket; -using Discord.API.Client.Rest; -using Discord.Logging; -using Discord.Net; -using Discord.Net.Rest; -using Discord.Net.WebSockets; -using Newtonsoft.Json; -using Nito.AsyncEx; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Security.Cryptography; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Discord -{ - /// Provides a connection to the DiscordApp service. - public partial class DiscordClient : IDisposable - { - private readonly AsyncLock _connectionLock; - private readonly ManualResetEvent _disconnectedEvent; - private readonly ManualResetEventSlim _connectedEvent; - private readonly TaskManager _taskManager; - private readonly ServiceCollection _services; - private ConcurrentDictionary _servers; - private ConcurrentDictionary _channels; - private ConcurrentDictionary _privateChannels; //Key = RecipientId - private Dictionary _regions; - private Stopwatch _connectionStopwatch; - private ConcurrentQueue _largeServers; - - internal Logger Logger { get; } - - /// Gets the configuration object used to make this client. - public DiscordConfig Config { get; } - /// Gets the log manager. - public LogManager Log { get; } - /// Gets the internal RestClient for the Client API endpoint. - public RestClient ClientAPI { get; } - /// Gets the internal RestClient for the Status API endpoint. - public RestClient StatusAPI { get; } - /// Gets the internal WebSocket for the Gateway event stream. - public GatewaySocket GatewaySocket { get; } - /// Gets the queue used for outgoing messages, if enabled. - public MessageQueue MessageQueue { get; } - /// Gets the JSON serializer used by this client. - public JsonSerializer Serializer { get; } - - /// Gets the current connection state of this client. - public ConnectionState State { get; private set; } - /// Gets a cancellation token that triggers when the client is manually disconnected. - public CancellationToken CancelToken { get; private set; } - /// Gets the current logged-in user used in private channels. - internal User PrivateUser { get; private set; } - /// Gets information about the current logged-in account. - public Profile CurrentUser { get; private set; } - /// Gets the session id for the current connection. - public string SessionId { get; private set; } - /// Gets the status of the current user. - public UserStatus Status { get; private set; } - /// Gets the game the current user is displayed as playing. - public Game CurrentGame { get; private set; } - - /// Gets a collection of all extensions added to this DiscordClient. - public IEnumerable Services => _services; - /// Gets a collection of all servers this client is a member of. - public IEnumerable Servers => _servers.Select(x => x.Value); - /// Gets a collection of all private channels this client is a member of. - public IEnumerable PrivateChannels => _privateChannels.Select(x => x.Value); - /// Gets a collection of all voice regions currently offered by Discord. - public IEnumerable Regions => _regions.Select(x => x.Value); - - /// Initializes a new instance of the DiscordClient class. - public DiscordClient(Action configFunc) - : this(ProcessConfig(configFunc)) - { - } - private static DiscordConfigBuilder ProcessConfig(Action func) - { - var config = new DiscordConfigBuilder(); - func(config); - return config; - } - - /// Initializes a new instance of the DiscordClient class. - public DiscordClient() - : this(new DiscordConfigBuilder()) - { - } - /// Initializes a new instance of the DiscordClient class. - public DiscordClient(DiscordConfigBuilder builder) - : this(builder.Build()) - { - if (builder.LogHandler != null) - Log.Message += builder.LogHandler; - } - /// Initializes a new instance of the DiscordClient class. - public DiscordClient(DiscordConfig config) - { - Config = config; - - State = (int)ConnectionState.Disconnected; - Status = UserStatus.Online; - - //Logging - Log = new LogManager(this); - Logger = Log.CreateLogger("Discord"); - if (config.LogLevel >= LogSeverity.Verbose) - _connectionStopwatch = new Stopwatch(); - - //Async - _taskManager = new TaskManager(Cleanup); - _connectionLock = new AsyncLock(); - _disconnectedEvent = new ManualResetEvent(true); - _connectedEvent = new ManualResetEventSlim(false); - CancelToken = new CancellationToken(true); - - //Cache - //ConcurrentLevel = 2 (only REST and WebSocket can add/remove) - _servers = new ConcurrentDictionary(2, 0); - _channels = new ConcurrentDictionary(2, 0); - _privateChannels = new ConcurrentDictionary(2, 0); - _largeServers = new ConcurrentQueue(); - - //Serialization - Serializer = new JsonSerializer(); - Serializer.DateTimeZoneHandling = DateTimeZoneHandling.Utc; -#if TEST_RESPONSES - Serializer.CheckAdditionalContent = true; - Serializer.MissingMemberHandling = MissingMemberHandling.Error; -#else - Serializer.CheckAdditionalContent = false; - Serializer.MissingMemberHandling = MissingMemberHandling.Ignore; -#endif - Serializer.Error += (s, e) => - { - e.ErrorContext.Handled = true; - Logger.Error("Serialization Failed", e.ErrorContext.Error); - }; - - //Networking - ClientAPI = new JsonRestClient(Config, DiscordConfig.ClientAPIUrl, Log.CreateLogger("ClientAPI")); - StatusAPI = new JsonRestClient(Config, DiscordConfig.StatusAPIUrl, Log.CreateLogger("StatusAPI")); - GatewaySocket = new GatewaySocket(Config, Serializer, Log.CreateLogger("Gateway")); - - //GatewaySocket.Disconnected += (s, e) => OnDisconnected(e.WasUnexpected, e.Exception); - GatewaySocket.ReceivedDispatch += (s, e) => OnReceivedEvent(e); - - MessageQueue = new MessageQueue(ClientAPI, Log.CreateLogger("MessageQueue")); - - //Extensibility - _services = new ServiceCollection(this); - } - - /// Connects to the Discord server with the provided email and password. - /// Returns a token that can be optionally stored to speed up future connections. - public async Task Connect(string email, string password, string token = null) - { - if (email == null) throw new ArgumentNullException(email); - if (password == null) throw new ArgumentNullException(password); - - await BeginConnect(email, password, null).ConfigureAwait(false); - return ClientAPI.Token; - } - /// Connects to the Discord server with the provided token. - public async Task Connect(string token, TokenType tokenType) - { - if (token == null) throw new ArgumentNullException(token); - - await BeginConnect(null, null, token, tokenType).ConfigureAwait(false); - } - - private async Task BeginConnect(string email, string password, string token = null, TokenType tokenType = TokenType.User) - { - try - { - using (await _connectionLock.LockAsync().ConfigureAwait(false)) - { - await Disconnect().ConfigureAwait(false); - _taskManager.ClearException(); - - Stopwatch stopwatch = null; - if (Config.LogLevel >= LogSeverity.Verbose) - { - _connectionStopwatch.Restart(); - stopwatch = Stopwatch.StartNew(); - } - State = ConnectionState.Connecting; - _disconnectedEvent.Reset(); - - var cancelSource = new CancellationTokenSource(); - CancelToken = cancelSource.Token; - ClientAPI.CancelToken = CancelToken; - StatusAPI.CancelToken = CancelToken; - - switch (tokenType) - { - case TokenType.Bot: - token = $"Bot {token}"; - break; - case TokenType.User: - break; - default: - throw new ArgumentException("Unknown oauth token type", nameof(tokenType)); - } - - await Login(email, password, token).ConfigureAwait(false); - await GatewaySocket.Connect(ClientAPI, CancelToken).ConfigureAwait(false); - - var tasks = new[] { CancelToken.Wait(), LargeServerDownloader(CancelToken) } - .Concat(MessageQueue.Run(CancelToken)); - - await _taskManager.Start(tasks, cancelSource).ConfigureAwait(false); - GatewaySocket.WaitForConnection(CancelToken); - - if (Config.LogLevel >= LogSeverity.Verbose) - { - stopwatch.Stop(); - double seconds = Math.Round(stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerSecond, 2); - Logger.Verbose($"Handshake + Ready took {seconds} sec"); - } - } - } - catch (Exception ex) - { - await _taskManager.SignalError(ex).ConfigureAwait(false); - throw; - } - } - private async Task Login(string email = null, string password = null, string token = null) - { - string tokenPath = null, oldToken = null; - byte[] cacheKey = null; - - //Get Token - if (email != null && Config.CacheDir != null) - { - tokenPath = GetTokenCachePath(email); - if (token == null && password != null) - { - Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(password, - new byte[] { 0x5A, 0x2A, 0xF8, 0xCF, 0x78, 0xD3, 0x7D, 0x0D }); - cacheKey = deriveBytes.GetBytes(16); - - oldToken = LoadToken(tokenPath, cacheKey); - token = oldToken; - } - } - - ClientAPI.Token = token; - if (email != null && password != null) - { - var request = new LoginRequest() { Email = email, Password = password }; - var response = await ClientAPI.Send(request).ConfigureAwait(false); - token = response.Token; - if (Config.CacheDir != null && token != oldToken && tokenPath != null) - SaveToken(tokenPath, cacheKey, token); - ClientAPI.Token = token; - } - - //Cache other stuff - var regionsResponse = (await ClientAPI.Send(new GetVoiceRegionsRequest()).ConfigureAwait(false)); - _regions = regionsResponse.Select(x => new Region(x.Id, x.Name, x.Hostname, x.Port, x.Vip)) - .ToDictionary(x => x.Id); - } - private void EndConnect() - { - if (State == ConnectionState.Connecting) - { - State = ConnectionState.Connected; - _connectedEvent.Set(); - - if (Config.LogLevel >= LogSeverity.Verbose) - { - _connectionStopwatch.Stop(); - double seconds = Math.Round(_connectionStopwatch.ElapsedTicks / (double)TimeSpan.TicksPerSecond, 2); - Logger.Verbose($"Connection took {seconds} sec"); - } - - SendStatus(); - OnReady(); - } - } - - /// Disconnects from the Discord server, canceling any pending requests. - public Task Disconnect() => _taskManager.Stop(true); - private async Task Cleanup() - { - var oldState = State; - State = ConnectionState.Disconnecting; - - if (oldState == ConnectionState.Connected) - { - try { await ClientAPI.Send(new LogoutRequest()).ConfigureAwait(false); } - catch (OperationCanceledException) { } - } - - ulong serverId; - while (_largeServers.TryDequeue(out serverId)) { } - - MessageQueue.Clear(); - - await GatewaySocket.Disconnect().ConfigureAwait(false); - ClientAPI.Token = null; - - _servers.Clear(); - _channels.Clear(); - _privateChannels.Clear(); - - PrivateUser = null; - CurrentUser = null; - - State = (int)ConnectionState.Disconnected; - _connectedEvent.Reset(); - _disconnectedEvent.Set(); - } - - public void SetStatus(UserStatus status) - { - if (status == null) throw new ArgumentNullException(nameof(status)); - if (status != UserStatus.Online && status != UserStatus.Idle && status != UserStatus.DoNotDisturb && status != UserStatus.Invisible) - throw new ArgumentException($"Invalid status, must be {UserStatus.Online}, {UserStatus.Idle}, {UserStatus.DoNotDisturb} or {UserStatus.Invisible}", nameof(status)); - - Status = status; - SendStatus(); - } - public void SetGame(Game game) - { - CurrentGame = game; - SendStatus(); - } - public void SetGame(string game) - { - CurrentGame = new Game(game); - SendStatus(); - } - public void SetGame(string game, GameType type, string url) - { - CurrentGame = new Game(game, type, url); - SendStatus(); - } - private void SendStatus() - { - PrivateUser.Status = Status; - PrivateUser.CurrentGame = CurrentGame; - foreach (var server in Servers) - { - var current = server.CurrentUser; - if (current != null) - { - current.Status = Status; - current.CurrentGame = CurrentGame; - } - } - var socket = GatewaySocket; - if (socket != null) - socket.SendUpdateStatus(Status == UserStatus.Idle ? EpochTime.GetMilliseconds() - (10 * 60 * 1000) : (long?)null, CurrentGame, Status == UserStatus.Idle, Status); - } - - #region Channels - internal void AddChannel(Channel channel) - { - _channels.GetOrAdd(channel.Id, channel); - } - private Channel RemoveChannel(ulong id) - { - Channel channel; - if (_channels.TryRemove(id, out channel)) - { - if (channel.IsPrivate) - _privateChannels.TryRemove(channel.Recipient.Id, out channel); - else - channel.Server.RemoveChannel(id); - } - return channel; - } - public Channel GetChannel(ulong id) - { - Channel channel; - _channels.TryGetValue(id, out channel); - return channel; - } - - private Channel AddPrivateChannel(ulong id, ulong recipientId) - { - Channel channel; - if (_channels.TryGetOrAdd(id, x => new Channel(this, x, new User(this, recipientId, null)), out channel)) - _privateChannels[recipientId] = channel; - return channel; - } - internal Channel GetPrivateChannel(ulong recipientId) - { - Channel channel; - _privateChannels.TryGetValue(recipientId, out channel); - return channel; - } - internal Task CreatePMChannel(User user) - => CreatePrivateChannel(user.Id); - public async Task CreatePrivateChannel(ulong userId) - { - var channel = GetPrivateChannel(userId); - if (channel != null) return channel; - - var request = new CreatePrivateChannelRequest() { RecipientId = userId }; - var response = await ClientAPI.Send(request).ConfigureAwait(false); - - channel = AddPrivateChannel(response.Id, userId); - channel.Update(response); - return channel; - } - #endregion - - #region Invites - /// Gets more info about the provided invite code. - /// Supported formats: inviteCode, xkcdCode, https://discord.gg/inviteCode, https://discord.gg/xkcdCode - /// The invite object if found, null if not. - public async Task GetInvite(string inviteIdOrXkcd) - { - if (inviteIdOrXkcd == null) throw new ArgumentNullException(nameof(inviteIdOrXkcd)); - - //Remove trailing slash - if (inviteIdOrXkcd.Length > 0 && inviteIdOrXkcd[inviteIdOrXkcd.Length - 1] == '/') - inviteIdOrXkcd = inviteIdOrXkcd.Substring(0, inviteIdOrXkcd.Length - 1); - //Remove leading URL - int index = inviteIdOrXkcd.LastIndexOf('/'); - if (index >= 0) - inviteIdOrXkcd = inviteIdOrXkcd.Substring(index + 1); - - try - { - var response = await ClientAPI.Send(new GetInviteRequest(inviteIdOrXkcd)).ConfigureAwait(false); - var invite = new Invite(this, response.Code, response.XkcdPass); - invite.Update(response); - return invite; - } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) - { - return null; - } - } - #endregion - - #region Regions - public Region GetRegion(string id) - { - Region region; - if (_regions.TryGetValue(id, out region)) - return region; - else - return new Region(id, id, "", 0, false); - } - #endregion - - #region Servers - private Server AddServer(ulong id) - => _servers.GetOrAdd(id, x => new Server(this, x)); - private Server RemoveServer(ulong id) - { - Server server; - if (_servers.TryRemove(id, out server)) - { - foreach (var channel in server.AllChannels) - RemoveChannel(channel.Id); - } - return server; - } - - public Server GetServer(ulong id) - { - Server server; - _servers.TryGetValue(id, out server); - return server; - } - public IEnumerable FindServers(string name) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - return _servers.Select(x => x.Value).Find(name); - } - - /// Creates a new server with the provided name and region. - public async Task CreateServer(string name, Region region, ImageType iconType = ImageType.None, Stream icon = null) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - if (region == null) throw new ArgumentNullException(nameof(region)); - - var request = new CreateGuildRequest() - { - Name = name, - Region = region.Id, - IconBase64 = icon.Base64(iconType, null) - }; - var response = await ClientAPI.Send(request).ConfigureAwait(false); - - var server = AddServer(response.Id); - server.Update(response); - return server; - } - #endregion - - #region Gateway Events - private void OnReceivedEvent(WebSocketEventEventArgs e) - { - try - { - switch (e.Type) - { - //Global - case "READY": - { - //TODO: None of this is really threadsafe - should only replace the cache collections when they have been fully populated - - var data = e.Payload.ToObject(Serializer); - - //ConcurrencyLevel = 2 (only REST and WebSocket can add/remove) - _servers = new ConcurrentDictionary(2, (int)(data.Guilds.Length * 1.05)); - _channels = new ConcurrentDictionary(2, (int)(data.Guilds.Length * 2 * 1.05)); - _privateChannels = new ConcurrentDictionary(2, (int)(data.PrivateChannels.Length * 1.05)); - - SessionId = data.SessionId; - PrivateUser = new User(this, data.User.Id, null); - PrivateUser.Update(data.User); - CurrentUser = new Profile(this, data.User.Id); - CurrentUser.Update(data.User); - - for (int i = 0; i < data.Guilds.Length; i++) - { - var model = data.Guilds[i]; - if (model.Unavailable != true) - { - var server = AddServer(model.Id); - server.Update(model); - } - if (model.IsLarge) - _largeServers.Enqueue(model.Id); - } - for (int i = 0; i < data.PrivateChannels.Length; i++) - { - var model = data.PrivateChannels[i]; - var channel = AddPrivateChannel(model.Id, model.Recipient.Id); - channel.Update(model); - } - - EndConnect(); - } - break; - - //Servers - case "GUILD_CREATE": - { - var data = e.Payload.ToObject(Serializer); - if (data.Unavailable != true) - { - var server = AddServer(data.Id); - server.Update(data); - - if (data.Unavailable != false) - { - Logger.Info($"GUILD_CREATE: {server.Path}"); - OnJoinedServer(server); - } - else - Logger.Info($"GUILD_AVAILABLE: {server.Path}"); - - if (!data.IsLarge) - OnServerAvailable(server); - else - _largeServers.Enqueue(data.Id); - } - } - break; - case "GUILD_UPDATE": - { - var data = e.Payload.ToObject(Serializer); - var server = GetServer(data.Id); - if (server != null) - { - var before = Config.EnablePreUpdateEvents ? server.Clone() : null; - server.Update(data); - Logger.Info($"GUILD_UPDATE: {server.Path}"); - OnServerUpdated(before, server); - } - else - Logger.Warning("GUILD_UPDATE referenced an unknown guild."); - } - break; - case "GUILD_DELETE": - { - var data = e.Payload.ToObject(Serializer); - Server server = RemoveServer(data.Id); - if (server != null) - { - if (data.Unavailable != true) - Logger.Info($"GUILD_DELETE: {server.Path}"); - else - Logger.Info($"GUILD_UNAVAILABLE: {server.Path}"); - - OnServerUnavailable(server); - if (data.Unavailable != true) - OnLeftServer(server); - } - else - Logger.Warning("GUILD_DELETE referenced an unknown guild."); - } - break; - - //Channels - case "CHANNEL_CREATE": - { - var data = e.Payload.ToObject(Serializer); - - Channel channel = null; - if (data.GuildId != null) - { - var server = GetServer(data.GuildId.Value); - if (server != null) - channel = server.AddChannel(data.Id, true); - else - Logger.Warning("CHANNEL_CREATE referenced an unknown guild."); - } - else - channel = AddPrivateChannel(data.Id, data.Recipient.Id); - if (channel != null) - { - channel.Update(data); - Logger.Info($"CHANNEL_CREATE: {channel.Path}"); - OnChannelCreated(channel); - } - } - break; - case "CHANNEL_UPDATE": - { - var data = e.Payload.ToObject(Serializer); - var channel = GetChannel(data.Id); - if (channel != null) - { - var before = Config.EnablePreUpdateEvents ? channel.Clone() : null; - channel.Update(data); - Logger.Info($"CHANNEL_UPDATE: {channel.Path}"); - OnChannelUpdated(before, channel); - } - else - Logger.Warning("CHANNEL_UPDATE referenced an unknown channel."); - } - break; - case "CHANNEL_DELETE": - { - var data = e.Payload.ToObject(Serializer); - var channel = RemoveChannel(data.Id); - if (channel != null) - { - Logger.Info($"CHANNEL_DELETE: {channel.Path}"); - OnChannelDestroyed(channel); - } - else - Logger.Warning("CHANNEL_DELETE referenced an unknown channel."); - } - break; - - //Members - case "GUILD_MEMBER_ADD": - { - var data = e.Payload.ToObject(Serializer); - var server = GetServer(data.GuildId.Value); - if (server != null) - { - var user = server.AddUser(data.User.Id, true, true); - user.Update(data); - user.UpdateActivity(); - Logger.Info($"GUILD_MEMBER_ADD: {user.Path}"); - OnUserJoined(user); - } - else - Logger.Warning("GUILD_MEMBER_ADD referenced an unknown guild."); - } - break; - case "GUILD_MEMBER_UPDATE": - { - var data = e.Payload.ToObject(Serializer); - var server = GetServer(data.GuildId.Value); - if (server != null) - { - var user = server.GetUser(data.User.Id); - if (user != null) - { - var before = Config.EnablePreUpdateEvents ? user.Clone() : null; - user.Update(data); - Logger.Info($"GUILD_MEMBER_UPDATE: {user.Path}"); - OnUserUpdated(before, user); - } - else - Logger.Warning("GUILD_MEMBER_UPDATE referenced an unknown user."); - } - else - Logger.Warning("GUILD_MEMBER_UPDATE referenced an unknown guild."); - } - break; - case "GUILD_MEMBER_REMOVE": - { - var data = e.Payload.ToObject(Serializer); - var server = GetServer(data.GuildId.Value); - if (server != null) - { - var user = server.RemoveUser(data.User.Id); - if (user != null) - { - Logger.Info($"GUILD_MEMBER_REMOVE: {user.Path}"); - OnUserLeft(user); - } - else - Logger.Warning("GUILD_MEMBER_REMOVE referenced an unknown user."); - } - else - Logger.Warning("GUILD_MEMBER_REMOVE referenced an unknown guild."); - } - break; - case "GUILD_MEMBERS_CHUNK": - { - var data = e.Payload.ToObject(Serializer); - var server = GetServer(data.GuildId); - if (server != null) - { - foreach (var memberData in data.Members) - { - var user = server.AddUser(memberData.User.Id, true, false); - user.Update(memberData); - } - Logger.Verbose($"GUILD_MEMBERS_CHUNK: {data.Members.Length} users"); - - if (server.CurrentUserCount >= server.UserCount) //Finished downloading for there - OnServerAvailable(server); - } - else - Logger.Warning("GUILD_MEMBERS_CHUNK referenced an unknown guild."); - } - break; - - //Roles - case "GUILD_ROLE_CREATE": - { - var data = e.Payload.ToObject(Serializer); - var server = GetServer(data.GuildId); - if (server != null) - { - var role = server.AddRole(data.Data.Id); - role.Update(data.Data, false); - Logger.Info($"GUILD_ROLE_CREATE: {role.Path}"); - OnRoleCreated(role); - } - else - Logger.Warning("GUILD_ROLE_CREATE referenced an unknown guild."); - } - break; - case "GUILD_ROLE_UPDATE": - { - var data = e.Payload.ToObject(Serializer); - var server = GetServer(data.GuildId); - if (server != null) - { - var role = server.GetRole(data.Data.Id); - if (role != null) - { - var before = Config.EnablePreUpdateEvents ? role.Clone() : null; - role.Update(data.Data, true); - Logger.Info($"GUILD_ROLE_UPDATE: {role.Path}"); - OnRoleUpdated(before, role); - } - else - Logger.Warning("GUILD_ROLE_UPDATE referenced an unknown role."); - } - else - Logger.Warning("GUILD_ROLE_UPDATE referenced an unknown guild."); - } - break; - case "GUILD_ROLE_DELETE": - { - var data = e.Payload.ToObject(Serializer); - var server = GetServer(data.GuildId); - if (server != null) - { - var role = server.RemoveRole(data.RoleId); - if (role != null) - { - Logger.Info($"GUILD_ROLE_DELETE: {role.Path}"); - OnRoleDeleted(role); - } - else - Logger.Warning("GUILD_ROLE_DELETE referenced an unknown role."); - } - else - Logger.Warning("GUILD_ROLE_DELETE referenced an unknown guild."); - } - break; - - //Bans - case "GUILD_BAN_ADD": - { - var data = e.Payload.ToObject(Serializer); - var server = GetServer(data.GuildId.Value); - if (server != null) - { - var user = server.GetUser(data.User.Id); - if (user != null) - { - Logger.Info($"GUILD_BAN_ADD: {user.Path}"); - OnUserBanned(user); - } - else - Logger.Warning("GUILD_BAN_ADD referenced an unknown user."); - } - else - Logger.Warning("GUILD_BAN_ADD referenced an unknown guild."); - } - break; - case "GUILD_BAN_REMOVE": - { - var data = e.Payload.ToObject(Serializer); - var server = GetServer(data.GuildId.Value); - if (server != null) - { - var user = new User(this, data.User.Id, server); - user.Update(data.User); - Logger.Info($"GUILD_BAN_REMOVE: {user.Path}"); - OnUserUnbanned(user); - } - else - Logger.Warning("GUILD_BAN_REMOVE referenced an unknown guild."); - } - break; - - //Messages - case "MESSAGE_CREATE": - { - var data = e.Payload.ToObject(Serializer); - - Channel channel = GetChannel(data.ChannelId); - if (channel != null) - { - var user = channel.GetUserFast(data.Author.Id); - - if (user != null) - { - Message msg = null; - bool isAuthor = data.Author.Id == CurrentUser.Id; - //ulong nonce = 0; - - /*if (data.Author.Id == _privateUser.Id && Config.UseMessageQueue) - { - if (data.Nonce != null && ulong.TryParse(data.Nonce, out nonce)) - msg = _messages[nonce]; - }*/ - if (msg == null) - { - msg = channel.AddMessage(data.Id, user, data.Timestamp.Value); - //nonce = 0; - } - - //Remapped queued message - /*if (nonce != 0) - { - msg = _messages.Remap(nonce, data.Id); - msg.Id = data.Id; - RaiseMessageSent(msg); - }*/ - - msg.Update(data); - user.UpdateActivity(); - - Logger.Verbose($"MESSAGE_CREATE: {channel.Path} ({user.Name ?? "Unknown"})"); - OnMessageReceived(msg); - } - else - Logger.Warning("MESSAGE_CREATE referenced an unknown user."); - } - else - Logger.Warning("MESSAGE_CREATE referenced an unknown channel."); - } - break; - case "MESSAGE_UPDATE": - { - var data = e.Payload.ToObject(Serializer); - var channel = GetChannel(data.ChannelId); - if (channel != null) - { - var msg = channel.GetMessage(data.Id, data.Author?.Id); - var before = Config.EnablePreUpdateEvents ? msg.Clone() : null; - msg.Update(data); - Logger.Verbose($"MESSAGE_UPDATE: {channel.Path} ({data.Author?.Username ?? "Unknown"})"); - OnMessageUpdated(before, msg); - } - else - Logger.Warning("MESSAGE_UPDATE referenced an unknown channel."); - } - break; - case "MESSAGE_DELETE": - { - var data = e.Payload.ToObject(Serializer); - var channel = GetChannel(data.ChannelId); - if (channel != null) - { - var msg = channel.RemoveMessage(data.Id); - Logger.Verbose($"MESSAGE_DELETE: {channel.Path} ({msg.User?.Name ?? "Unknown"})"); - OnMessageDeleted(msg); - } - else - Logger.Warning("MESSAGE_DELETE referenced an unknown channel."); - } - break; - - //Statuses - case "PRESENCE_UPDATE": - { - var data = e.Payload.ToObject(Serializer); - User user; - Server server; - if (data.GuildId == null) - { - server = null; - user = GetPrivateChannel(data.User.Id)?.Recipient; - } - else - { - server = GetServer(data.GuildId.Value); - if (server == null) - { - Logger.Warning("PRESENCE_UPDATE referenced an unknown server."); - break; - } - else - user = server.GetUser(data.User.Id); - } - - if (user != null) - { - if (Config.LogLevel == LogSeverity.Debug) - Logger.Debug($"PRESENCE_UPDATE: {user.Path}"); - var before = Config.EnablePreUpdateEvents ? user.Clone() : null; - user.Update(data); - OnUserUpdated(before, user); - } - /*else //Occurs when a user leaves a server - Logger.Warning("PRESENCE_UPDATE referenced an unknown user.");*/ - } - break; - case "TYPING_START": - { - var data = e.Payload.ToObject(Serializer); - var channel = GetChannel(data.ChannelId); - if (channel != null) - { - User user; - if (channel.IsPrivate) - { - if (channel.Recipient.Id == data.UserId) - user = channel.Recipient; - else - break; - } - else - user = channel.Server.GetUser(data.UserId); - if (user != null) - { - if (Config.LogLevel == LogSeverity.Debug) - Logger.Debug($"TYPING_START: {channel.Path} ({user.Name})"); - OnUserIsTypingUpdated(channel, user); - user.UpdateActivity(); - } - } - else - Logger.Warning("TYPING_START referenced an unknown channel."); - } - break; - - //Voice - case "VOICE_STATE_UPDATE": - { - var data = e.Payload.ToObject(Serializer); - var server = GetServer(data.GuildId); - if (server != null) - { - var user = server.GetUser(data.UserId); - if (user != null) - { - if (Config.LogLevel == LogSeverity.Debug) - Logger.Debug($"VOICE_STATE_UPDATE: {user.Path}"); - var before = Config.EnablePreUpdateEvents ? user.Clone() : null; - user.Update(data); - //Logger.Verbose($"Voice Updated: {server.Name}/{user.Name}"); - OnUserUpdated(before, user); - } - /*else //Occurs when a user leaves a server - Logger.Warning("VOICE_STATE_UPDATE referenced an unknown user.");*/ - } - else - Logger.Warning("VOICE_STATE_UPDATE referenced an unknown server."); - } - break; - - //Settings - case "USER_UPDATE": - { - var data = e.Payload.ToObject(Serializer); - if (data.Id == CurrentUser.Id) - { - var before = Config.EnablePreUpdateEvents ? CurrentUser.Clone() : null; - CurrentUser.Update(data); - PrivateUser.Update(data); - foreach (var server in _servers) - server.Value.CurrentUser.Update(data); - Logger.Info($"USER_UPDATE"); - OnProfileUpdated(before, CurrentUser); - } - } - break; - - //Handled in GatewaySocket - case "RESUMED": - break; - - //Ignored - case "USER_SETTINGS_UPDATE": - case "GUILD_INTEGRATIONS_UPDATE": - case "VOICE_SERVER_UPDATE": - case "GUILD_EMOJIS_UPDATE": - case "MESSAGE_ACK": - Logger.Debug($"{e.Type} [Ignored]"); - break; - - //Others - default: - Logger.Warning($"Unknown message type: {e.Type}"); - break; - } - } - catch (Exception ex) - { - Logger.Error($"Error handling {e.Type} event", ex); - } - } - #endregion - - #region Services - public T AddService(T instance) - where T : class, IService - => _services.Add(instance); - public T AddService() - where T : class, IService, new() - => _services.Add(new T()); - public T GetService(bool isRequired = true) - where T : class, IService - => _services.Get(isRequired); - #endregion - - #region Async Wrapper - /// Blocking call that will execute the provided async method and wait until the client has been manually stopped. This is mainly intended for use in console applications. - public void ExecuteAndWait(Func asyncAction) - { - asyncAction().GetAwaiter().GetResult(); - _disconnectedEvent.WaitOne(); - } - #endregion - - #region IDisposable - private bool _isDisposed = false; - - protected virtual void Dispose(bool isDisposing) - { - if (!_isDisposed) - { - if (isDisposing) - { - _disconnectedEvent.Dispose(); - _connectedEvent.Dispose(); - } - _isDisposed = true; - } - } - - public void Dispose() - { - Dispose(true); - } - #endregion - - private Task LargeServerDownloader(CancellationToken cancelToken) - { - //Temporary hotfix to download all large guilds before raising READY - return Task.Run(async () => - { - try - { - const short batchSize = 50; - ulong[] serverIds = new ulong[batchSize]; - - while (!cancelToken.IsCancellationRequested && State == ConnectionState.Connecting) - await Task.Delay(1000, cancelToken).ConfigureAwait(false); - - while (!cancelToken.IsCancellationRequested && State == ConnectionState.Connected) - { - if (_largeServers.Count > 0) - { - int count = 0; - while (count < batchSize && _largeServers.TryDequeue(out serverIds[count])) - count++; - - if (count > 0) - GatewaySocket.SendRequestMembers(serverIds.Take(count), "", 0); - } - await Task.Delay(1250, cancelToken).ConfigureAwait(false); - } - } - catch (OperationCanceledException) { } - }); - } - - //Helpers - private string GetTokenCachePath(string email) - { - using (var md5 = MD5.Create()) - { - byte[] data = md5.ComputeHash(Encoding.UTF8.GetBytes(email.ToLowerInvariant())); - StringBuilder filenameBuilder = new StringBuilder(); - for (int i = 0; i < data.Length; i++) - filenameBuilder.Append(data[i].ToString("x2")); - return Path.Combine(Config.CacheDir, filenameBuilder.ToString()); - } - } - private string LoadToken(string path, byte[] key) - { - if (File.Exists(path)) - { - try - { - using (var fileStream = File.Open(path, FileMode.Open)) - using (var aes = Aes.Create()) - { - byte[] iv = new byte[aes.BlockSize / 8]; - fileStream.Read(iv, 0, iv.Length); - aes.IV = iv; - aes.Key = key; - using (var cryptoStream = new CryptoStream(fileStream, aes.CreateDecryptor(), CryptoStreamMode.Read)) - { - byte[] tokenBuffer = new byte[64]; - int length = cryptoStream.Read(tokenBuffer, 0, tokenBuffer.Length); - return Encoding.UTF8.GetString(tokenBuffer, 0, length); - } - } - } - catch (Exception ex) - { - Logger.Warning("Failed to load cached token. Wrong/changed password?", ex); - } - } - return null; - } - private void SaveToken(string path, byte[] key, string token) - { - byte[] tokenBytes = Encoding.UTF8.GetBytes(token); - try - { - string parentDir = Path.GetDirectoryName(path); - if (!Directory.Exists(parentDir)) - Directory.CreateDirectory(parentDir); - - using (var fileStream = File.Open(path, FileMode.Create)) - using (var aes = Aes.Create()) - { - aes.GenerateIV(); - aes.Key = key; - using (var cryptoStream = new CryptoStream(fileStream, aes.CreateEncryptor(), CryptoStreamMode.Write)) - { - fileStream.Write(aes.IV, 0, aes.IV.Length); - cryptoStream.Write(tokenBytes, 0, tokenBytes.Length); - } - } - } - catch (Exception ex) - { - Logger.Warning("Failed to cache token", ex); - } - } - } -} \ No newline at end of file