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 { public class ApiService { HttpClient _client; JsonSerializerOptions _serializerOptions; public const string BaseUrl = "https://api.omg.lol"; public ApiService(string? token = null) { _client = new HttpClient(); _client.BaseAddress = new Uri(BaseUrl); _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; try { responseObj = JsonSerializer.Deserialize(str, _serializerOptions); } catch (JsonException ex) { Debug.WriteLine(@"\tERROR {0}", ex.Message); Debug.WriteLine(str); } return responseObj; } #region Base Requests /// /// 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 { string str = await response.Content.ReadAsStringAsync(); if (response.IsSuccessStatusCode) { 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); } return responseData; } /// /// 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 { 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); } 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(); public async Task> Statuslog(string address) => (await Get($"/address/{address}/statuses"))?.Statuses ?? new List(); public async Task StatuslogBio(string address) { StatusBioResponseData? responseData = await Get($"/address/{address}/statuses/bio"); return Utilities.MdToHtmlMarkup(responseData?.Bio ?? ""); } public async Task AccountInfo() => await Get("/account/application/info"); public async Task Addresses() => await Get("/account/application/addresses"); public async Task StatusPost(string address, StatusPost statusPost) => await Post($"/address/{address}/statuses", statusPost); public async Task> SomePics() => (await Get("/pics"))?.Pics ?? new List(); public async Task> SomePics(string address) => (await Get($"/address/{address}/pics"))?.Pics ?? new List(); public async Task PutPic(string address, string base64Image) => (await Put($"/address/{address}/pics/upload", new PutPic { Pic = base64Image })); public async Task PutPic(string address, byte[] bytes) => await PutPic(address, Convert.ToBase64String(bytes)); public async Task PostPicDescription(string address, string id, string? description) => (await Post($"/address/{address}/pics/{id}", new PostPic { Description = description })); public async Task DeletePic(string address, string id) => (await Delete($"/address/{address}/pics/{id}")); public async Task PatchStatus(string address, string id, string content, string? emoji) => (await Patch($"/address/{address}/statuses/", new PatchStatus { Id = id, Content = content, Emoji = emoji })); public async Task DeleteStatus(string address, string id) => (await Delete($"/address/{address}/statuses/{id}")); public async Task?> NowGarden() => (await Get($"/now/garden"))?.Garden ?? new List(); public async Task?> Directory() => (await Get($"/directory"))?.Directory ?? new List(); public async Task GetNowPage(string address) => (await Get($"/address/{address}/now"))?.Now; public async Task PostNowPage(string address, string content, bool listed) => await Post($"/address/{address}/now", new NowContentData { Content = content, Listed = listed ? 1 : 0 }); public async Task> Ephemeral() => (await Get($"/ephemeral"))?.Content?.Select(s => (MarkupString)s)?.ToList() ?? new List(); public async Task PostEphemeral(string content) => await Post("/ephemeral", new EphemeralData { Content = content }); public async Task GetProfile(string address) => await Get($"/address/{address}/web"); public async Task PostProfile(string address, string content, bool publish = true) => await Post($"/address/{address}/web", new PostProfile { Content = content, Publish = publish }); public async Task PostProfile(string address, PostProfile data) => await Post($"/address/{address}/web", data); public async Task?> GetThemes() => (await Get($"/theme/list"))?.Themes; public async Task GetThemePreview(string theme) => (MarkupString)((await Get($"/theme/{theme}/preview"))?.Html ?? string.Empty); public async Task PostProfilePic(string address, 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; string uri = $"/oauth/?code={code}&client_id={client_id}&client_secret={client_secret}&redirect_uri={redirect_uri}&scope=everything"; try { HttpResponseMessage response = await _client.GetAsync(uri); if (response.IsSuccessStatusCode) { TokenResponseData? responseObj = await response.Content.ReadFromJsonAsync(_serializerOptions); if (responseObj != null && !string.IsNullOrEmpty(responseObj.AccessToken)) { token = responseObj.AccessToken; } } } catch (Exception ex) { Debug.WriteLine(@"\tERROR {0}", ex.Message); } return token; } #endregion public async Task GetHtml(string url) { string? raw = null; try { HttpResponseMessage response = await _client.GetAsync(url); if (response.IsSuccessStatusCode) { raw = await response.Content.ReadAsStringAsync(); } } catch (Exception ex) { Debug.WriteLine(@"\tERROR {0}", ex.Message); } return string.IsNullOrEmpty(raw) ? null : (MarkupString)raw; } } }