diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/ValueFileExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/ValueFileExtensions.cs new file mode 100644 index 00000000..8ec238bd --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/ValueFileExtensions.cs @@ -0,0 +1,54 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using System.IO; +using System.Net.Http; + +namespace Snap.Hutao.Core.IO; + +internal static class ValueFileExtensions +{ + public static async ValueTask DownloadAsync(this ValueFile valueFile, string url) + { + try + { + using (HttpClient httpClient = new()) + { + using (HttpResponseMessage response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false)) + { + long contentLength = response.Content.Headers.ContentLength ?? 0; + using (Stream content = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) + { + using (FileStream stream = File.Create((string)valueFile)) + { + Progress progress = new(); + await new StreamCopyWorker(content, stream, contentLength).CopyAsync(progress).ConfigureAwait(false); + return true; + } + } + } + } + } + catch (Exception) + { + return false; + } + } + + public static async ValueTask WritePngBase64Async(this ValueFile valueFile, string base64) + { + try + { + using (FileStream stream = File.Create((string)valueFile)) + { + byte[] bytes = System.Convert.FromBase64String(base64); + await stream.WriteAsync(bytes).ConfigureAwait(false); + return true; + } + } + catch (Exception) + { + return false; + } + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj index d22d2b04..ee4a78f5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj +++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj @@ -310,6 +310,7 @@ + diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSBridge.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSBridge.cs index b2561573..4513be0a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSBridge.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSBridge.cs @@ -2,8 +2,12 @@ // Licensed under the MIT license. using Microsoft.Web.WebView2.Core; +using Qhy04.WebView2.DevTools.Protocol.WinUI3; using Snap.Hutao.Core.DependencyInjection.Abstraction; +using Snap.Hutao.Core.IO; +using Snap.Hutao.Factory.Picker; using Snap.Hutao.Service.Metadata; +using Snap.Hutao.Service.Notification; using Snap.Hutao.Service.User; using Snap.Hutao.ViewModel.User; using Snap.Hutao.Web.Bridge.Model; @@ -84,6 +88,7 @@ internal class MiHoYoJSBridge private readonly IServiceProvider serviceProvider; private readonly ITaskContext taskContext; private readonly ILogger logger; + private readonly DevToolsProtocolHelper devToolsProtocolHelper; private readonly TypedEventHandler webMessageReceivedEventHandler; private readonly TypedEventHandler domContentLoadedEventHandler; @@ -100,6 +105,7 @@ internal class MiHoYoJSBridge taskContext = serviceProvider.GetRequiredService(); logger = serviceProvider.GetRequiredService>(); + devToolsProtocolHelper = coreWebView2.GetDevToolsProtocolHelper(); webMessageReceivedEventHandler = OnWebMessageReceived; domContentLoadedEventHandler = OnDOMContentLoaded; @@ -362,8 +368,89 @@ internal class MiHoYoJSBridge return null; } - protected virtual IJsBridgeResult? Share(JsParam param) + protected virtual async ValueTask Share(JsParam param) { + IInfoBarService infoBarService = serviceProvider.GetRequiredService(); + IFileSystemPickerInteraction fileSystemPickerInteraction = serviceProvider.GetRequiredService(); + + string shareType = param.Payload.Type; + ShareContent shareContent = param.Payload.Content; + if (shareType == "image") + { + if (shareContent.ImageUrl is not null) + { + string fileName = shareContent.ImageUrl.Split("/").Last(); + string format = fileName.Split(".").Last(); + + (bool isOk, ValueFile file) = fileSystemPickerInteraction.SaveFile( + "保存分享图片", + fileName, + [("图片文件", format)]); + + if (isOk) + { + if (await file.DownloadAsync(shareContent.ImageUrl).ConfigureAwait(false)) + { + infoBarService.Success("保存成功", "成功保存到指定位置"); + } + else + { + infoBarService.Error("保存失败", "无法保存到指定位置"); + } + } + } + else if (shareContent.ImageBase64 is not null) + { + string fileName = "image.png"; + string format = "png"; + + (bool isOk, ValueFile file) = fileSystemPickerInteraction.SaveFile( + "保存分享图片", + fileName, + [("图片文件", format)]); + + if (isOk) + { + if (await file.WritePngBase64Async(shareContent.ImageBase64).ConfigureAwait(false)) + { + infoBarService.Success("保存成功", "成功保存到指定位置"); + } + else + { + infoBarService.Error("保存失败", "无法保存到指定位置"); + } + } + } + } + else if (shareType == "screenshot") + { + string fileName = "image.png"; + string format = "png"; + + (bool isOk, ValueFile file) = fileSystemPickerInteraction.SaveFile( + "保存分享图片", + fileName, + [("图片文件", format)]); + + if (isOk) + { + if (shareContent.Preview) + { + await taskContext.SwitchToMainThreadAsync(); + string base64 = await devToolsProtocolHelper.Page.CaptureScreenshotAsync(format: "png", captureBeyondViewport: true).ConfigureAwait(false); + + if (await file.WritePngBase64Async(base64).ConfigureAwait(false)) + { + infoBarService.Success("保存成功", "成功保存到指定位置"); + } + else + { + infoBarService.Error("保存失败", "无法保存到指定位置"); + } + } + } + } + return new JsResult>() { Data = new() @@ -503,7 +590,7 @@ internal class MiHoYoJSBridge "hideLoading" => null, "login" => null, "pushPage" => await PushPageAsync(param).ConfigureAwait(false), - "share" => Share(param), + "share" => await Share(param).ConfigureAwait(false), "showLoading" => null, _ => LogUnhandledMessage("Unhandled Message Type: {Method}", param.Method), }; diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/ShareContent.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/ShareContent.cs index e7764da3..26437013 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/ShareContent.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/ShareContent.cs @@ -3,8 +3,23 @@ namespace Snap.Hutao.Web.Bridge.Model; +[SuppressMessage("", "SA1124")] internal sealed class ShareContent { + #region Screenshot + [JsonPropertyName("preview")] public bool Preview { get; set; } -} \ No newline at end of file + + #endregion + + #region Image + + [JsonPropertyName("image_url")] + public string? ImageUrl { get; set; } + + [JsonPropertyName("image_base64")] + public string? ImageBase64 { get; set; } + + #endregion +}