diff --git a/Maki/Discord.cs b/Maki/Discord.cs index 45818c4..8d33fe5 100644 --- a/Maki/Discord.cs +++ b/Maki/Discord.cs @@ -706,7 +706,7 @@ namespace Maki { wr.Perform(); - if (wr.Status != 200 || wr.Response.Length < 1) + if (wr.Status != 200 || wr.ResponseString.Length < 1) throw new DiscordException("Failed to load message from API"); message = wr.ResponseJson(); diff --git a/Maki/DiscordChannel.cs b/Maki/DiscordChannel.cs index a0a5e4c..338a258 100644 --- a/Maki/DiscordChannel.cs +++ b/Maki/DiscordChannel.cs @@ -58,7 +58,7 @@ namespace Maki wr.Perform(); - if (wr.Status != 200 || wr.Response.Length < 1) + if (wr.Status != 200 || wr.ResponseString.Length < 1) // TODO: elaborate //throw new DiscordException("Failed to send message"); return null; diff --git a/Maki/DiscordRole.cs b/Maki/DiscordRole.cs index 69f221f..b1d4d30 100644 --- a/Maki/DiscordRole.cs +++ b/Maki/DiscordRole.cs @@ -60,7 +60,7 @@ namespace Maki }); wr.Perform(); - if (wr.Status != 200 || wr.Response.Length < 1) + if (wr.Status != 200 || wr.ResponseString.Length < 1) // TODO: elaborate throw new DiscordException("Failed to edit role"); diff --git a/Maki/DiscordServer.cs b/Maki/DiscordServer.cs index 5af2153..7b889ce 100644 --- a/Maki/DiscordServer.cs +++ b/Maki/DiscordServer.cs @@ -56,7 +56,7 @@ namespace Maki }); wr.Perform(); - if (wr.Status != 200 || wr.Response.Length < 1) + if (wr.Status != 200 || wr.ResponseString.Length < 1) throw new DiscordException("Failed to create role"); roleStruct = wr.ResponseJson(); diff --git a/Maki/Maki.csproj b/Maki/Maki.csproj index 9a03f35..e0da28b 100644 --- a/Maki/Maki.csproj +++ b/Maki/Maki.csproj @@ -35,6 +35,15 @@ 0649 + + ..\..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll + + + ..\..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll + + + ..\..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll + $(SolutionDir)\packages\Newtonsoft.Json.10.0.3\lib\net40\Newtonsoft.Json.dll @@ -42,6 +51,7 @@ ..\..\packages\Microsoft.Bcl.1.1.10\lib\net40\System.IO.dll + ..\..\packages\Microsoft.Net.Http.2.2.29\lib\net40\System.Net.Http.dll diff --git a/Maki/Rest/WebRequest.cs b/Maki/Rest/WebRequest.cs index 679b259..92dff4f 100644 --- a/Maki/Rest/WebRequest.cs +++ b/Maki/Rest/WebRequest.cs @@ -5,8 +5,10 @@ using System.IO; using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Http.Headers; using System.Text; using System.Threading; +using System.Threading.Tasks; namespace Maki.Rest { @@ -15,47 +17,95 @@ namespace Maki.Rest 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"; - private const long BUFFER_SIZE = 8192000; + private const int MAX_RETRIES = 1; + private const int TIMEOUT = 10000; + + public int Timeout { get; set; } = TIMEOUT; + + public event Action Started; + public event Action Finished; + public event Action Failed; + + public event Action DownloadProgress; + public event Action UploadProgress; + + public bool IsAborted { get; private set; } + + public string Accept { get; set; } + + private bool PrivateCompleted; + public bool IsCompleted + { + get => PrivateCompleted; + private set + { + PrivateCompleted = value; + + if (!PrivateCompleted) + return; + + Started = null; + Finished = null; + DownloadProgress = null; + UploadProgress = null; + } + } + + private string PrivateUrl; + private string Url + { + get => PrivateUrl; + set + { + if (!value.StartsWith(@"http://") && !value.StartsWith(@"https://")) + value = RestEndpoints.BASE_URL + RestEndpoints.BASE_PATH + value; + + PrivateUrl = value; + } + } + + private const int BUFFER_SIZE = 4096; + private byte[] Buffer; private static HttpClient HttpClient; public readonly HttpMethod Method; - public readonly string Url; - - public string UserAgent { get; set; } = USER_AGENT; + private bool HasBody => Method == HttpMethod.PUT || Method == HttpMethod.POST || Method == HttpMethod.PATCH; public string ContentType { get; set; } = GENERIC_CONTENT_TYPE; - public long ContentLength => HttpWebResponse.ContentLength < 1 ? BUFFER_SIZE : HttpWebResponse.ContentLength; - // TODO: make this not static + [Obsolete] + public long ContentLength => Response.Content.Headers.ContentLength ?? BytesDownloaded; + + [Obsolete] internal static string Authorisation { get; set; } private readonly Dictionary Headers = new Dictionary(); private readonly Dictionary Parameters = new Dictionary(); private readonly Dictionary Files = new Dictionary(); - private readonly Dictionary MimeTypes = new Dictionary() - { - { "png", "image/png" }, - { "jpg", "image/jpeg" }, - { "jpeg", "image/jpeg" }, - { "gif", "image/gif" }, - }; + private long BytesUploaded = 0; + private long BytesDownloaded = 0; + + private HttpResponseMessage Response; + + private MemoryStream RawRequestBody; - private byte[] RawRequestBody = new byte[0]; - private HttpWebRequest HttpWebRequest; private Stream RequestStream; - private HttpWebResponse HttpWebResponse; private Stream ResponseStream; - private byte[] RawResponseValue; - public byte[] RawResponse + private CancellationTokenSource AbortToken; + private CancellationTokenSource TimeoutToken; + + public int Retries { get; private set; } = 0; + + private byte[] PrivateResponseBytes; + public byte[] ResponseBytes { get { - if (RawResponseValue == null) + if (PrivateResponseBytes == null) using (MemoryStream ms = new MemoryStream()) { byte[] bytes = new byte[4096]; @@ -65,36 +115,39 @@ namespace Maki.Rest ms.Write(bytes, 0, read); ms.Seek(0, SeekOrigin.Begin); - RawResponseValue = new byte[ms.Length]; - ms.Read(RawResponseValue, 0, RawResponseValue.Length); + PrivateResponseBytes = new byte[ms.Length]; + ms.Read(PrivateResponseBytes, 0, PrivateResponseBytes.Length); } - return RawResponseValue; + return PrivateResponseBytes; } } - private string ResponseString = string.Empty; - - public string Response + private string PrivateResponseString = string.Empty; + public string ResponseString { get { - if (string.IsNullOrEmpty(ResponseString)) - ResponseString = Encoding.UTF8.GetString(RawResponse); + if (string.IsNullOrEmpty(PrivateResponseString)) + PrivateResponseString = Encoding.UTF8.GetString(ResponseBytes); - return ResponseString; + return PrivateResponseString; } } public T ResponseJson() => - JsonConvert.DeserializeObject(Response); + JsonConvert.DeserializeObject(ResponseString); - public short Status => - (short)HttpWebResponse?.StatusCode; + [Obsolete] + public short Status => (short)Response?.StatusCode; static WebRequest() + => CreateHttpClientInstance(); + + public WebRequest(HttpMethod method, string url) { - CreateHttpClientInstance(); + Method = method; + Url = url; } private static void CreateHttpClientInstance() @@ -108,17 +161,25 @@ namespace Maki.Rest HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(USER_AGENT); HttpClient.DefaultRequestHeaders.ExpectContinue = true; - HttpClient.Timeout = new TimeSpan(0, 0, 0, 0, Timeout.Infinite); + HttpClient.Timeout = new TimeSpan(0, 0, 0, 0, System.Threading.Timeout.Infinite); } - public WebRequest(HttpMethod method, string url) + public void AddRaw(Stream stream) { - Method = method; - Url = url; + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + + RawRequestBody?.Dispose(); + RawRequestBody = new MemoryStream(); + + stream.CopyTo(RawRequestBody); } - public void AddRaw(byte[] bytes) => - RawRequestBody = bytes; + public void AddRaw(byte[] bytes) + { + using (MemoryStream ms = new MemoryStream(bytes)) + AddRaw(ms); + } public void AddRaw(string str) => AddRaw(Encoding.UTF8.GetBytes(str)); @@ -135,144 +196,293 @@ namespace Maki.Rest public void AddFile(string name, byte[] bytes) => Files.Add(name, bytes); - public void Perform() + public void AddHeader(string name, string value) { - StringBuilder urlBuilder = new StringBuilder(); + if (string.IsNullOrEmpty(name)) + throw new ArgumentNullException(nameof(name)); - if (!Url.StartsWith("http://") && !Url.StartsWith("https://")) - { - urlBuilder.Append(RestEndpoints.BASE_URL); - urlBuilder.Append(RestEndpoints.BASE_PATH); - } + if (value == null) + throw new ArgumentNullException(nameof(value)); - urlBuilder.Append(Url); + if (Headers.ContainsKey(name)) + Headers[name] = value; + else + Headers.Add(name, value); + } - if (Method == HttpMethod.GET - || Method == HttpMethod.DELETE) - if (Parameters.Count > 1) - { - if (!Url.Contains('?')) - urlBuilder.Append(@"?"); - - foreach (KeyValuePair param in Parameters) - urlBuilder.Append($@"{param.Key}={param.Value}&"); - } - - string url = urlBuilder.ToString().TrimEnd('&'); - - HttpWebRequest = System.Net.WebRequest.Create(url) as HttpWebRequest; - HttpWebRequest.Method = Method.ToString(); - HttpWebRequest.UserAgent = UserAgent; - HttpWebRequest.KeepAlive = true; - HttpWebRequest.ReadWriteTimeout = Timeout.Infinite; - HttpWebRequest.Timeout = Timeout.Infinite; - - if (!string.IsNullOrEmpty(Authorisation) && url.StartsWith(RestEndpoints.BASE_URL + RestEndpoints.BASE_PATH)) - HttpWebRequest.Headers[HttpRequestHeader.Authorization] = Authorisation; - - foreach (KeyValuePair header in Headers) - HttpWebRequest.Headers[header.Key] = header.Value; - - if (Method == HttpMethod.POST - || Method == HttpMethod.PUT - || Method == HttpMethod.PATCH) - { - RequestStream = HttpWebRequest.GetRequestStream(); - - if (Parameters.Count + Files.Count < 1) - RequestStream.Write(RawRequestBody, 0, RawRequestBody.Length); - else - { - string boundary = $@"-----------------------------{DateTime.Now.Ticks}"; - ContentType = $@"{FORM_CONTENT_TYPE}; boundary={boundary}"; - - if (Parameters.Count >= 1) - { - StringBuilder postBodyBuilder = new StringBuilder(); - byte[] postBody = new byte[0]; - - foreach (KeyValuePair 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 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); - } - } - - HttpWebRequest.ContentType = ContentType; + public void Abort() + { + IsAborted = true; + IsCompleted = true; try { - HttpWebResponse = HttpWebRequest.GetResponse() as HttpWebResponse; - } catch (WebException ex) - { - HttpWebResponse = ex.Response as HttpWebResponse; + AbortToken?.Cancel(); + } + catch (ObjectDisposedException) + { + // just do nothign in this case } - - ResponseStream = HttpWebResponse.GetResponseStream(); } - #region IDisposable + private System.Net.Http.HttpMethod FromInternalHttpMethod(HttpMethod method) + { + switch (method) + { + case HttpMethod.GET: + return System.Net.Http.HttpMethod.Get; - private bool IsDisposed = false; + case HttpMethod.DELETE: + return System.Net.Http.HttpMethod.Delete; - /// - /// Disconnects and releases all unmanaged objects - /// + case HttpMethod.POST: + return System.Net.Http.HttpMethod.Post; + + case HttpMethod.PATCH: + return new System.Net.Http.HttpMethod(@"PATCH"); + + case HttpMethod.PUT: + return System.Net.Http.HttpMethod.Put; + } + + throw new InvalidOperationException($"Unsupported HTTP method {method}."); + } + + private void PrivatePerform() + { + using (AbortToken = new CancellationTokenSource()) + using (TimeoutToken = new CancellationTokenSource()) + using (CancellationTokenSource linkedToken = CancellationTokenSource.CreateLinkedTokenSource(AbortToken.Token, TimeoutToken.Token)) + { + try + { + string requestUri = Url; + HttpRequestMessage request = new HttpRequestMessage(FromInternalHttpMethod(Method), requestUri); + + foreach (KeyValuePair h in Headers) + request.Headers.Add(h.Key, h.Value); + + if (!string.IsNullOrEmpty(Accept)) + request.Headers.Accept.TryParseAdd(Accept); + + if (HasBody) + { + Stream bodyContent; + + if (RawRequestBody == null) + { + MultipartFormDataContent formData = new MultipartFormDataContent(); + + foreach (KeyValuePair p in Parameters) + formData.Add(new StringContent(p.Value), p.Key); + + foreach (KeyValuePair f in Files) + { + ByteArrayContent bac = new ByteArrayContent(f.Value); + bac.Headers.Add("Content-Type", GENERIC_CONTENT_TYPE); + formData.Add(bac, f.Key, f.Key); + } + + bodyContent = formData.ReadAsStreamAsync().Result; + } else + { + if (Parameters.Count > 0 || Files.Count > 0) + throw new InvalidOperationException($"You cannot use {nameof(AddRaw)} at the same time as {nameof(AddParam)} or {nameof(AddFile)}"); + + bodyContent = new MemoryStream(); + RawRequestBody.Seek(0, SeekOrigin.Begin); + RawRequestBody.CopyTo(bodyContent); + bodyContent.Seek(0, SeekOrigin.Begin); + } + + request.Content = new StreamContent(RequestStream); + + if (!string.IsNullOrEmpty(ContentType)) + request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(ContentType); + } + else + { + if (Parameters.Count > 1) + { + StringBuilder urlBuilder = new StringBuilder(); + urlBuilder.Append(Url); + + if (!Url.Contains('?')) + urlBuilder.Append('?'); + + foreach (KeyValuePair param in Parameters) + { + urlBuilder.Append(param.Key); + urlBuilder.Append('='); + urlBuilder.Append(param.Value); + urlBuilder.Append('&'); + } + + urlBuilder.Length -= 1; + requestUri = urlBuilder.ToString(); + } + } + + ReportProgress(); + + using (request) + Response = HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, linkedToken.Token).Result; + + ResponseStream = new MemoryStream(); + + if (HasBody) + { + ReportProgress(); + UploadProgress?.Invoke(0, BytesUploaded); + } + + HandleResponse(linkedToken.Token); + } catch (Exception) when (AbortToken.IsCancellationRequested) + { + Complete(new WebException(string.Format("Request to {0} was aborted by the user.", Url), WebExceptionStatus.RequestCanceled)); + } catch (Exception) when (TimeoutToken.IsCancellationRequested) + { + Complete(new WebException(string.Format("Request to {0} timed out after {1:N0} seconds idle (read {2:N0} bytes).", Url, TimeSinceLastAction / 1000, BytesDownloaded), WebExceptionStatus.Timeout)); + } catch (Exception ex) + { + if (IsCompleted) + throw; + + Complete(ex); + } + } + } + + public async Task PerformAsync() + { + if (IsCompleted) + throw new InvalidOperationException($"{nameof(WebRequest)} has already been run, you can't reuse WebRequest objects."); + + try + { + await Task.Factory.StartNew(PrivatePerform, TaskCreationOptions.LongRunning); + } + catch (AggregateException ex) + { + if (ex.InnerExceptions.Count != 1) + throw ex; + + while (ex.InnerExceptions.Count == 1) + { + AggregateException innerEx = ex.InnerException as AggregateException; + ex = innerEx ?? throw innerEx.InnerException; + } + + throw ex; + } + } + + public void Perform() + => PerformAsync().Wait(); + + private void HandleResponse(CancellationToken cancellationToken) + { + using (Stream responseStream = Response.Content.ReadAsStreamAsync().Result) + { + Started?.Invoke(); + + Buffer = new byte[BUFFER_SIZE]; + + while (true) + { + cancellationToken.ThrowIfCancellationRequested(); + + int read = responseStream.Read(Buffer, 0, BUFFER_SIZE); + + ReportProgress(); + + if (read > 0) + { + ResponseStream.Write(Buffer, 0, read); + BytesDownloaded += read; + DownloadProgress?.Invoke(BytesDownloaded, Response.Content.Headers.ContentLength ?? BytesDownloaded); + } else + { + ResponseStream.Seek(0, SeekOrigin.Begin); + break; + } + } + } + } + + private void Complete(Exception exception = null) + { + if (IsAborted || IsCompleted) + return; + + bool allowRetry = true; + + if (exception != null) + { + allowRetry = exception is WebException && (exception as WebException)?.Status == WebExceptionStatus.Timeout; + } else if (!Response.IsSuccessStatusCode) + { + exception = new WebException($@"HTTP {Response.StatusCode}"); + + switch (Response.StatusCode) + { + case HttpStatusCode.NotFound: + case HttpStatusCode.MethodNotAllowed: + case HttpStatusCode.Forbidden: + case HttpStatusCode.Unauthorized: + allowRetry = false; + break; + } + } + + if (exception != null) + if (allowRetry && Retries < MAX_RETRIES && BytesDownloaded < 1) + { + ++Retries; + PrivatePerform(); + } + + try + { + // process + } catch (Exception ex) + { + exception = exception == null ? ex : new AggregateException(exception, ex); + } + + IsCompleted = true; + + if (exception != null) + { + IsAborted = true; + Failed?.Invoke(exception); + throw exception; + } + + Finished?.Invoke(); + } + + #region Timeout + private long LastReportedAction = 0; + private long TimeSinceLastAction => (DateTime.Now.Ticks - LastReportedAction) / TimeSpan.TicksPerMillisecond; + + private void ReportProgress() + { + LastReportedAction = DateTime.Now.Ticks; + TimeoutToken.CancelAfter(Timeout); + } + #endregion + + #region Disposal + public bool IsDisposed { get; private set; } = false; + private void Dispose(bool disposing) { if (IsDisposed) return; IsDisposed = true; - RequestStream?.Dispose(); - HttpWebRequest?.Abort(); - ResponseStream?.Dispose(); - HttpWebResponse?.Close(); + // TODO: reimplement disposal if (disposing) GC.SuppressFinalize(this); @@ -283,7 +493,6 @@ namespace Maki.Rest public void Dispose() => Dispose(true); - #endregion } } diff --git a/Maki/app.config b/Maki/app.config index 9f4c521..e493363 100644 --- a/Maki/app.config +++ b/Maki/app.config @@ -6,6 +6,14 @@ + + + + + + + + \ No newline at end of file diff --git a/Maki/packages.config b/Maki/packages.config index 2998e90..2f81865 100644 --- a/Maki/packages.config +++ b/Maki/packages.config @@ -1,6 +1,7 @@  + diff --git a/MakiTest/MakiTest.csproj b/MakiTest/MakiTest.csproj index 9b6727f..3741248 100644 --- a/MakiTest/MakiTest.csproj +++ b/MakiTest/MakiTest.csproj @@ -34,12 +34,6 @@ - - - - - - diff --git a/MakiTest/Program.cs b/MakiTest/Program.cs index c587433..59e9330 100644 --- a/MakiTest/Program.cs +++ b/MakiTest/Program.cs @@ -1,13 +1,45 @@ -using Maki; +using Maki.Rest; using System; using System.IO; using System.Threading; -namespace MakiTest +namespace Maki.Testing { class Program { - static void Main(string[] args) + public static void Main(string[] args) + { + TestWebRequest(false, HttpMethod.GET, WEB_REQUEST_GET); + TestWebRequest(true, HttpMethod.GET, WEB_REQUEST_GET); + + //TestMainLibrary(); + + Console.WriteLine("Stopped, press any key to close the program..."); + Console.ReadKey(); + } + + #region WebRequest tests + private const string WEB_REQUEST_SECURE = @"https://"; + private const string WEB_REQUEST_INSECURE = @"http://"; + private const string WEB_REQUEST_BASE = @"httpbin.org"; + + private const string WEB_REQUEST_GET = WEB_REQUEST_BASE + @"/get"; + private const string WEB_REQUEST_FAIL = @"thisshouldneveresolve.flash.moe"; + + private static void TestWebRequest(bool secure, HttpMethod method, string url) + { + url = (secure ? WEB_REQUEST_SECURE : WEB_REQUEST_INSECURE) + url; + + using (WebRequest wr = new WebRequest(method, url)) + { + wr.Perform(); + + Console.WriteLine(wr.ResponseString); + } + } + #endregion + + private static void TestMainLibrary() { string[] tokenInfo = File.ReadAllLines("token.txt"); string token = tokenInfo[0] ?? string.Empty; @@ -23,34 +55,31 @@ namespace MakiTest client.Connect(token, type); - client.OnReady += (me) => Console.WriteLine($"Connected as {me.NameWithTag} ({me.Id})!"); - client.OnServerCreate += (serv) => Console.WriteLine($"Server {serv.Name} ({serv.Id}) created."); - client.OnServerUpdate += (serv) => Console.WriteLine($"Server {serv.Name} ({serv.Id}) updated."); - client.OnServerDelete += (serv) => Console.WriteLine($"Server {serv.Name} ({serv.Id}) deleted."); - client.OnEmojisUpdate += (serv) => Console.WriteLine($"Server {serv.Name} ({serv.Id}) emojis updated."); - client.OnChannelCreate += (chan) => Console.WriteLine($"Channel #{chan.Name} ({chan.Id}) in {chan.Server.Name} ({chan.Server.Id}) created."); - client.OnChannelUpdate += (chan) => Console.WriteLine($"Channel #{chan.Name} ({chan.Id}) in {chan.Server.Name} ({chan.Server.Id}) updated."); - client.OnChannelDelete += (chan) => Console.WriteLine($"Channel #{chan.Name} ({chan.Id}) in {chan.Server.Name} ({chan.Server.Id}) deleted."); - client.OnBanAdd += (user, serv) => Console.WriteLine($"{user.NameWithTag} ({user.Id}) been banned from {serv.Name} ({serv.Id})."); - client.OnBanRemove += (user, serv) => Console.WriteLine($"{user.NameWithTag} ({user.Id}) been unbanned from {serv.Name} ({serv.Id})."); - client.OnMemberAdd += (member) => Console.WriteLine($"{member.NameWithTag} ({member.User.Id}) joined {member.Server.Name} ({member.Server.Id})."); - client.OnMemberRemove += (member) => Console.WriteLine($"{member.NameWithTag} ({member.User.Id}) left {member.Server.Name} ({member.Server.Id})."); - client.OnMemberUpdate += (member) => Console.WriteLine($"{member.NameWithTag} ({member.User.Id}) was updated for {member.Server.Name} ({member.Server.Id})."); - client.OnMessageCreate += (msg) => Console.WriteLine($"{msg.User.NameWithTag} ({msg.User.Id}) in {msg.Channel.Server.Name} ({msg.Channel.Server.Id}) #{msg.Channel.Name} ({msg.Channel.Id}) {msg.Id}: {msg.Text}"); - client.OnMessageUpdate += (msg) => Console.WriteLine($"{msg.User.NameWithTag} ({msg.User.Id}) in {msg.Channel.Server.Name} ({msg.Channel.Server.Id}) #{msg.Channel.Name} ({msg.Channel.Id}) {msg.Id}: {msg.Text}"); - client.OnMessageDelete += (msg) => Console.WriteLine($"{msg.User.NameWithTag} ({msg.User.Id}) in {msg.Channel.Server.Name} ({msg.Channel.Server.Id}) #{msg.Channel.Name} ({msg.Channel.Id}) {msg.Id}"); - client.OnRoleCreate += (role) => Console.WriteLine($"Role {role.Name} ({role.Id}) created in {role.Server.Name} ({role.Server.Id})."); - client.OnRoleUpdate += (role) => Console.WriteLine($"Role {role.Name} ({role.Id}) updated in {role.Server.Name} ({role.Server.Id})."); - client.OnRoleDelete += (role) => Console.WriteLine($"Role {role.Name} ({role.Id}) deleted in {role.Server.Name} ({role.Server.Id})."); - client.OnTypingStart += (user, chan) => Console.WriteLine($"{user.NameWithTag} ({user.Id}) started typing in #{chan.Name} ({chan.Id}) in {chan.Server.Name} ({chan.Server.Id})"); - client.OnPresenceUpdate += (user) => Console.WriteLine($"Presence of {user.NameWithTag} ({user.User.Id}) update for {user.Server.Name} ({user.Server.Id})."); - client.OnUserUpdate += (user) => Console.WriteLine($"{user.NameWithTag} ({user.Id}) updated."); + client.OnReady += (me) => Console.WriteLine($"Connected as {me.NameWithTag} ({me.Id})!"); + client.OnServerCreate += (serv) => Console.WriteLine($"Server {serv.Name} ({serv.Id}) created."); + client.OnServerUpdate += (serv) => Console.WriteLine($"Server {serv.Name} ({serv.Id}) updated."); + client.OnServerDelete += (serv) => Console.WriteLine($"Server {serv.Name} ({serv.Id}) deleted."); + client.OnEmojisUpdate += (serv) => Console.WriteLine($"Server {serv.Name} ({serv.Id}) emojis updated."); + client.OnChannelCreate += (chan) => Console.WriteLine($"Channel #{chan.Name} ({chan.Id}) in {chan.Server.Name} ({chan.Server.Id}) created."); + client.OnChannelUpdate += (chan) => Console.WriteLine($"Channel #{chan.Name} ({chan.Id}) in {chan.Server.Name} ({chan.Server.Id}) updated."); + client.OnChannelDelete += (chan) => Console.WriteLine($"Channel #{chan.Name} ({chan.Id}) in {chan.Server.Name} ({chan.Server.Id}) deleted."); + client.OnBanAdd += (user, serv) => Console.WriteLine($"{user.NameWithTag} ({user.Id}) been banned from {serv.Name} ({serv.Id})."); + client.OnBanRemove += (user, serv) => Console.WriteLine($"{user.NameWithTag} ({user.Id}) been unbanned from {serv.Name} ({serv.Id})."); + client.OnMemberAdd += (member) => Console.WriteLine($"{member.NameWithTag} ({member.User.Id}) joined {member.Server.Name} ({member.Server.Id})."); + client.OnMemberRemove += (member) => Console.WriteLine($"{member.NameWithTag} ({member.User.Id}) left {member.Server.Name} ({member.Server.Id})."); + client.OnMemberUpdate += (member) => Console.WriteLine($"{member.NameWithTag} ({member.User.Id}) was updated for {member.Server.Name} ({member.Server.Id})."); + client.OnMessageCreate += (msg) => Console.WriteLine($"{msg.User.NameWithTag} ({msg.User.Id}) in {msg.Channel.Server.Name} ({msg.Channel.Server.Id}) #{msg.Channel.Name} ({msg.Channel.Id}) {msg.Id}: {msg.Text}"); + client.OnMessageUpdate += (msg) => Console.WriteLine($"{msg.User.NameWithTag} ({msg.User.Id}) in {msg.Channel.Server.Name} ({msg.Channel.Server.Id}) #{msg.Channel.Name} ({msg.Channel.Id}) {msg.Id}: {msg.Text}"); + client.OnMessageDelete += (msg) => Console.WriteLine($"{msg.User.NameWithTag} ({msg.User.Id}) in {msg.Channel.Server.Name} ({msg.Channel.Server.Id}) #{msg.Channel.Name} ({msg.Channel.Id}) {msg.Id}"); + client.OnRoleCreate += (role) => Console.WriteLine($"Role {role.Name} ({role.Id}) created in {role.Server.Name} ({role.Server.Id})."); + client.OnRoleUpdate += (role) => Console.WriteLine($"Role {role.Name} ({role.Id}) updated in {role.Server.Name} ({role.Server.Id})."); + client.OnRoleDelete += (role) => Console.WriteLine($"Role {role.Name} ({role.Id}) deleted in {role.Server.Name} ({role.Server.Id})."); + client.OnTypingStart += (user, chan) => Console.WriteLine($"{user.NameWithTag} ({user.Id}) started typing in #{chan.Name} ({chan.Id}) in {chan.Server.Name} ({chan.Server.Id})"); + client.OnPresenceUpdate += (user) => Console.WriteLine($"Presence of {user.NameWithTag} ({user.User.Id}) update for {user.Server.Name} ({user.Server.Id})."); + client.OnUserUpdate += (user) => Console.WriteLine($"{user.NameWithTag} ({user.Id}) updated."); mre.WaitOne(); } - - Console.WriteLine("Stopped, press any key to close the program..."); - Console.ReadKey(); } } }