rewrite rest request backend
This commit is contained in:
parent
063a12e804
commit
e1672b7256
13 changed files with 462 additions and 264 deletions
136
Maki/Discord.cs
136
Maki/Discord.cs
|
@ -34,21 +34,30 @@ namespace Maki
|
|||
/// </summary>
|
||||
public DiscordTokenType TokenType = DiscordTokenType.Bot;
|
||||
|
||||
#region Token
|
||||
|
||||
/// <summary>
|
||||
/// Discord token
|
||||
/// </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>
|
||||
/// Gateway Url
|
||||
/// </summary>
|
||||
internal string Gateway { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Rest API request client
|
||||
/// </summary>
|
||||
internal readonly RestClient RestClient;
|
||||
|
||||
/// <summary>
|
||||
/// Gateway shard client
|
||||
/// </summary>
|
||||
|
@ -209,7 +218,6 @@ namespace Maki
|
|||
public Discord(DiscordParams parameters = null)
|
||||
{
|
||||
Params = parameters ?? new DiscordParams();
|
||||
RestClient = new RestClient(this);
|
||||
ShardClient = new GatewayShardClient(this);
|
||||
|
||||
#region Assigning event handlers
|
||||
|
@ -268,15 +276,22 @@ namespace Maki
|
|||
{
|
||||
ClearContainers();
|
||||
|
||||
RestResponse<GatewayInfo> gateway = RestClient.Request<GatewayInfo>(
|
||||
RestRequestMethod.GET,
|
||||
IsBot ? RestEndpoints.GatewayBot : RestEndpoints.Gateway
|
||||
);
|
||||
int shards = 1;
|
||||
|
||||
if (gateway.ErrorCode != RestErrorCode.Ok)
|
||||
throw new DiscordException($"{gateway.ErrorCode}: {gateway.ErrorMessage}");
|
||||
using (WebRequest wr = new WebRequest(HttpMethod.GET, IsBot ? RestEndpoints.GatewayBot : RestEndpoints.Gateway))
|
||||
{
|
||||
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("?"))
|
||||
Gateway = Gateway.Substring(0, Gateway.IndexOf('?'));
|
||||
|
@ -284,8 +299,6 @@ namespace Maki
|
|||
if (!Gateway.EndsWith("/"))
|
||||
Gateway += "/";
|
||||
|
||||
int shards = gateway.Response.Shards ?? 1;
|
||||
|
||||
for (int i = 0; i < shards; i++)
|
||||
ShardClient.Create(i);
|
||||
}
|
||||
|
@ -298,54 +311,64 @@ namespace Maki
|
|||
/// <param name="code">Multi factor authentication token</param>
|
||||
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>(
|
||||
RestRequestMethod.POST,
|
||||
RestEndpoints.AuthLogin,
|
||||
new LoginRequest
|
||||
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 (login.ErrorCode != RestErrorCode.Ok)
|
||||
throw new DiscordException($"{login.ErrorCode}: {login.ErrorMessage}");
|
||||
if (wr.Response.Length < 1)
|
||||
throw new DiscordException("Login failed");
|
||||
|
||||
if (login.Response.UsernameError?.Length > 0)
|
||||
throw new DiscordException(login.Response.UsernameError.FirstOrDefault());
|
||||
login = wr.ResponseJson<LoginResponse>();
|
||||
}
|
||||
|
||||
if (login.Response.PasswordError?.Length > 0)
|
||||
throw new DiscordException(login.Response.PasswordError.FirstOrDefault());
|
||||
if (login.UsernameError?.Length > 0)
|
||||
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 (login.Response.MFA == true && !string.IsNullOrEmpty(login.Response.Ticket))
|
||||
if (login.MFA == true && !string.IsNullOrEmpty(login.Ticket))
|
||||
{
|
||||
RestResponse<LoginResponse> totp = RestClient.Request<LoginResponse>(
|
||||
RestRequestMethod.POST,
|
||||
RestEndpoints.AuthMfaTotp,
|
||||
new LoginMultiFactorAuth
|
||||
using (WebRequest wr = new WebRequest(HttpMethod.POST, RestEndpoints.AuthMfaTotp))
|
||||
{
|
||||
wr.AddJson(new LoginMultiFactorAuth
|
||||
{
|
||||
Code = code,
|
||||
Ticket = login.Response.Ticket,
|
||||
}
|
||||
);
|
||||
Ticket = login.Ticket,
|
||||
});
|
||||
wr.Perform();
|
||||
|
||||
if (totp.ErrorCode != RestErrorCode.Ok)
|
||||
throw new DiscordException($"{totp.ErrorCode}: {totp.ErrorMessage}");
|
||||
if (wr.Response.Length < 1)
|
||||
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
|
||||
throw new DiscordException("Token was null but MFA is false and/or ticket is empty?");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(Token))
|
||||
throw new DiscordException("Authentication failed!");
|
||||
throw new DiscordException("Authentication failed");
|
||||
|
||||
Connect();
|
||||
}
|
||||
|
@ -468,6 +491,7 @@ namespace Maki
|
|||
server.Name = guild.Name;
|
||||
server.OwnerId = guild.OwnerId ?? 0;
|
||||
server.IconHash = guild.IconHash;
|
||||
server.Created = guild.Created ?? DateTime.MinValue;
|
||||
}
|
||||
|
||||
if (guild.Channels != null)
|
||||
|
@ -693,20 +717,30 @@ namespace Maki
|
|||
|
||||
if (msg == null)
|
||||
{
|
||||
RestResponse<Message> getMsg = RestClient.Request<Message>(RestRequestMethod.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();
|
||||
msg = new DiscordMessage(this, getMsg.Response, channel, member);
|
||||
using (WebRequest wr = new WebRequest(HttpMethod.GET, RestEndpoints.ChannelMessage(message.ChannelId, message.Id)))
|
||||
{
|
||||
wr.Perform();
|
||||
|
||||
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);
|
||||
} else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(message.Content))
|
||||
msg.Text = message.Content;
|
||||
|
||||
msg.IsPinned = message.IsPinned == true;
|
||||
}
|
||||
|
||||
msg.Edited = DateTime.Now;
|
||||
|
||||
if (!string.IsNullOrEmpty(message.Content))
|
||||
msg.Text = message.Content;
|
||||
|
||||
msg.IsPinned = message.IsPinned == true;
|
||||
|
||||
OnMessageUpdate?.Invoke(msg);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
using Maki.Structures.Channels;
|
||||
using Maki.Structures.Messages;
|
||||
using Maki.Structures.Rest;
|
||||
using Newtonsoft.Json;
|
||||
using System.IO;
|
||||
|
||||
namespace Maki
|
||||
{
|
||||
|
@ -38,19 +40,41 @@ namespace Maki
|
|||
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>(
|
||||
RestRequestMethod.POST,
|
||||
RestEndpoints.ChannelMessages(Id),
|
||||
new MessageCreate
|
||||
byte[] bytes = new byte[stream.Length];
|
||||
stream.Read(bytes, 0, bytes.Length);
|
||||
return Send(text, embed, filename, bytes);
|
||||
}
|
||||
|
||||
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,
|
||||
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);
|
||||
return message;
|
||||
}
|
||||
|
|
|
@ -44,7 +44,9 @@ namespace Maki
|
|||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,20 +55,31 @@ namespace Maki
|
|||
|
||||
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 {
|
||||
Text = text,
|
||||
Embed = embed?.ToStruct(),
|
||||
});
|
||||
using (WebRequest wr = new WebRequest(HttpMethod.PATCH, RestEndpoints.ChannelMessage(Channel.Id, Id)))
|
||||
{
|
||||
wr.AddJson(new MessageEdit
|
||||
{
|
||||
Text = text,
|
||||
Embed = embed?.ToStruct(),
|
||||
});
|
||||
wr.Perform();
|
||||
|
||||
Text = msg.Response.Content;
|
||||
Edited = msg.Response.Edited ?? DateTime.UtcNow;
|
||||
if (wr.Status != 200)
|
||||
return this;
|
||||
|
||||
Message msg = wr.ResponseJson<Message>();
|
||||
|
||||
Text = msg.Content;
|
||||
Edited = msg.Edited ?? DateTime.UtcNow;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
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)
|
||||
throw new DiscordException("Failed to delete role!");
|
||||
if (wr.Status != 204)
|
||||
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)
|
||||
{
|
||||
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,
|
||||
Perms = perms ?? Perms,
|
||||
Colour = colour == null ? 0 : colour.Raw,
|
||||
Hoist = hoist ?? IsHoisted,
|
||||
Mentionable = mentionable ?? IsMentionable
|
||||
});
|
||||
wr.AddJson(new RoleCreate
|
||||
{
|
||||
Name = name ?? Name,
|
||||
Perms = perms ?? Perms,
|
||||
Colour = colour == null ? 0 : colour.Raw,
|
||||
Hoist = hoist ?? IsHoisted,
|
||||
Mentionable = mentionable ?? IsMentionable
|
||||
});
|
||||
wr.Perform();
|
||||
|
||||
if (role.ErrorCode != RestErrorCode.Ok)
|
||||
throw new DiscordException($"{role.ErrorCode}: {role.ErrorMessage}");
|
||||
if (wr.Status != 200 || wr.Response.Length < 1)
|
||||
// TODO: elaborate
|
||||
throw new DiscordException("Failed to edit role");
|
||||
|
||||
Name = role.Response.Name;
|
||||
Perms = (DiscordPermission)role.Response.Permissions;
|
||||
Colour.Raw = role.Response.Colour ?? 0;
|
||||
IsHoisted = role.Response.IsHoisted == true;
|
||||
IsMentionable = role.Response.IsMentionable == true;
|
||||
role = wr.ResponseJson<Role>();
|
||||
}
|
||||
|
||||
if (!role.HasValue)
|
||||
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 ulong OwnerId { get; internal set; }
|
||||
public DateTime Created { get; internal set; }
|
||||
internal string IconHash;
|
||||
|
||||
public DiscordMember[] Members => client.members.Where(x => x.Server == this).ToArray();
|
||||
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).OrderByDescending(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[] 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}";
|
||||
|
||||
|
@ -29,29 +30,41 @@ namespace Maki
|
|||
client = discord;
|
||||
Id = guild.Id;
|
||||
Name = guild.Name;
|
||||
Created = guild.Created ?? DateTime.MinValue;
|
||||
OwnerId = guild.OwnerId ?? ulong.MinValue;
|
||||
IconHash = guild.IconHash;
|
||||
}
|
||||
|
||||
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,
|
||||
Perms = perms,
|
||||
Colour = colour == null ? 0 : colour.Raw,
|
||||
Hoist = hoist,
|
||||
Mentionable = mentionable
|
||||
});
|
||||
wr.AddJson(new RoleCreate
|
||||
{
|
||||
Name = name,
|
||||
Perms = perms,
|
||||
Colour = colour == null ? 0 : colour.Raw,
|
||||
Hoist = hoist,
|
||||
Mentionable = mentionable
|
||||
});
|
||||
wr.Perform();
|
||||
|
||||
if (roleResp.ErrorCode != RestErrorCode.Ok)
|
||||
throw new DiscordException($"{roleResp.ErrorCode}: {roleResp.ErrorMessage}");
|
||||
if (wr.Status != 200 || wr.Response.Length < 1)
|
||||
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))
|
||||
{
|
||||
role = new DiscordRole(client, roleResp.Response, this);
|
||||
role = new DiscordRole(client, roleStruct.Value, this);
|
||||
client.roles.Add(role);
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ namespace Maki
|
|||
{
|
||||
client = discord;
|
||||
Id = user.Id;
|
||||
Created = Utility.FromDiscordTimeMilliseconds((long) Id >> 22);
|
||||
Created = Utility.FromDiscordSnowflake(Id);
|
||||
Username = user.Username;
|
||||
Tag = user.Tag.Value;
|
||||
IsBot = user.IsBot;
|
||||
|
|
|
@ -70,15 +70,14 @@
|
|||
<Compile Include="DiscordUser.cs" />
|
||||
<Compile Include="Gateway\GatewayEvent.cs" />
|
||||
<Compile Include="Rest\RestErrorCode.cs" />
|
||||
<Compile Include="Rest\RestRequestMethod.cs" />
|
||||
<Compile Include="Rest\RestClient.cs" />
|
||||
<Compile Include="Rest\HttpMethod.cs" />
|
||||
<Compile Include="Rest\RestEndpoints.cs" />
|
||||
<Compile Include="Gateway\GatewayHeartbeatManager.cs" />
|
||||
<Compile Include="Gateway\GatewayOPCode.cs" />
|
||||
<Compile Include="DiscordPermission.cs" />
|
||||
<Compile Include="DiscordTokenType.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\LoginRequest.cs" />
|
||||
<Compile Include="Structures\Auth\LoginResponse.cs" />
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
/// <summary>
|
||||
/// Discord Rest request methods
|
||||
/// </summary>
|
||||
enum RestRequestMethod
|
||||
enum HttpMethod
|
||||
{
|
||||
/// <summary>
|
||||
/// 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 FromDiscordTimeMilliseconds(long ms) => DiscordEpoch.AddMilliseconds(ms);
|
||||
|
||||
public static DateTime FromDiscordSnowflake(ulong snowflake) => FromDiscordTimeMilliseconds((long)snowflake >> 22);
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue