Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
463 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
using Miki.API.Images.Models; | ||
using Moq; | ||
using System.Threading.Tasks; | ||
using Xunit; | ||
|
||
namespace Miki.API.Images.Tests | ||
{ | ||
public class ClientTests | ||
{ | ||
[Fact] | ||
public void ConstructClientTest() | ||
{ | ||
var i = new ImghoardClient(); | ||
|
||
Assert.NotNull(i); | ||
Assert.Equal(Config.Default().Endpoint, i.GetEndpoint()); | ||
} | ||
|
||
[Fact] | ||
public async Task GetSingleImageAsync() | ||
{ | ||
var mock = new Mock<IImghoardClient>(); | ||
|
||
mock.Setup(x => x.GetImageAsync(It.IsAny<ulong>())) | ||
.Returns(Task.FromResult(new Image | ||
{ | ||
Id = 1171190856858734592, | ||
Tags = new[] { "animal", "cat" }, | ||
Url = "https://cdn.miki.ai/ext/imgh/1ciajYwALX.jpeg" | ||
})); | ||
|
||
var response = await mock.Object.GetImageAsync(1171190856858734592); | ||
|
||
Assert.Equal<ulong>(1171190856858734592, response.Id); | ||
Assert.NotNull(response.Tags); | ||
Assert.NotNull(response.Url); | ||
} | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
src/Miki.API.Images/Miki.API.Images.Tests/Miki.API.Images.Tests.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netcoreapp3.0</TargetFramework> | ||
<ApplicationIcon /> | ||
<OutputType>Exe</OutputType> | ||
<StartupObject /> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Moq" Version="4.13.1" /> | ||
<PackageReference Include="xunit" Version="2.4.1" /> | ||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1"> | ||
<PrivateAssets>all</PrivateAssets> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
</PackageReference> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Miki.API.Images\Miki.API.Images.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio Version 16 | ||
VisualStudioVersion = 16.0.29424.173 | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Miki.API.Images", "Miki.API.Images\Miki.API.Images.csproj", "{AA62EFC3-BA7E-4FAC-89C8-18ED66A5ED48}" | ||
EndProject | ||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Miki.API.Images.Tests", "Miki.API.Images.Tests\Miki.API.Images.Tests.csproj", "{D9B41632-B8D6-40A3-8C54-C95C1FF525E6}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{AA62EFC3-BA7E-4FAC-89C8-18ED66A5ED48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{AA62EFC3-BA7E-4FAC-89C8-18ED66A5ED48}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{AA62EFC3-BA7E-4FAC-89C8-18ED66A5ED48}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{AA62EFC3-BA7E-4FAC-89C8-18ED66A5ED48}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{D9B41632-B8D6-40A3-8C54-C95C1FF525E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{D9B41632-B8D6-40A3-8C54-C95C1FF525E6}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{D9B41632-B8D6-40A3-8C54-C95C1FF525E6}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{D9B41632-B8D6-40A3-8C54-C95C1FF525E6}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
GlobalSection(ExtensibilityGlobals) = postSolution | ||
SolutionGuid = {1CE187D2-8805-4389-B106-7FD9891DCF1C} | ||
EndGlobalSection | ||
EndGlobal |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
using System; | ||
using System.Reflection; | ||
|
||
namespace Miki.API.Images | ||
{ | ||
public class Config : IEquatable<Config> | ||
{ | ||
public string Tenancy { get; set; } = "production"; | ||
public string Endpoint { get; set; } = "https://api.miki.ai/images"; | ||
public string UserAgent { get; set; } = | ||
$"Miki.API.Images/"+ | ||
Assembly.GetExecutingAssembly() | ||
.GetName() | ||
.Version | ||
.ToString() | ||
.Substring(0, 3) + | ||
" (https://github.com/Mikibot/dotnet-miki-api)"; | ||
|
||
/// <summary> | ||
/// Allows the client to use unstable, experimental features | ||
/// </summary> | ||
public bool Experimental { get; set; } = false; | ||
|
||
public static Config Default() | ||
{ | ||
return new Config(); | ||
} | ||
|
||
public override bool Equals(object obj) | ||
{ | ||
return Equals(obj as Config); | ||
} | ||
|
||
public bool Equals(Config other) | ||
{ | ||
return other != null && | ||
Tenancy == other.Tenancy && | ||
Endpoint == other.Endpoint && | ||
UserAgent == other.UserAgent && | ||
Experimental == other.Experimental; | ||
} | ||
|
||
public override int GetHashCode() | ||
{ | ||
return HashCode.Combine(Tenancy, Endpoint, UserAgent, Experimental); | ||
} | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
src/Miki.API.Images/Miki.API.Images/Exceptions/ResponseException.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
using System; | ||
|
||
namespace Miki.API.Images.Exceptions | ||
{ | ||
public class ResponseException : Exception | ||
{ | ||
public ResponseException(string reason = null, Exception innerException = null) : base(reason, innerException) { } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
using Miki.API.Images.Models; | ||
using System; | ||
using System.Threading.Tasks; | ||
|
||
namespace Miki.API.Images | ||
{ | ||
public interface IImghoardClient | ||
{ | ||
Task<ImagesResponse> GetImagesAsync(params string[] Tags); | ||
Task<ImagesResponse> GetImagesAsync(int page = 0, params string[] Tags); | ||
Task<Image> GetImageAsync(ulong Id); | ||
Task<string> PostImageAsync(Memory<byte> bytes, params string[] Tags); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
using Miki.API.Images.Exceptions; | ||
using Miki.API.Images.Models; | ||
using Miki.Utils.Imaging.Headers; | ||
using Miki.Utils.Imaging.Headers.Models; | ||
using Newtonsoft.Json; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Net.Http; | ||
using System.Threading.Tasks; | ||
|
||
namespace Miki.API.Images | ||
{ | ||
public class ImghoardClient : IImghoardClient | ||
{ | ||
private HttpClient apiClient; | ||
private readonly Config config; | ||
private const int Mb = 1000000; | ||
private readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings | ||
{ | ||
DefaultValueHandling = DefaultValueHandling.Ignore, | ||
NullValueHandling = NullValueHandling.Ignore | ||
}; | ||
|
||
public ImghoardClient() : this(Config.Default()) { } | ||
|
||
public ImghoardClient(Config config) | ||
{ | ||
this.config = config; | ||
|
||
this.apiClient = new HttpClient(); | ||
this.apiClient.DefaultRequestHeaders.Add("x-miki-tenancy", config.Tenancy); | ||
this.apiClient.DefaultRequestHeaders.Add("User-Agent", config.UserAgent); | ||
} | ||
|
||
public string GetEndpoint() | ||
=> config.Endpoint; | ||
|
||
/// <summary> | ||
/// Gets the first page of results given an array of Tags to find | ||
/// </summary> | ||
/// <param name="tags">Tags to search for</param> | ||
/// <returns>A readonly list of images found with the Tags entered</returns> | ||
public async Task<ImagesResponse> GetImagesAsync(params string[] tags) | ||
=> await GetImagesAsync(0, tags); | ||
|
||
/// <summary> | ||
/// Gets the given page of results given an array of Tags to find | ||
/// </summary> | ||
/// <param name="tags">Tags to search for</param> | ||
/// <returns>A readonly list of images found with the Tags entered</returns> | ||
public async Task<ImagesResponse> GetImagesAsync(int page = 0, params string[] tags) | ||
{ | ||
List<string> args = new List<string>(); | ||
|
||
if (page > 0) | ||
{ | ||
args.Add($"page{page}"); | ||
} | ||
|
||
if (tags.Any()) | ||
{ | ||
args.Add(string.Join("+", tags)); | ||
} | ||
|
||
string url = ""; | ||
|
||
if (args.Any()) | ||
url = $"?{string.Join("&", args)}"; | ||
|
||
var response = await apiClient.GetAsync(config.Endpoint + url); | ||
|
||
if (response.IsSuccessStatusCode) | ||
{ | ||
return new ImagesResponse(this, JsonConvert.DeserializeObject<IReadOnlyList<Image>>(await response.Content.ReadAsStringAsync()), tags, page); | ||
} | ||
|
||
throw new ResponseException("Response was not successfull; Reason: \"" + response.ReasonPhrase + "\""); | ||
} | ||
|
||
/// <summary> | ||
/// Get an image with a given Id | ||
/// </summary> | ||
/// <param name="id">The snowflake Id of the Image to get</param> | ||
/// <returns>The image with the given snowflake</returns> | ||
public async Task<Image> GetImageAsync(ulong id) | ||
{ | ||
var response = await apiClient.GetAsync(config.Endpoint + $"/{id}"); | ||
|
||
if (response.IsSuccessStatusCode) | ||
{ | ||
return JsonConvert.DeserializeObject<Image>(await response.Content.ReadAsStringAsync()); | ||
} | ||
|
||
throw new ResponseException(response.ReasonPhrase); | ||
} | ||
|
||
/// <summary> | ||
/// Posts a new image to the Imghoard instance | ||
/// </summary> | ||
/// <param name="image">The image stream to upload</param> | ||
/// <param name="tags">The tags of the image being uploaded</param> | ||
/// <returns>The url of the uploaded image or null on failure</returns> | ||
public async Task<string> PostImageAsync(Stream image, params string[] tags) | ||
{ | ||
byte[] bytes; | ||
|
||
using (var mStream = new MemoryStream()) | ||
{ | ||
await image.CopyToAsync(mStream); | ||
bytes = mStream.ToArray(); | ||
} | ||
image.Position = 0; | ||
|
||
return await PostImageAsync(bytes, tags); | ||
} | ||
|
||
/// <summary> | ||
/// Posts a new image to the Imghoard instance | ||
/// </summary> | ||
/// <param name="bytes">The raw bytes of the image to upload</param> | ||
/// <param name="tags">The tags of the image being uploaded</param> | ||
/// <returns>The url of the uploaded image or null on failure</returns> | ||
public async Task<string> PostImageAsync(Memory<byte> bytes, params string[] tags) | ||
{ | ||
if (bytes.Length >= Mb && !config.Experimental) | ||
{ | ||
throw new NotSupportedException("In order to upload images larger than 1MB you need to enable experimental features in the config"); | ||
} | ||
|
||
var check = IsSupported(bytes.Span); | ||
|
||
if (!check.Supported) | ||
{ | ||
throw new NotSupportedException("You have given an incorrect image format, currently supported formats are: png, jpeg, gif"); | ||
} | ||
|
||
if (bytes.Length < Mb) | ||
{ | ||
var body = JsonConvert.SerializeObject( | ||
new PostImage | ||
{ | ||
Data = $"data:image/{check.Prefix};base64,{Convert.ToBase64String(bytes.Span)}", | ||
Tags = tags | ||
}, | ||
serializerSettings | ||
); | ||
|
||
var response = await apiClient.PostAsync(config.Endpoint, new StringContent(body)); | ||
|
||
if (response.IsSuccessStatusCode) | ||
{ | ||
return JsonConvert.DeserializeObject<UploadResponse>(await response.Content.ReadAsStringAsync()).File; | ||
} | ||
|
||
throw new ResponseException("Response was not successfull; Reason: \"" + response.ReasonPhrase + "\""); | ||
} | ||
else | ||
{ | ||
var body = new MultipartFormDataContent | ||
{ | ||
{ new StringContent($"image/{check.Prefix}"), "data-type" }, | ||
{ new ByteArrayContent(bytes.Span.ToArray()), "data" }, | ||
{ new StringContent(string.Join(",", tags)), "tags" } | ||
}; | ||
|
||
var response = await apiClient.PostAsync(config.Endpoint, body); | ||
|
||
if (response.IsSuccessStatusCode) | ||
{ | ||
return JsonConvert.DeserializeObject<UploadResponse>(await response.Content.ReadAsStringAsync()).File; | ||
} | ||
|
||
throw new ResponseException("Response was not successfull; Reason: \"" + response.ReasonPhrase + "\""); | ||
} | ||
} | ||
|
||
private SupportedImage IsSupported(Span<byte> image) | ||
{ | ||
if (ImageHeaders.Validate(image, ImageType.Png)) | ||
{ | ||
return new SupportedImage(true, "png"); | ||
} | ||
if (ImageHeaders.Validate(image, ImageType.Jpeg)) | ||
{ | ||
return new SupportedImage(true, "jpeg"); | ||
} | ||
if (ImageHeaders.Validate(image, ImageType.Gif89a) | ||
|| ImageHeaders.Validate(image, ImageType.Gif87a)) | ||
{ | ||
return new SupportedImage(true, "gif"); | ||
} | ||
return new SupportedImage(false, null); | ||
} | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
src/Miki.API.Images/Miki.API.Images/Miki.API.Images.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netstandard2.1</TargetFramework> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Miki.Utils.Imaging.Headers" Version="1.0.0" /> | ||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> | ||
<PackageReference Include="System.Memory" Version="4.5.3" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
using Newtonsoft.Json; | ||
using System.Runtime.CompilerServices; | ||
|
||
[assembly: InternalsVisibleTo("Miki.API.Images.Tests")] | ||
namespace Miki.API.Images.Models | ||
{ | ||
public class Image | ||
{ | ||
[JsonProperty("ID")] | ||
public ulong Id { get; internal set; } | ||
[JsonProperty("Tags")] | ||
public string[] Tags { get; internal set; } | ||
[JsonProperty("URL")] | ||
public string Url { get; internal set; } | ||
} | ||
} |
Oops, something went wrong.