Archived
1
0
Fork 0

rewrite rest request backend

This commit is contained in:
flash 2017-05-31 23:02:38 +02:00
parent 063a12e804
commit e1672b7256
13 changed files with 462 additions and 264 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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" />

View file

@ -3,7 +3,7 @@
/// <summary>
/// Discord Rest request methods
/// </summary>
enum RestRequestMethod
enum HttpMethod
{
/// <summary>
/// GET, does not send additional data

View file

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

View file

@ -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
View 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
}
}

View file

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