diff --git a/Classes/ApiService.cs b/Classes/ApiService.cs
index 2863a2b..39da78e 100644
--- a/Classes/ApiService.cs
+++ b/Classes/ApiService.cs
@@ -1,10 +1,12 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Neighbourhood.omg.lol.Models;
+using System;
using System.Diagnostics;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
+using System.Threading;
namespace Neighbourhood.omg.lol
{
@@ -19,13 +21,23 @@ namespace Neighbourhood.omg.lol
_client.DefaultRequestHeaders.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue(App.Name, App.Version));
_serializerOptions = new JsonSerializerOptions {
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
+#if DEBUG
WriteIndented = true
+#else
+ WriteIndented = false
+#endif
};
AddToken(token);
}
+ ///
+ /// Deserialize json convenience function with default serializer options
+ ///
+ /// The type to deserialize
+ /// The string to deserialize
+ /// The deserialized object if successful, otherwise default
public T? Deserialize(string str) {
- T? responseObj = default(T);
+ T? responseObj = default;
try {
responseObj = JsonSerializer.Deserialize(str, _serializerOptions);
}
@@ -36,166 +48,31 @@ namespace Neighbourhood.omg.lol
return responseObj;
}
- public void AddToken(string? token = null) {
- if (token == null) token = Task.Run(() => SecureStorage.GetAsync("accounttoken")).GetAwaiter().GetResult();
- if (token != null) _client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
- }
+ #region Base Requests
- public void RemoveToken() {
- _client.DefaultRequestHeaders.Remove("Authorization");
- }
-
- private async Task Get(string uri, CancellationToken cancellationToken = default) where T : IOmgLolResponseData {
- T? responseData = default(T);
- try {
- HttpResponseMessage response = await _client.GetAsync(uri, cancellationToken: cancellationToken);
- if (response.IsSuccessStatusCode) {
- string str = await response.Content.ReadAsStringAsync();
- try {
- OmgLolResponse? responseObj = await response.Content.ReadFromJsonAsync>(_serializerOptions, cancellationToken: cancellationToken);
- if (responseObj?.Request == null || (responseObj?.Request?.Success ?? false)) {
- responseData = responseObj!.Response;
- }
- }
- catch (JsonException ex) {
- Debug.WriteLine(@"\tERROR {0}", ex.Message);
- Debug.WriteLine(str);
- }
- }
- }
- catch (Exception ex) {
- Debug.WriteLine(@"\tERROR {0}", ex.Message);
- }
-
- return responseData;
- }
-
- private async Task Post(string uri, TData data, CancellationToken cancellationToken = default) where TResponse : IOmgLolResponseData {
- TResponse? responseData = default(TResponse);
- try {
- HttpResponseMessage response = await _client.PostAsJsonAsync(uri, data, _serializerOptions, cancellationToken: cancellationToken);
- string str = await response.Content.ReadAsStringAsync();
- if (response.IsSuccessStatusCode) {
- OmgLolResponse? responseObj = await response.Content.ReadFromJsonAsync>(_serializerOptions, cancellationToken: cancellationToken);
- if (responseObj?.Request?.Success ?? false) {
- responseData = responseObj.Response;
- }
- }
- }
- catch (Exception ex) {
- Debug.WriteLine(@"\tERROR {0}", ex.Message);
- }
-
- return responseData;
- }
-
- private async Task PostBinary(string uri, FileResult? fileResult = null, CancellationToken cancellationToken = default) where TResponse : IOmgLolResponseData {
- TResponse? responseData = default(TResponse);
- try {
- if (fileResult != null) using (var fileStream = await fileResult.OpenReadAsync()) {
- Uri url = new Uri(_client.BaseAddress?.AbsoluteUri + uri);
- if (string.IsNullOrEmpty(url.Query)) uri += "?binary";
- else if (!url.Query.Contains("binary")) uri += "&binary";
-
- HttpContent fileStreamContent = new StreamContent(fileStream);
- fileStreamContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(fileResult.ContentType ?? "application/octet-stream");
- fileStreamContent.Headers.ContentLength = fileStream.Length;
- HttpResponseMessage response = await _client.PostAsync(uri, fileStreamContent, cancellationToken: cancellationToken);
- string str = await response.Content.ReadAsStringAsync();
- if (response.IsSuccessStatusCode) {
- OmgLolResponse? responseObj = await response.Content.ReadFromJsonAsync>(_serializerOptions, cancellationToken: cancellationToken);
- if (responseObj?.Request?.Success ?? false) {
- responseData = responseObj.Response;
- }
- }
- }
- }
- catch (Exception ex) {
- Debug.WriteLine(@"\tERROR {0}", ex.Message);
- }
-
- return responseData;
- }
-
- private async Task PostMultipart(string uri, TData? data = null, FileResult? fileResult = null, CancellationToken cancellationToken = default)
- where TResponse : IOmgLolResponseData where TData : class
- {
- if(fileResult != null) {
- using (var fileStream = await fileResult.OpenReadAsync())
- return await PostMultipart(uri, data: data, fileStream: fileStream, fileName: fileResult.FileName, contentType: fileResult.ContentType);
- }
- else return await PostMultipart(uri, data, fileStream: null);
- }
-
- private async Task PostMultipart(string uri, TData? data = null, Stream? fileStream = null, string? fileName = null, string? contentType = null, CancellationToken cancellationToken = default)
- where TResponse : IOmgLolResponseData where TData : class
+ ///
+ /// Decode the response from an API call
+ ///
+ /// The type of response object we are trying to get
+ /// The raw Http Response Message
+ /// A cancellation token to cancel the operation
+ /// The decoded object if successfull, otherwise default
+ private async Task DecodeResponse(HttpResponseMessage response, CancellationToken cancellationToken = default)
+ where TResponse : IOmgLolResponseData
{
TResponse? responseData = default;
try {
- using (MultipartFormDataContent formData = new MultipartFormDataContent()) {
- if(fileStream != null) {
- HttpContent fileStreamContent = new StreamContent(fileStream);
- fileStreamContent.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data") {
- Name = "\"file\"",
- FileName = $"\"{fileName}\"" ?? "\"unknown\""
- };
- fileStreamContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType ?? "application/octet-stream");
- formData.Add(fileStreamContent);
- }
- if (data != null) {
- HttpContent jsonContent = JsonContent.Create(data, options: _serializerOptions);
- formData.Add(jsonContent);
- }
-
- HttpResponseMessage response = await _client.PostAsync(uri, formData, cancellationToken: cancellationToken);
- string str = await response.Content.ReadAsStringAsync();
- if (response.IsSuccessStatusCode) {
- OmgLolResponse? responseObj = await response.Content.ReadFromJsonAsync>(_serializerOptions, cancellationToken: cancellationToken);
- if (responseObj?.Request?.Success ?? false) {
- responseData = responseObj.Response;
- }
- }
- }
- }
- catch (Exception ex) {
- Debug.WriteLine(@"\tERROR {0}", ex.Message);
- }
-
- return responseData;
- }
-
- private async Task Put(string uri, TData data, CancellationToken cancellationToken = default) where TResponse : IOmgLolResponseData {
- TResponse? responseData = default(TResponse);
- try {
- string json = JsonSerializer.Serialize(data, _serializerOptions);
- HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Put, uri);
- request.Content = new StringContent(json, Encoding.UTF8, "application/json");
- HttpResponseMessage response = await _client.SendAsync(request, cancellationToken: cancellationToken);
- if (response.IsSuccessStatusCode) {
- OmgLolResponse? responseObj = await response.Content.ReadFromJsonAsync>(_serializerOptions, cancellationToken: cancellationToken);
- if (responseObj?.Request?.Success ?? false) {
- responseData = responseObj.Response;
- }
- }
- }
- catch (Exception ex) {
- Debug.WriteLine(@"\tERROR {0}", ex.Message);
- }
-
- return responseData;
- }
-
- private async Task Patch(string uri, TData data, CancellationToken cancellationToken = default) where TResponse : IOmgLolResponseData {
- TResponse? responseData = default(TResponse);
- try {
- HttpResponseMessage response = await _client.PatchAsJsonAsync(uri, data, _serializerOptions, cancellationToken: cancellationToken);
string str = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode) {
- OmgLolResponse? responseObj = await response.Content.ReadFromJsonAsync>(_serializerOptions, cancellationToken: cancellationToken);
- if (responseObj?.Request?.Success ?? false) {
- responseData = responseObj.Response;
+ OmgLolResponse? responseObj = Deserialize>(str);
+ if (responseObj?.Request == null || (responseObj?.Request?.Success ?? false)) {
+ responseData = responseObj!.Response;
}
}
+ else {
+ OmgLolResponse? responseObj = Deserialize>(str);
+ throw responseObj == null ? new OmgLolApiException(str) : new OmgLolApiException(responseObj);
+ }
}
catch (Exception ex) {
Debug.WriteLine(@"\tERROR {0}", ex.Message);
@@ -204,23 +81,46 @@ namespace Neighbourhood.omg.lol
return responseData;
}
- private async Task Delete(string uri, CancellationToken cancellationToken = default) where T : IOmgLolResponseData {
- T? responseData = default(T);
+ ///
+ /// Performs a request for the supplied uri, with the supplied Http Method,
+ /// with the supplied data in the body (if present)
+ ///
+ /// The type of response we are expecting
+ /// The type of data we are sending
+ /// The uri to request
+ /// The Http Method to use for the request
+ /// The data to send in the body of the request
+ /// A FileResult for the file to send in the body of the request as binary data
+ /// A cancellation token
+ /// The returned data if successful, otherwise default
+ private async Task Request(string uri, HttpMethod method, TData? data = default, FileResult? file = null, CancellationToken cancellationToken = default)
+ where TResponse : IOmgLolResponseData
+ {
+ TResponse? responseData = default;
try {
- HttpResponseMessage response = await _client.DeleteAsync(uri, cancellationToken: cancellationToken);
- if (response.IsSuccessStatusCode) {
- string str = await response.Content.ReadAsStringAsync();
- try {
- OmgLolResponse? responseObj = await response.Content.ReadFromJsonAsync>(_serializerOptions, cancellationToken: cancellationToken);
- if (responseObj?.Request?.Success ?? false) {
- responseData = responseObj.Response;
- }
- }
- catch (JsonException ex) {
- Debug.WriteLine(@"\tERROR {0}", ex.Message);
- Debug.WriteLine(str);
- }
+ HttpRequestMessage request = new HttpRequestMessage(method, uri);
+ Stream? fileStream = null;
+ if (file != null) {
+ // append "binary" query parameter (if not already present)
+ Uri url = new Uri(_client.BaseAddress?.AbsoluteUri + uri);
+ if (string.IsNullOrEmpty(url.Query)) uri += "?binary";
+ else if (!url.Query.Contains("binary")) uri += "&binary";
+ request = new HttpRequestMessage(method, uri);
+
+ fileStream = await file.OpenReadAsync();
+ HttpContent fileStreamContent = new StreamContent(fileStream);
+ fileStreamContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(file.ContentType ?? "application/octet-stream");
+ fileStreamContent.Headers.ContentLength = fileStream.Length;
+ request.Content = fileStreamContent;
}
+ else if (data != null) {
+ string json = JsonSerializer.Serialize(data, _serializerOptions);
+ request.Content = new StringContent(json, Encoding.UTF8, "application/json");
+ }
+ HttpResponseMessage response = await _client.SendAsync(request, cancellationToken: cancellationToken);
+ responseData = await DecodeResponse(response, cancellationToken);
+
+ fileStream?.Dispose();
}
catch (Exception ex) {
Debug.WriteLine(@"\tERROR {0}", ex.Message);
@@ -229,6 +129,38 @@ namespace Neighbourhood.omg.lol
return responseData;
}
+ // GET request
+ private async Task Get(string uri, CancellationToken cancellationToken = default)
+ where TResponse : IOmgLolResponseData
+ => await Request(uri, HttpMethod.Get, cancellationToken: cancellationToken);
+
+ // POST request
+ private async Task Post(string uri, TData data, CancellationToken cancellationToken = default)
+ where TResponse : IOmgLolResponseData
+ => await Request(uri, HttpMethod.Post, data: data, cancellationToken: cancellationToken);
+
+ // POST request, but with a file as binary data
+ private async Task PostBinary(string uri, FileResult? fileResult = null, CancellationToken cancellationToken = default)
+ where TResponse : IOmgLolResponseData
+ => await Request(uri, HttpMethod.Post, file: fileResult, cancellationToken: cancellationToken);
+
+ // PUT request
+ private async Task Put(string uri, TData data, CancellationToken cancellationToken = default)
+ where TResponse : IOmgLolResponseData
+ => await Request(uri, HttpMethod.Put, data: data, cancellationToken: cancellationToken);
+
+ // PATCH request
+ private async Task Patch(string uri, TData data, CancellationToken cancellationToken = default)
+ where TResponse : IOmgLolResponseData
+ => await Request(uri, HttpMethod.Patch, data: data, cancellationToken: cancellationToken);
+
+ // Delete request
+ private async Task Delete(string uri, CancellationToken cancellationToken = default)
+ where TResponse : IOmgLolResponseData
+ => await Request(uri, HttpMethod.Delete, cancellationToken: cancellationToken);
+ #endregion
+
+ #region Specific Requests
public async Task> StatuslogLatest() =>
(await Get("/statuslog/latest"))?.Statuses ?? new List();
@@ -257,25 +189,6 @@ namespace Neighbourhood.omg.lol
public async Task PutPic(string address, string base64Image) =>
(await Put($"/address/{address}/pics/upload", new PutPic { Pic = base64Image }));
- public async Task PutPic(string address, IBrowserFile file) {
- byte[] bytes;
- using (var memoryStream = new MemoryStream()) {
- await file.OpenReadStream().CopyToAsync(memoryStream);
- bytes = memoryStream.ToArray();
- }
-
- return await PutPic(address, bytes);
- }
-
- public async Task PutPic(string address, FileResult file) {
- byte[] bytes;
- using var memoryStream = new MemoryStream();
- using var fileStream = await file.OpenReadAsync();
- await fileStream.CopyToAsync(memoryStream);
- bytes = memoryStream.ToArray();
-
- return await PutPic(address, bytes);
- }
public async Task PutPic(string address, byte[] bytes) =>
await PutPic(address, Convert.ToBase64String(bytes));
@@ -324,7 +237,26 @@ namespace Neighbourhood.omg.lol
(MarkupString)((await Get($"/theme/{theme}/preview"))?.Html ?? string.Empty);
public async Task PostProfilePic(string address, FileResult image) =>
- await PostBinary($"/address/{address}/pfp", fileResult: image);
+ await PostBinary($"/address/{address}/pfp", fileResult: image);
+
+ #endregion
+
+ #region Auth
+ ///
+ /// Add the api token into the default headers
+ ///
+ /// The api token
+ public void AddToken(string? token = null) {
+ if (token == null) token = Task.Run(() => SecureStorage.GetAsync("accounttoken")).GetAwaiter().GetResult();
+ if (token != null) _client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
+ }
+
+ ///
+ /// Remove the api token from the default headers
+ ///
+ public void RemoveToken() {
+ _client.DefaultRequestHeaders.Remove("Authorization");
+ }
public async Task OAuth(string code, string client_id, string client_secret, string redirect_uri) {
string? token = null;
@@ -343,6 +275,7 @@ namespace Neighbourhood.omg.lol
}
return token;
}
+ #endregion
public async Task GetHtml(string url) {
string? raw = null;
diff --git a/Models/API/AddressResponseList.cs b/Models/API/AddressResponseList.cs
index ab787b3..33ab468 100644
--- a/Models/API/AddressResponseList.cs
+++ b/Models/API/AddressResponseList.cs
@@ -1,4 +1,5 @@
namespace Neighbourhood.omg.lol.Models {
public class AddressResponseList : List, IOmgLolResponseList {
+ public string Message { get; set; } = string.Empty;
}
}
diff --git a/Models/API/IOmgLolResponseData.cs b/Models/API/IOmgLolResponseData.cs
index d126f13..8b4161c 100644
--- a/Models/API/IOmgLolResponseData.cs
+++ b/Models/API/IOmgLolResponseData.cs
@@ -1,4 +1,5 @@
namespace Neighbourhood.omg.lol.Models {
public interface IOmgLolResponseData {
+ public string Message { get; set; }
}
}
diff --git a/Models/API/OmgLolApiException.cs b/Models/API/OmgLolApiException.cs
new file mode 100644
index 0000000..062b407
--- /dev/null
+++ b/Models/API/OmgLolApiException.cs
@@ -0,0 +1,11 @@
+namespace Neighbourhood.omg.lol.Models {
+ public class OmgLolApiException : Exception where T : IOmgLolResponseData {
+ public OmgLolResponse? Response { get; set; }
+
+ public OmgLolApiException(OmgLolResponse? response) : base(response?.Response?.Message) {
+ Response = response;
+ }
+
+ public OmgLolApiException(string? response) : base(response) { }
+ }
+}
diff --git a/Models/API/StatusPostResponseData.cs b/Models/API/StatusPostResponseData.cs
index 05f34da..67ea2f5 100644
--- a/Models/API/StatusPostResponseData.cs
+++ b/Models/API/StatusPostResponseData.cs
@@ -1,6 +1,6 @@
namespace Neighbourhood.omg.lol.Models {
public class StatusPostResponseData : IOmgLolResponseData {
- public string? Message { get; set; }
+ public string Message { get; set; } = string.Empty;
public string? Id { get; set; }
public string? Url { get; set; }
public string? ExternalUrl { get; set; }