Archived
1
0
Fork 0

use managers rather than random lists

This commit is contained in:
flash 2017-10-13 22:05:50 +02:00
parent 82a74a3e63
commit ca02bba839
22 changed files with 640 additions and 468 deletions

64
Maki/BaseManager.cs Normal file
View file

@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Maki
{
internal abstract class BaseManager<T> : IDisposable
{
protected readonly HashSet<T> Collection = new HashSet<T>();
public IEnumerable<T> Items => Collection;
public int Count => Collection.Count;
public event Action<T> OnAdd;
public event Action<T> 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();
}
}
}

30
Maki/ChannelManager.cs Normal file
View file

@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Linq;
namespace Maki
{
internal sealed class ChannelManager : BaseManager<DiscordChannel>
{
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<DiscordChannel> Server(DiscordServer server)
{
lock (Collection)
return Collection.Where(x => x.Server == server).OrderBy(x => x.Position);
}
}
}

View file

@ -1,6 +1,5 @@
using Maki.Gateway; using Maki.Gateway;
using Maki.Rest; using Maki.Rest;
using Maki.Structures.Auth;
using Maki.Structures.Channels; using Maki.Structures.Channels;
using Maki.Structures.Gateway; using Maki.Structures.Gateway;
using Maki.Structures.Guilds; using Maki.Structures.Guilds;
@ -10,6 +9,7 @@ using Maki.Structures.Roles;
using Maki.Structures.Users; using Maki.Structures.Users;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
namespace Maki namespace Maki
@ -64,35 +64,49 @@ namespace Maki
internal readonly GatewayShardClient ShardClient; internal readonly GatewayShardClient ShardClient;
#region Containers #region Containers
internal readonly ServerManager ServerManager;
/// <summary> /// <summary>
/// Servers we're in /// Servers we're in
/// </summary> /// </summary>
internal readonly List<DiscordServer> servers = new List<DiscordServer>(); public IEnumerable<DiscordServer> Servers => ServerManager.Items;
internal readonly UserManager UserManager;
/// <summary> /// <summary>
/// Users we are affiliated with /// Users we are affiliated with
/// </summary> /// </summary>
internal readonly List<DiscordUser> users = new List<DiscordUser>(); public IEnumerable<DiscordUser> Users => UserManager.Items;
internal readonly MemberManager MemberManager;
/// <summary> /// <summary>
/// Members of servers, different from users! /// Members of servers, different from users!
/// </summary> /// </summary>
internal readonly List<DiscordMember> members = new List<DiscordMember>(); public IEnumerable<DiscordMember> Members => MemberManager.Items;
internal readonly RoleManager RoleManager;
/// <summary> /// <summary>
/// Roles in servers /// Roles in servers
/// </summary> /// </summary>
internal readonly List<DiscordRole> roles = new List<DiscordRole>(); public IEnumerable<DiscordRole> Roles => RoleManager.Items;
internal readonly ChannelManager ChannelManager;
/// <summary> /// <summary>
/// Channels /// Channels
/// </summary> /// </summary>
internal readonly List<DiscordChannel> channels = new List<DiscordChannel>(); public IEnumerable<DiscordChannel> Channels => ChannelManager.Items;
internal readonly MessageManager MessageManager;
/// <summary> /// <summary>
/// Messages /// Messages
/// </summary> /// </summary>
internal readonly List<DiscordMessage> messages = new List<DiscordMessage>(); public IEnumerable<DiscordMessage> Messages => MessageManager.Items;
#endregion #endregion
#region Events #region Events
@ -207,8 +221,7 @@ namespace Maki
public event Action<DiscordUser> OnUserUpdate; public event Action<DiscordUser> OnUserUpdate;
#endregion #endregion
public DiscordServer[] Servers => servers.ToArray(); public DiscordUser Me => UserManager.Items.FirstOrDefault();
public DiscordUser Me => users.FirstOrDefault();
private DiscordParams Params; private DiscordParams Params;
@ -220,6 +233,13 @@ namespace Maki
Params = parameters ?? new DiscordParams(); Params = parameters ?? new DiscordParams();
ShardClient = new GatewayShardClient(this); 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 #region Assigning event handlers
ShardClient.OnChannelCreate += ShardManager_OnChannelCreate; ShardClient.OnChannelCreate += ShardManager_OnChannelCreate;
ShardClient.OnChannelUpdate += ShardManager_OnChannelUpdate; ShardClient.OnChannelUpdate += ShardManager_OnChannelUpdate;
@ -259,23 +279,11 @@ namespace Maki
#endregion #endregion
} }
private void ClearContainers()
{
servers.Clear();
users.Clear();
roles.Clear();
channels.Clear();
messages.Clear();
members.Clear();
}
/// <summary> /// <summary>
/// Connects to Discord using the token assigned to Token /// Connects to Discord using the token assigned to Token
/// </summary> /// </summary>
public void Connect() public void Connect()
{ {
ClearContainers();
int shards = 1; int shards = 1;
using (WebRequest wr = new WebRequest(HttpMethod.GET, IsBot ? RestEndpoints.GatewayBot : RestEndpoints.Gateway)) using (WebRequest wr = new WebRequest(HttpMethod.GET, IsBot ? RestEndpoints.GatewayBot : RestEndpoints.Gateway))
@ -303,76 +311,6 @@ namespace Maki
ShardClient.Create(i); ShardClient.Create(i);
} }
/// <summary>
/// Connects to Discord with the provided email, password and, optionally, mfa token
/// </summary>
/// <param name="email">Discord account email</param>
/// <param name="password">Discord account password</param>
/// <param name="code">Multi factor authentication token</param>
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<LoginResponse>();
}
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<LoginResponse>();
}
//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();
}
/// <summary> /// <summary>
/// Connects to Discord using a provided token and token type /// Connects to Discord using a provided token and token type
/// </summary> /// </summary>
@ -391,23 +329,27 @@ namespace Maki
public void Disconnect() public void Disconnect()
{ {
ShardClient.Disconnect(); ShardClient.Disconnect();
ClearContainers();
Token = string.Empty; Token = string.Empty;
Gateway = string.Empty; Gateway = string.Empty;
} }
#region Event Handlers #region Event Handlers
private void ShardManager_OnChannelCreate(GatewayShard shard, Channel channel) 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); DiscordChannel chan = new DiscordChannel(this, channel, server);
channels.Add(chan); ChannelManager.Add(chan);
OnChannelCreate?.Invoke(chan); OnChannelCreate?.Invoke(chan);
} }
private void ShardManager_OnChannelUpdate(GatewayShard shard, Channel channel) 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; chan.Name = channel.Name;
@ -435,8 +377,10 @@ namespace Maki
private void ShardManager_OnChannelDelete(GatewayShard shard, Channel channel) private void ShardManager_OnChannelDelete(GatewayShard shard, Channel channel)
{ {
DiscordChannel chan = channels.Find(x => x.Id == channel.Id); DiscordChannel chan = ChannelManager.Id(channel.Id);
channels.Remove(chan); Debug.Assert(chan != null, "channel is null");
ChannelManager.Remove(chan);
OnChannelDelete?.Invoke(chan); OnChannelDelete?.Invoke(chan);
} }
@ -452,9 +396,15 @@ namespace Maki
$"Is verified?: {user.IsVerified}", $"Is verified?: {user.IsVerified}",
$"E-mail: {user.EMail}"*/ $"E-mail: {user.EMail}"*/
DiscordUser user = users.Find(x => x.Id == sUser.Id); DiscordUser user = UserManager.Id(sUser.Id);
DiscordServer server = servers.Find(x => x.Id == sUser.GuildId); 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); OnBanAdd?.Invoke(user, server);
} }
@ -470,24 +420,28 @@ namespace Maki
$"Is verified?: {user.IsVerified}", $"Is verified?: {user.IsVerified}",
$"E-mail: {user.EMail}"*/ $"E-mail: {user.EMail}"*/
DiscordUser user = users.Find(x => x.Id == sUser.Id); DiscordUser user = UserManager.Id(sUser.Id);
DiscordServer server = servers.Find(x => x.Id == sUser.GuildId); 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); OnBanRemove?.Invoke(user, server);
} }
private void ShardManager_OnGuildCreate(GatewayShard shard, Guild guild) 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); server = new DiscordServer(this, guild);
servers.Add(server); ServerManager.Add(server);
} }
else else
{ {
server = servers.Find(x => x.Id == guild.Id);
server.Name = guild.Name; server.Name = guild.Name;
server.OwnerId = guild.OwnerId ?? 0; server.OwnerId = guild.OwnerId ?? 0;
server.IconHash = guild.IconHash; server.IconHash = guild.IconHash;
@ -500,7 +454,7 @@ namespace Maki
Channel channel = guild.Channels[i]; Channel channel = guild.Channels[i];
channel.GuildId = server.Id; channel.GuildId = server.Id;
if (channels.Where(x => x.Id == channel.Id).Count() > 0) if (ChannelManager.Exists(channel.Id))
ShardManager_OnChannelUpdate(shard, channel); ShardManager_OnChannelUpdate(shard, channel);
else else
ShardManager_OnChannelCreate(shard, channel); ShardManager_OnChannelCreate(shard, channel);
@ -515,7 +469,7 @@ namespace Maki
RoleId = role.Id RoleId = role.Id
}; };
if (roles.Where(x => x.Id == role.Id).Count() > 0) if (RoleManager.Exists(role.Id))
ShardManager_OnGuildRoleUpdate(shard, gRole); ShardManager_OnGuildRoleUpdate(shard, gRole);
else else
ShardManager_OnGuildRoleCreate(shard, gRole); ShardManager_OnGuildRoleCreate(shard, gRole);
@ -532,7 +486,8 @@ namespace Maki
private void ShardManager_OnGuildDelete(GatewayShard shard, Guild guild) 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.Name = guild.Name;
server.OwnerId = guild.OwnerId ?? 0; server.OwnerId = guild.OwnerId ?? 0;
@ -549,16 +504,20 @@ namespace Maki
server.MultiFactorAuthLevel = guild.MultiFactorAuthLevel;*/ server.MultiFactorAuthLevel = guild.MultiFactorAuthLevel;*/
OnServerDelete?.Invoke(server); 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)); // NOT THREAD SAFE AT ALL
members.Where(x => x.Server == server).ToList().ForEach(x => members.Remove(x)); MessageManager .Server(server).ToList().ForEach(x => MessageManager.Remove(x));
roles.Where(x => x.Server == server).ToList().ForEach(x => roles.Remove(x)); ChannelManager .Server(server).ToList().ForEach(x => ChannelManager.Remove(x));
servers.Remove(server); 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) 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.Name = guild.Name;
server.OwnerId = guild.OwnerId ?? 0; server.OwnerId = guild.OwnerId ?? 0;
@ -580,31 +539,41 @@ namespace Maki
private void ShardManager_OnGuildEmojisUpdate(GatewayShard shard, Guild guild) 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; //servers.Emojis = guild.Emojis;
OnEmojisUpdate?.Invoke(server); OnEmojisUpdate?.Invoke(server);
} }
private void ShardManager_OnGuildMemberAdd(GatewayShard shard, GuildMember sMember) private void ShardManager_OnGuildMemberAdd(GatewayShard shard, GuildMember sMember)
{ {
DiscordServer server = servers.Find(x => x.Id == sMember.GuildId); Debug.Assert(sMember.GuildId.HasValue, "GuildId has no value");
DiscordUser user = users.Where(x => x.Id == sMember.User.Id).FirstOrDefault();
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); user = new DiscordUser(this, sMember.User);
users.Add(user); UserManager.Add(user);
} }
DiscordMember member = new DiscordMember(this, sMember, user, server); DiscordMember member = new DiscordMember(this, sMember, user, server);
members.Add(member); MemberManager.Add(member);
OnMemberAdd?.Invoke(member); OnMemberAdd?.Invoke(member);
} }
private void ShardManager_OnGuildMemberUpdate(GatewayShard shard, GuildMember sMember) 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.Nickname = sMember.Nickname;
member.IsDeaf = sMember.IsDeafened == true; member.IsDeaf = sMember.IsDeafened == true;
member.IsMute = sMember.IsMuted == true; member.IsMute = sMember.IsMuted == true;
@ -615,8 +584,12 @@ namespace Maki
private void ShardManager_OnGuildMemberRemove(GatewayShard shard, GuildMember sMember) private void ShardManager_OnGuildMemberRemove(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");
members.Remove(member);
DiscordMember member = MemberManager.Id(sMember.GuildId ?? 0, sMember.User.Id);
Debug.Assert(member != null, "member is null");
MemberManager.Remove(member);
OnMemberRemove?.Invoke(member); OnMemberRemove?.Invoke(member);
} }
@ -632,13 +605,20 @@ namespace Maki
$"Is Mentionable?: {role.Role.Value.IsMentionable}", $"Is Mentionable?: {role.Role.Value.IsMentionable}",
$"Permissions: {role.Role.Value.Permissions}"*/ $"Permissions: {role.Role.Value.Permissions}"*/
DiscordServer server = servers.Find(x => x.Id == sRole.Guild); DiscordServer server = ServerManager.Id(sRole.Guild);
DiscordRole role = roles.Where(x => x.Id == sRole.Role.Value.Id).FirstOrDefault(); 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); role = new DiscordRole(this, sRole.Role.Value, server);
roles.Add(role); RoleManager.Add(role);
} else } else
{ {
role.Colour.Raw = sRole.Role.Value.Colour.Value; role.Colour.Raw = sRole.Role.Value.Colour.Value;
@ -649,17 +629,25 @@ namespace Maki
private void ShardManager_OnGuildRoleDelete(GatewayShard shard, GuildRole sRole) private void ShardManager_OnGuildRoleDelete(GatewayShard shard, GuildRole sRole)
{ {
DiscordRole role = roles.Find(x => x.Id == sRole.RoleId && x.Server.Id == sRole.Guild); Debug.Assert(sRole.RoleId.HasValue, "RoleId has no value");
members.Where(x => x.roles.Contains(role.Id)).ToList().ForEach(x => x.roles.RemoveAll(y => y == role.Id)); DiscordRole role = RoleManager.Id(sRole.RoleId ?? 0);
roles.Remove(role); 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); OnRoleDelete?.Invoke(role);
} }
private void ShardManager_OnGuildRoleUpdate(GatewayShard shard, GuildRole sRole) private void ShardManager_OnGuildRoleUpdate(GatewayShard shard, GuildRole sRole)
{ {
Debug.Assert(sRole.Role.HasValue, "Role has no value");
Role dRole = sRole.Role.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.Name = dRole.Name;
role.IsHoisted = dRole.IsHoisted == true; role.IsHoisted = dRole.IsHoisted == true;
role.IsMentionable = dRole.IsMentionable == true; role.IsMentionable = dRole.IsMentionable == true;
@ -697,24 +685,32 @@ namespace Maki
private void ShardManager_OnMessageCreate(GatewayShard shard, Message message) private void ShardManager_OnMessageCreate(GatewayShard shard, Message message)
{ {
DiscordChannel channel = channels.Find(x => x.Id == message.ChannelId); DiscordChannel channel = ChannelManager.Id(message.ChannelId);
DiscordMember member = members.Where(x => x.User.Id == message.User.Id && x.Server == channel.Server).FirstOrDefault(); 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); DiscordMessage msg = new DiscordMessage(this, message, channel, member);
messages.Add(msg); MessageManager.Add(msg);
OnMessageCreate?.Invoke(msg); OnMessageCreate?.Invoke(msg);
} }
private void ShardManager_OnMessageDelete(GatewayShard shard, Message message) private void ShardManager_OnMessageDelete(GatewayShard shard, Message message)
{ {
DiscordMessage msg = messages.Find(x => x.Id == message.Id); DiscordMessage msg = MessageManager.Id(message.Id);
messages.Remove(msg); Debug.Assert(msg != null, "message is null");
MessageManager.Remove(msg);
OnMessageDelete?.Invoke(msg); OnMessageDelete?.Invoke(msg);
} }
private void ShardManager_OnMessageUpdate(GatewayShard shard, Message message) 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) if (msg == null)
{ {
using (WebRequest wr = new WebRequest(HttpMethod.GET, RestEndpoints.ChannelMessage(message.ChannelId, message.Id))) using (WebRequest wr = new WebRequest(HttpMethod.GET, RestEndpoints.ChannelMessage(message.ChannelId, message.Id)))
@ -727,10 +723,14 @@ namespace Maki
message = wr.ResponseJson<Message>(); message = wr.ResponseJson<Message>();
} }
DiscordChannel channel = channels.Where(x => x.Id == message.ChannelId).FirstOrDefault(); DiscordChannel channel = ChannelManager.Id(message.ChannelId);
DiscordMember member = members.Where(x => x.User.Id == message.User.Id && (channel.Server == null || channel.Server == x.Server)).FirstOrDefault(); 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); msg = new DiscordMessage(this, message, channel, member);
messages.Add(msg); MessageManager.Add(msg);
} else } else
{ {
if (!string.IsNullOrEmpty(message.Content)) if (!string.IsNullOrEmpty(message.Content))
@ -746,14 +746,19 @@ namespace Maki
private void ShardManager_OnTypingStart(GatewayShard shard, TypingStart typing) private void ShardManager_OnTypingStart(GatewayShard shard, TypingStart typing)
{ {
DiscordChannel channel = channels.Find(x => x.Id == typing.Channel); DiscordChannel channel = ChannelManager.Id(typing.Channel);
DiscordUser user = users.Find(x => x.Id == typing.User); Debug.Assert(channel != null, "channel is null");
DiscordUser user = UserManager.Id(typing.User);
Debug.Assert(user != null, "user is null");
OnTypingStart?.Invoke(user, channel); OnTypingStart?.Invoke(user, channel);
} }
private void ShardManager_OnPresenceUpdate(GatewayShard shard, Presence presence) 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)) if (!string.IsNullOrEmpty(presence.User.Username))
member.User.Username = presence.User.Username; member.User.Username = presence.User.Username;
@ -771,20 +776,20 @@ namespace Maki
switch (presence.Status.ToLower()) switch (presence.Status.ToLower())
{ {
case @"online": case "online":
member.User.Status = DiscordUserStatus.Online; member.User.Status = DiscordUserStatus.Online;
break; break;
case @"away": case "away":
case @"idle": case "idle":
member.User.Status = DiscordUserStatus.Idle; member.User.Status = DiscordUserStatus.Idle;
break; break;
case @"dnd": case "dnd":
member.User.Status = DiscordUserStatus.DoNotDisturb; member.User.Status = DiscordUserStatus.DoNotDisturb;
break; break;
case @"offline": case "offline":
default: default:
member.User.Status = DiscordUserStatus.Offline; member.User.Status = DiscordUserStatus.Offline;
break; break;
@ -800,24 +805,24 @@ namespace Maki
DiscordChannel channel = new DiscordChannel(this, chan); DiscordChannel channel = new DiscordChannel(this, chan);
// this shouldn't ever happen but just in case // 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; continue;
channels.Add(channel); ChannelManager.Add(channel);
} }
foreach (Guild guild in ready.UnavailableGuilds) foreach (Guild guild in ready.UnavailableGuilds)
{ {
DiscordServer server = new DiscordServer(this, guild); DiscordServer server = new DiscordServer(this, guild);
if (servers.Where(x => x.Id == server.Id).Count() > 0) if (ServerManager.Exists(server.Id))
continue; continue;
servers.Add(server); ServerManager.Add(server);
} }
DiscordUser user = new DiscordUser(this, ready.User); DiscordUser user = new DiscordUser(this, ready.User);
users.Add(user); UserManager.Add(user);
OnReady?.Invoke(user); OnReady?.Invoke(user);
} }
@ -838,12 +843,13 @@ namespace Maki
$"Is verified?: {user.IsVerified}", $"Is verified?: {user.IsVerified}",
$"E-mail: {user.EMail}"*/ $"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); user = new DiscordUser(this, sUser);
users.Add(user); UserManager.Add(user);
} }
OnUserUpdate?.Invoke(user); OnUserUpdate?.Invoke(user);
@ -864,32 +870,41 @@ namespace Maki
private void ShardManager_OnSocketMessage(GatewayShard shard, string text) private void ShardManager_OnSocketMessage(GatewayShard shard, string text)
{ {
} }
#endregion #endregion
#region IDisposable #region IDisposable
private bool isDisposed = false;
private void Dispose(bool disposing) private bool IsDisposed = false;
{
if (!isDisposed) {
isDisposed = true;
Disconnect();
}
}
~Discord()
{
Dispose(false);
}
/// <summary> /// <summary>
/// Disconnects and releases all unmanaged objects /// Disconnects and releases all unmanaged objects
/// </summary> /// </summary>
public void Dispose() private void Dispose(bool disposing)
{ {
Dispose(true); if (IsDisposed)
GC.SuppressFinalize(true); 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 #endregion
} }
} }

View file

@ -3,6 +3,7 @@ using Maki.Structures.Channels;
using Maki.Structures.Messages; using Maki.Structures.Messages;
using Maki.Structures.Rest; using Maki.Structures.Rest;
using Newtonsoft.Json; using Newtonsoft.Json;
using System.Diagnostics;
using System.IO; using System.IO;
namespace Maki namespace Maki
@ -59,7 +60,8 @@ namespace Maki
if (wr.Status != 200 || wr.Response.Length < 1) if (wr.Status != 200 || wr.Response.Length < 1)
// TODO: elaborate // TODO: elaborate
throw new DiscordException("Failed to send message"); //throw new DiscordException("Failed to send message");
return null;
msg = wr.ResponseJson<Message>(); msg = wr.ResponseJson<Message>();
} }
@ -67,8 +69,11 @@ namespace Maki
if (!msg.HasValue) if (!msg.HasValue)
throw new DiscordException("Empty response?"); throw new DiscordException("Empty response?");
DiscordMessage message = new DiscordMessage(client, msg.Value, this, client.members.Find(x => x.User.Id == msg.Value.User.Id)); DiscordMember member = client.MemberManager.Id(Server, msg.Value.User.Id);
client.messages.Add(message); Debug.Assert(member != null, "member is null");
DiscordMessage message = new DiscordMessage(client, msg.Value, this, member);
client.MessageManager.Add(message);
return message; return message;
} }

View file

@ -5,6 +5,41 @@ namespace Maki
{ {
public class DiscordColour 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) public DiscordColour(int raw)
{ {
Raw = raw; Raw = raw;
@ -12,7 +47,9 @@ namespace Maki
public DiscordColour(byte red, byte green, byte blue) 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) public DiscordColour(string hex)
@ -24,32 +61,10 @@ namespace Maki
if (hex.Length != 6) if (hex.Length != 6)
throw new FormatException("Invalid hex colour format!"); throw new FormatException("Invalid hex colour format!");
Red = byte.Parse(hex.Substring(0, 2), NumberStyles.HexNumber); Red = byte.Parse(hex.Substring(0, 2), NumberStyles.HexNumber);
Green = byte.Parse(hex.Substring(2, 2), NumberStyles.HexNumber); Green = byte.Parse(hex.Substring(2, 2), NumberStyles.HexNumber);
Blue = byte.Parse(hex.Substring(4, 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}";
} }
} }

View file

@ -12,7 +12,7 @@ namespace Maki
public readonly DiscordServer Server; public readonly DiscordServer Server;
private readonly Discord client; 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<DiscordRole> Roles => client.RoleManager.Server(Server).Where(x => HasRole(x.Id));
public DateTime Joined { get; internal set; } public DateTime Joined { get; internal set; }
public string Nickname { get; internal set; } public string Nickname { get; internal set; }

View file

@ -2,6 +2,8 @@
using Maki.Structures.Messages; using Maki.Structures.Messages;
using Maki.Structures.Rest; using Maki.Structures.Rest;
using System; using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
namespace Maki namespace Maki
@ -17,18 +19,20 @@ namespace Maki
public string Text { get; internal set; } public string Text { get; internal set; }
public DateTime Edited { get; internal set; } public DateTime Edited { get; internal set; }
public DiscordMember[] MentionsUsers { get; internal set; } public IEnumerable<DiscordMember> MentionsUsers { get; internal set; }
public DiscordRole[] MentionsRoles { get; internal set; } public IEnumerable<DiscordRole> MentionsRoles { get; internal set; }
public bool MentionsEveryone { get; internal set; } public bool MentionsEveryone { get; internal set; }
public bool IsPinned { get; internal set; } public bool IsPinned { get; internal set; }
public bool MentionsMe(bool everyone = false, bool roles = true) => public bool MentionsMe(bool everyone = false, bool roles = true) =>
(everyone && MentionsEveryone) (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); || MentionsUsers.Select(x => x.User).Contains(client.Me);
public bool IsMe => client.Me == User; public bool IsMe => client.Me == User;
public DiscordServer Server => Channel.Server;
internal DiscordMessage(Discord discord, Message msg, DiscordChannel channel, DiscordMember member = null) internal DiscordMessage(Discord discord, Message msg, DiscordChannel channel, DiscordMember member = null)
{ {
client = discord; client = discord;
@ -43,12 +47,13 @@ namespace Maki
} else } else
{ {
member = null; 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; Channel = channel;
MentionsUsers = client.members.Where(x => x.Server == channel.Server && msg.Mentions.Select(y => y.Id).Contains(x.User.Id)).ToArray(); MentionsUsers = client.MemberManager.Server(channel.Server).Where(x => msg.Mentions.Select(y => y.Id).Contains(x.User.Id));
MentionsRoles = client.roles.Where(x => x.Server == channel.Server && msg.MentionsRoles.Contains(x.Id)).ToArray(); MentionsRoles = client.RoleManager .Server(channel.Server).Where(x => msg.MentionsRoles.Contains(x.Id));
MentionsEveryone = msg.MentioningEveryone; MentionsEveryone = msg.MentioningEveryone;
IsPinned = msg.IsPinned == true; IsPinned = msg.IsPinned == true;
} }

View file

@ -3,6 +3,7 @@ using Maki.Structures.Guilds;
using Maki.Structures.Rest; using Maki.Structures.Rest;
using Maki.Structures.Roles; using Maki.Structures.Roles;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Maki namespace Maki
@ -17,13 +18,17 @@ namespace Maki
public DateTime Created { get; internal set; } public DateTime Created { get; internal set; }
internal string IconHash; internal string IconHash;
public DiscordMember[] Members => client.members.Where(x => x.Server == this).ToArray(); public IEnumerable<DiscordMember> Members => client.MemberManager.Server(this);
public DiscordMember Owner => Members.Where(x => x.User.Id == OwnerId).FirstOrDefault(); public DiscordMember Owner => Members.Where(x => x.User.Id == OwnerId).FirstOrDefault();
public DiscordMember Me => Members.Where(x => x.User == client.Me).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 IEnumerable<DiscordChannel> Channels => client.ChannelManager.Server(this);
public DiscordRole[] Roles => client.roles.Where(x => x.Server == this).OrderByDescending(x => x.Position).ToArray(); public IEnumerable<DiscordChannel> TextChannels => Channels.Where(x => x.Type == DiscordChannelType.Text);
public string Icon(string ext = @"png", int size = 128) => RestEndpoints.CDN_URL + $@"/icons/{Id}/{IconHash}.{ext}?size={size}"; public IEnumerable<DiscordChannel> VoiceChannels => Channels.Where(x => x.Type == DiscordChannelType.Voice);
public IEnumerable<DiscordRole> 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) internal DiscordServer(Discord discord, Guild guild)
{ {
@ -60,14 +65,15 @@ namespace Maki
if (!roleStruct.HasValue) if (!roleStruct.HasValue)
throw new DiscordException("Empty response?"); 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); role = new DiscordRole(client, roleStruct.Value, this);
client.roles.Add(role); client.RoleManager.Add(role);
} }
return role; return role;
} }
} }

View file

@ -18,26 +18,27 @@ namespace Maki.Gateway
/// <summary> /// <summary>
/// Managed (active) shards /// Managed (active) shards
/// </summary> /// </summary>
private readonly Dictionary<int, GatewayShard> shards = new Dictionary<int, GatewayShard>(); private readonly List<GatewayShard> Shards = new List<GatewayShard>();
private readonly object Lock = new object();
/// <summary> /// <summary>
/// Parent DiscordClient instance /// Parent DiscordClient instance
/// </summary> /// </summary>
private Discord client; private Discord Client;
/// <summary> /// <summary>
/// Number of active managed shards /// Number of active managed shards
/// </summary> /// </summary>
public int ShardCount => shards.Count; public int ShardCount
=> Shards.Count;
/// <summary> /// <summary>
/// Constructor /// Constructor
/// </summary> /// </summary>
/// <param name="c">Parent DiscordClient instance</param> /// <param name="client">Parent DiscordClient instance</param>
public GatewayShardClient(Discord c) public GatewayShardClient(Discord client)
{ => Client = client;
client = c;
}
/// <summary> /// <summary>
/// Creates a new Gateway Shard /// Creates a new Gateway Shard
@ -46,25 +47,23 @@ namespace Maki.Gateway
/// <returns>New Gateway Shard</returns> /// <returns>New Gateway Shard</returns>
public GatewayShard Create(int id) public GatewayShard Create(int id)
{ {
GatewayShard shard = new GatewayShard(id, client); GatewayShard shard = new GatewayShard(id, Client);
ApplyEvents(shard); ApplyEvents(shard);
shards.Add(id, shard);
lock (Lock)
Shards.Add(shard);
return shard; return shard;
} }
/// <summary>
/// Destroys a Gateway Shard using its id
/// </summary>
/// <param name="id">Id of the shard to destroy</param>
public void Destroy(int id) => Destroy(shards[id]);
/// <summary> /// <summary>
/// Destroys a Gateway Shard /// Destroys a Gateway Shard
/// </summary> /// </summary>
/// <param name="shard">Shard to destroy</param> /// <param name="shard">Shard to destroy</param>
public void Destroy(GatewayShard shard) 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 // Disconnect and Dispose are called separately so OnSocketClose is called properly
shard.Disconnect(); shard.Disconnect();
@ -75,7 +74,11 @@ namespace Maki.Gateway
/// <summary> /// <summary>
/// Destroys all shards /// Destroys all shards
/// </summary> /// </summary>
public void Disconnect() => shards.Keys.ToList().ForEach(x => Destroy(x)); public void Disconnect()
{
lock (Lock)
Shards.ToList().ForEach(x => Destroy(x));
}
/// <summary> /// <summary>
/// Links all gateway events handlers to the shard /// Links all gateway events handlers to the shard
@ -271,29 +274,27 @@ namespace Maki.Gateway
private bool IsDisposed = false; private bool IsDisposed = false;
private void Dispose(bool disposing)
{
if (!IsDisposed)
{
IsDisposed = true;
Disconnect();
}
}
~GatewayShardClient()
{
Dispose(false);
}
/// <summary> /// <summary>
/// Releases all unmanaged resources used by this object /// Releases all unmanaged resources used by this object
/// </summary> /// </summary>
public void Dispose() private void Dispose(bool disposing)
{ {
Dispose(true); if (IsDisposed)
GC.SuppressFinalize(true); return;
IsDisposed = true;
Disconnect();
if (disposing)
GC.SuppressFinalize(true);
} }
~GatewayShardClient()
=> Dispose(false);
public void Dispose()
=> Dispose(true);
#endregion #endregion
} }
} }

View file

@ -39,17 +39,13 @@
<HintPath>..\..\packages\Newtonsoft.Json.10.0.3\lib\net40\Newtonsoft.Json.dll</HintPath> <HintPath>..\..\packages\Newtonsoft.Json.10.0.3\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="websocket-sharp, Version=1.0.2.59611, Culture=neutral, PublicKeyToken=5660b08a1845a91e, processorArchitecture=MSIL"> <Reference Include="websocket-sharp, Version=1.0.2.59611, Culture=neutral, PublicKeyToken=5660b08a1845a91e, processorArchitecture=MSIL">
<HintPath>..\..\packages\WebSocketSharp.1.0.3-rc11\lib\websocket-sharp.dll</HintPath> <HintPath>..\..\packages\WebSocketSharp.1.0.3-rc11\lib\websocket-sharp.dll</HintPath>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="BaseManager.cs" />
<Compile Include="ChannelManager.cs" />
<Compile Include="DiscordChannel.cs" /> <Compile Include="DiscordChannel.cs" />
<Compile Include="Discord.cs" /> <Compile Include="Discord.cs" />
<Compile Include="DiscordChannelType.cs" /> <Compile Include="DiscordChannelType.cs" />
@ -69,6 +65,8 @@
<Compile Include="DiscordUserStatus.cs" /> <Compile Include="DiscordUserStatus.cs" />
<Compile Include="DiscordUser.cs" /> <Compile Include="DiscordUser.cs" />
<Compile Include="Gateway\GatewayEvent.cs" /> <Compile Include="Gateway\GatewayEvent.cs" />
<Compile Include="MemberManager.cs" />
<Compile Include="MessageManager.cs" />
<Compile Include="Rest\RestErrorCode.cs" /> <Compile Include="Rest\RestErrorCode.cs" />
<Compile Include="Rest\HttpMethod.cs" /> <Compile Include="Rest\HttpMethod.cs" />
<Compile Include="Rest\RestEndpoints.cs" /> <Compile Include="Rest\RestEndpoints.cs" />
@ -78,9 +76,8 @@
<Compile Include="DiscordTokenType.cs" /> <Compile Include="DiscordTokenType.cs" />
<Compile Include="Gateway\GatewayCloseCode.cs" /> <Compile Include="Gateway\GatewayCloseCode.cs" />
<Compile Include="Rest\WebRequest.cs" /> <Compile Include="Rest\WebRequest.cs" />
<Compile Include="Structures\Auth\LoginMultiFactorAuth.cs" /> <Compile Include="RoleManager.cs" />
<Compile Include="Structures\Auth\LoginRequest.cs" /> <Compile Include="ServerManager.cs" />
<Compile Include="Structures\Auth\LoginResponse.cs" />
<Compile Include="Structures\Channels\Channel.cs" /> <Compile Include="Structures\Channels\Channel.cs" />
<Compile Include="Structures\Channels\ChannelType.cs" /> <Compile Include="Structures\Channels\ChannelType.cs" />
<Compile Include="Structures\Embeds\Embed.cs" /> <Compile Include="Structures\Embeds\Embed.cs" />
@ -125,6 +122,7 @@
<Compile Include="Structures\Roles\Role.cs" /> <Compile Include="Structures\Roles\Role.cs" />
<Compile Include="Structures\Users\PermissionOverwrite.cs" /> <Compile Include="Structures\Users\PermissionOverwrite.cs" />
<Compile Include="Structures\Users\User.cs" /> <Compile Include="Structures\Users\User.cs" />
<Compile Include="UserManager.cs" />
<Compile Include="Utility.cs" /> <Compile Include="Utility.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

48
Maki/MemberManager.cs Normal file
View file

@ -0,0 +1,48 @@
using System.Collections.Generic;
using System.Linq;
namespace Maki
{
internal sealed class MemberManager : BaseManager<DiscordMember>
{
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<DiscordMember> Server(DiscordServer server)
{
lock (Collection)
return Collection.Where(x => x.Server == server);
}
public IEnumerable<DiscordMember> Role(DiscordRole role)
{
lock (Collection)
return Collection.Where(x => x.HasRole(role.Id));
}
}
}

30
Maki/MessageManager.cs Normal file
View file

@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Linq;
namespace Maki
{
internal sealed class MessageManager : BaseManager<DiscordMessage>
{
internal MessageManager()
{
}
public DiscordMessage Id(ulong id)
{
lock (Collection)
return Collection.Where(x => x.Id == id).FirstOrDefault();
}
public IEnumerable<DiscordMessage> Channel(DiscordChannel channel)
{
lock (Collection)
return Collection.Where(x => x.Channel == channel);
}
public IEnumerable<DiscordMessage> Server(DiscordServer server)
{
lock (Collection)
return Collection.Where(x => x.Server == server);
}
}
}

View file

@ -1,36 +1,10 @@
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; 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: AssemblyTitle("Maki")]
[assembly: AssemblyDescription("Discord Client library")] [assembly: AssemblyDescription("Discord Client library")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Maki")] [assembly: AssemblyProduct("Maki")]
[assembly: AssemblyCopyright("flash.moe 2017")] [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)] [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")] [assembly: Guid("97523aed-b694-42c2-96ac-86a1d65109f7")]
[assembly: AssemblyVersion("1.0.*")]
// 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")]

View file

@ -8,17 +8,17 @@
/// <summary> /// <summary>
/// Base URL of Discord /// Base URL of Discord
/// </summary> /// </summary>
public const string BASE_URL = @"https://discordapp.com"; public const string BASE_URL = "https://discordapp.com";
/// <summary> /// <summary>
/// Path to the REST API /// Path to the REST API
/// </summary> /// </summary>
public static string BASE_PATH => @"/api/v" + Discord.GATEWAY_VERSION; public static string BASE_PATH => "/api/v" + Discord.GATEWAY_VERSION;
/// <summary> /// <summary>
/// Url of Discord's CDN /// Url of Discord's CDN
/// </summary> /// </summary>
public const string CDN_URL = @"https://cdn.discordapp.com"; public const string CDN_URL = "https://cdn.discordapp.com";
#region Channels #region Channels
/// <summary> /// <summary>
@ -349,18 +349,6 @@
public static string OAuth2Application(ulong appId) => $@"/oauth2/applications/{appId}"; public static string OAuth2Application(ulong appId) => $@"/oauth2/applications/{appId}";
#endregion #endregion
#region Auth
/// <summary>
/// Auth Login endpoint
/// </summary>
public static string AuthLogin => @"/auth/login";
/// <summary>
/// Auth MFA TOTP endpoint
/// </summary>
public static string AuthMfaTotp => @"/auth/mfa/totp";
#endregion
#region User #region User
/// <summary> /// <summary>
/// Gets a User endpoint /// Gets a User endpoint

View file

@ -16,7 +16,7 @@ namespace Maki.Rest
private const string JSON_CONTENT_TYPE = @"application/json"; private const string JSON_CONTENT_TYPE = @"application/json";
private const string FORM_CONTENT_TYPE = @"multipart/form-data"; 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 HttpMethod Method;
public readonly string Url; public readonly string Url;
@ -24,16 +24,16 @@ namespace Maki.Rest
public string UserAgent { get; set; } = USER_AGENT; public string UserAgent { get; set; } = USER_AGENT;
public string ContentType { get; set; } = GENERIC_CONTENT_TYPE; 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 // TODO: make this not static
internal static string Authorisation { get; set; } internal static string Authorisation { get; set; }
private readonly Dictionary<string, string> headers = new Dictionary<string, string>(); private readonly Dictionary<string, string> Headers = new Dictionary<string, string>();
private readonly Dictionary<string, string> parameters = new Dictionary<string, string>(); private readonly Dictionary<string, string> Parameters = new Dictionary<string, string>();
private readonly Dictionary<string, byte[]> files = new Dictionary<string, byte[]>(); private readonly Dictionary<string, byte[]> Files = new Dictionary<string, byte[]>();
private readonly Dictionary<string, string> mimeTypes = new Dictionary<string, string>() private readonly Dictionary<string, string> MimeTypes = new Dictionary<string, string>()
{ {
{ "png", "image/png" }, { "png", "image/png" },
{ "jpg", "image/jpeg" }, { "jpg", "image/jpeg" },
@ -41,37 +41,47 @@ namespace Maki.Rest
{ "gif", "image/gif" }, { "gif", "image/gif" },
}; };
private byte[] rawContent = new byte[0]; private byte[] RawRequestBody = new byte[0];
private HttpWebRequest wRequest; private HttpWebRequest HttpWebRequest;
private Stream requestStream; private Stream RequestStream;
private HttpWebResponse wResponse; private HttpWebResponse HttpWebResponse;
private Stream responseStream; private Stream ResponseStream;
private byte[] rawResponse; private byte[] RawResponseValue;
public byte[] RawResponse public byte[] RawResponse
{ {
get get
{ {
if (rawResponse == null) if (RawResponseValue == null)
{ {
rawResponse = new byte[BUFFER_SIZE]; using (MemoryStream ms = new MemoryStream())
responseStream.Read(rawResponse, 0, rawResponse.Length); {
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 public string Response
{ {
get get
{ {
if (string.IsNullOrEmpty(responseString)) if (string.IsNullOrEmpty(ResponseString))
responseString = Encoding.UTF8.GetString(RawResponse).Trim('\0'); ResponseString = Encoding.UTF8.GetString(RawResponse);
return responseString; return ResponseString;
} }
} }
@ -79,7 +89,7 @@ namespace Maki.Rest
JsonConvert.DeserializeObject<T>(Response); JsonConvert.DeserializeObject<T>(Response);
public short Status => public short Status =>
(short)wResponse?.StatusCode; (short)HttpWebResponse?.StatusCode;
static WebRequest() static WebRequest()
{ {
@ -93,7 +103,7 @@ namespace Maki.Rest
} }
public void AddRaw(byte[] bytes) => public void AddRaw(byte[] bytes) =>
rawContent = bytes; RawRequestBody = bytes;
public void AddRaw(string str) => public void AddRaw(string str) =>
AddRaw(Encoding.UTF8.GetBytes(str)); AddRaw(Encoding.UTF8.GetBytes(str));
@ -105,10 +115,10 @@ namespace Maki.Rest
} }
public void AddParam(string name, string contents) => public void AddParam(string name, string contents) =>
parameters.Add(name, contents); Parameters.Add(name, contents);
public void AddFile(string name, byte[] bytes) => public void AddFile(string name, byte[] bytes) =>
files.Add(name, bytes); Files.Add(name, bytes);
public void Perform() public void Perform()
{ {
@ -124,50 +134,50 @@ namespace Maki.Rest
if (Method == HttpMethod.GET if (Method == HttpMethod.GET
|| Method == HttpMethod.DELETE) || Method == HttpMethod.DELETE)
if (parameters.Count > 1) if (Parameters.Count > 1)
{ {
if (!Url.Contains('?')) if (!Url.Contains('?'))
urlBuilder.Append(@"?"); urlBuilder.Append(@"?");
foreach (KeyValuePair<string, string> param in parameters) foreach (KeyValuePair<string, string> param in Parameters)
urlBuilder.Append($@"{param.Key}={param.Value}&"); urlBuilder.Append($@"{param.Key}={param.Value}&");
} }
string url = urlBuilder.ToString().TrimEnd('&'); string url = urlBuilder.ToString().TrimEnd('&');
wRequest = System.Net.WebRequest.Create(url) as HttpWebRequest; HttpWebRequest = System.Net.WebRequest.Create(url) as HttpWebRequest;
wRequest.Method = Method.ToString(); HttpWebRequest.Method = Method.ToString();
wRequest.UserAgent = UserAgent; HttpWebRequest.UserAgent = UserAgent;
wRequest.KeepAlive = true; HttpWebRequest.KeepAlive = true;
//wRequest.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; //wRequest.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
wRequest.ReadWriteTimeout = Timeout.Infinite; HttpWebRequest.ReadWriteTimeout = Timeout.Infinite;
wRequest.Timeout = Timeout.Infinite; HttpWebRequest.Timeout = Timeout.Infinite;
if (!string.IsNullOrEmpty(Authorisation) && url.StartsWith(RestEndpoints.BASE_URL + RestEndpoints.BASE_PATH)) if (!string.IsNullOrEmpty(Authorisation) && url.StartsWith(RestEndpoints.BASE_URL + RestEndpoints.BASE_PATH))
wRequest.Headers[HttpRequestHeader.Authorization] = Authorisation; HttpWebRequest.Headers[HttpRequestHeader.Authorization] = Authorisation;
foreach (KeyValuePair<string, string> header in headers) foreach (KeyValuePair<string, string> header in Headers)
wRequest.Headers[header.Key] = header.Value; HttpWebRequest.Headers[header.Key] = header.Value;
if (Method == HttpMethod.POST if (Method == HttpMethod.POST
|| Method == HttpMethod.PUT || Method == HttpMethod.PUT
|| Method == HttpMethod.PATCH) || Method == HttpMethod.PATCH)
{ {
requestStream = wRequest.GetRequestStream(); RequestStream = HttpWebRequest.GetRequestStream();
if (parameters.Count + files.Count < 1) if (Parameters.Count + Files.Count < 1)
requestStream.Write(rawContent, 0, rawContent.Length); RequestStream.Write(RawRequestBody, 0, RawRequestBody.Length);
else else
{ {
string boundary = $@"-----------------------------{DateTime.Now.Ticks}"; string boundary = $@"-----------------------------{DateTime.Now.Ticks}";
ContentType = $@"{FORM_CONTENT_TYPE}; boundary={boundary}"; ContentType = $@"{FORM_CONTENT_TYPE}; boundary={boundary}";
if (parameters.Count >= 1) if (Parameters.Count >= 1)
{ {
StringBuilder postBodyBuilder = new StringBuilder(); StringBuilder postBodyBuilder = new StringBuilder();
byte[] postBody = new byte[0]; byte[] postBody = new byte[0];
foreach (KeyValuePair<string, string> param in parameters) foreach (KeyValuePair<string, string> param in Parameters)
{ {
postBodyBuilder.AppendLine($@"--{boundary}"); postBodyBuilder.AppendLine($@"--{boundary}");
postBodyBuilder.AppendLine($@"Content-Disposition: form-data; name=""{param.Key}"""); postBodyBuilder.AppendLine($@"Content-Disposition: form-data; name=""{param.Key}""");
@ -176,90 +186,90 @@ namespace Maki.Rest
} }
postBody = Encoding.UTF8.GetBytes(postBodyBuilder.ToString()); 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[] boundaryBytes = Encoding.UTF8.GetBytes($@"--{boundary}");
byte[] newLineBytes = Encoding.UTF8.GetBytes("\r\n"); byte[] newLineBytes = Encoding.UTF8.GetBytes("\r\n");
foreach (KeyValuePair<string, byte[]> file in files) foreach (KeyValuePair<string, byte[]> file in Files)
{ {
string cType = GENERIC_CONTENT_TYPE; string cType = GENERIC_CONTENT_TYPE;
string fileExt = Path.GetExtension(file.Key).ToLower().TrimStart('.'); string fileExt = Path.GetExtension(file.Key).ToLower().TrimStart('.');
if (mimeTypes.ContainsKey(fileExt)) if (MimeTypes.ContainsKey(fileExt))
cType = mimeTypes[fileExt]; cType = MimeTypes[fileExt];
byte[] cDisposBytes = Encoding.UTF8.GetBytes($@"Content-Disposition: form-data; name=""{file.Key}""; filename=""{file.Key}"""); byte[] cDisposBytes = Encoding.UTF8.GetBytes($@"Content-Disposition: form-data; name=""{file.Key}""; filename=""{file.Key}""");
byte[] cTypeBytes = Encoding.UTF8.GetBytes($@"Content-Type: {cType}"); byte[] cTypeBytes = Encoding.UTF8.GetBytes($@"Content-Type: {cType}");
// Boundary + newline // Boundary + newline
requestStream.Write(boundaryBytes, 0, boundaryBytes.Length); RequestStream.Write(boundaryBytes, 0, boundaryBytes.Length);
requestStream.Write(newLineBytes, 0, newLineBytes.Length); RequestStream.Write(newLineBytes, 0, newLineBytes.Length);
// Disposition header + newline // Disposition header + newline
requestStream.Write(cDisposBytes, 0, cDisposBytes.Length); RequestStream.Write(cDisposBytes, 0, cDisposBytes.Length);
requestStream.Write(newLineBytes, 0, newLineBytes.Length); RequestStream.Write(newLineBytes, 0, newLineBytes.Length);
// Type header + newline // Type header + newline
requestStream.Write(cTypeBytes, 0, cTypeBytes.Length); RequestStream.Write(cTypeBytes, 0, cTypeBytes.Length);
requestStream.Write(newLineBytes, 0, newLineBytes.Length); RequestStream.Write(newLineBytes, 0, newLineBytes.Length);
// newline + contents + newline // newline + contents + newline
requestStream.Write(newLineBytes, 0, newLineBytes.Length); RequestStream.Write(newLineBytes, 0, newLineBytes.Length);
requestStream.Write(file.Value, 0, file.Value.Length); RequestStream.Write(file.Value, 0, file.Value.Length);
requestStream.Write(newLineBytes, 0, newLineBytes.Length); RequestStream.Write(newLineBytes, 0, newLineBytes.Length);
} }
} }
byte[] closingBound = Encoding.UTF8.GetBytes($@"--{boundary}--"); 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 try
{ {
wResponse = wRequest.GetResponse() as HttpWebResponse; HttpWebResponse = HttpWebRequest.GetResponse() as HttpWebResponse;
} catch (WebException ex) } catch (WebException ex)
{ {
wResponse = ex.Response as HttpWebResponse; HttpWebResponse = ex.Response as HttpWebResponse;
} }
responseStream = wResponse.GetResponseStream(); ResponseStream = HttpWebResponse.GetResponseStream();
} }
#region IDisposable #region IDisposable
private bool isDisposed = false;
private void Dispose(bool disposing) private bool IsDisposed = false;
{
if (!isDisposed)
{
isDisposed = true;
requestStream?.Dispose();
wRequest?.Abort();
responseStream?.Dispose();
wResponse?.Close();
}
}
~WebRequest()
{
Dispose(false);
}
/// <summary> /// <summary>
/// Disconnects and releases all unmanaged objects /// Disconnects and releases all unmanaged objects
/// </summary> /// </summary>
public void Dispose() private void Dispose(bool disposing)
{ {
Dispose(true); if (IsDisposed)
GC.SuppressFinalize(true); 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 #endregion
} }
} }

30
Maki/RoleManager.cs Normal file
View file

@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Linq;
namespace Maki
{
internal sealed class RoleManager : BaseManager<DiscordRole>
{
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<DiscordRole> Server(DiscordServer server)
{
lock (Collection)
return Collection.Where(x => x.Server == server).OrderByDescending(x => x.Position);
}
}
}

23
Maki/ServerManager.cs Normal file
View file

@ -0,0 +1,23 @@
using System.Linq;
namespace Maki
{
internal sealed class ServerManager : BaseManager<DiscordServer>
{
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;
}
}
}

View file

@ -1,22 +0,0 @@
using Newtonsoft.Json;
namespace Maki.Structures.Auth
{
/// <summary>
/// Multi Factor Auth login request
/// </summary>
internal struct LoginMultiFactorAuth
{
/// <summary>
/// Multi factor auth code
/// </summary>
[JsonProperty("code")]
public string Code;
/// <summary>
/// Multi factor auth ticket
/// </summary>
[JsonProperty("ticket")]
public string Ticket;
}
}

View file

@ -1,22 +0,0 @@
using Newtonsoft.Json;
namespace Maki.Structures.Auth
{
/// <summary>
/// Login request
/// </summary>
internal struct LoginRequest
{
/// <summary>
/// E-mail address
/// </summary>
[JsonProperty("email")]
public string Email;
/// <summary>
/// Password
/// </summary>
[JsonProperty("password")]
public string Password;
}
}

View file

@ -1,40 +0,0 @@
using Newtonsoft.Json;
namespace Maki.Structures.Auth
{
/// <summary>
/// Login response
/// </summary>
internal struct LoginResponse
{
/// <summary>
/// Indicates whether we require multi factor, is usually either null or true
/// </summary>
[JsonProperty("mfa")]
public bool? MFA;
/// <summary>
/// Token
/// </summary>
[JsonProperty("token")]
public string Token;
/// <summary>
/// MFA Ticket, to be bundled with the mfa request
/// </summary>
[JsonProperty("ticket")]
public string Ticket;
/// <summary>
/// Username errors
/// </summary>
[JsonProperty("username")]
public string[] UsernameError;
/// <summary>
/// Password errors
/// </summary>
[JsonProperty("password")]
public string[] PasswordError;
}
}

17
Maki/UserManager.cs Normal file
View file

@ -0,0 +1,17 @@
using System.Linq;
namespace Maki
{
internal sealed class UserManager : BaseManager<DiscordUser>
{
internal UserManager()
{
}
public DiscordUser Id(ulong id)
{
lock (Collection)
return Collection.Where(x => x.Id == id).FirstOrDefault();
}
}
}

View file

@ -4,11 +4,8 @@ namespace Maki
{ {
internal static class Utility 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 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 FromDiscordTimeMilliseconds(long ms) => DiscordEpoch.AddMilliseconds(ms);
public static DateTime FromDiscordSnowflake(ulong snowflake) => FromDiscordTimeMilliseconds((long)snowflake >> 22); public static DateTime FromDiscordSnowflake(ulong snowflake) => FromDiscordTimeMilliseconds((long)snowflake >> 22);