diff --git a/extra/admin-api/Interop/Spacebar.Interop.Cdn.Abstractions/FilesystemFileSource.cs b/extra/admin-api/Interop/Spacebar.Interop.Cdn.Abstractions/FilesystemFileSource.cs
index a3f0a7d8..8a67dfe3 100644
--- a/extra/admin-api/Interop/Spacebar.Interop.Cdn.Abstractions/FilesystemFileSource.cs
+++ b/extra/admin-api/Interop/Spacebar.Interop.Cdn.Abstractions/FilesystemFileSource.cs
@@ -1,20 +1,27 @@
-using ArcaneLibs;
-
namespace Spacebar.Interop.Cdn.Abstractions;
public class FilesystemFileSource(string baseUrl) : IFileSource {
public string BaseUrl => baseUrl;
- public async Task Init(CancellationToken? cancellationToken = null) {
+ private string GetPathSafe(string path) {
+ var p = Path.Join(baseUrl, path);
+ if (!p.StartsWith(baseUrl)) throw new UnauthorizedAccessException("Invalid path: " + p);
+ Console.WriteLine($"Resolved \"safe\" path: {p}");
+ return p;
+ }
+
+ public async Task<IFileSource> Init(CancellationToken? cancellationToken = null) {
foreach (var dir in new[] { "avatars", "banners", "icons", "stickers", "emojis" }) {
var fullPath = Path.Join(baseUrl, dir);
if (!Directory.Exists(fullPath))
Directory.CreateDirectory(fullPath);
}
+
+ return this;
}
public async Task<FileInfo> GetFile(string path, CancellationToken? cancellationToken = null) {
- await using var rs = File.OpenRead(Path.Join(baseUrl, path));
+ await using var rs = File.OpenRead(GetPathSafe(path));
var ms = new MemoryStream();
await rs.CopyToAsync(ms);
return new() {
@@ -24,11 +31,11 @@ public class FilesystemFileSource(string baseUrl) : IFileSource {
}
public Task<bool> FileExists(string path, CancellationToken? cancellationToken = null) {
- return Task.FromResult(File.Exists(Path.Join(baseUrl, path)));
+ return Task.FromResult(File.Exists(GetPathSafe(path)));
}
public async Task WriteFile(string path, Stream stream) {
- var fullPath = Path.Join(baseUrl, path);
+ var fullPath = GetPathSafe(path);
// Console.WriteLine($"Writing file to {fullPath}... ");
if (!Directory.Exists(Path.GetDirectoryName(fullPath)!))
Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!);
@@ -43,6 +50,6 @@ public class FilesystemFileSource(string baseUrl) : IFileSource {
// return Mimes.GetMime(mic.First().Format);
// }
public Task<bool> DirectoryExists(string path) {
- return Task.FromResult(Directory.Exists(Path.Join(baseUrl, path)));
+ return Task.FromResult(Directory.Exists(GetPathSafe(path)));
}
}
\ No newline at end of file
diff --git a/extra/admin-api/Interop/Spacebar.Interop.Cdn.Abstractions/IFileSource.cs b/extra/admin-api/Interop/Spacebar.Interop.Cdn.Abstractions/IFileSource.cs
index a69764d5..41f27c3f 100644
--- a/extra/admin-api/Interop/Spacebar.Interop.Cdn.Abstractions/IFileSource.cs
+++ b/extra/admin-api/Interop/Spacebar.Interop.Cdn.Abstractions/IFileSource.cs
@@ -2,7 +2,7 @@ namespace Spacebar.Interop.Cdn.Abstractions;
public interface IFileSource {
public string BaseUrl { get; }
- public Task Init(CancellationToken? cancellationToken = null);
+ public Task<IFileSource> Init(CancellationToken? cancellationToken = null);
public Task<FileInfo> GetFile(string path, CancellationToken? cancellationToken = null);
public Task<bool> FileExists(string path, CancellationToken? cancellationToken = null);
public Task WriteFile(string path, Stream stream);
diff --git a/extra/admin-api/Interop/Spacebar.Interop.Cdn.Abstractions/ProxyFileSource.cs b/extra/admin-api/Interop/Spacebar.Interop.Cdn.Abstractions/ProxyFileSource.cs
index 6a84bd4d..b0990706 100644
--- a/extra/admin-api/Interop/Spacebar.Interop.Cdn.Abstractions/ProxyFileSource.cs
+++ b/extra/admin-api/Interop/Spacebar.Interop.Cdn.Abstractions/ProxyFileSource.cs
@@ -18,7 +18,9 @@ public class ProxyFileSource(string baseUrl) : IFileSource {
public string BaseUrl => baseUrl;
- public async Task Init(CancellationToken? cancellationToken = null) { }
+ public async Task<IFileSource> Init(CancellationToken? cancellationToken = null) {
+ return this;
+ }
public async Task<FileInfo> GetFile(string path, CancellationToken? cancellationToken = null) {
var res = await _cache.GetOrAdd(path, async () => {
@@ -44,6 +46,7 @@ public class ProxyFileSource(string baseUrl) : IFileSource {
await using var s = await res.Content.ReadAsStreamAsync();
Console.WriteLine($"Got {res.StatusCode}: ({res.Content.Headers.ContentLength}b of {res.Content.Headers.ContentType})\n{s.ReadToEnd().AsString()}");
}
+
return res.IsSuccessStatusCode;
}
diff --git a/extra/admin-api/Spacebar.Cdn.Worker/DiscordImageResizeService.cs b/extra/admin-api/Spacebar.Cdn.Worker/DiscordImageResizeService.cs
new file mode 100644
index 00000000..64e72660
--- /dev/null
+++ b/extra/admin-api/Spacebar.Cdn.Worker/DiscordImageResizeService.cs
@@ -0,0 +1,91 @@
+using System.Runtime.Serialization;
+using ImageMagick;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Spacebar.AdminApi.TestClient.Services.Services;
+
+public class DiscordImageResizeParams {
+ public uint? Size { get; set; }
+ public DiscordImageResizeQuality Quality { get; set; } = DiscordImageResizeQuality.High;
+ public bool KeepAspectRatio { get; set; } = true;
+ public bool Passthrough { get; set; } = true;
+ public bool Animated { get; set; } = true;
+
+ public bool SpacebarAllowUpscale { get; set; } = false;
+ public bool SpacebarOptimiseGif { get; set; } = true;
+
+ public string ToSerializedName() {
+ return $"{(Animated ? "a_" : "")}{Size}px_{Quality.ToString()}_u.{SpacebarAllowUpscale}_o.{SpacebarOptimiseGif}";
+ }
+}
+
+public static class HttpRequestExtensions {
+ extension(HttpRequest request) {
+ public DiscordImageResizeParams GetResizeParams() {
+ return new() {
+ Size = request.Query.ContainsKey("size") && uint.TryParse(request.Query["size"], out uint size) ? size : null,
+ Quality = request.Query.ContainsKey("quality") && Enum.TryParse<DiscordImageResizeQuality>(request.Query["quality"], true, out var quality)
+ ? quality
+ : DiscordImageResizeQuality.High,
+ KeepAspectRatio = !request.Query.ContainsKey("keepAspectRatio") || !bool.TryParse(request.Query["keepAspectRatio"], out bool kar) || kar,
+ Passthrough = request.Query.ContainsKey("passthrough") && bool.TryParse(request.Query["passthrough"], out bool pt) && pt,
+ Animated = request.Query.ContainsKey("animated") && bool.TryParse(request.Query["animated"], out bool an) && an,
+ SpacebarAllowUpscale = request.Query.ContainsKey("allowUpscale") && bool.TryParse(request.Query["allowUpscale"], out bool au) && au,
+ SpacebarOptimiseGif = request.Query.ContainsKey("optimiseGif") && bool.TryParse(request.Query["optimiseGif"], out bool og) && og,
+ };
+ }
+ }
+
+ extension (HttpResponse response) {
+ public void SetSuccessCacheHeader() {
+ int cacheDuration = (int)TimeSpan.FromHours(6).TotalSeconds;
+ response.Headers.CacheControl = $"public, max-age={cacheDuration}, s-maxage={cacheDuration}, immutable";
+ }
+
+ public void SetFailureCacheHeader() {
+ int cacheDuration = (int)TimeSpan.FromMinutes(5).TotalSeconds;
+ response.Headers.CacheControl = $"public, max-age={cacheDuration}, s-maxage={cacheDuration}, immutable";
+ }
+ }
+}
+
+public enum DiscordImageResizeQuality {
+ [EnumMember(Value = "low")] Low,
+ [EnumMember(Value = "high")] High,
+ [EnumMember(Value = "lossless")] Lossless
+}
+
+public class DiscordImageResizeService {
+ //(PixelArtDetectionService pads) {
+ public MagickImageCollection Apply(MagickImageCollection img, DiscordImageResizeParams resizeParams) {
+ if (resizeParams.Passthrough) return img;
+ if (img.First().Format == MagickFormat.Gif) {
+ Console.WriteLine("Coalescing gif for resize");
+ img.Coalesce();
+ }
+
+ if (resizeParams.Size.HasValue) {
+ if (resizeParams.Size > 4096)
+ resizeParams.Size = 4096;
+
+ if (img.Max(x => Math.Max(x.Height, x.Width)) > resizeParams.Size || resizeParams.SpacebarAllowUpscale) {
+ Parallel.ForEach(img, new ParallelOptions() { MaxDegreeOfParallelism = 8 }, frame => {
+ if (resizeParams.Size.HasValue) {
+ uint oldWidth = frame.Width, oldHeight = frame.Height;
+ // pads.IsPixelArt(frame)
+ frame.Resize(resizeParams.Size.Value, resizeParams.Size.Value,
+ resizeParams.Quality == DiscordImageResizeQuality.Low ? FilterType.Point : FilterType.Gaussian);
+ Console.WriteLine($"Resized frame from {oldWidth}x{oldHeight} to {frame.Width}x{frame.Height}: {img.IndexOf(frame)}/{img.Count}");
+ }
+ });
+ }
+ }
+
+ if (img.First().Format == MagickFormat.Gif && resizeParams.SpacebarOptimiseGif) {
+ Console.WriteLine("Optimizing gif after resize");
+ img.OptimizePlus();
+ }
+
+ return img;
+ }
+}
\ No newline at end of file
diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Program.cs b/extra/admin-api/Spacebar.Cdn.Worker/Program.cs
index c75151b5..a722e666 100644
--- a/extra/admin-api/Spacebar.Cdn.Worker/Program.cs
+++ b/extra/admin-api/Spacebar.Cdn.Worker/Program.cs
@@ -2,8 +2,11 @@ using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using ArcaneLibs;
+using ArcaneLibs.Extensions.Streams;
using ImageMagick;
+using Microsoft.AspNetCore.Mvc;
using Spacebar.AdminApi.TestClient.Services.Helpers;
+using Spacebar.AdminApi.TestClient.Services.Services;
using Spacebar.Cdn.Worker;
using Spacebar.Interop.Cdn.Abstractions;
@@ -57,8 +60,8 @@ MagickNET.Initialize();
// builder.WebHost.ConfigureKestrel(opts => opts.ListenUnixSocket(Environment.GetEnvironmentVariable("SOCKET_PATH")!));
-builder.Services.AddSingleton<IFileSource>(new FilesystemFileSource(Environment.GetEnvironmentVariable("STORAGE_PATH") ?? throw new InvalidOperationException("STORAGE_PATH not set!")));
-// builder.Services.AddSingleton<DiscordImageResizeService>();
+builder.Services.AddSingleton<IFileSource>(await new FilesystemFileSource(Environment.GetEnvironmentVariable("STORAGE_PATH") ?? throw new InvalidOperationException("STORAGE_PATH not set!")).Init());
+builder.Services.AddSingleton<DiscordImageResizeService>();
builder.Services.AddControllers();
var app = builder.Build();
@@ -87,6 +90,13 @@ app.MapGet("/defaultAvatar/_{bg:length(6)}_{fg:length(6)}.{ext}", async (HttpCon
return Results.File(res, Mimes.GetMime(Mimes.GetFormatForExtension(ext)));
});
-app.MapGet("/*", async (ctx) => { });
+app.MapGet("/scale/{*path}", async (HttpContext ctx, IFileSource ifs, DiscordImageResizeService dirs, string path) => {
+ var f = await ifs.GetFile(path);
+ f.Stream.Position = 0;
+ var res = dirs.Apply(new MagickImageCollection(f.Stream), ctx.Request.GetResizeParams());
+ await ctx.Response.StartAsync();
+ await res.WriteAsync(ctx.Response.Body);
+ await ctx.Response.CompleteAsync();
+});
app.Run();
\ No newline at end of file
diff --git a/extra/admin-api/Spacebar.Cdn.Worker/emmatests.sh b/extra/admin-api/Spacebar.Cdn.Worker/emmatests.sh
new file mode 100755
index 00000000..88a97ae1
--- /dev/null
+++ b/extra/admin-api/Spacebar.Cdn.Worker/emmatests.sh
@@ -0,0 +1,7 @@
+#! /usr/bin/env sh
+#for s in {16,32,48,64,96,128,256,512,4096}; do time curl http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db\?size=$s\&quality\=high | kitty icat; done
+for i in {1..6}; do
+ for s in {16,32,48,64,96,128,256,512,1024,2048,4096}; do
+ echo curl http://localhost:5000/defaultAvatar/${i}.png\?size=$s\&quality\=low #| kitty icat
+ done
+done
diff --git a/extra/admin-api/Spacebar.Cdn.Worker/outputs.nix b/extra/admin-api/Spacebar.Cdn.Worker/outputs.nix
index ad451920..12d51d9d 100644
--- a/extra/admin-api/Spacebar.Cdn.Worker/outputs.nix
+++ b/extra/admin-api/Spacebar.Cdn.Worker/outputs.nix
@@ -28,7 +28,7 @@ flake-utils.lib.eachSystem flake-utils.lib.allSystems (
makeWorkerPackage =
arch: variant:
buildSpacebarDotnetModule {
- name = "Spacebar.Cdn.Worker-${variant}.${arch}";
+ name = "Spacebar.Cdn.Worker.${variant}.${arch}";
nugetDeps = ./deps.${variant}.${arch}.json;
projectFile = "Spacebar.Cdn.Worker.${variant}.${arch}.csproj";
srcRoot = ./.;
@@ -36,6 +36,17 @@ flake-utils.lib.eachSystem flake-utils.lib.allSystems (
projectReferences = [
proj.Spacebar-Interop-Cdn-Abstractions
];
+ nativeBuildInputs = [
+ pkgs.autoPatchelfHook
+ pkgs.libgcc.lib
+ ];
+ runtimeDependencies = [ (lib.getLib pkgs.libgcc) ];
+ #makeWrapperArgs = [
+ # "--prefix"
+ # "LD_PRELOAD"
+ # ":"
+ # "${pkgs.libgcc}/lib/libgomp.so"
+ #];
};
in
{
diff --git a/extra/admin-api/Spacebar.Cdn.Worker/test.html b/extra/admin-api/Spacebar.Cdn.Worker/test.html
new file mode 100644
index 00000000..fbd2f9e9
--- /dev/null
+++ b/extra/admin-api/Spacebar.Cdn.Worker/test.html
@@ -0,0 +1,91 @@
+<img src="http://localhost:5000/defaultAvatar/1.png?size=16&quality=high" alt="1 s=16 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/1.png?size=32&quality=high" alt="1 s=32 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/1.png?size=48&quality=high" alt="1 s=48 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/1.png?size=64&quality=high" alt="1 s=64 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/1.png?size=96&quality=high" alt="1 s=96 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/1.png?size=128&quality=high" alt="1 s=128 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/1.png?size=256&quality=high" alt="1 s=256 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/1.png?size=512&quality=high" alt="1 s=512 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/1.png?size=1024&quality=high" alt="1 s=1024 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/1.png?size=2048&quality=high" alt="1 s=2048 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/1.png?size=4096&quality=high" alt="1 s=4096 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/2.png?size=16&quality=high" alt="2 s=16 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/2.png?size=32&quality=high" alt="2 s=32 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/2.png?size=48&quality=high" alt="2 s=48 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/2.png?size=64&quality=high" alt="2 s=64 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/2.png?size=96&quality=high" alt="2 s=96 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/2.png?size=128&quality=high" alt="2 s=128 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/2.png?size=256&quality=high" alt="2 s=256 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/2.png?size=512&quality=high" alt="2 s=512 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/2.png?size=1024&quality=high" alt="2 s=1024 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/2.png?size=2048&quality=high" alt="2 s=2048 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/2.png?size=4096&quality=high" alt="2 s=4096 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/3.png?size=16&quality=high" alt="3 s=16 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/3.png?size=32&quality=high" alt="3 s=32 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/3.png?size=48&quality=high" alt="3 s=48 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/3.png?size=64&quality=high" alt="3 s=64 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/3.png?size=96&quality=high" alt="3 s=96 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/3.png?size=128&quality=high" alt="3 s=128 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/3.png?size=256&quality=high" alt="3 s=256 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/3.png?size=512&quality=high" alt="3 s=512 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/3.png?size=1024&quality=high" alt="3 s=1024 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/3.png?size=2048&quality=high" alt="3 s=2048 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/3.png?size=4096&quality=high" alt="3 s=4096 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/4.png?size=16&quality=high" alt="4 s=16 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/4.png?size=32&quality=high" alt="4 s=32 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/4.png?size=48&quality=high" alt="4 s=48 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/4.png?size=64&quality=high" alt="4 s=64 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/4.png?size=96&quality=high" alt="4 s=96 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/4.png?size=128&quality=high" alt="4 s=128 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/4.png?size=256&quality=high" alt="4 s=256 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/4.png?size=512&quality=high" alt="4 s=512 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/4.png?size=1024&quality=high" alt="4 s=1024 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/4.png?size=2048&quality=high" alt="4 s=2048 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/4.png?size=4096&quality=high" alt="4 s=4096 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/5.png?size=16&quality=high" alt="5 s=16 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/5.png?size=32&quality=high" alt="5 s=32 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/5.png?size=48&quality=high" alt="5 s=48 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/5.png?size=64&quality=high" alt="5 s=64 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/5.png?size=96&quality=high" alt="5 s=96 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/5.png?size=128&quality=high" alt="5 s=128 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/5.png?size=256&quality=high" alt="5 s=256 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/5.png?size=512&quality=high" alt="5 s=512 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/5.png?size=1024&quality=high" alt="5 s=1024 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/5.png?size=2048&quality=high" alt="5 s=2048 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/5.png?size=4096&quality=high" alt="5 s=4096 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/6.png?size=16&quality=high" alt="6 s=16 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/6.png?size=32&quality=high" alt="6 s=32 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/6.png?size=48&quality=high" alt="6 s=48 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/6.png?size=64&quality=high" alt="6 s=64 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/6.png?size=96&quality=high" alt="6 s=96 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/6.png?size=128&quality=high" alt="6 s=128 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/6.png?size=256&quality=high" alt="6 s=256 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/6.png?size=512&quality=high" alt="6 s=512 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/6.png?size=1024&quality=high" alt="6 s=1024 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/6.png?size=2048&quality=high" alt="6 s=2048 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/defaultAvatar/6.png?size=4096&quality=high" alt="6 s=4096 q=low" width="128" height="128"/>
+
+
+<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=16&quality=low" alt="a s=16 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=32&quality=low" alt="a s=32 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=48&quality=low" alt="a s=48 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=64&quality=low" alt="a s=64 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=96&quality=low" alt="a s=96 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=128&quality=low" alt="a s=128 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=256&quality=low" alt="a s=256 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=512&quality=low" alt="a s=512 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=1024&quality=low" alt="a s=1024 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=2048&quality=low" alt="a s=2048 q=low" width="128" height="128"/>
+<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=4096&quality=low" alt="a s=4096 q=low" width="128" height="128"/>
+
+<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=16&quality=high" alt="a s=16 q=high" width="128" height="128"/>
+<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=32&quality=high" alt="a s=32 q=high" width="128" height="128"/>
+<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=48&quality=high" alt="a s=48 q=high" width="128" height="128"/>
+<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=64&quality=high" alt="a s=64 q=high" width="128" height="128"/>
+<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=96&quality=high" alt="a s=96 q=high" width="128" height="128"/>
+<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=128&quality=high" alt="a s=128 q=high" width="128" height="128"/>
+<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=256&quality=high" alt="a s=256 q=high" width="128" height="128"/>
+<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=512&quality=high" alt="a s=512 q=high" width="128" height="128"/>
+<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=1024&quality=high" alt="a s=1024 q=high" width="128" height="128"/>
+<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=2048&quality=high" alt="a s=2048 q=high" width="128" height="128"/>
+<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=4096&quality=high" alt="a s=4096 q=high" width="128" height="128"/>
diff --git a/extra/admin-api/Spacebar.Cdn/Mimes.cs b/extra/admin-api/Spacebar.Cdn/Mimes.cs
index 273df2dd..efef1e80 100644
--- a/extra/admin-api/Spacebar.Cdn/Mimes.cs
+++ b/extra/admin-api/Spacebar.Cdn/Mimes.cs
@@ -31,8 +31,7 @@ public static class Mimes {
or "emf" // some microsoft meta format, windows only
or "mpr" // Magick Persistent Registry - basically a resident in-memory image
) throw new AccessViolationException("Disallowed extension: " + extension);
-
- MagickFormat
+
var matchingFormat = Enum.GetNames<MagickFormat>().FirstOrDefault(f => f.ToLower() == extension);
if (string.IsNullOrWhiteSpace(matchingFormat)) throw new InvalidEnumArgumentException("Unknown format: " + extension);
return Enum.TryParse(matchingFormat, out MagickFormat fmt) ? fmt : throw new InvalidEnumArgumentException("Unknown format: " + extension);
diff --git a/nix/lib/buildSpacebarDotnetModule.nix b/nix/lib/buildSpacebarDotnetModule.nix
index c76ab4e2..7b7c02ae 100644
--- a/nix/lib/buildSpacebarDotnetModule.nix
+++ b/nix/lib/buildSpacebarDotnetModule.nix
@@ -8,37 +8,54 @@
useAppHost ? null,
packNupkg ? true,
srcRoot ? ./.,
+ ...
}@args:
-pkgs.buildDotnetModule rec {
- inherit
- projectReferences
- nugetDeps
- projectFile
- runtimeId
- useAppHost
- srcRoot
- packNupkg
- ;
+pkgs.buildDotnetModule (pkgs.lib.recursiveUpdate
+ (
+ rec {
+ inherit
+ projectReferences
+ nugetDeps
+ projectFile
+ runtimeId
+ useAppHost
+ srcRoot
+ packNupkg
+ ;
- pname = "${name}";
- version = "1.0.0-" + rVersion;
- dotnetPackFlags = [
- "--include-symbols"
- "--include-source"
- "--version-suffix ${rVersion}"
- ];
- # dotnetFlags = [ "-v:n" ]; # diag
- dotnet-sdk = pkgs.dotnet-sdk_10;
- dotnet-runtime = pkgs.dotnet-aspnetcore_10;
- src = pkgs.lib.cleanSource srcRoot;
- meta = with pkgs.lib; {
- description = "Spacebar Server, Typescript Edition (C# extensions)";
- homepage = "https://github.com/spacebarchat/server";
- license = licenses.agpl3Plus;
- maintainers = with maintainers; [ RorySys ];
- mainProgram = name;
- };
-}
-// {
- __nugetDeps = args.nugetDeps;
-}
+ pname = "${name}";
+ version = "1.0.0-" + rVersion;
+ dotnetPackFlags = [
+ "--include-symbols"
+ "--include-source"
+ "--version-suffix ${rVersion}"
+ ];
+ # dotnetFlags = [ "-v:n" ]; # diag
+ dotnet-sdk = pkgs.dotnet-sdk_10;
+ dotnet-runtime = pkgs.dotnet-aspnetcore_10;
+ src = pkgs.lib.cleanSource srcRoot;
+ meta = with pkgs.lib; {
+ description = "Spacebar Server, Typescript Edition (C# extensions)";
+ homepage = "https://github.com/spacebarchat/server";
+ license = licenses.agpl3Plus;
+ maintainers = with maintainers; [ RorySys ];
+ mainProgram = name;
+ };
+ }
+ // {
+ __nugetDeps = args.nugetDeps;
+ }
+ )
+ (
+ builtins.removeAttrs args [
+ "name"
+ "nugetDeps"
+ "projectReferences"
+ "projectFile"
+ "runtimeId"
+ "useAppHost"
+ "packNupkg"
+ "srcRoot"
+ ]
+ )
+)
\ No newline at end of file
|