From 9a56b88d70bd6c245d04cdd4bf0be534dd9b1ece Mon Sep 17 00:00:00 2001 From: MrlingXD Date: Sat, 12 Jul 2025 21:48:53 +0800 Subject: [PATCH] [Onebot] Improves file stream handling --- .../Operation/Ability/UploadImageOperation.cs | 14 +++-- .../Operation/Generic/OcrImageOperation.cs | 14 +++-- .../Generic/SetGroupPortraitOperation.cs | 15 +++--- .../Message/Entity/CommonResolver.cs | 54 +++++++++++++++++-- 4 files changed, 76 insertions(+), 21 deletions(-) diff --git a/Lagrange.OneBot/Core/Operation/Ability/UploadImageOperation.cs b/Lagrange.OneBot/Core/Operation/Ability/UploadImageOperation.cs index 45b13ece5..e9e4d81f1 100644 --- a/Lagrange.OneBot/Core/Operation/Ability/UploadImageOperation.cs +++ b/Lagrange.OneBot/Core/Operation/Ability/UploadImageOperation.cs @@ -12,13 +12,17 @@ public class UploadImageOperation : IOperation { public async Task HandleOperation(BotContext context, JsonNode? payload) { - if (payload?["file"]?.ToString() is { } file && CommonResolver.ResolveStream(file) is { } stream) + if (payload?["file"]?.ToString() is { } file) { - var entity = new ImageEntity(stream); - var url = await context.UploadImage(entity); - return new OneBotResult(url, 0, "ok"); + using var stream = await CommonResolver.ResolveStreamAsync(file); + if (stream != null) + { + var entity = new ImageEntity(stream); + var url = await context.UploadImage(entity); + return new OneBotResult(url, 0, "ok"); + } } - throw new Exception(); + throw new Exception("Failed to resolve image file"); } } \ No newline at end of file diff --git a/Lagrange.OneBot/Core/Operation/Generic/OcrImageOperation.cs b/Lagrange.OneBot/Core/Operation/Generic/OcrImageOperation.cs index b203c4688..45cd4e7e5 100644 --- a/Lagrange.OneBot/Core/Operation/Generic/OcrImageOperation.cs +++ b/Lagrange.OneBot/Core/Operation/Generic/OcrImageOperation.cs @@ -16,12 +16,16 @@ public class OcrImageOperation() : IOperation { public async Task HandleOperation(BotContext context, JsonNode? payload) { - if (payload.Deserialize(SerializerOptions.DefaultOptions) is { } data && CommonResolver.ResolveStream(data.Image) is { } stream) + if (payload.Deserialize(SerializerOptions.DefaultOptions) is { } data) { - var entity = new ImageEntity(stream); - var res = await context.OcrImage(entity); - return new OneBotResult(res, 0, "ok"); + using var stream = await CommonResolver.ResolveStreamAsync(data.Image); + if (stream != null) + { + var entity = new ImageEntity(stream); + var res = await context.OcrImage(entity); + return new OneBotResult(res, 0, "ok"); + } } - throw new Exception(); + throw new Exception("Failed to resolve image for OCR"); } } \ No newline at end of file diff --git a/Lagrange.OneBot/Core/Operation/Generic/SetGroupPortraitOperation.cs b/Lagrange.OneBot/Core/Operation/Generic/SetGroupPortraitOperation.cs index eeda99954..647761558 100644 --- a/Lagrange.OneBot/Core/Operation/Generic/SetGroupPortraitOperation.cs +++ b/Lagrange.OneBot/Core/Operation/Generic/SetGroupPortraitOperation.cs @@ -14,14 +14,15 @@ public async Task HandleOperation(BotContext context, JsonNode? pa { if (payload?["file"]?.ToString() is { } portrait) { - var image = CommonResolver.ResolveStream(portrait); - if (image == null) throw new Exception(); - - var imageEntity = new ImageEntity(image); - bool result = await context.SetAvatar(imageEntity); - return new OneBotResult(null, result ? 0 : 1, ""); + using var image = await CommonResolver.ResolveStreamAsync(portrait); + if (image != null) + { + var imageEntity = new ImageEntity(image); + bool result = await context.SetAvatar(imageEntity); + return new OneBotResult(null, result ? 0 : 1, result ? "ok" : "failed"); + } } - throw new Exception(); + throw new Exception("Failed to resolve avatar image"); } } \ No newline at end of file diff --git a/Lagrange.OneBot/Message/Entity/CommonResolver.cs b/Lagrange.OneBot/Message/Entity/CommonResolver.cs index fa2a9accb..198113b03 100644 --- a/Lagrange.OneBot/Message/Entity/CommonResolver.cs +++ b/Lagrange.OneBot/Message/Entity/CommonResolver.cs @@ -4,30 +4,76 @@ public static class CommonResolver { private static readonly HttpClient Client = new(); + public static async Task ResolveAsync(string url, CancellationToken cancellationToken = default) + { + try + { + if (url.StartsWith("base64://")) + return Convert.FromBase64String(url.Substring("base64://".Length)); + + Uri uri = new(url); + + return uri.Scheme switch + { + "http" or "https" => await (await Client.GetAsync(uri, cancellationToken)).Content.ReadAsByteArrayAsync(cancellationToken), + "file" => await File.ReadAllBytesAsync(Path.GetFullPath(uri.LocalPath), cancellationToken), + _ => null, + }; + } + catch (Exception ex) when (ex is HttpRequestException or FileNotFoundException or DirectoryNotFoundException) + { + return null; + } + } + + public static async Task ResolveStreamAsync(string url, CancellationToken cancellationToken = default) + { + try + { + if (url.StartsWith("base64://")) + return new MemoryStream(Convert.FromBase64String(url.Substring("base64://".Length))); + + Uri uri = new(url); + + return uri.Scheme switch + { + "http" or "https" => await (await Client.GetAsync(uri, cancellationToken)).Content.ReadAsStreamAsync(cancellationToken), + "file" => new FileStream(Path.GetFullPath(uri.LocalPath), FileMode.Open, FileAccess.Read, FileShare.Read), + _ => null, + }; + } + catch (Exception ex) when (ex is HttpRequestException or FileNotFoundException or DirectoryNotFoundException) + { + return null; + } + } + public static byte[]? Resolve(string url) { - if (url.StartsWith("base64://")) return Convert.FromBase64String(url.Replace("base64://", "")); + if (url.StartsWith("base64://")) + return Convert.FromBase64String(url.Substring("base64://".Length)); Uri uri = new(url); return uri.Scheme switch { - "http" or "https" => Client.GetAsync(uri).Result.Content.ReadAsByteArrayAsync().Result, "file" => File.ReadAllBytes(Path.GetFullPath(uri.LocalPath)), + "http" or "https" => ResolveAsync(url).GetAwaiter().GetResult(), _ => null, }; } public static Stream? ResolveStream(string url) { - if (url.StartsWith("base64://")) return new MemoryStream(Convert.FromBase64String(url.Replace("base64://", ""))); + if (url.StartsWith("base64://")) + return new MemoryStream(Convert.FromBase64String(url.Substring("base64://".Length))); Uri uri = new(url); return uri.Scheme switch { - "http" or "https" => Client.GetAsync(uri).Result.Content.ReadAsStreamAsync().Result, "file" => new FileStream(Path.GetFullPath(uri.LocalPath), FileMode.Open, FileAccess.Read, FileShare.Read), + "http" or "https" => ResolveStreamAsync(url).GetAwaiter().GetResult(), _ => null, }; }