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.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;
/// <summary>
/// Servers we're in
/// </summary>
internal readonly List<DiscordServer> servers = new List<DiscordServer>();
public IEnumerable<DiscordServer> Servers => ServerManager.Items;
internal readonly UserManager UserManager;
/// <summary>
/// Users we are affiliated with
/// </summary>
internal readonly List<DiscordUser> users = new List<DiscordUser>();
public IEnumerable<DiscordUser> Users => UserManager.Items;
internal readonly MemberManager MemberManager;
/// <summary>
/// Members of servers, different from users!
/// </summary>
internal readonly List<DiscordMember> members = new List<DiscordMember>();
public IEnumerable<DiscordMember> Members => MemberManager.Items;
internal readonly RoleManager RoleManager;
/// <summary>
/// Roles in servers
/// </summary>
internal readonly List<DiscordRole> roles = new List<DiscordRole>();
public IEnumerable<DiscordRole> Roles => RoleManager.Items;
internal readonly ChannelManager ChannelManager;
/// <summary>
/// Channels
/// </summary>
internal readonly List<DiscordChannel> channels = new List<DiscordChannel>();
public IEnumerable<DiscordChannel> Channels => ChannelManager.Items;
internal readonly MessageManager MessageManager;
/// <summary>
/// Messages
/// </summary>
internal readonly List<DiscordMessage> messages = new List<DiscordMessage>();
public IEnumerable<DiscordMessage> Messages => MessageManager.Items;
#endregion
#region Events
@ -207,8 +221,7 @@ namespace Maki
public event Action<DiscordUser> 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();
}
/// <summary>
/// Connects to Discord using the token assigned to Token
/// </summary>
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);
}
/// <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>
/// Connects to Discord using a provided token and token type
/// </summary>
@ -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<Message>();
}
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;
/// <summary>
/// Disconnects and releases all unmanaged objects
/// </summary>
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
}
}

View file

@ -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<Message>();
}
@ -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;
}

View file

@ -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}";
}
}

View file

@ -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<DiscordRole> Roles => client.RoleManager.Server(Server).Where(x => HasRole(x.Id));
public DateTime Joined { get; internal set; }
public string Nickname { get; internal set; }

View file

@ -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<DiscordMember> MentionsUsers { get; internal set; }
public IEnumerable<DiscordRole> 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;
}

View file

@ -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<DiscordMember> 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<DiscordChannel> Channels => client.ChannelManager.Server(this);
public IEnumerable<DiscordChannel> TextChannels => Channels.Where(x => x.Type == DiscordChannelType.Text);
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)
{
@ -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;
}
}

View file

@ -18,26 +18,27 @@ namespace Maki.Gateway
/// <summary>
/// Managed (active) shards
/// </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>
/// Parent DiscordClient instance
/// </summary>
private Discord client;
private Discord Client;
/// <summary>
/// Number of active managed shards
/// </summary>
public int ShardCount => shards.Count;
public int ShardCount
=> Shards.Count;
/// <summary>
/// Constructor
/// </summary>
/// <param name="c">Parent DiscordClient instance</param>
public GatewayShardClient(Discord c)
{
client = c;
}
/// <param name="client">Parent DiscordClient instance</param>
public GatewayShardClient(Discord client)
=> Client = client;
/// <summary>
/// Creates a new Gateway Shard
@ -46,25 +47,23 @@ namespace Maki.Gateway
/// <returns>New Gateway Shard</returns>
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;
}
/// <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>
/// Destroys a Gateway Shard
/// </summary>
/// <param name="shard">Shard to destroy</param>
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
/// <summary>
/// Destroys all shards
/// </summary>
public void Disconnect() => shards.Keys.ToList().ForEach(x => Destroy(x));
public void Disconnect()
{
lock (Lock)
Shards.ToList().ForEach(x => Destroy(x));
}
/// <summary>
/// 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);
}
/// <summary>
/// Releases all unmanaged resources used by this object
/// </summary>
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
}
}

View file

@ -39,17 +39,13 @@
<HintPath>..\..\packages\Newtonsoft.Json.10.0.3\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference>
<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">
<HintPath>..\..\packages\WebSocketSharp.1.0.3-rc11\lib\websocket-sharp.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="BaseManager.cs" />
<Compile Include="ChannelManager.cs" />
<Compile Include="DiscordChannel.cs" />
<Compile Include="Discord.cs" />
<Compile Include="DiscordChannelType.cs" />
@ -69,6 +65,8 @@
<Compile Include="DiscordUserStatus.cs" />
<Compile Include="DiscordUser.cs" />
<Compile Include="Gateway\GatewayEvent.cs" />
<Compile Include="MemberManager.cs" />
<Compile Include="MessageManager.cs" />
<Compile Include="Rest\RestErrorCode.cs" />
<Compile Include="Rest\HttpMethod.cs" />
<Compile Include="Rest\RestEndpoints.cs" />
@ -78,9 +76,8 @@
<Compile Include="DiscordTokenType.cs" />
<Compile Include="Gateway\GatewayCloseCode.cs" />
<Compile Include="Rest\WebRequest.cs" />
<Compile Include="Structures\Auth\LoginMultiFactorAuth.cs" />
<Compile Include="Structures\Auth\LoginRequest.cs" />
<Compile Include="Structures\Auth\LoginResponse.cs" />
<Compile Include="RoleManager.cs" />
<Compile Include="ServerManager.cs" />
<Compile Include="Structures\Channels\Channel.cs" />
<Compile Include="Structures\Channels\ChannelType.cs" />
<Compile Include="Structures\Embeds\Embed.cs" />
@ -125,6 +122,7 @@
<Compile Include="Structures\Roles\Role.cs" />
<Compile Include="Structures\Users\PermissionOverwrite.cs" />
<Compile Include="Structures\Users\User.cs" />
<Compile Include="UserManager.cs" />
<Compile Include="Utility.cs" />
</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.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.*")]

View file

@ -8,17 +8,17 @@
/// <summary>
/// Base URL of Discord
/// </summary>
public const string BASE_URL = @"https://discordapp.com";
public const string BASE_URL = "https://discordapp.com";
/// <summary>
/// Path to the REST API
/// </summary>
public static string BASE_PATH => @"/api/v" + Discord.GATEWAY_VERSION;
public static string BASE_PATH => "/api/v" + Discord.GATEWAY_VERSION;
/// <summary>
/// Url of Discord's CDN
/// </summary>
public const string CDN_URL = @"https://cdn.discordapp.com";
public const string CDN_URL = "https://cdn.discordapp.com";
#region Channels
/// <summary>
@ -349,18 +349,6 @@
public static string OAuth2Application(ulong appId) => $@"/oauth2/applications/{appId}";
#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
/// <summary>
/// Gets a User endpoint

View file

@ -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<string, string> headers = 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, string> Headers = 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, string> mimeTypes = new Dictionary<string, string>()
private readonly Dictionary<string, string> MimeTypes = new Dictionary<string, string>()
{
{ "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<T>(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<string, string> param in parameters)
foreach (KeyValuePair<string, string> 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<string, string> header in headers)
wRequest.Headers[header.Key] = header.Value;
foreach (KeyValuePair<string, string> 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<string, string> param in parameters)
foreach (KeyValuePair<string, string> 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<string, byte[]> file in files)
foreach (KeyValuePair<string, byte[]> 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;
/// <summary>
/// Disconnects and releases all unmanaged objects
/// </summary>
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
}
}

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
{
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);