From ca02bba839bfe3ee882cf97d972736349dfff7af Mon Sep 17 00:00:00 2001 From: flashwave Date: Fri, 13 Oct 2017 22:05:50 +0200 Subject: [PATCH] use managers rather than random lists --- Maki/BaseManager.cs | 64 ++++ Maki/ChannelManager.cs | 30 ++ Maki/Discord.cs | 361 ++++++++++--------- Maki/DiscordChannel.cs | 11 +- Maki/DiscordColour.cs | 63 ++-- Maki/DiscordMember.cs | 2 +- Maki/DiscordMessage.cs | 17 +- Maki/DiscordServer.cs | 24 +- Maki/Gateway/GatewayShardClient.cs | 71 ++-- Maki/Maki.csproj | 16 +- Maki/MemberManager.cs | 48 +++ Maki/MessageManager.cs | 30 ++ Maki/Properties/AssemblyInfo.cs | 28 +- Maki/Rest/RestEndpoints.cs | 18 +- Maki/Rest/WebRequest.cs | 168 +++++---- Maki/RoleManager.cs | 30 ++ Maki/ServerManager.cs | 23 ++ Maki/Structures/Auth/LoginMultiFactorAuth.cs | 22 -- Maki/Structures/Auth/LoginRequest.cs | 22 -- Maki/Structures/Auth/LoginResponse.cs | 40 -- Maki/UserManager.cs | 17 + Maki/Utility.cs | 3 - 22 files changed, 640 insertions(+), 468 deletions(-) create mode 100644 Maki/BaseManager.cs create mode 100644 Maki/ChannelManager.cs create mode 100644 Maki/MemberManager.cs create mode 100644 Maki/MessageManager.cs create mode 100644 Maki/RoleManager.cs create mode 100644 Maki/ServerManager.cs delete mode 100644 Maki/Structures/Auth/LoginMultiFactorAuth.cs delete mode 100644 Maki/Structures/Auth/LoginRequest.cs delete mode 100644 Maki/Structures/Auth/LoginResponse.cs create mode 100644 Maki/UserManager.cs diff --git a/Maki/BaseManager.cs b/Maki/BaseManager.cs new file mode 100644 index 0000000..3d76617 --- /dev/null +++ b/Maki/BaseManager.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Maki +{ + internal abstract class BaseManager : IDisposable + { + protected readonly HashSet Collection = new HashSet(); + public IEnumerable Items => Collection; + public int Count => Collection.Count; + + public event Action OnAdd; + public event Action OnRemove; + + protected virtual bool DisposeOnRemove => true; + + protected bool IsDisposed = false; + + ~BaseManager() + => Dispose(false); + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected void Dispose(bool disposing) + { + if (IsDisposed) + return; + + IsDisposed = true; + Collection.ToList().ForEach(x => Remove(x)); + Collection.Clear(); + } + + public virtual void Add(T item) + { + if (item == null) + return; + + lock (Collection) + Collection.Add(item); + + OnAdd?.Invoke(item); + } + + public virtual void Remove(T item) + { + if (item == null) + return; + + lock (Collection) + Collection.Remove(item); + + OnRemove?.Invoke(item); + + if (DisposeOnRemove && item is IDisposable) + (item as IDisposable).Dispose(); + } + } +} diff --git a/Maki/ChannelManager.cs b/Maki/ChannelManager.cs new file mode 100644 index 0000000..944de52 --- /dev/null +++ b/Maki/ChannelManager.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Maki +{ + internal sealed class ChannelManager : BaseManager + { + internal ChannelManager() + { + } + + public DiscordChannel Id(ulong id) + { + lock (Collection) + return Collection.Where(x => x.Id == id).FirstOrDefault(); + } + + public bool Exists(ulong id) + { + lock (Collection) + return Collection.Where(x => x.Id == id).Count() > 0; + } + + public IEnumerable Server(DiscordServer server) + { + lock (Collection) + return Collection.Where(x => x.Server == server).OrderBy(x => x.Position); + } + } +} diff --git a/Maki/Discord.cs b/Maki/Discord.cs index ccb6acc..4c4d5a6 100644 --- a/Maki/Discord.cs +++ b/Maki/Discord.cs @@ -1,6 +1,5 @@ using Maki.Gateway; using Maki.Rest; -using Maki.Structures.Auth; using Maki.Structures.Channels; using Maki.Structures.Gateway; using Maki.Structures.Guilds; @@ -10,6 +9,7 @@ using Maki.Structures.Roles; using Maki.Structures.Users; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; namespace Maki @@ -64,35 +64,49 @@ namespace Maki internal readonly GatewayShardClient ShardClient; #region Containers + + internal readonly ServerManager ServerManager; + /// /// Servers we're in /// - internal readonly List servers = new List(); + public IEnumerable Servers => ServerManager.Items; + + internal readonly UserManager UserManager; /// /// Users we are affiliated with /// - internal readonly List users = new List(); + public IEnumerable Users => UserManager.Items; + + internal readonly MemberManager MemberManager; /// /// Members of servers, different from users! /// - internal readonly List members = new List(); + public IEnumerable Members => MemberManager.Items; + + internal readonly RoleManager RoleManager; /// /// Roles in servers /// - internal readonly List roles = new List(); + public IEnumerable Roles => RoleManager.Items; + + internal readonly ChannelManager ChannelManager; /// /// Channels /// - internal readonly List channels = new List(); + public IEnumerable Channels => ChannelManager.Items; + + internal readonly MessageManager MessageManager; /// /// Messages /// - internal readonly List messages = new List(); + public IEnumerable Messages => MessageManager.Items; + #endregion #region Events @@ -207,8 +221,7 @@ namespace Maki public event Action OnUserUpdate; #endregion - public DiscordServer[] Servers => servers.ToArray(); - public DiscordUser Me => users.FirstOrDefault(); + public DiscordUser Me => UserManager.Items.FirstOrDefault(); private DiscordParams Params; @@ -220,6 +233,13 @@ namespace Maki Params = parameters ?? new DiscordParams(); ShardClient = new GatewayShardClient(this); + UserManager = new UserManager(); + RoleManager = new RoleManager(); + MemberManager = new MemberManager(); + ChannelManager = new ChannelManager(); + MessageManager = new MessageManager(); + ServerManager = new ServerManager(); + #region Assigning event handlers ShardClient.OnChannelCreate += ShardManager_OnChannelCreate; ShardClient.OnChannelUpdate += ShardManager_OnChannelUpdate; @@ -259,23 +279,11 @@ namespace Maki #endregion } - private void ClearContainers() - { - servers.Clear(); - users.Clear(); - roles.Clear(); - channels.Clear(); - messages.Clear(); - members.Clear(); - } - /// /// Connects to Discord using the token assigned to Token /// public void Connect() { - ClearContainers(); - int shards = 1; using (WebRequest wr = new WebRequest(HttpMethod.GET, IsBot ? RestEndpoints.GatewayBot : RestEndpoints.Gateway)) @@ -303,76 +311,6 @@ namespace Maki ShardClient.Create(i); } - /// - /// Connects to Discord with the provided email, password and, optionally, mfa token - /// - /// Discord account email - /// Discord account password - /// Multi factor authentication token - public void Connect(string email, string password, string code = null) - { - // TODO: not tested with new request backend yet - - TokenType = DiscordTokenType.User; - LoginResponse login; - - using (WebRequest wr = new WebRequest(HttpMethod.POST, RestEndpoints.AuthLogin)) - { - wr.AddJson(new LoginRequest - { - Email = email, - Password = password, - }); - wr.Perform(); - - if (wr.Response.Length < 1) - throw new DiscordException("Login failed"); - - login = wr.ResponseJson(); - } - - if (login.UsernameError?.Length > 0) - throw new DiscordException(login.UsernameError.FirstOrDefault()); - - if (login.PasswordError?.Length > 0) - throw new DiscordException(login.PasswordError.FirstOrDefault()); - - Token = login.Token; - - if (string.IsNullOrEmpty(Token)) - { - if (login.MFA == true && !string.IsNullOrEmpty(login.Ticket)) - { - using (WebRequest wr = new WebRequest(HttpMethod.POST, RestEndpoints.AuthMfaTotp)) - { - wr.AddJson(new LoginMultiFactorAuth - { - Code = code, - Ticket = login.Ticket, - }); - wr.Perform(); - - if (wr.Response.Length < 1) - throw new DiscordException("Multi factor auth failed"); - - login = wr.ResponseJson(); - } - - //if (totp.ErrorCode != RestErrorCode.Ok) - // throw new DiscordException($"{totp.ErrorCode}: {totp.ErrorMessage}"); - - Token = login.Token; - } - else - throw new DiscordException("Token was null but MFA is false and/or ticket is empty?"); - } - - if (string.IsNullOrEmpty(Token)) - throw new DiscordException("Authentication failed"); - - Connect(); - } - /// /// Connects to Discord using a provided token and token type /// @@ -391,23 +329,27 @@ namespace Maki public void Disconnect() { ShardClient.Disconnect(); - ClearContainers(); Token = string.Empty; Gateway = string.Empty; } #region Event Handlers + private void ShardManager_OnChannelCreate(GatewayShard shard, Channel channel) { - DiscordServer server = servers.Find(x => x.Id == channel.GuildId); + Debug.Assert(channel.GuildId.HasValue, "Guild ID does not have a value?"); + DiscordServer server = ServerManager.Id(channel.GuildId ?? 0); + Debug.Assert(server != null, "Target guild/server was not present in ServerManager"); + DiscordChannel chan = new DiscordChannel(this, channel, server); - channels.Add(chan); + ChannelManager.Add(chan); OnChannelCreate?.Invoke(chan); } private void ShardManager_OnChannelUpdate(GatewayShard shard, Channel channel) { - DiscordChannel chan = channels.Find(x => x.Id == channel.Id); + DiscordChannel chan = ChannelManager.Id(channel.Id); + Debug.Assert(chan != null, "channel is null"); chan.Name = channel.Name; @@ -435,8 +377,10 @@ namespace Maki private void ShardManager_OnChannelDelete(GatewayShard shard, Channel channel) { - DiscordChannel chan = channels.Find(x => x.Id == channel.Id); - channels.Remove(chan); + DiscordChannel chan = ChannelManager.Id(channel.Id); + Debug.Assert(chan != null, "channel is null"); + + ChannelManager.Remove(chan); OnChannelDelete?.Invoke(chan); } @@ -452,9 +396,15 @@ namespace Maki $"Is verified?: {user.IsVerified}", $"E-mail: {user.EMail}"*/ - DiscordUser user = users.Find(x => x.Id == sUser.Id); - DiscordServer server = servers.Find(x => x.Id == sUser.GuildId); + DiscordUser user = UserManager.Id(sUser.Id); + Debug.Assert(user != null, "user is null"); + Debug.Assert(sUser.GuildId.HasValue, "Guild ID does not have a value."); + DiscordServer server = ServerManager.Id(sUser.GuildId ?? 0); + + Debug.Assert(user != null, "user is null"); + Debug.Assert(server != null, "server is null"); + OnBanAdd?.Invoke(user, server); } @@ -470,24 +420,28 @@ namespace Maki $"Is verified?: {user.IsVerified}", $"E-mail: {user.EMail}"*/ - DiscordUser user = users.Find(x => x.Id == sUser.Id); - DiscordServer server = servers.Find(x => x.Id == sUser.GuildId); + DiscordUser user = UserManager.Id(sUser.Id); + Debug.Assert(user != null, "user is null"); + + DiscordServer server = ServerManager.Id(sUser.GuildId ?? 0); + + Debug.Assert(user != null, "user is null"); + Debug.Assert(server != null, "server is null"); OnBanRemove?.Invoke(user, server); } private void ShardManager_OnGuildCreate(GatewayShard shard, Guild guild) { - DiscordServer server = servers.Where(x => x.Id == guild.Id).FirstOrDefault(); + DiscordServer server = ServerManager.Id(guild.Id); - if (server == default(DiscordServer)) + if (server == null) { server = new DiscordServer(this, guild); - servers.Add(server); + ServerManager.Add(server); } else { - server = servers.Find(x => x.Id == guild.Id); server.Name = guild.Name; server.OwnerId = guild.OwnerId ?? 0; server.IconHash = guild.IconHash; @@ -500,7 +454,7 @@ namespace Maki Channel channel = guild.Channels[i]; channel.GuildId = server.Id; - if (channels.Where(x => x.Id == channel.Id).Count() > 0) + if (ChannelManager.Exists(channel.Id)) ShardManager_OnChannelUpdate(shard, channel); else ShardManager_OnChannelCreate(shard, channel); @@ -515,7 +469,7 @@ namespace Maki RoleId = role.Id }; - if (roles.Where(x => x.Id == role.Id).Count() > 0) + if (RoleManager.Exists(role.Id)) ShardManager_OnGuildRoleUpdate(shard, gRole); else ShardManager_OnGuildRoleCreate(shard, gRole); @@ -532,7 +486,8 @@ namespace Maki private void ShardManager_OnGuildDelete(GatewayShard shard, Guild guild) { - DiscordServer server = servers.Find(x => x.Id == guild.Id); + DiscordServer server = ServerManager.Id(guild.Id); + Debug.Assert(server != null, "server is null"); server.Name = guild.Name; server.OwnerId = guild.OwnerId ?? 0; @@ -549,16 +504,20 @@ namespace Maki server.MultiFactorAuthLevel = guild.MultiFactorAuthLevel;*/ OnServerDelete?.Invoke(server); - messages.Where(x => x.Channel.Server == server).ToList().ForEach(x => messages.Remove(x)); - channels.Where(x => x.Server == server).ToList().ForEach(x => channels.Remove(x)); - members.Where(x => x.Server == server).ToList().ForEach(x => members.Remove(x)); - roles.Where(x => x.Server == server).ToList().ForEach(x => roles.Remove(x)); - servers.Remove(server); + + // NOT THREAD SAFE AT ALL + MessageManager .Server(server).ToList().ForEach(x => MessageManager.Remove(x)); + ChannelManager .Server(server).ToList().ForEach(x => ChannelManager.Remove(x)); + MemberManager .Server(server).ToList().ForEach(x => MemberManager.Remove(x)); + RoleManager .Server(server).ToList().ForEach(x => RoleManager.Remove(x)); + + ServerManager.Remove(server); } private void ShardManager_OnGuildUpdate(GatewayShard shard, Guild guild) { - DiscordServer server = servers.Find(x => x.Id == guild.Id); + DiscordServer server = ServerManager.Id(guild.Id); + Debug.Assert(server != null, "server is null"); server.Name = guild.Name; server.OwnerId = guild.OwnerId ?? 0; @@ -580,31 +539,41 @@ namespace Maki private void ShardManager_OnGuildEmojisUpdate(GatewayShard shard, Guild guild) { - DiscordServer server = servers.Find(x => x.Id == guild.Id); + DiscordServer server = ServerManager.Id(guild.Id); + Debug.Assert(server != null, "server is null"); + //servers.Emojis = guild.Emojis; OnEmojisUpdate?.Invoke(server); } private void ShardManager_OnGuildMemberAdd(GatewayShard shard, GuildMember sMember) { - DiscordServer server = servers.Find(x => x.Id == sMember.GuildId); - DiscordUser user = users.Where(x => x.Id == sMember.User.Id).FirstOrDefault(); + Debug.Assert(sMember.GuildId.HasValue, "GuildId has no value"); - if (user == default(DiscordUser)) + DiscordServer server = ServerManager.Id(sMember.GuildId ?? 0); + Debug.Assert(server != null, "server is null"); + + DiscordUser user = UserManager.Id(sMember.User.Id); + //Debug.Assert(user != null, "user is null"); + + if (user == null) { user = new DiscordUser(this, sMember.User); - users.Add(user); + UserManager.Add(user); } DiscordMember member = new DiscordMember(this, sMember, user, server); - members.Add(member); + MemberManager.Add(member); OnMemberAdd?.Invoke(member); } private void ShardManager_OnGuildMemberUpdate(GatewayShard shard, GuildMember sMember) { - DiscordMember member = members.Find(x => x.User.Id == sMember.User.Id && x.Server.Id == sMember.GuildId); - + Debug.Assert(sMember.GuildId.HasValue, "GuildId has no value"); + + DiscordMember member = MemberManager.Id(sMember.GuildId ?? 0, sMember.User.Id); + Debug.Assert(member != null, "member is null"); + member.Nickname = sMember.Nickname; member.IsDeaf = sMember.IsDeafened == true; member.IsMute = sMember.IsMuted == true; @@ -615,8 +584,12 @@ namespace Maki private void ShardManager_OnGuildMemberRemove(GatewayShard shard, GuildMember sMember) { - DiscordMember member = members.Find(x => x.User.Id == sMember.User.Id && x.Server.Id == sMember.GuildId); - members.Remove(member); + Debug.Assert(sMember.GuildId.HasValue, "GuildId has no value"); + + DiscordMember member = MemberManager.Id(sMember.GuildId ?? 0, sMember.User.Id); + Debug.Assert(member != null, "member is null"); + + MemberManager.Remove(member); OnMemberRemove?.Invoke(member); } @@ -632,13 +605,20 @@ namespace Maki $"Is Mentionable?: {role.Role.Value.IsMentionable}", $"Permissions: {role.Role.Value.Permissions}"*/ - DiscordServer server = servers.Find(x => x.Id == sRole.Guild); - DiscordRole role = roles.Where(x => x.Id == sRole.Role.Value.Id).FirstOrDefault(); + DiscordServer server = ServerManager.Id(sRole.Guild); + Debug.Assert(server != null, "server is null"); - if (role == default(DiscordRole)) + DiscordRole role = RoleManager.Id(sRole.Role.Value.Id); + //Debug.Assert(role != null, "role is null"); + + // fixme + if (server == null) + return; + + if (role == null) { role = new DiscordRole(this, sRole.Role.Value, server); - roles.Add(role); + RoleManager.Add(role); } else { role.Colour.Raw = sRole.Role.Value.Colour.Value; @@ -649,17 +629,25 @@ namespace Maki private void ShardManager_OnGuildRoleDelete(GatewayShard shard, GuildRole sRole) { - DiscordRole role = roles.Find(x => x.Id == sRole.RoleId && x.Server.Id == sRole.Guild); - members.Where(x => x.roles.Contains(role.Id)).ToList().ForEach(x => x.roles.RemoveAll(y => y == role.Id)); - roles.Remove(role); + Debug.Assert(sRole.RoleId.HasValue, "RoleId has no value"); + DiscordRole role = RoleManager.Id(sRole.RoleId ?? 0); + Debug.Assert(role != null, "role is null"); + + // NOT THREAD SAFE + MemberManager.Role(role).ToList().ForEach(x => x.roles.RemoveAll(y => y == role.Id)); + RoleManager.Remove(role); + OnRoleDelete?.Invoke(role); } private void ShardManager_OnGuildRoleUpdate(GatewayShard shard, GuildRole sRole) { + Debug.Assert(sRole.Role.HasValue, "Role has no value"); Role dRole = sRole.Role.Value; - DiscordRole role = roles.Find(x => x.Id == dRole.Id && x.Server.Id == sRole.Guild); + DiscordRole role = RoleManager.Id(dRole.Id); + Debug.Assert(role != null, "role is null"); + role.Name = dRole.Name; role.IsHoisted = dRole.IsHoisted == true; role.IsMentionable = dRole.IsMentionable == true; @@ -697,24 +685,32 @@ namespace Maki private void ShardManager_OnMessageCreate(GatewayShard shard, Message message) { - DiscordChannel channel = channels.Find(x => x.Id == message.ChannelId); - DiscordMember member = members.Where(x => x.User.Id == message.User.Id && x.Server == channel.Server).FirstOrDefault(); + DiscordChannel channel = ChannelManager.Id(message.ChannelId); + Debug.Assert(channel != null, "channel is null"); + + DiscordMember member = MemberManager.Id(channel.Server, message.User.Id); + Debug.Assert(member != null, "member is null"); + DiscordMessage msg = new DiscordMessage(this, message, channel, member); - messages.Add(msg); + MessageManager.Add(msg); OnMessageCreate?.Invoke(msg); } private void ShardManager_OnMessageDelete(GatewayShard shard, Message message) { - DiscordMessage msg = messages.Find(x => x.Id == message.Id); - messages.Remove(msg); + DiscordMessage msg = MessageManager.Id(message.Id); + Debug.Assert(msg != null, "message is null"); + + MessageManager.Remove(msg); OnMessageDelete?.Invoke(msg); } private void ShardManager_OnMessageUpdate(GatewayShard shard, Message message) { - DiscordMessage msg = messages.Where(x => x.Id == message.Id).FirstOrDefault(); + DiscordMessage msg = MessageManager.Id(message.Id); + //Debug.Assert(msg != null, "message is null"); + // basically what should happen if (msg == null) { using (WebRequest wr = new WebRequest(HttpMethod.GET, RestEndpoints.ChannelMessage(message.ChannelId, message.Id))) @@ -727,10 +723,14 @@ namespace Maki message = wr.ResponseJson(); } - DiscordChannel channel = channels.Where(x => x.Id == message.ChannelId).FirstOrDefault(); - DiscordMember member = members.Where(x => x.User.Id == message.User.Id && (channel.Server == null || channel.Server == x.Server)).FirstOrDefault(); + DiscordChannel channel = ChannelManager.Id(message.ChannelId); + Debug.Assert(channel != null, "channel is null"); + + DiscordMember member = MemberManager.Id(channel.Server, message.User.Id) ?? MemberManager.Id(message.User.Id); + Debug.Assert(member != null, "member is null"); + msg = new DiscordMessage(this, message, channel, member); - messages.Add(msg); + MessageManager.Add(msg); } else { if (!string.IsNullOrEmpty(message.Content)) @@ -746,14 +746,19 @@ namespace Maki private void ShardManager_OnTypingStart(GatewayShard shard, TypingStart typing) { - DiscordChannel channel = channels.Find(x => x.Id == typing.Channel); - DiscordUser user = users.Find(x => x.Id == typing.User); + DiscordChannel channel = ChannelManager.Id(typing.Channel); + Debug.Assert(channel != null, "channel is null"); + + DiscordUser user = UserManager.Id(typing.User); + Debug.Assert(user != null, "user is null"); + OnTypingStart?.Invoke(user, channel); } private void ShardManager_OnPresenceUpdate(GatewayShard shard, Presence presence) { - DiscordMember member = members.Find(x => x.User.Id == presence.User.Id && x.Server.Id == presence.Guild); + DiscordMember member = MemberManager.Id(presence.Guild, presence.User.Id); + Debug.Assert(member != null, "member is null"); if (!string.IsNullOrEmpty(presence.User.Username)) member.User.Username = presence.User.Username; @@ -771,20 +776,20 @@ namespace Maki switch (presence.Status.ToLower()) { - case @"online": + case "online": member.User.Status = DiscordUserStatus.Online; break; - case @"away": - case @"idle": + case "away": + case "idle": member.User.Status = DiscordUserStatus.Idle; break; - case @"dnd": + case "dnd": member.User.Status = DiscordUserStatus.DoNotDisturb; break; - case @"offline": + case "offline": default: member.User.Status = DiscordUserStatus.Offline; break; @@ -800,24 +805,24 @@ namespace Maki DiscordChannel channel = new DiscordChannel(this, chan); // this shouldn't ever happen but just in case - if (channels.Where(x => x.Id == channel.Id).Count() > 0) + if (ChannelManager.Exists(channel.Id)) continue; - channels.Add(channel); + ChannelManager.Add(channel); } foreach (Guild guild in ready.UnavailableGuilds) { DiscordServer server = new DiscordServer(this, guild); - if (servers.Where(x => x.Id == server.Id).Count() > 0) + if (ServerManager.Exists(server.Id)) continue; - servers.Add(server); + ServerManager.Add(server); } DiscordUser user = new DiscordUser(this, ready.User); - users.Add(user); + UserManager.Add(user); OnReady?.Invoke(user); } @@ -838,12 +843,13 @@ namespace Maki $"Is verified?: {user.IsVerified}", $"E-mail: {user.EMail}"*/ - DiscordUser user = users.Where(x => x.Id == sUser.Id).FirstOrDefault(); + DiscordUser user = UserManager.Id(sUser.Id); + //Debug.Assert(user != null, "user is null"); - if (user == default(DiscordUser)) + if (user == null) { user = new DiscordUser(this, sUser); - users.Add(user); + UserManager.Add(user); } OnUserUpdate?.Invoke(user); @@ -864,32 +870,41 @@ namespace Maki private void ShardManager_OnSocketMessage(GatewayShard shard, string text) { } + #endregion #region IDisposable - private bool isDisposed = false; - private void Dispose(bool disposing) - { - if (!isDisposed) { - isDisposed = true; - Disconnect(); - } - } - - ~Discord() - { - Dispose(false); - } + private bool IsDisposed = false; /// /// Disconnects and releases all unmanaged objects /// - public void Dispose() + private void Dispose(bool disposing) { - Dispose(true); - GC.SuppressFinalize(true); + if (IsDisposed) + return; + + IsDisposed = true; + Disconnect(); + + ServerManager.Dispose(); + MessageManager.Dispose(); + ChannelManager.Dispose(); + MemberManager.Dispose(); + RoleManager.Dispose(); + UserManager.Dispose(); + + if (disposing) + GC.SuppressFinalize(true); } + + ~Discord() + => Dispose(false); + + public void Dispose() + => Dispose(true); + #endregion } } diff --git a/Maki/DiscordChannel.cs b/Maki/DiscordChannel.cs index a748743..a0a5e4c 100644 --- a/Maki/DiscordChannel.cs +++ b/Maki/DiscordChannel.cs @@ -3,6 +3,7 @@ using Maki.Structures.Channels; using Maki.Structures.Messages; using Maki.Structures.Rest; using Newtonsoft.Json; +using System.Diagnostics; using System.IO; namespace Maki @@ -59,7 +60,8 @@ namespace Maki if (wr.Status != 200 || wr.Response.Length < 1) // TODO: elaborate - throw new DiscordException("Failed to send message"); + //throw new DiscordException("Failed to send message"); + return null; msg = wr.ResponseJson(); } @@ -67,8 +69,11 @@ namespace Maki if (!msg.HasValue) throw new DiscordException("Empty response?"); - DiscordMessage message = new DiscordMessage(client, msg.Value, this, client.members.Find(x => x.User.Id == msg.Value.User.Id)); - client.messages.Add(message); + DiscordMember member = client.MemberManager.Id(Server, msg.Value.User.Id); + Debug.Assert(member != null, "member is null"); + + DiscordMessage message = new DiscordMessage(client, msg.Value, this, member); + client.MessageManager.Add(message); return message; } diff --git a/Maki/DiscordColour.cs b/Maki/DiscordColour.cs index 2d40e34..3a2d272 100644 --- a/Maki/DiscordColour.cs +++ b/Maki/DiscordColour.cs @@ -5,6 +5,41 @@ namespace Maki { public class DiscordColour { + public int Raw = 0; + + public byte Red + { + get => (byte)(Raw >> 16 & 0xFF); + set + { + Raw &= ~0xFF0000; + Raw |= value << 16; + } + } + + public byte Green + { + get => (byte)(Raw >> 8 & 0xFF); + set + { + Raw &= ~0xFF00; + Raw |= value << 8; + } + } + + public byte Blue + { + get => (byte)(Raw & 0xFF); + set + { + Raw &= ~0xFF; + Raw |= value; + } + } + + public string Hex => $"{Red:X2}{Green:X2}{Blue:X2}"; + public override string ToString() => $"#{Hex}"; + public DiscordColour(int raw) { Raw = raw; @@ -12,7 +47,9 @@ namespace Maki public DiscordColour(byte red, byte green, byte blue) { - Raw = (red << 16) + (green << 8) + blue; + Raw |= red << 16; + Raw |= green << 8; + Raw |= blue; } public DiscordColour(string hex) @@ -24,32 +61,10 @@ namespace Maki if (hex.Length != 6) throw new FormatException("Invalid hex colour format!"); - + Red = byte.Parse(hex.Substring(0, 2), NumberStyles.HexNumber); Green = byte.Parse(hex.Substring(2, 2), NumberStyles.HexNumber); Blue = byte.Parse(hex.Substring(4, 2), NumberStyles.HexNumber); } - - public int Raw = int.MinValue; - - public byte Red - { - get => (byte)(Raw >> 16 & 0xFF); - set => Raw = (value << 16) + (Green << 8) + Blue; - } - - public byte Green - { - get => (byte)(Raw >> 8 & 0xFF); - set => Raw = (Red << 16) + (value << 8) + Blue; - } - - public byte Blue - { - get => (byte)(Raw & 0xFF); - set => Raw = (Red << 16) + (Green << 8) + value; - } - - public string Hex => $"{Red:X2}{Green:X2}{Blue:X2}"; } } diff --git a/Maki/DiscordMember.cs b/Maki/DiscordMember.cs index 1f3b959..3e197bb 100644 --- a/Maki/DiscordMember.cs +++ b/Maki/DiscordMember.cs @@ -12,7 +12,7 @@ namespace Maki public readonly DiscordServer Server; private readonly Discord client; - public DiscordRole[] Roles => client.roles.Where(x => HasRole(x.Id) || x.Id == Server.Id).OrderByDescending(x => x.Position).ToArray(); + public IEnumerable Roles => client.RoleManager.Server(Server).Where(x => HasRole(x.Id)); public DateTime Joined { get; internal set; } public string Nickname { get; internal set; } diff --git a/Maki/DiscordMessage.cs b/Maki/DiscordMessage.cs index 586f2aa..faf404c 100644 --- a/Maki/DiscordMessage.cs +++ b/Maki/DiscordMessage.cs @@ -2,6 +2,8 @@ using Maki.Structures.Messages; using Maki.Structures.Rest; using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Linq; namespace Maki @@ -17,18 +19,20 @@ namespace Maki public string Text { get; internal set; } public DateTime Edited { get; internal set; } - public DiscordMember[] MentionsUsers { get; internal set; } - public DiscordRole[] MentionsRoles { get; internal set; } + public IEnumerable MentionsUsers { get; internal set; } + public IEnumerable MentionsRoles { get; internal set; } public bool MentionsEveryone { get; internal set; } public bool IsPinned { get; internal set; } public bool MentionsMe(bool everyone = false, bool roles = true) => (everyone && MentionsEveryone) - || (Channel.Type != DiscordChannelType.Private && roles && client.members.Where(x => x.User == client.Me && x.Server == Channel.Server).FirstOrDefault()?.Roles.Where(x => MentionsRoles.Contains(x)).Count() > 0) + || (Channel.Type != DiscordChannelType.Private && roles && client.MemberManager.Id(Channel.Server, client.Me)?.Roles.Where(x => MentionsRoles.Contains(x)).Count() > 0) || MentionsUsers.Select(x => x.User).Contains(client.Me); public bool IsMe => client.Me == User; + public DiscordServer Server => Channel.Server; + internal DiscordMessage(Discord discord, Message msg, DiscordChannel channel, DiscordMember member = null) { client = discord; @@ -43,12 +47,13 @@ namespace Maki } else { member = null; - User = client.users.Where(x => x.Id == msg.User.Id).FirstOrDefault(); + User = client.UserManager.Id(msg.User.Id); + Debug.Assert(User != null, "user is null"); } Channel = channel; - MentionsUsers = client.members.Where(x => x.Server == channel.Server && msg.Mentions.Select(y => y.Id).Contains(x.User.Id)).ToArray(); - MentionsRoles = client.roles.Where(x => x.Server == channel.Server && msg.MentionsRoles.Contains(x.Id)).ToArray(); + MentionsUsers = client.MemberManager.Server(channel.Server).Where(x => msg.Mentions.Select(y => y.Id).Contains(x.User.Id)); + MentionsRoles = client.RoleManager .Server(channel.Server).Where(x => msg.MentionsRoles.Contains(x.Id)); MentionsEveryone = msg.MentioningEveryone; IsPinned = msg.IsPinned == true; } diff --git a/Maki/DiscordServer.cs b/Maki/DiscordServer.cs index 85292a3..5af2153 100644 --- a/Maki/DiscordServer.cs +++ b/Maki/DiscordServer.cs @@ -3,6 +3,7 @@ using Maki.Structures.Guilds; using Maki.Structures.Rest; using Maki.Structures.Roles; using System; +using System.Collections.Generic; using System.Linq; namespace Maki @@ -17,13 +18,17 @@ namespace Maki public DateTime Created { get; internal set; } internal string IconHash; - public DiscordMember[] Members => client.members.Where(x => x.Server == this).ToArray(); + public IEnumerable Members => client.MemberManager.Server(this); public DiscordMember Owner => Members.Where(x => x.User.Id == OwnerId).FirstOrDefault(); public DiscordMember Me => Members.Where(x => x.User == client.Me).FirstOrDefault(); - public DiscordChannel[] TextChannels => client.channels.Where(x => x.Server == this && x.Type == DiscordChannelType.Text).OrderBy(x => x.Position).ToArray(); - public DiscordChannel[] VoiceChannels => client.channels.Where(x => x.Server == this && x.Type == DiscordChannelType.Voice).OrderBy(x => x.Position).ToArray(); - public DiscordRole[] Roles => client.roles.Where(x => x.Server == this).OrderByDescending(x => x.Position).ToArray(); - public string Icon(string ext = @"png", int size = 128) => RestEndpoints.CDN_URL + $@"/icons/{Id}/{IconHash}.{ext}?size={size}"; + + public IEnumerable Channels => client.ChannelManager.Server(this); + public IEnumerable TextChannels => Channels.Where(x => x.Type == DiscordChannelType.Text); + public IEnumerable VoiceChannels => Channels.Where(x => x.Type == DiscordChannelType.Voice); + + public IEnumerable Roles => client.RoleManager.Server(this); + + public string Icon(string ext = "png", int size = 128) => RestEndpoints.CDN_URL + $@"/icons/{Id}/{IconHash}.{ext}?size={size}"; internal DiscordServer(Discord discord, Guild guild) { @@ -60,14 +65,15 @@ namespace Maki if (!roleStruct.HasValue) throw new DiscordException("Empty response?"); - DiscordRole role = client.roles.Where(x => x.Id == roleStruct.Value.Id).FirstOrDefault(); + DiscordRole role = client.RoleManager.Id(roleStruct.Value.Id); + //Debug.Assert(role != null, "role is null"); - if (role == default(DiscordRole)) + if (role == null) { role = new DiscordRole(client, roleStruct.Value, this); - client.roles.Add(role); + client.RoleManager.Add(role); } - + return role; } } diff --git a/Maki/Gateway/GatewayShardClient.cs b/Maki/Gateway/GatewayShardClient.cs index d06537c..8f2f8e5 100644 --- a/Maki/Gateway/GatewayShardClient.cs +++ b/Maki/Gateway/GatewayShardClient.cs @@ -18,26 +18,27 @@ namespace Maki.Gateway /// /// Managed (active) shards /// - private readonly Dictionary shards = new Dictionary(); + private readonly List Shards = new List(); + + private readonly object Lock = new object(); /// /// Parent DiscordClient instance /// - private Discord client; + private Discord Client; /// /// Number of active managed shards /// - public int ShardCount => shards.Count; + public int ShardCount + => Shards.Count; /// /// Constructor /// - /// Parent DiscordClient instance - public GatewayShardClient(Discord c) - { - client = c; - } + /// Parent DiscordClient instance + public GatewayShardClient(Discord client) + => Client = client; /// /// Creates a new Gateway Shard @@ -46,25 +47,23 @@ namespace Maki.Gateway /// New Gateway Shard public GatewayShard Create(int id) { - GatewayShard shard = new GatewayShard(id, client); + GatewayShard shard = new GatewayShard(id, Client); ApplyEvents(shard); - shards.Add(id, shard); + + lock (Lock) + Shards.Add(shard); + return shard; } - /// - /// Destroys a Gateway Shard using its id - /// - /// Id of the shard to destroy - public void Destroy(int id) => Destroy(shards[id]); - /// /// Destroys a Gateway Shard /// /// Shard to destroy public void Destroy(GatewayShard shard) { - shards.Remove(shard.Id); + lock (Lock) + Shards.Remove(shard); // Disconnect and Dispose are called separately so OnSocketClose is called properly shard.Disconnect(); @@ -75,7 +74,11 @@ namespace Maki.Gateway /// /// Destroys all shards /// - public void Disconnect() => shards.Keys.ToList().ForEach(x => Destroy(x)); + public void Disconnect() + { + lock (Lock) + Shards.ToList().ForEach(x => Destroy(x)); + } /// /// Links all gateway events handlers to the shard @@ -271,29 +274,27 @@ namespace Maki.Gateway private bool IsDisposed = false; - private void Dispose(bool disposing) - { - if (!IsDisposed) - { - IsDisposed = true; - Disconnect(); - } - } - - ~GatewayShardClient() - { - Dispose(false); - } - /// /// Releases all unmanaged resources used by this object /// - public void Dispose() + private void Dispose(bool disposing) { - Dispose(true); - GC.SuppressFinalize(true); + if (IsDisposed) + return; + + IsDisposed = true; + Disconnect(); + + if (disposing) + GC.SuppressFinalize(true); } + ~GatewayShardClient() + => Dispose(false); + + public void Dispose() + => Dispose(true); + #endregion } } diff --git a/Maki/Maki.csproj b/Maki/Maki.csproj index b08744a..0fe3db3 100644 --- a/Maki/Maki.csproj +++ b/Maki/Maki.csproj @@ -39,17 +39,13 @@ ..\..\packages\Newtonsoft.Json.10.0.3\lib\net40\Newtonsoft.Json.dll - - - - - - ..\..\packages\WebSocketSharp.1.0.3-rc11\lib\websocket-sharp.dll + + @@ -69,6 +65,8 @@ + + @@ -78,9 +76,8 @@ - - - + + @@ -125,6 +122,7 @@ + diff --git a/Maki/MemberManager.cs b/Maki/MemberManager.cs new file mode 100644 index 0000000..3428920 --- /dev/null +++ b/Maki/MemberManager.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Maki +{ + internal sealed class MemberManager : BaseManager + { + internal MemberManager() + { + } + + public DiscordMember Id(ulong user) + { + lock (Collection) + return Collection.Where(x => x.Server == null && x.User.Id == user).FirstOrDefault(); + } + + public DiscordMember Id(ulong server, ulong user) + { + lock (Collection) + return Collection.Where(x => x.Server.Id == server && x.User.Id == user).FirstOrDefault(); + } + + public DiscordMember Id(DiscordServer server, ulong user) + { + lock (Collection) + return Collection.Where(x => x.Server == server && x.User.Id == user).FirstOrDefault(); + } + + public DiscordMember Id(DiscordServer server, DiscordUser user) + { + lock (Collection) + return Collection.Where(x => x.Server == server && x.User == user).FirstOrDefault(); + } + + public IEnumerable Server(DiscordServer server) + { + lock (Collection) + return Collection.Where(x => x.Server == server); + } + + public IEnumerable Role(DiscordRole role) + { + lock (Collection) + return Collection.Where(x => x.HasRole(role.Id)); + } + } +} diff --git a/Maki/MessageManager.cs b/Maki/MessageManager.cs new file mode 100644 index 0000000..cb2dd1a --- /dev/null +++ b/Maki/MessageManager.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Maki +{ + internal sealed class MessageManager : BaseManager + { + internal MessageManager() + { + } + + public DiscordMessage Id(ulong id) + { + lock (Collection) + return Collection.Where(x => x.Id == id).FirstOrDefault(); + } + + public IEnumerable Channel(DiscordChannel channel) + { + lock (Collection) + return Collection.Where(x => x.Channel == channel); + } + + public IEnumerable Server(DiscordServer server) + { + lock (Collection) + return Collection.Where(x => x.Server == server); + } + } +} diff --git a/Maki/Properties/AssemblyInfo.cs b/Maki/Properties/AssemblyInfo.cs index b9d7728..c5d8e86 100644 --- a/Maki/Properties/AssemblyInfo.cs +++ b/Maki/Properties/AssemblyInfo.cs @@ -1,36 +1,10 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. [assembly: AssemblyTitle("Maki")] [assembly: AssemblyDescription("Discord Client library")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Maki")] [assembly: AssemblyCopyright("flash.moe 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("97523aed-b694-42c2-96ac-86a1d65109f7")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("1.0.*")] diff --git a/Maki/Rest/RestEndpoints.cs b/Maki/Rest/RestEndpoints.cs index 93e61e9..f6f13aa 100644 --- a/Maki/Rest/RestEndpoints.cs +++ b/Maki/Rest/RestEndpoints.cs @@ -8,17 +8,17 @@ /// /// Base URL of Discord /// - public const string BASE_URL = @"https://discordapp.com"; + public const string BASE_URL = "https://discordapp.com"; /// /// Path to the REST API /// - public static string BASE_PATH => @"/api/v" + Discord.GATEWAY_VERSION; + public static string BASE_PATH => "/api/v" + Discord.GATEWAY_VERSION; /// /// Url of Discord's CDN /// - public const string CDN_URL = @"https://cdn.discordapp.com"; + public const string CDN_URL = "https://cdn.discordapp.com"; #region Channels /// @@ -349,18 +349,6 @@ public static string OAuth2Application(ulong appId) => $@"/oauth2/applications/{appId}"; #endregion - #region Auth - /// - /// Auth Login endpoint - /// - public static string AuthLogin => @"/auth/login"; - - /// - /// Auth MFA TOTP endpoint - /// - public static string AuthMfaTotp => @"/auth/mfa/totp"; - #endregion - #region User /// /// Gets a User endpoint diff --git a/Maki/Rest/WebRequest.cs b/Maki/Rest/WebRequest.cs index 127fb2e..12feb59 100644 --- a/Maki/Rest/WebRequest.cs +++ b/Maki/Rest/WebRequest.cs @@ -16,7 +16,7 @@ namespace Maki.Rest private const string JSON_CONTENT_TYPE = @"application/json"; private const string FORM_CONTENT_TYPE = @"multipart/form-data"; - private const long BUFFER_SIZE = 5242880; + private const long BUFFER_SIZE = 8192000; public readonly HttpMethod Method; public readonly string Url; @@ -24,16 +24,16 @@ namespace Maki.Rest public string UserAgent { get; set; } = USER_AGENT; public string ContentType { get; set; } = GENERIC_CONTENT_TYPE; - public long ContentLength => wResponse.ContentLength < 1 ? BUFFER_SIZE : wResponse.ContentLength; + public long ContentLength => HttpWebResponse.ContentLength < 1 ? BUFFER_SIZE : HttpWebResponse.ContentLength; // TODO: make this not static internal static string Authorisation { get; set; } - private readonly Dictionary headers = new Dictionary(); - private readonly Dictionary parameters = new Dictionary(); - private readonly Dictionary files = new Dictionary(); + private readonly Dictionary Headers = new Dictionary(); + private readonly Dictionary Parameters = new Dictionary(); + private readonly Dictionary Files = new Dictionary(); - private readonly Dictionary mimeTypes = new Dictionary() + private readonly Dictionary MimeTypes = new Dictionary() { { "png", "image/png" }, { "jpg", "image/jpeg" }, @@ -41,37 +41,47 @@ namespace Maki.Rest { "gif", "image/gif" }, }; - private byte[] rawContent = new byte[0]; - private HttpWebRequest wRequest; - private Stream requestStream; - private HttpWebResponse wResponse; - private Stream responseStream; + private byte[] RawRequestBody = new byte[0]; + private HttpWebRequest HttpWebRequest; + private Stream RequestStream; + private HttpWebResponse HttpWebResponse; + private Stream ResponseStream; - private byte[] rawResponse; + private byte[] RawResponseValue; public byte[] RawResponse { get { - if (rawResponse == null) + if (RawResponseValue == null) { - rawResponse = new byte[BUFFER_SIZE]; - responseStream.Read(rawResponse, 0, rawResponse.Length); + using (MemoryStream ms = new MemoryStream()) + { + byte[] bytes = new byte[4096]; + int read = 0; + + while ((read = ResponseStream.Read(bytes, 0, bytes.Length)) > 0) + ms.Write(bytes, 0, read); + + ms.Seek(0, SeekOrigin.Begin); + RawResponseValue = new byte[ms.Length]; + ms.Read(RawResponseValue, 0, RawResponseValue.Length); + } } - return rawResponse; + return RawResponseValue; } } - private string responseString = string.Empty; + private string ResponseString = string.Empty; public string Response { get { - if (string.IsNullOrEmpty(responseString)) - responseString = Encoding.UTF8.GetString(RawResponse).Trim('\0'); + if (string.IsNullOrEmpty(ResponseString)) + ResponseString = Encoding.UTF8.GetString(RawResponse); - return responseString; + return ResponseString; } } @@ -79,7 +89,7 @@ namespace Maki.Rest JsonConvert.DeserializeObject(Response); public short Status => - (short)wResponse?.StatusCode; + (short)HttpWebResponse?.StatusCode; static WebRequest() { @@ -93,7 +103,7 @@ namespace Maki.Rest } public void AddRaw(byte[] bytes) => - rawContent = bytes; + RawRequestBody = bytes; public void AddRaw(string str) => AddRaw(Encoding.UTF8.GetBytes(str)); @@ -105,10 +115,10 @@ namespace Maki.Rest } public void AddParam(string name, string contents) => - parameters.Add(name, contents); + Parameters.Add(name, contents); public void AddFile(string name, byte[] bytes) => - files.Add(name, bytes); + Files.Add(name, bytes); public void Perform() { @@ -124,50 +134,50 @@ namespace Maki.Rest if (Method == HttpMethod.GET || Method == HttpMethod.DELETE) - if (parameters.Count > 1) + if (Parameters.Count > 1) { if (!Url.Contains('?')) urlBuilder.Append(@"?"); - foreach (KeyValuePair param in parameters) + foreach (KeyValuePair param in Parameters) urlBuilder.Append($@"{param.Key}={param.Value}&"); } string url = urlBuilder.ToString().TrimEnd('&'); - wRequest = System.Net.WebRequest.Create(url) as HttpWebRequest; - wRequest.Method = Method.ToString(); - wRequest.UserAgent = UserAgent; - wRequest.KeepAlive = true; + HttpWebRequest = System.Net.WebRequest.Create(url) as HttpWebRequest; + HttpWebRequest.Method = Method.ToString(); + HttpWebRequest.UserAgent = UserAgent; + HttpWebRequest.KeepAlive = true; //wRequest.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; - wRequest.ReadWriteTimeout = Timeout.Infinite; - wRequest.Timeout = Timeout.Infinite; + HttpWebRequest.ReadWriteTimeout = Timeout.Infinite; + HttpWebRequest.Timeout = Timeout.Infinite; if (!string.IsNullOrEmpty(Authorisation) && url.StartsWith(RestEndpoints.BASE_URL + RestEndpoints.BASE_PATH)) - wRequest.Headers[HttpRequestHeader.Authorization] = Authorisation; + HttpWebRequest.Headers[HttpRequestHeader.Authorization] = Authorisation; - foreach (KeyValuePair header in headers) - wRequest.Headers[header.Key] = header.Value; + foreach (KeyValuePair header in Headers) + HttpWebRequest.Headers[header.Key] = header.Value; if (Method == HttpMethod.POST || Method == HttpMethod.PUT || Method == HttpMethod.PATCH) { - requestStream = wRequest.GetRequestStream(); + RequestStream = HttpWebRequest.GetRequestStream(); - if (parameters.Count + files.Count < 1) - requestStream.Write(rawContent, 0, rawContent.Length); + if (Parameters.Count + Files.Count < 1) + RequestStream.Write(RawRequestBody, 0, RawRequestBody.Length); else { string boundary = $@"-----------------------------{DateTime.Now.Ticks}"; ContentType = $@"{FORM_CONTENT_TYPE}; boundary={boundary}"; - if (parameters.Count >= 1) + if (Parameters.Count >= 1) { StringBuilder postBodyBuilder = new StringBuilder(); byte[] postBody = new byte[0]; - foreach (KeyValuePair param in parameters) + foreach (KeyValuePair param in Parameters) { postBodyBuilder.AppendLine($@"--{boundary}"); postBodyBuilder.AppendLine($@"Content-Disposition: form-data; name=""{param.Key}"""); @@ -176,90 +186,90 @@ namespace Maki.Rest } postBody = Encoding.UTF8.GetBytes(postBodyBuilder.ToString()); - requestStream.Write(postBody, 0, postBody.Length); + RequestStream.Write(postBody, 0, postBody.Length); } - if (files.Count >= 1) + if (Files.Count >= 1) { byte[] boundaryBytes = Encoding.UTF8.GetBytes($@"--{boundary}"); byte[] newLineBytes = Encoding.UTF8.GetBytes("\r\n"); - foreach (KeyValuePair file in files) + foreach (KeyValuePair file in Files) { string cType = GENERIC_CONTENT_TYPE; string fileExt = Path.GetExtension(file.Key).ToLower().TrimStart('.'); - if (mimeTypes.ContainsKey(fileExt)) - cType = mimeTypes[fileExt]; + if (MimeTypes.ContainsKey(fileExt)) + cType = MimeTypes[fileExt]; byte[] cDisposBytes = Encoding.UTF8.GetBytes($@"Content-Disposition: form-data; name=""{file.Key}""; filename=""{file.Key}"""); byte[] cTypeBytes = Encoding.UTF8.GetBytes($@"Content-Type: {cType}"); // Boundary + newline - requestStream.Write(boundaryBytes, 0, boundaryBytes.Length); - requestStream.Write(newLineBytes, 0, newLineBytes.Length); + RequestStream.Write(boundaryBytes, 0, boundaryBytes.Length); + RequestStream.Write(newLineBytes, 0, newLineBytes.Length); // Disposition header + newline - requestStream.Write(cDisposBytes, 0, cDisposBytes.Length); - requestStream.Write(newLineBytes, 0, newLineBytes.Length); + RequestStream.Write(cDisposBytes, 0, cDisposBytes.Length); + RequestStream.Write(newLineBytes, 0, newLineBytes.Length); // Type header + newline - requestStream.Write(cTypeBytes, 0, cTypeBytes.Length); - requestStream.Write(newLineBytes, 0, newLineBytes.Length); + RequestStream.Write(cTypeBytes, 0, cTypeBytes.Length); + RequestStream.Write(newLineBytes, 0, newLineBytes.Length); // newline + contents + newline - requestStream.Write(newLineBytes, 0, newLineBytes.Length); - requestStream.Write(file.Value, 0, file.Value.Length); - requestStream.Write(newLineBytes, 0, newLineBytes.Length); + RequestStream.Write(newLineBytes, 0, newLineBytes.Length); + RequestStream.Write(file.Value, 0, file.Value.Length); + RequestStream.Write(newLineBytes, 0, newLineBytes.Length); } } byte[] closingBound = Encoding.UTF8.GetBytes($@"--{boundary}--"); - requestStream.Write(closingBound, 0, closingBound.Length); + RequestStream.Write(closingBound, 0, closingBound.Length); } } - wRequest.ContentType = ContentType; + HttpWebRequest.ContentType = ContentType; try { - wResponse = wRequest.GetResponse() as HttpWebResponse; + HttpWebResponse = HttpWebRequest.GetResponse() as HttpWebResponse; } catch (WebException ex) { - wResponse = ex.Response as HttpWebResponse; + HttpWebResponse = ex.Response as HttpWebResponse; } - responseStream = wResponse.GetResponseStream(); + ResponseStream = HttpWebResponse.GetResponseStream(); } #region IDisposable - private bool isDisposed = false; - private void Dispose(bool disposing) - { - if (!isDisposed) - { - isDisposed = true; - requestStream?.Dispose(); - wRequest?.Abort(); - responseStream?.Dispose(); - wResponse?.Close(); - } - } - - ~WebRequest() - { - Dispose(false); - } + private bool IsDisposed = false; /// /// Disconnects and releases all unmanaged objects /// - public void Dispose() + private void Dispose(bool disposing) { - Dispose(true); - GC.SuppressFinalize(true); + if (IsDisposed) + return; + + IsDisposed = true; + RequestStream?.Dispose(); + HttpWebRequest?.Abort(); + ResponseStream?.Dispose(); + HttpWebResponse?.Close(); + + if (disposing) + GC.SuppressFinalize(true); } + + ~WebRequest() + => Dispose(false); + + public void Dispose() + => Dispose(true); + #endregion } } diff --git a/Maki/RoleManager.cs b/Maki/RoleManager.cs new file mode 100644 index 0000000..4ea7a57 --- /dev/null +++ b/Maki/RoleManager.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Maki +{ + internal sealed class RoleManager : BaseManager + { + internal RoleManager() + { + } + + public DiscordRole Id(ulong id) + { + lock (Collection) + return Collection.Where(x => x.Id == id).FirstOrDefault(); + } + + public bool Exists(ulong id) + { + lock (Collection) + return Collection.Where(x => x.Id == id).Count() > 0; + } + + public IEnumerable Server(DiscordServer server) + { + lock (Collection) + return Collection.Where(x => x.Server == server).OrderByDescending(x => x.Position); + } + } +} diff --git a/Maki/ServerManager.cs b/Maki/ServerManager.cs new file mode 100644 index 0000000..2381084 --- /dev/null +++ b/Maki/ServerManager.cs @@ -0,0 +1,23 @@ +using System.Linq; + +namespace Maki +{ + internal sealed class ServerManager : BaseManager + { + internal ServerManager() + { + } + + public DiscordServer Id(ulong id) + { + lock (Collection) + return Collection.Where(x => x.Id == id).FirstOrDefault(); + } + + public bool Exists(ulong id) + { + lock (Collection) + return Collection.Where(x => x.Id == id).Count() > 0; + } + } +} diff --git a/Maki/Structures/Auth/LoginMultiFactorAuth.cs b/Maki/Structures/Auth/LoginMultiFactorAuth.cs deleted file mode 100644 index 1bb1731..0000000 --- a/Maki/Structures/Auth/LoginMultiFactorAuth.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Newtonsoft.Json; - -namespace Maki.Structures.Auth -{ - /// - /// Multi Factor Auth login request - /// - internal struct LoginMultiFactorAuth - { - /// - /// Multi factor auth code - /// - [JsonProperty("code")] - public string Code; - - /// - /// Multi factor auth ticket - /// - [JsonProperty("ticket")] - public string Ticket; - } -} diff --git a/Maki/Structures/Auth/LoginRequest.cs b/Maki/Structures/Auth/LoginRequest.cs deleted file mode 100644 index da30424..0000000 --- a/Maki/Structures/Auth/LoginRequest.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Newtonsoft.Json; - -namespace Maki.Structures.Auth -{ - /// - /// Login request - /// - internal struct LoginRequest - { - /// - /// E-mail address - /// - [JsonProperty("email")] - public string Email; - - /// - /// Password - /// - [JsonProperty("password")] - public string Password; - } -} diff --git a/Maki/Structures/Auth/LoginResponse.cs b/Maki/Structures/Auth/LoginResponse.cs deleted file mode 100644 index 04b6f5d..0000000 --- a/Maki/Structures/Auth/LoginResponse.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Newtonsoft.Json; - -namespace Maki.Structures.Auth -{ - /// - /// Login response - /// - internal struct LoginResponse - { - /// - /// Indicates whether we require multi factor, is usually either null or true - /// - [JsonProperty("mfa")] - public bool? MFA; - - /// - /// Token - /// - [JsonProperty("token")] - public string Token; - - /// - /// MFA Ticket, to be bundled with the mfa request - /// - [JsonProperty("ticket")] - public string Ticket; - - /// - /// Username errors - /// - [JsonProperty("username")] - public string[] UsernameError; - - /// - /// Password errors - /// - [JsonProperty("password")] - public string[] PasswordError; - } -} diff --git a/Maki/UserManager.cs b/Maki/UserManager.cs new file mode 100644 index 0000000..21e946e --- /dev/null +++ b/Maki/UserManager.cs @@ -0,0 +1,17 @@ +using System.Linq; + +namespace Maki +{ + internal sealed class UserManager : BaseManager + { + internal UserManager() + { + } + + public DiscordUser Id(ulong id) + { + lock (Collection) + return Collection.Where(x => x.Id == id).FirstOrDefault(); + } + } +} diff --git a/Maki/Utility.cs b/Maki/Utility.cs index 047f811..c47bff4 100644 --- a/Maki/Utility.cs +++ b/Maki/Utility.cs @@ -4,11 +4,8 @@ namespace Maki { internal static class Utility { - public static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); public static readonly DateTime DiscordEpoch = new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Utc); - public static DateTime FromUnixTimeMilliseconds(long ms) => UnixEpoch.AddMilliseconds(ms); - public static DateTime FromDiscordTimeMilliseconds(long ms) => DiscordEpoch.AddMilliseconds(ms); public static DateTime FromDiscordSnowflake(ulong snowflake) => FromDiscordTimeMilliseconds((long)snowflake >> 22);