rewrite rest request backend
This commit is contained in:
parent
063a12e804
commit
e1672b7256
136
Maki/Discord.cs
136
Maki/Discord.cs
|
@ -34,21 +34,30 @@ namespace Maki
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DiscordTokenType TokenType = DiscordTokenType.Bot;
|
public DiscordTokenType TokenType = DiscordTokenType.Bot;
|
||||||
|
|
||||||
|
#region Token
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Discord token
|
/// Discord token
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Token = string.Empty;
|
public string Token {
|
||||||
|
get => token;
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
token = value;
|
||||||
|
WebRequest.Authorisation = (IsBot ? "Bot " : string.Empty) + token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string token = string.Empty;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gateway Url
|
/// Gateway Url
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal string Gateway { get; private set; }
|
internal string Gateway { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Rest API request client
|
|
||||||
/// </summary>
|
|
||||||
internal readonly RestClient RestClient;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gateway shard client
|
/// Gateway shard client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -209,7 +218,6 @@ namespace Maki
|
||||||
public Discord(DiscordParams parameters = null)
|
public Discord(DiscordParams parameters = null)
|
||||||
{
|
{
|
||||||
Params = parameters ?? new DiscordParams();
|
Params = parameters ?? new DiscordParams();
|
||||||
RestClient = new RestClient(this);
|
|
||||||
ShardClient = new GatewayShardClient(this);
|
ShardClient = new GatewayShardClient(this);
|
||||||
|
|
||||||
#region Assigning event handlers
|
#region Assigning event handlers
|
||||||
|
@ -268,15 +276,22 @@ namespace Maki
|
||||||
{
|
{
|
||||||
ClearContainers();
|
ClearContainers();
|
||||||
|
|
||||||
RestResponse<GatewayInfo> gateway = RestClient.Request<GatewayInfo>(
|
int shards = 1;
|
||||||
RestRequestMethod.GET,
|
|
||||||
IsBot ? RestEndpoints.GatewayBot : RestEndpoints.Gateway
|
|
||||||
);
|
|
||||||
|
|
||||||
if (gateway.ErrorCode != RestErrorCode.Ok)
|
using (WebRequest wr = new WebRequest(HttpMethod.GET, IsBot ? RestEndpoints.GatewayBot : RestEndpoints.Gateway))
|
||||||
throw new DiscordException($"{gateway.ErrorCode}: {gateway.ErrorMessage}");
|
{
|
||||||
|
wr.Perform();
|
||||||
|
|
||||||
Gateway = gateway.Response.Url;
|
if (wr.Status != 200)
|
||||||
|
throw new DiscordException("Failed to retrieve gateway url, is your token valid?");
|
||||||
|
|
||||||
|
GatewayInfo gi = wr.ResponseJson<GatewayInfo>();
|
||||||
|
|
||||||
|
Gateway = gi.Url;
|
||||||
|
|
||||||
|
if (gi.Shards.HasValue)
|
||||||
|
shards = gi.Shards.Value;
|
||||||
|
}
|
||||||
|
|
||||||
if (Gateway.Contains("?"))
|
if (Gateway.Contains("?"))
|
||||||
Gateway = Gateway.Substring(0, Gateway.IndexOf('?'));
|
Gateway = Gateway.Substring(0, Gateway.IndexOf('?'));
|
||||||
|
@ -284,8 +299,6 @@ namespace Maki
|
||||||
if (!Gateway.EndsWith("/"))
|
if (!Gateway.EndsWith("/"))
|
||||||
Gateway += "/";
|
Gateway += "/";
|
||||||
|
|
||||||
int shards = gateway.Response.Shards ?? 1;
|
|
||||||
|
|
||||||
for (int i = 0; i < shards; i++)
|
for (int i = 0; i < shards; i++)
|
||||||
ShardClient.Create(i);
|
ShardClient.Create(i);
|
||||||
}
|
}
|
||||||
|
@ -298,54 +311,64 @@ namespace Maki
|
||||||
/// <param name="code">Multi factor authentication token</param>
|
/// <param name="code">Multi factor authentication token</param>
|
||||||
public void Connect(string email, string password, string code = null)
|
public void Connect(string email, string password, string code = null)
|
||||||
{
|
{
|
||||||
TokenType = DiscordTokenType.User;
|
// TODO: not tested with new request backend yet
|
||||||
|
|
||||||
RestResponse<LoginResponse> login = RestClient.Request<LoginResponse>(
|
TokenType = DiscordTokenType.User;
|
||||||
RestRequestMethod.POST,
|
LoginResponse login;
|
||||||
RestEndpoints.AuthLogin,
|
|
||||||
new LoginRequest
|
using (WebRequest wr = new WebRequest(HttpMethod.POST, RestEndpoints.AuthLogin))
|
||||||
|
{
|
||||||
|
wr.AddJson(new LoginRequest
|
||||||
{
|
{
|
||||||
Email = email,
|
Email = email,
|
||||||
Password = password,
|
Password = password,
|
||||||
}
|
});
|
||||||
);
|
wr.Perform();
|
||||||
|
|
||||||
if (login.ErrorCode != RestErrorCode.Ok)
|
if (wr.Response.Length < 1)
|
||||||
throw new DiscordException($"{login.ErrorCode}: {login.ErrorMessage}");
|
throw new DiscordException("Login failed");
|
||||||
|
|
||||||
if (login.Response.UsernameError?.Length > 0)
|
login = wr.ResponseJson<LoginResponse>();
|
||||||
throw new DiscordException(login.Response.UsernameError.FirstOrDefault());
|
}
|
||||||
|
|
||||||
if (login.Response.PasswordError?.Length > 0)
|
if (login.UsernameError?.Length > 0)
|
||||||
throw new DiscordException(login.Response.PasswordError.FirstOrDefault());
|
throw new DiscordException(login.UsernameError.FirstOrDefault());
|
||||||
|
|
||||||
Token = login.Response.Token;
|
if (login.PasswordError?.Length > 0)
|
||||||
|
throw new DiscordException(login.PasswordError.FirstOrDefault());
|
||||||
|
|
||||||
|
Token = login.Token;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(Token))
|
if (string.IsNullOrEmpty(Token))
|
||||||
{
|
{
|
||||||
if (login.Response.MFA == true && !string.IsNullOrEmpty(login.Response.Ticket))
|
if (login.MFA == true && !string.IsNullOrEmpty(login.Ticket))
|
||||||
{
|
{
|
||||||
RestResponse<LoginResponse> totp = RestClient.Request<LoginResponse>(
|
using (WebRequest wr = new WebRequest(HttpMethod.POST, RestEndpoints.AuthMfaTotp))
|
||||||
RestRequestMethod.POST,
|
{
|
||||||
RestEndpoints.AuthMfaTotp,
|
wr.AddJson(new LoginMultiFactorAuth
|
||||||
new LoginMultiFactorAuth
|
|
||||||
{
|
{
|
||||||
Code = code,
|
Code = code,
|
||||||
Ticket = login.Response.Ticket,
|
Ticket = login.Ticket,
|
||||||
}
|
});
|
||||||
);
|
wr.Perform();
|
||||||
|
|
||||||
if (totp.ErrorCode != RestErrorCode.Ok)
|
if (wr.Response.Length < 1)
|
||||||
throw new DiscordException($"{totp.ErrorCode}: {totp.ErrorMessage}");
|
throw new DiscordException("Multi factor auth failed");
|
||||||
|
|
||||||
Token = totp.Response.Token;
|
login = wr.ResponseJson<LoginResponse>();
|
||||||
|
}
|
||||||
|
|
||||||
|
//if (totp.ErrorCode != RestErrorCode.Ok)
|
||||||
|
// throw new DiscordException($"{totp.ErrorCode}: {totp.ErrorMessage}");
|
||||||
|
|
||||||
|
Token = login.Token;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
throw new DiscordException("Token was null but MFA is false and/or ticket is empty?");
|
throw new DiscordException("Token was null but MFA is false and/or ticket is empty?");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(Token))
|
if (string.IsNullOrEmpty(Token))
|
||||||
throw new DiscordException("Authentication failed!");
|
throw new DiscordException("Authentication failed");
|
||||||
|
|
||||||
Connect();
|
Connect();
|
||||||
}
|
}
|
||||||
|
@ -468,6 +491,7 @@ namespace Maki
|
||||||
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;
|
||||||
|
server.Created = guild.Created ?? DateTime.MinValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (guild.Channels != null)
|
if (guild.Channels != null)
|
||||||
|
@ -693,20 +717,30 @@ namespace Maki
|
||||||
|
|
||||||
if (msg == null)
|
if (msg == null)
|
||||||
{
|
{
|
||||||
RestResponse<Message> getMsg = RestClient.Request<Message>(RestRequestMethod.GET, RestEndpoints.ChannelMessage(message.ChannelId, message.Id));
|
using (WebRequest wr = new WebRequest(HttpMethod.GET, RestEndpoints.ChannelMessage(message.ChannelId, message.Id)))
|
||||||
DiscordChannel channel = channels.Where(x => x.Id == getMsg.Response.ChannelId).FirstOrDefault();
|
{
|
||||||
DiscordMember member = members.Where(x => x.User.Id == getMsg.Response.User.Id && (channel.Server == null || channel.Server == x.Server)).FirstOrDefault();
|
wr.Perform();
|
||||||
msg = new DiscordMessage(this, getMsg.Response, channel, member);
|
|
||||||
|
if (wr.Status != 200 || wr.Response.Length < 1)
|
||||||
|
throw new DiscordException("Failed to load message from API");
|
||||||
|
|
||||||
|
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();
|
||||||
|
msg = new DiscordMessage(this, message, channel, member);
|
||||||
messages.Add(msg);
|
messages.Add(msg);
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(message.Content))
|
||||||
|
msg.Text = message.Content;
|
||||||
|
|
||||||
|
msg.IsPinned = message.IsPinned == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.Edited = DateTime.Now;
|
msg.Edited = DateTime.Now;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(message.Content))
|
|
||||||
msg.Text = message.Content;
|
|
||||||
|
|
||||||
msg.IsPinned = message.IsPinned == true;
|
|
||||||
|
|
||||||
OnMessageUpdate?.Invoke(msg);
|
OnMessageUpdate?.Invoke(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
using Maki.Structures.Channels;
|
using Maki.Structures.Channels;
|
||||||
using Maki.Structures.Messages;
|
using Maki.Structures.Messages;
|
||||||
using Maki.Structures.Rest;
|
using Maki.Structures.Rest;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace Maki
|
namespace Maki
|
||||||
{
|
{
|
||||||
|
@ -38,19 +40,41 @@ namespace Maki
|
||||||
Server = server;
|
Server = server;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DiscordMessage Send(string text = "", DiscordEmbed embed = null)
|
public DiscordMessage Send(Stream stream, string filename, string text = "", DiscordEmbed embed = null)
|
||||||
{
|
{
|
||||||
RestResponse<Message> msg = client.RestClient.Request<Message>(
|
byte[] bytes = new byte[stream.Length];
|
||||||
RestRequestMethod.POST,
|
stream.Read(bytes, 0, bytes.Length);
|
||||||
RestEndpoints.ChannelMessages(Id),
|
return Send(text, embed, filename, bytes);
|
||||||
new MessageCreate
|
}
|
||||||
|
|
||||||
|
public DiscordMessage Send(string text = "", DiscordEmbed embed = null, string filename = null, byte[] file = null)
|
||||||
|
{
|
||||||
|
Message? msg = null;
|
||||||
|
|
||||||
|
using (WebRequest wr = new WebRequest(HttpMethod.POST, RestEndpoints.ChannelMessages(Id)))
|
||||||
|
{
|
||||||
|
wr.AddParam("payload_json", JsonConvert.SerializeObject(new MessageCreate
|
||||||
{
|
{
|
||||||
Text = text,
|
Text = text,
|
||||||
Embed = embed?.ToStruct(),
|
Embed = embed?.ToStruct(),
|
||||||
}
|
}));
|
||||||
);
|
|
||||||
|
if (file != null && !string.IsNullOrEmpty(filename))
|
||||||
|
wr.AddFile(filename, file);
|
||||||
|
|
||||||
DiscordMessage message = new DiscordMessage(client, msg.Response, this, client.members.Find(x => x.User.Id == msg.Response.User.Id));
|
wr.Perform();
|
||||||
|
|
||||||
|
if (wr.Status != 200 || wr.Response.Length < 1)
|
||||||
|
// TODO: elaborate
|
||||||
|
throw new DiscordException("Failed to send message");
|
||||||
|
|
||||||
|
msg = wr.ResponseJson<Message>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
client.messages.Add(message);
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,9 @@ namespace Maki
|
||||||
{
|
{
|
||||||
foreach (DiscordRole role in roles)
|
foreach (DiscordRole role in roles)
|
||||||
{
|
{
|
||||||
client.RestClient.Request<object>(RestRequestMethod.PUT, RestEndpoints.GuildMemberRole(Server.Id, User.Id, role.Id));
|
using (WebRequest wr = new WebRequest(HttpMethod.PUT, RestEndpoints.GuildMemberRole(Server.Id, User.Id, role.Id)))
|
||||||
|
wr.Perform();
|
||||||
|
|
||||||
this.roles.Add(role.Id);
|
this.roles.Add(role.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,20 +55,31 @@ namespace Maki
|
||||||
|
|
||||||
public DiscordMessage Edit(string text = null, DiscordEmbed embed = null)
|
public DiscordMessage Edit(string text = null, DiscordEmbed embed = null)
|
||||||
{
|
{
|
||||||
RestResponse<Message> msg = client.RestClient.Request<Message>(RestRequestMethod.PATCH, RestEndpoints.ChannelMessage(Channel.Id, Id), new MessageEdit {
|
using (WebRequest wr = new WebRequest(HttpMethod.PATCH, RestEndpoints.ChannelMessage(Channel.Id, Id)))
|
||||||
Text = text,
|
{
|
||||||
Embed = embed?.ToStruct(),
|
wr.AddJson(new MessageEdit
|
||||||
});
|
{
|
||||||
|
Text = text,
|
||||||
|
Embed = embed?.ToStruct(),
|
||||||
|
});
|
||||||
|
wr.Perform();
|
||||||
|
|
||||||
Text = msg.Response.Content;
|
if (wr.Status != 200)
|
||||||
Edited = msg.Response.Edited ?? DateTime.UtcNow;
|
return this;
|
||||||
|
|
||||||
|
Message msg = wr.ResponseJson<Message>();
|
||||||
|
|
||||||
|
Text = msg.Content;
|
||||||
|
Edited = msg.Edited ?? DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Delete()
|
public void Delete()
|
||||||
{
|
{
|
||||||
client.RestClient.Request<object>(RestRequestMethod.DELETE, RestEndpoints.ChannelMessage(Channel.Id, Id));
|
using (WebRequest wr = new WebRequest(HttpMethod.DELETE, RestEndpoints.ChannelMessage(Channel.Id, Id)))
|
||||||
|
wr.Perform();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,31 +35,46 @@ namespace Maki
|
||||||
|
|
||||||
public void Delete()
|
public void Delete()
|
||||||
{
|
{
|
||||||
RestResponse<object> resp = client.RestClient.Request<object>(RestRequestMethod.DELETE, RestEndpoints.GuildRole(Server.Id, Id));
|
using (WebRequest wr = new WebRequest(HttpMethod.DELETE, RestEndpoints.GuildRole(Server.Id, Id)))
|
||||||
|
{
|
||||||
|
wr.Perform();
|
||||||
|
|
||||||
if (resp.Status != 204)
|
if (wr.Status != 204)
|
||||||
throw new DiscordException("Failed to delete role!");
|
throw new DiscordException($"Failed to delete role {Id} in guild {Server.Id}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Edit(string name = null, DiscordPermission? perms = null, DiscordColour colour = null, bool? hoist = null, bool? mentionable = null)
|
public void Edit(string name = null, DiscordPermission? perms = null, DiscordColour colour = null, bool? hoist = null, bool? mentionable = null)
|
||||||
{
|
{
|
||||||
RestResponse<Role> role = client.RestClient.Request<Role>(RestRequestMethod.PATCH, RestEndpoints.GuildRole(Server.Id, Id), new RoleCreate
|
Role? role;
|
||||||
|
|
||||||
|
using (WebRequest wr = new WebRequest(HttpMethod.PATCH, RestEndpoints.GuildRole(Server.Id, Id)))
|
||||||
{
|
{
|
||||||
Name = name ?? Name,
|
wr.AddJson(new RoleCreate
|
||||||
Perms = perms ?? Perms,
|
{
|
||||||
Colour = colour == null ? 0 : colour.Raw,
|
Name = name ?? Name,
|
||||||
Hoist = hoist ?? IsHoisted,
|
Perms = perms ?? Perms,
|
||||||
Mentionable = mentionable ?? IsMentionable
|
Colour = colour == null ? 0 : colour.Raw,
|
||||||
});
|
Hoist = hoist ?? IsHoisted,
|
||||||
|
Mentionable = mentionable ?? IsMentionable
|
||||||
|
});
|
||||||
|
wr.Perform();
|
||||||
|
|
||||||
if (role.ErrorCode != RestErrorCode.Ok)
|
if (wr.Status != 200 || wr.Response.Length < 1)
|
||||||
throw new DiscordException($"{role.ErrorCode}: {role.ErrorMessage}");
|
// TODO: elaborate
|
||||||
|
throw new DiscordException("Failed to edit role");
|
||||||
|
|
||||||
Name = role.Response.Name;
|
role = wr.ResponseJson<Role>();
|
||||||
Perms = (DiscordPermission)role.Response.Permissions;
|
}
|
||||||
Colour.Raw = role.Response.Colour ?? 0;
|
|
||||||
IsHoisted = role.Response.IsHoisted == true;
|
if (!role.HasValue)
|
||||||
IsMentionable = role.Response.IsMentionable == true;
|
throw new DiscordException("Empty response?");
|
||||||
|
|
||||||
|
Name = role.Value.Name;
|
||||||
|
Perms = (DiscordPermission)role.Value.Permissions;
|
||||||
|
Colour.Raw = role.Value.Colour ?? 0;
|
||||||
|
IsHoisted = role.Value.IsHoisted == true;
|
||||||
|
IsMentionable = role.Value.IsMentionable == true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,13 +14,14 @@ namespace Maki
|
||||||
|
|
||||||
public string Name { get; internal set; }
|
public string Name { get; internal set; }
|
||||||
public ulong OwnerId { get; internal set; }
|
public ulong OwnerId { 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 DiscordMember[] Members => client.members.Where(x => x.Server == this).ToArray();
|
||||||
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).OrderByDescending(x => x.Position).ToArray();
|
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).OrderByDescending(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 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 string Icon(string ext = @"png", int size = 128) => RestEndpoints.CDN_URL + $@"/icons/{Id}/{IconHash}.{ext}?size={size}";
|
||||||
|
|
||||||
|
@ -29,29 +30,41 @@ namespace Maki
|
||||||
client = discord;
|
client = discord;
|
||||||
Id = guild.Id;
|
Id = guild.Id;
|
||||||
Name = guild.Name;
|
Name = guild.Name;
|
||||||
|
Created = guild.Created ?? DateTime.MinValue;
|
||||||
OwnerId = guild.OwnerId ?? ulong.MinValue;
|
OwnerId = guild.OwnerId ?? ulong.MinValue;
|
||||||
IconHash = guild.IconHash;
|
IconHash = guild.IconHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DiscordRole CreateRole(string name = null, DiscordPermission perms = DiscordPermission.None, DiscordColour colour = null, bool hoist = false, bool mentionable = false)
|
public DiscordRole CreateRole(string name = null, DiscordPermission perms = DiscordPermission.None, DiscordColour colour = null, bool hoist = false, bool mentionable = false)
|
||||||
{
|
{
|
||||||
RestResponse<Role> roleResp = client.RestClient.Request<Role>(RestRequestMethod.POST, RestEndpoints.GuildRoles(Id), new RoleCreate
|
Role? roleStruct;
|
||||||
|
|
||||||
|
using (WebRequest wr = new WebRequest(HttpMethod.POST, RestEndpoints.GuildRoles(Id)))
|
||||||
{
|
{
|
||||||
Name = name,
|
wr.AddJson(new RoleCreate
|
||||||
Perms = perms,
|
{
|
||||||
Colour = colour == null ? 0 : colour.Raw,
|
Name = name,
|
||||||
Hoist = hoist,
|
Perms = perms,
|
||||||
Mentionable = mentionable
|
Colour = colour == null ? 0 : colour.Raw,
|
||||||
});
|
Hoist = hoist,
|
||||||
|
Mentionable = mentionable
|
||||||
|
});
|
||||||
|
wr.Perform();
|
||||||
|
|
||||||
if (roleResp.ErrorCode != RestErrorCode.Ok)
|
if (wr.Status != 200 || wr.Response.Length < 1)
|
||||||
throw new DiscordException($"{roleResp.ErrorCode}: {roleResp.ErrorMessage}");
|
throw new DiscordException("Failed to create role");
|
||||||
|
|
||||||
DiscordRole role = client.roles.Where(x => x.Id == roleResp.Response.Id).FirstOrDefault();
|
roleStruct = wr.ResponseJson<Role>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!roleStruct.HasValue)
|
||||||
|
throw new DiscordException("Empty response?");
|
||||||
|
|
||||||
|
DiscordRole role = client.roles.Where(x => x.Id == roleStruct.Value.Id).FirstOrDefault();
|
||||||
|
|
||||||
if (role == default(DiscordRole))
|
if (role == default(DiscordRole))
|
||||||
{
|
{
|
||||||
role = new DiscordRole(client, roleResp.Response, this);
|
role = new DiscordRole(client, roleStruct.Value, this);
|
||||||
client.roles.Add(role);
|
client.roles.Add(role);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ namespace Maki
|
||||||
{
|
{
|
||||||
client = discord;
|
client = discord;
|
||||||
Id = user.Id;
|
Id = user.Id;
|
||||||
Created = Utility.FromDiscordTimeMilliseconds((long) Id >> 22);
|
Created = Utility.FromDiscordSnowflake(Id);
|
||||||
Username = user.Username;
|
Username = user.Username;
|
||||||
Tag = user.Tag.Value;
|
Tag = user.Tag.Value;
|
||||||
IsBot = user.IsBot;
|
IsBot = user.IsBot;
|
||||||
|
|
|
@ -70,15 +70,14 @@
|
||||||
<Compile Include="DiscordUser.cs" />
|
<Compile Include="DiscordUser.cs" />
|
||||||
<Compile Include="Gateway\GatewayEvent.cs" />
|
<Compile Include="Gateway\GatewayEvent.cs" />
|
||||||
<Compile Include="Rest\RestErrorCode.cs" />
|
<Compile Include="Rest\RestErrorCode.cs" />
|
||||||
<Compile Include="Rest\RestRequestMethod.cs" />
|
<Compile Include="Rest\HttpMethod.cs" />
|
||||||
<Compile Include="Rest\RestClient.cs" />
|
|
||||||
<Compile Include="Rest\RestEndpoints.cs" />
|
<Compile Include="Rest\RestEndpoints.cs" />
|
||||||
<Compile Include="Gateway\GatewayHeartbeatManager.cs" />
|
<Compile Include="Gateway\GatewayHeartbeatManager.cs" />
|
||||||
<Compile Include="Gateway\GatewayOPCode.cs" />
|
<Compile Include="Gateway\GatewayOPCode.cs" />
|
||||||
<Compile Include="DiscordPermission.cs" />
|
<Compile Include="DiscordPermission.cs" />
|
||||||
<Compile Include="DiscordTokenType.cs" />
|
<Compile Include="DiscordTokenType.cs" />
|
||||||
<Compile Include="Gateway\GatewayCloseCode.cs" />
|
<Compile Include="Gateway\GatewayCloseCode.cs" />
|
||||||
<Compile Include="Rest\RestResponse.cs" />
|
<Compile Include="Rest\WebRequest.cs" />
|
||||||
<Compile Include="Structures\Auth\LoginMultiFactorAuth.cs" />
|
<Compile Include="Structures\Auth\LoginMultiFactorAuth.cs" />
|
||||||
<Compile Include="Structures\Auth\LoginRequest.cs" />
|
<Compile Include="Structures\Auth\LoginRequest.cs" />
|
||||||
<Compile Include="Structures\Auth\LoginResponse.cs" />
|
<Compile Include="Structures\Auth\LoginResponse.cs" />
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Discord Rest request methods
|
/// Discord Rest request methods
|
||||||
/// </summary>
|
/// </summary>
|
||||||
enum RestRequestMethod
|
enum HttpMethod
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// GET, does not send additional data
|
/// GET, does not send additional data
|
|
@ -1,147 +0,0 @@
|
||||||
using Maki.Structures.Rest;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Maki.Rest
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Handles requests to Discord's REST API
|
|
||||||
/// </summary>
|
|
||||||
internal class RestClient
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// User agent that is send alongside requests
|
|
||||||
/// </summary>
|
|
||||||
private const string USER_AGENT = @"DiscordBot (https://github.com/flashwave/maki, 1.0.0.0)";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Container for parent DiscordClient instance
|
|
||||||
/// </summary>
|
|
||||||
private readonly Discord client;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Request types that should be handled as data requests
|
|
||||||
/// </summary>
|
|
||||||
private readonly RestRequestMethod[] dataRequestTypes = new RestRequestMethod[] {
|
|
||||||
RestRequestMethod.POST,
|
|
||||||
RestRequestMethod.PUT,
|
|
||||||
RestRequestMethod.PATCH,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="c">Parent DiscordClient instance</param>
|
|
||||||
public RestClient(Discord c)
|
|
||||||
{
|
|
||||||
client = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a base HttpWebRequest
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="method">Request method</param>
|
|
||||||
/// <param name="url">Endpoint url</param>
|
|
||||||
/// <returns>Prepared HttpWebRequest</returns>
|
|
||||||
private HttpWebRequest BaseRequest(RestRequestMethod method, string url)
|
|
||||||
{
|
|
||||||
Uri uri = new Uri(RestEndpoints.BASE_URL + RestEndpoints.BASE_PATH + url);
|
|
||||||
HttpWebRequest request = WebRequest.Create(uri) as HttpWebRequest;
|
|
||||||
|
|
||||||
request.Method = method.ToString();
|
|
||||||
request.UserAgent = USER_AGENT;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(client.Token))
|
|
||||||
request.Headers[HttpRequestHeader.Authorization] = (client.IsBot ? "Bot " : string.Empty) + client.Token;
|
|
||||||
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a response string from HttpWebRequest
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="request">HttpWebRequest instance</param>
|
|
||||||
/// <returns>string output</returns>
|
|
||||||
private RestResponse<T> HandleResponse<T>(HttpWebRequest request)
|
|
||||||
{
|
|
||||||
HttpWebResponse webResponse;
|
|
||||||
RestResponse<T> response = new RestResponse<T>();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
webResponse = request.GetResponse() as HttpWebResponse;
|
|
||||||
}
|
|
||||||
catch (WebException ex)
|
|
||||||
{
|
|
||||||
webResponse = ex.Response as HttpWebResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
Enum.TryParse(webResponse.Method, out response.Method);
|
|
||||||
response.Uri = webResponse.ResponseUri;
|
|
||||||
response.Status = (ushort)webResponse.StatusCode;
|
|
||||||
|
|
||||||
using (Stream httpResponseStream = webResponse.GetResponseStream())
|
|
||||||
using (StreamReader streamReader = new StreamReader(httpResponseStream))
|
|
||||||
response.RawResponse = streamReader.ReadToEnd();
|
|
||||||
|
|
||||||
webResponse.Close();
|
|
||||||
|
|
||||||
Error error = default(Error);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
error = JsonConvert.DeserializeObject<Error>(response.RawResponse);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error.Equals(default(Error)))
|
|
||||||
{
|
|
||||||
response.ErrorCode = RestErrorCode.Ok;
|
|
||||||
response.ErrorMessage = string.Empty;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
response.ErrorCode = (RestErrorCode)error.Code;
|
|
||||||
response.ErrorMessage = error.Message;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.Status == 200)
|
|
||||||
response.Response = JsonConvert.DeserializeObject<T>(response.RawResponse);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Make a json request
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">Type to use when deserialising the JSON object</typeparam>
|
|
||||||
/// <param name="method">Request method</param>
|
|
||||||
/// <param name="url">Endpoint url</param>
|
|
||||||
/// <param name="data">Request data</param>
|
|
||||||
/// <returns>Deserialised JSON object</returns>
|
|
||||||
public RestResponse<T> Request<T>(RestRequestMethod method, string url, object data = null)
|
|
||||||
{
|
|
||||||
HttpWebRequest request = BaseRequest(method, url);
|
|
||||||
|
|
||||||
request.ContentType = "application/json;charset=utf-8";
|
|
||||||
request.ContentLength = 0;
|
|
||||||
|
|
||||||
if (dataRequestTypes.Contains(method) && data != null)
|
|
||||||
{
|
|
||||||
byte[] bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data));
|
|
||||||
request.ContentLength = bytes.LongLength;
|
|
||||||
|
|
||||||
using (Stream requestStream = request.GetRequestStream())
|
|
||||||
requestStream.Write(bytes, 0, bytes.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
return HandleResponse<T>(request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Maki.Rest
|
|
||||||
{
|
|
||||||
internal class RestResponse<T>
|
|
||||||
{
|
|
||||||
public RestRequestMethod Method;
|
|
||||||
public Uri Uri;
|
|
||||||
public ushort Status;
|
|
||||||
public RestErrorCode ErrorCode;
|
|
||||||
public string ErrorMessage;
|
|
||||||
public string RawResponse;
|
|
||||||
public T Response;
|
|
||||||
}
|
|
||||||
}
|
|
260
Maki/Rest/WebRequest.cs
Normal file
260
Maki/Rest/WebRequest.cs
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Maki.Rest
|
||||||
|
{
|
||||||
|
internal class WebRequest : IDisposable
|
||||||
|
{
|
||||||
|
private const string USER_AGENT = @"DiscordBot (https://github.com/flashwave/maki, 1.0.0.0)";
|
||||||
|
private const string GENERIC_CONTENT_TYPE = @"application/octet-stream";
|
||||||
|
private const string JSON_CONTENT_TYPE = @"application/json";
|
||||||
|
private const string FORM_CONTENT_TYPE = @"multipart/form-data";
|
||||||
|
|
||||||
|
internal readonly HttpMethod Method;
|
||||||
|
internal readonly string Url;
|
||||||
|
|
||||||
|
internal string ContentType { get; set; } = GENERIC_CONTENT_TYPE;
|
||||||
|
|
||||||
|
// 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> mimeTypes = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ "png", "image/png" },
|
||||||
|
{ "jpg", "image/jpeg" },
|
||||||
|
{ "jpeg", "image/jpeg" },
|
||||||
|
{ "gif", "image/gif" },
|
||||||
|
};
|
||||||
|
|
||||||
|
private byte[] rawContent = new byte[0];
|
||||||
|
private HttpWebRequest wRequest;
|
||||||
|
private Stream requestStream;
|
||||||
|
private HttpWebResponse wResponse;
|
||||||
|
private Stream responseStream;
|
||||||
|
|
||||||
|
private byte[] rawResponse;
|
||||||
|
internal byte[] RawResponse
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (rawResponse == null)
|
||||||
|
{
|
||||||
|
rawResponse = new byte[4096];
|
||||||
|
responseStream.Read(rawResponse, 0, rawResponse.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string responseString = string.Empty;
|
||||||
|
|
||||||
|
internal string Response
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(responseString))
|
||||||
|
responseString = Encoding.UTF8.GetString(RawResponse).Trim('\0');
|
||||||
|
|
||||||
|
return responseString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal T ResponseJson<T>() =>
|
||||||
|
JsonConvert.DeserializeObject<T>(Response);
|
||||||
|
|
||||||
|
internal short Status =>
|
||||||
|
(short)wResponse?.StatusCode;
|
||||||
|
|
||||||
|
static WebRequest()
|
||||||
|
{
|
||||||
|
ServicePointManager.Expect100Continue = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal WebRequest(HttpMethod method, string url)
|
||||||
|
{
|
||||||
|
Method = method;
|
||||||
|
Url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void AddRaw(byte[] bytes) =>
|
||||||
|
rawContent = bytes;
|
||||||
|
|
||||||
|
internal void AddRaw(string str) =>
|
||||||
|
AddRaw(Encoding.UTF8.GetBytes(str));
|
||||||
|
|
||||||
|
internal void AddJson(object obj)
|
||||||
|
{
|
||||||
|
ContentType = JSON_CONTENT_TYPE;
|
||||||
|
AddRaw(JsonConvert.SerializeObject(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void AddParam(string name, string contents) =>
|
||||||
|
parameters.Add(name, contents);
|
||||||
|
|
||||||
|
internal void AddFile(string name, byte[] bytes) =>
|
||||||
|
files.Add(name, bytes);
|
||||||
|
|
||||||
|
internal void Perform()
|
||||||
|
{
|
||||||
|
StringBuilder urlBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
if (!Url.StartsWith("http://") || !Url.StartsWith("https://"))
|
||||||
|
{
|
||||||
|
urlBuilder.Append(RestEndpoints.BASE_URL);
|
||||||
|
urlBuilder.Append(RestEndpoints.BASE_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
urlBuilder.Append(Url);
|
||||||
|
|
||||||
|
if (Method == HttpMethod.GET
|
||||||
|
|| Method == HttpMethod.DELETE)
|
||||||
|
if (parameters.Count > 1)
|
||||||
|
{
|
||||||
|
if (!Url.Contains('?'))
|
||||||
|
urlBuilder.Append(@"?");
|
||||||
|
|
||||||
|
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 = USER_AGENT;
|
||||||
|
wRequest.KeepAlive = true;
|
||||||
|
//wRequest.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
|
||||||
|
wRequest.ReadWriteTimeout = Timeout.Infinite;
|
||||||
|
wRequest.Timeout = Timeout.Infinite;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(Authorisation))
|
||||||
|
wRequest.Headers[HttpRequestHeader.Authorization] = Authorisation;
|
||||||
|
|
||||||
|
foreach (KeyValuePair<string, string> header in headers)
|
||||||
|
wRequest.Headers[header.Key] = header.Value;
|
||||||
|
|
||||||
|
if (Method == HttpMethod.POST
|
||||||
|
|| Method == HttpMethod.PUT
|
||||||
|
|| Method == HttpMethod.PATCH)
|
||||||
|
{
|
||||||
|
requestStream = wRequest.GetRequestStream();
|
||||||
|
|
||||||
|
if (parameters.Count + files.Count < 1)
|
||||||
|
requestStream.Write(rawContent, 0, rawContent.Length);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string boundary = $@"-----------------------------{DateTime.Now.Ticks}";
|
||||||
|
ContentType = $@"{FORM_CONTENT_TYPE}; boundary={boundary}";
|
||||||
|
|
||||||
|
if (parameters.Count >= 1)
|
||||||
|
{
|
||||||
|
StringBuilder postBodyBuilder = new StringBuilder();
|
||||||
|
byte[] postBody = new byte[0];
|
||||||
|
|
||||||
|
foreach (KeyValuePair<string, string> param in parameters)
|
||||||
|
{
|
||||||
|
postBodyBuilder.AppendLine($@"--{boundary}");
|
||||||
|
postBodyBuilder.AppendLine($@"Content-Disposition: form-data; name=""{param.Key}""");
|
||||||
|
postBodyBuilder.AppendLine();
|
||||||
|
postBodyBuilder.AppendLine(param.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
postBody = Encoding.UTF8.GetBytes(postBodyBuilder.ToString());
|
||||||
|
requestStream.Write(postBody, 0, postBody.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (files.Count >= 1)
|
||||||
|
{
|
||||||
|
byte[] boundaryBytes = Encoding.UTF8.GetBytes($@"--{boundary}");
|
||||||
|
byte[] newLineBytes = Encoding.UTF8.GetBytes("\r\n");
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Disposition header + newline
|
||||||
|
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);
|
||||||
|
|
||||||
|
// newline + contents + newline
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wRequest.ContentType = ContentType;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
wResponse = wRequest.GetResponse() as HttpWebResponse;
|
||||||
|
} catch (WebException ex)
|
||||||
|
{
|
||||||
|
wResponse = ex.Response as HttpWebResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
responseStream = wResponse.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disconnects and releases all unmanaged objects
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(true);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,5 +10,7 @@ namespace Maki
|
||||||
public static DateTime FromUnixTimeMilliseconds(long ms) => UnixEpoch.AddMilliseconds(ms);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue