@page "/NonAdmin/Guilds/{GuildId}/StickerManager"
@using System.Diagnostics
@using System.Diagnostics.CodeAnalysis
@using System.Net.Http.Headers
@using System.Text.Json.Serialization
@using ArcaneLibs
@using ArcaneLibs.Blazor.Components
@using ArcaneLibs.Blazor.Components.Services
@using ArcaneLibs.Extensions
@using Microsoft.JSInterop.WebAssembly
@using Spacebar.AdminApi.TestClient.Services
@inject Config Config
@inject WebAssemblyJSRuntime JSRuntime
@inject JsConsoleService JsConsole
StickerManager
@* drop zone for uploads *@
Drag and drop files here, or click to select.
@Stickers.Count stickers, of which @Stickers.Count(x => x is LocalSticker) are not yet uploaded.
Save all
@if (SaveProgress.End.Value > 0) {
@SaveProgress.Start.Value / @Stickers.Count
}
@foreach (var sticker in Stickers.OrderBy(x => ulong.Parse(x.Id))) {
ID: @sticker.Id
Name:
Description:
Tags:
Available:
Type: @sticker.Type.ToString()
Format Type: @sticker.FormatType.ToString()
@if (sticker is LocalSticker ls2) {
Upload
}
else {
Save
}
Delete
}
@code {
[Parameter]
public required string GuildId { get; set; }
private List Stickers { get; set; } = [];
private Range SaveProgress { get; set; } = ..0;
protected override async Task OnInitializedAsync() {
var hc = new StreamingHttpClient();
hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Config.AccessToken);
Stickers = await hc.GetFromJsonAsync>($"{Config.ApiUrl}/api/v9/guilds/{GuildId}/stickers");
}
private class Sticker {
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("description")]
public string Description { get; set; }
[JsonPropertyName("tags")]
public string Tags { get; set; }
[JsonPropertyName("available")]
public bool Available { get; set; }
[JsonPropertyName("type")]
public StickerType Type { get; set; }
[JsonPropertyName("format_type")]
public StickerFormatType FormatType { get; set; }
}
enum StickerType {
Standard = 1,
Guild = 2
}
enum StickerFormatType {
Png = 1,
Apng = 2,
Lottie = 3,
Gif = 4
}
private class LocalSticker : Sticker {
[JsonIgnore]
public required byte[] Data { get; set; }
[JsonIgnore]
public required string FileName { get; set; }
[JsonIgnore]
public required string ContentType { get; set; }
[JsonIgnore]
public required string BlobUri { get; set; }
}
private async Task SaveChangesAsync(Sticker sticker) {
var hc = new StreamingHttpClient();
hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Config.AccessToken);
var res = await hc.PatchAsJsonAsync($"{Config.ApiUrl}/api/v9/guilds/{GuildId}/stickers/{sticker.Id}", new {
name = sticker.Name,
description = sticker.Description,
tags = sticker.Tags
// available = sticker.Available
});
return await res.Content.ReadFromJsonAsync();
}
private async Task DeleteAsync(Sticker sticker) {
var hc = new StreamingHttpClient();
hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Config.AccessToken);
if ((await hc.DeleteAsync($"{Config.ApiUrl}/api/v9/guilds/{GuildId}/stickers/{sticker.Id}")).IsSuccessStatusCode)
Stickers.Remove(sticker);
StateHasChanged();
}
private async Task UploadAsync(LocalSticker sticker) {
var hc = new StreamingHttpClient();
hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Config.AccessToken);
var req = new HttpRequestMessage(HttpMethod.Post, $"{Config.ApiUrl}/api/v9/guilds/{GuildId}/stickers");
MultipartFormDataContent content;
req.Content = content = new MultipartFormDataContent();
content.Add(new StringContent(sticker.Name), "name");
content.Add(new StringContent(sticker.Description ?? ""), "description");
content.Add(new StringContent(sticker.Tags ?? ""), "tags");
content.Add(new ByteArrayContent(sticker.Data) {
Headers = {
ContentType = new MediaTypeHeaderValue(sticker.ContentType)
}
}, "file", sticker.FileName);
var res = await hc.SendAsync(req);
return await res.Content.ReadFromJsonAsync();
}
private async Task HandleFilesDropped(InputFileChangeEventArgs arg) {
var tasks = arg.GetMultipleFiles(10000).Select(async file => {
await using var ms = new MemoryStream();
await using var stream = file.OpenReadStream(10 * 1024 * 1024);
await stream.CopyToAsync(ms);
return new LocalSticker {
Id = "0",
Name = Path.GetFileNameWithoutExtension(file.Name),
Available = true,
Type = StickerType.Guild,
FormatType = file.ContentType switch {
"image/png" => StickerFormatType.Png,
"image/apng" => StickerFormatType.Apng,
"application/json" => StickerFormatType.Lottie,
"image/gif" => StickerFormatType.Gif,
_ => throw new Exception("Unsupported sticker format: " + file.ContentType)
},
FileName = file.Name,
ContentType = file.ContentType,
Data = ms.ToArray(),
// DataUri = $"data:{file.ContentType};base64,{Convert.ToBase64String(ms.ToArray())}"
BlobUri = await GetBlobUriAsync(ms.ToArray(), file.ContentType)
};
}).ToList().ToAsyncResultEnumerable();
await foreach (var sticker in tasks) {
Stickers.Add(sticker);
if (Stickers.Count % 25 == 0) StateHasChanged();
}
StateHasChanged();
}
private async Task GetBlobUriAsync(byte[] data, string type = "application/octet-stream") {
await using var jsBlob = JSRuntime.InvokeConstructor("Blob", (byte[][])[data], new { type });
var uri = JSRuntime.Invoke("URL.createObjectURL", jsBlob);
return uri;
}
private async Task SaveAll() {
SaveProgress = ..Stickers.Count;
StateHasChanged();
var ss = new SemaphoreSlim(32, 32);
var tasks = Stickers.ToList().Select(async s => {
return await ProcessSticker();
async Task<(Sticker Old, Sticker? New)> ProcessSticker() {
await ss.WaitAsync();
try {
return (Old: s, New: await (s is LocalSticker ls ? UploadAsync(ls) : SaveChangesAsync(s)));
}
finally {
ss.Release();
}
}
}).ToList().ToAsyncResultEnumerable();
var sw = Stopwatch.StartNew();
await foreach (var r in tasks) {
SaveProgress = (SaveProgress.Start.Value + 1)..SaveProgress.End.Value;
Stickers[Stickers.IndexOf(r.Old)] = r.New ?? r.Old;
if (r.Old is LocalSticker ls)
JSRuntime.InvokeVoid("URL.revokeObjectURL", ls.BlobUri);
if (sw.ElapsedMilliseconds > 2000) {
StateHasChanged();
await Task.Delay(100);
sw.Restart();
}
}
SaveProgress = ..0;
StateHasChanged();
}
}