From 8f273e69b5eda81318bdaa149e7642e958d08702 Mon Sep 17 00:00:00 2001
From: DismissedLight <1686188646@qq.com>
Date: Sun, 27 Nov 2022 16:00:15 +0800
Subject: [PATCH] support sign in bbs
---
.../InvokeCommandOnUnloadedBehavior.cs | 2 +-
.../Snap.Hutao/Core/CoreEnvironment.cs | 5 +
.../Snap.Hutao/Core/TaskSchedulerHelper.cs | 5 +
.../Snap.Hutao/Core/Validation/Must.cs | 13 -
.../Snap.Hutao/Package.appxmanifest | 2 +-
.../Service/DailyNote/DailyNoteService.cs | 2 +-
.../Service/Game/Locator/IGameLocator.cs | 1 +
.../Snap.Hutao/Service/InfoBarService.cs | 2 +
.../View/Dialog/SignInWebViewDialog.xaml | 12 +-
.../View/Dialog/SignInWebViewDialog.xaml.cs | 49 ++-
.../Snap.Hutao/View/Page/SettingPage.xaml | 45 ++-
src/Snap.Hutao/Snap.Hutao/View/UserView.xaml | 16 +-
.../Snap.Hutao/ViewModel/SettingViewModel.cs | 21 ++
.../Snap.Hutao/ViewModel/UserViewModel.cs | 9 -
.../Snap.Hutao/Web/Bridge/BridgeExtension.cs | 107 ------
.../Web/Bridge/CoreWebView2Extension.cs | 61 ++++
.../Web/Bridge/ICoreWebView2Interop.cs | 24 --
.../Snap.Hutao/Web/Bridge/IMiHoYoJsBridge.cs | 20 --
.../Web/Bridge/JsMethodAttribute.cs | 25 ++
.../Web/Bridge/MiHoYoJSInterface.cs | 335 ++++++++++++++++++
.../Snap.Hutao/Web/Bridge/MiHoYoJsBridge.cs | 103 ------
.../Web/Bridge/Model/AccountInformation.cs | 77 ++++
.../Web/Bridge/Model/ActionTypePayload.cs | 16 +
.../Web/Bridge/Model/CookieTokenPayload.cs | 16 +
.../Bridge/Model/Event/WebInvokeAttribute.cs | 196 ----------
.../Snap.Hutao/Web/Bridge/Model/IJsResult.cs | 17 +
.../Snap.Hutao/Web/Bridge/Model/JsParam.cs | 59 +--
.../Snap.Hutao/Web/Bridge/Model/JsResult.cs | 8 +-
.../Web/Bridge/Model/JsonElementExtension.cs | 22 --
.../Web/Bridge/SignInJsInterface.cs | 33 ++
.../Snap.Hutao/Web/Hoyolab/Cookie.Constant.cs | 2 +
.../DynamicSecret/DynamicSecretHandler.cs | 39 +-
.../Web/Hoyolab/Takumi/Auth/AuthClient.cs | 18 +
.../Web/Response/KnownReturnCode.cs | 5 +
.../Snap.Hutao/Web/Response/Response.cs | 9 +-
35 files changed, 763 insertions(+), 613 deletions(-)
delete mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Bridge/BridgeExtension.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Bridge/CoreWebView2Extension.cs
delete mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Bridge/ICoreWebView2Interop.cs
delete mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Bridge/IMiHoYoJsBridge.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Bridge/JsMethodAttribute.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs
delete mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJsBridge.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/AccountInformation.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/ActionTypePayload.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/CookieTokenPayload.cs
delete mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/Event/WebInvokeAttribute.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/IJsResult.cs
delete mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsonElementExtension.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJsInterface.cs
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Behavior/InvokeCommandOnUnloadedBehavior.cs b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/InvokeCommandOnUnloadedBehavior.cs
index cb218fae31..7976e75b8e 100644
--- a/src/Snap.Hutao/Snap.Hutao/Control/Behavior/InvokeCommandOnUnloadedBehavior.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/InvokeCommandOnUnloadedBehavior.cs
@@ -32,4 +32,4 @@ protected override void OnDetaching()
base.OnDetaching();
}
-}
\ No newline at end of file
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs
index de168e725f..3e1d6e213a 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs
@@ -20,6 +20,11 @@ internal static class CoreEnvironment
///
public const string HoyolabUA = $"Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBS/{HoyolabXrpcVersion}";
+ ///
+ /// 米游社移动端请求UA
+ ///
+ public const string HoyolabMobileUA = $"Mozilla/5.0 (Linux; Android 12) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/106.0.5249.126 Mobile Safari/537.36 miHoYoBBS/{HoyolabXrpcVersion}";
+
///
/// 米游社 Rpc 版本
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/TaskSchedulerHelper.cs b/src/Snap.Hutao/Snap.Hutao/Core/TaskSchedulerHelper.cs
index 004c5c485b..254468f59b 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/TaskSchedulerHelper.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/TaskSchedulerHelper.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Microsoft.Win32.TaskScheduler;
+using System.Runtime.InteropServices;
using SchedulerTask = Microsoft.Win32.TaskScheduler.Task;
namespace Snap.Hutao.Core;
@@ -39,5 +40,9 @@ public static bool RegisterForDailyNoteRefresh(int interval)
{
return false;
}
+ catch (COMException)
+ {
+ return false;
+ }
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs b/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs
index 1f0420ad11..429cabcfa6 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs
@@ -38,19 +38,6 @@ public static void Range([DoesNotReturnIf(false)] bool condition, string? messag
}
}
- ///
- /// 任务异常
- ///
- /// 任务结果类型
- /// 异常消息
- /// 异常的任务
- [SuppressMessage("", "VSTHRD200")]
- public static Task Fault(string message)
- {
- InvalidOperationException exception = new(message);
- return Task.FromException(exception);
- }
-
///
/// Unconditionally throws an .
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
index eb02f63f0a..5dcd809687 100644
--- a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
+++ b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
@@ -12,7 +12,7 @@
+ Version="1.2.5.0" />
胡桃
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs
index 9de0e456a8..094c4d226d 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs
@@ -109,7 +109,7 @@ public async ValueTask RefreshDailyNotesAsync(bool notify)
// cache
await ThreadHelper.SwitchToMainThreadAsync();
- entries?.Single(e => e.UserId == entry.UserId && e.Uid == entry.Uid).UpdateDailyNote(dailyNote);
+ entries?.SingleOrDefault(e => e.UserId == entry.UserId && e.Uid == entry.Uid)?.UpdateDailyNote(dailyNote);
if (notify)
{
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/IGameLocator.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/IGameLocator.cs
index e65c2c73c4..5055bd4543 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/IGameLocator.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/IGameLocator.cs
@@ -22,5 +22,6 @@ internal interface IGameLocator : INamed
/// 路径应当包含启动器文件名称
///
/// 游戏启动器位置
+ [Obsolete("不应定位启动器位置")]
Task> LocateLauncherPathAsync();
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs b/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs
index 58dd762041..f684ad305f 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs
@@ -3,6 +3,8 @@
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation;
+using Microsoft.Xaml.Interactivity;
+using Snap.Hutao.Control.Behavior;
using Snap.Hutao.Service.Abstraction;
namespace Snap.Hutao.Service;
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/SignInWebViewDialog.xaml b/src/Snap.Hutao/Snap.Hutao/View/Dialog/SignInWebViewDialog.xaml
index e642b2ff8b..2c0e8b7098 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/SignInWebViewDialog.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/SignInWebViewDialog.xaml
@@ -1,13 +1,17 @@
-
+ Closed="OnContentDialogClosed"
+ Style="{StaticResource DefaultContentDialogStyle}"
+ Title="米游社每日签到"
+ PrimaryButtonText="完成"
+ DefaultButton="Primary">
-
-
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/SignInWebViewDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/SignInWebViewDialog.xaml.cs
index 85c71a9011..3018f04443 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/SignInWebViewDialog.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/SignInWebViewDialog.xaml.cs
@@ -1,16 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Web.WebView2.Core;
using Snap.Hutao.Model.Binding.User;
-using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.User;
using Snap.Hutao.Web.Bridge;
-using Snap.Hutao.Web.Bridge.Model;
-using Snap.Hutao.Web.Bridge.Model.Event;
-using Snap.Hutao.Web.Hoyolab.DynamicSecret;
-using Windows.UI.Popups;
namespace Snap.Hutao.View.Dialog;
@@ -19,6 +15,10 @@ namespace Snap.Hutao.View.Dialog;
///
public sealed partial class SignInWebViewDialog : ContentDialog
{
+ private readonly IServiceScope scope;
+ [SuppressMessage("", "IDE0052")]
+ private SignInJsInterface? signInJsInterface;
+
///
/// һµǩҳͼԻ
///
@@ -27,6 +27,7 @@ public SignInWebViewDialog(MainWindow window)
{
InitializeComponent();
XamlRoot = window.Content.XamlRoot;
+ scope = Ioc.Default.CreateScope();
}
private void OnGridLoaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
@@ -38,31 +39,27 @@ private async Task InitializeAsync()
{
await WebView.EnsureCoreWebView2Async();
CoreWebView2 coreWebView2 = WebView.CoreWebView2;
- IUserService userService = Ioc.Default.GetRequiredService();
- IInfoBarService infoBarService = Ioc.Default.GetRequiredService();
- ILogger logger = Ioc.Default.GetRequiredService>();
+ IUserService userService = scope.ServiceProvider.GetRequiredService();
+
User? user = userService.Current;
- coreWebView2.SetCookie(user?.CookieToken, user?.Ltoken);
- coreWebView2.SetMobileUserAgent();
- coreWebView2.InitializeBridge(logger, false)
- .Register(e => Hide())
- .Register(e => infoBarService.Information("ʹô˹", "ǰʵ֤"))
- .Register(s => s.Callback(result => result.Data["statusBarHeight"] = 0))
- .Register(s => s.Callback(result =>
- {
- result.Data["DS"] = DynamicSecretHandler.GetDynamicSecret(nameof(SaltType.K2), nameof(DynamicSecretVersion.Gen1), includeChars: true);
- }))
- .Register(s => s.Callback(result =>
- {
- result.Data["id"] = "111";
- result.Data["gender"] = 0;
- result.Data["nickname"] = "222";
- result.Data["introduce"] = "333";
- result.Data["avatar_url"] = "https://img-static.mihoyo.com/communityweb/upload/52de23f1b1a060e4ccaa8b24c1305dd9.png";
- }));
+ if (user == null)
+ {
+ return;
+ }
+
+ coreWebView2.SetCookie(user.CookieToken, user.Ltoken, null).SetMobileUserAgent();
+ signInJsInterface = new(coreWebView2, scope.ServiceProvider);
+#if DEBUG
coreWebView2.OpenDevToolsWindow();
+#endif
coreWebView2.Navigate("https://webstatic.mihoyo.com/bbs/event/signin-ys/index.html?act_id=e202009291139501");
}
+
+ private void OnContentDialogClosed(ContentDialog sender, ContentDialogClosedEventArgs args)
+ {
+ signInJsInterface = null;
+ scope.Dispose();
+ }
}
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml
index a94dcace32..18116a9a87 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml
@@ -61,6 +61,16 @@
Icon=""
Header="胡桃"
Description="{Binding AppVersion}"/>
+
+
+
+
+
+ Icon=""
+ Header="米游社每日签到">
+
+
+
+
+
+
+ Content="打开签到对话框"
+ Command="{Binding ShowSignInWebViewDialogCommand}"/>
@@ -179,6 +198,20 @@
+
+
+
+
+
+
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml
index 2f351d567e..64b6b907be 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml
@@ -194,26 +194,12 @@
-
-
-
-
-
-
-
+
@@ -92,6 +93,15 @@ public string AppVersion
get => Core.CoreEnvironment.Version.ToString();
}
+ ///
+ /// 设备Id
+ ///
+ [SuppressMessage("", "CA1822")]
+ public string DeviceId
+ {
+ get => Core.CoreEnvironment.HutaoDeviceId;
+ }
+
///
/// 空的历史卡池是否可见
///
@@ -161,6 +171,11 @@ public NamedValue SelectedBackdropType
///
public ICommand DeleteGameWebCacheCommand { get; }
+ ///
+ /// 签到对话框命令
+ ///
+ public ICommand ShowSignInWebViewDialogCommand { get; }
+
private async Task SetGamePathAsync()
{
IGameLocator locator = Ioc.Default.GetRequiredService>()
@@ -188,6 +203,12 @@ private void DeleteGameWebCache()
}
}
+ private async Task ShowSignInWebViewDialogAsync()
+ {
+ MainWindow mainWindow = Ioc.Default.GetRequiredService();
+ await new SignInWebViewDialog(mainWindow).ShowAsync();
+ }
+
private async Task DebugThrowExceptionAsync()
{
#if DEBUG
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs
index 4e14fb3453..924a053c89 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs
@@ -46,7 +46,6 @@ public UserViewModel(IUserService userService, IInfoBarService infoBarService, I
LoginMihoyoUserCommand = new RelayCommand(LoginMihoyoUser);
RemoveUserCommand = asyncRelayCommandFactory.Create(RemoveUserAsync);
CopyCookieCommand = new RelayCommand(CopyCookie);
- ShowSignInWebViewDialogCommand = asyncRelayCommandFactory.Create(ShowSignInWebViewDialogAsync);
}
///
@@ -84,8 +83,6 @@ public User? SelectedUser
///
public ICommand LoginMihoyoUserCommand { get; }
- public ICommand ShowSignInWebViewDialogCommand { get; }
-
///
/// 移除用户命令
///
@@ -168,10 +165,4 @@ private void CopyCookie(User? user)
infoBarService.Error(e);
}
}
-
- private async Task ShowSignInWebViewDialogAsync()
- {
- MainWindow mainWindow = Ioc.Default.GetRequiredService();
- await new SignInWebViewDialog(mainWindow).ShowAsync();
- }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/BridgeExtension.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/BridgeExtension.cs
deleted file mode 100644
index cca23e81f5..0000000000
--- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/BridgeExtension.cs
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright (c) DGP Studio. All rights reserved.
-// Licensed under the MIT license.
-
-using Microsoft.Web.WebView2.Core;
-using Snap.Hutao.Web.Hoyolab;
-using WinRT;
-
-namespace Snap.Hutao.Web.Bridge;
-
-///
-/// Bridge 拓展
-///
-public static class BridgeExtension
-{
- private const string InitializeJsInterfaceScript = """
- let c = {};
- c.postMessage = str => chrome.webview.hostObjects.MiHoYoJsBridge.OnMessage(str);
- c.closePage = () => c.postMessage('{"method":"closePage"}');
- window.MiHoYoJSInterface = c;
- """;
-
- private const string HideScrollBarScript = """
- let st = document.createElement('style');
- st.innerHTML = '::-webkit-scrollbar{display:none}';
- document.querySelector('body').appendChild(st);
- """;
-
- ///
- /// 设置 移动端UA
- ///
- /// webview2
- public static void SetMobileUserAgent(this CoreWebView2 webView)
- {
- webView.Settings.UserAgent = "Mozilla/5.0 (Linux; Android 12) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/106.0.5249.126 Mobile Safari/537.36 miHoYoBBS/2.41.0";
- }
-
- ///
- /// 初始化调用桥
- ///
- /// webview2
- /// 日志器
- /// 检查主机
- /// 初始化后的调用桥
- public static MiHoYoJsBridge InitializeBridge(this CoreWebView2 webView, ILogger logger, bool checkHost = true)
- {
- MiHoYoJsBridge bridge = new(webView, logger);
- var result = webView.As().AddHostObjectToScript("MiHoYoJsBridge", bridge);
-
- webView.DOMContentLoaded += OnDOMContentLoaded;
- webView.NavigationStarting += (coreWebView2, args) => OnWebViewNavigationStarting(coreWebView2, args, checkHost);
-
- return bridge;
- }
-
- ///
- /// 设置WebView2的Cookie
- ///
- /// webview2
- /// CookieToken
- /// Ltoken
- /// Stoken
- /// 链式调用的WebView2
- public static CoreWebView2 SetCookie(this CoreWebView2 webView, Cookie? cookieToken = null, Cookie? ltoken = null, Cookie? stoken = null)
- {
- CoreWebView2CookieManager cookieManager = webView.CookieManager;
-
- if (cookieToken != null)
- {
- cookieManager.AddMihoyoCookie("account_id", cookieToken).AddMihoyoCookie("cookie_token", cookieToken);
- }
-
- if (ltoken != null)
- {
- cookieManager.AddMihoyoCookie("ltuid", ltoken).AddMihoyoCookie("ltoken", ltoken);
- }
-
- if (stoken != null)
- {
- cookieManager.AddMihoyoCookie("stuid", stoken).AddMihoyoCookie("stoken", stoken);
- }
-
- return webView;
- }
-
- private static CoreWebView2CookieManager AddMihoyoCookie(this CoreWebView2CookieManager manager, string name, Cookie cookie)
- {
- manager.AddOrUpdateCookie(manager.CreateCookie(name, cookie[name], ".mihoyo.com", "/"));
- return manager;
- }
-
- [SuppressMessage("", "VSTHRD100")]
- private static async void OnDOMContentLoaded(CoreWebView2 coreWebView2, CoreWebView2DOMContentLoadedEventArgs args)
- {
- string result = await coreWebView2.ExecuteScriptAsync(HideScrollBarScript);
- _ = result;
- }
-
- [SuppressMessage("", "VSTHRD100")]
- private static async void OnWebViewNavigationStarting(CoreWebView2 coreWebView2, CoreWebView2NavigationStartingEventArgs args, bool checkHost)
- {
- if (!checkHost || new Uri(args.Uri).Host.EndsWith("mihoyo.com"))
- {
- string result = await coreWebView2.ExecuteScriptAsync(InitializeJsInterfaceScript);
- _ = result;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/CoreWebView2Extension.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/CoreWebView2Extension.cs
new file mode 100644
index 0000000000..92391a9161
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/CoreWebView2Extension.cs
@@ -0,0 +1,61 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.Web.WebView2.Core;
+using Snap.Hutao.Web.Hoyolab;
+using WinRT;
+
+namespace Snap.Hutao.Web.Bridge;
+
+///
+/// Bridge 拓展
+///
+public static class CoreWebView2Extension
+{
+ ///
+ /// 设置 移动端UA
+ ///
+ /// webview2
+ /// 链式调用的WebView2
+ public static CoreWebView2 SetMobileUserAgent(this CoreWebView2 webView)
+ {
+ webView.Settings.UserAgent = Core.CoreEnvironment.HoyolabMobileUA;
+ return webView;
+ }
+
+ ///
+ /// 设置WebView2的Cookie
+ ///
+ /// webview2
+ /// CookieToken
+ /// Ltoken
+ /// Stoken
+ /// 链式调用的WebView2
+ public static CoreWebView2 SetCookie(this CoreWebView2 webView, Cookie? cookieToken = null, Cookie? ltoken = null, Cookie? stoken = null)
+ {
+ CoreWebView2CookieManager cookieManager = webView.CookieManager;
+
+ if (cookieToken != null)
+ {
+ cookieManager.AddMihoyoCookie("account_id", cookieToken).AddMihoyoCookie("cookie_token", cookieToken);
+ }
+
+ if (ltoken != null)
+ {
+ cookieManager.AddMihoyoCookie("ltuid", ltoken).AddMihoyoCookie("ltoken", ltoken);
+ }
+
+ if (stoken != null)
+ {
+ cookieManager.AddMihoyoCookie("stuid", stoken).AddMihoyoCookie("stoken", stoken);
+ }
+
+ return webView;
+ }
+
+ private static CoreWebView2CookieManager AddMihoyoCookie(this CoreWebView2CookieManager manager, string name, Cookie cookie)
+ {
+ manager.AddOrUpdateCookie(manager.CreateCookie(name, cookie[name], ".mihoyo.com", "/"));
+ return manager;
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/ICoreWebView2Interop.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/ICoreWebView2Interop.cs
deleted file mode 100644
index 7df0bb6910..0000000000
--- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/ICoreWebView2Interop.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) DGP Studio. All rights reserved.
-// Licensed under the MIT license.
-
-using System.Runtime.InteropServices;
-using Windows.Win32.Foundation;
-
-namespace Snap.Hutao.Web.Bridge;
-
-///
-/// ICoreWebView2Interop
-///
-[ComImport]
-[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
-[Guid("912b34a7-d10b-49c4-af18-7cb7e604e01a")]
-public interface ICoreWebView2Interop
-{
- ///
- /// Add the provided host object to script running in the WebView with the specified name.
- ///
- /// 名称
- /// 对象
- /// 结果
- HRESULT AddHostObjectToScript([In] string name, [In] ref object obj);
-}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/IMiHoYoJsBridge.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/IMiHoYoJsBridge.cs
deleted file mode 100644
index 0c8f2c2a34..0000000000
--- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/IMiHoYoJsBridge.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) DGP Studio. All rights reserved.
-// Licensed under the MIT license.
-
-using System.Runtime.InteropServices;
-
-namespace Snap.Hutao.Web.Bridge;
-
-///
-/// 调用桥暴露的COM接口
-///
-[ComVisible(true)]
-[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
-public interface IMiHoYoJsBridge
-{
- ///
- /// 消息发生时调用
- ///
- /// 消息
- void OnMessage(string str);
-}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/JsMethodAttribute.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/JsMethodAttribute.cs
new file mode 100644
index 0000000000..736ea07c09
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/JsMethodAttribute.cs
@@ -0,0 +1,25 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Web.Bridge;
+
+///
+/// Web 调用
+///
+[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+public class JsMethodAttribute : Attribute
+{
+ ///
+ /// 构造一个新的Web 调用特性
+ ///
+ /// 函数名称
+ public JsMethodAttribute(string name)
+ {
+ Name = name;
+ }
+
+ ///
+ /// 调用函数名称
+ ///
+ public string Name { get; init; }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs
new file mode 100644
index 0000000000..56e621b975
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs
@@ -0,0 +1,335 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Web.WebView2.Core;
+using Snap.Hutao.Context.Database;
+using Snap.Hutao.Core.Convert;
+using Snap.Hutao.Core.Database;
+using Snap.Hutao.Extension;
+using Snap.Hutao.Model.Binding.User;
+using Snap.Hutao.Service.User;
+using Snap.Hutao.Web.Bridge.Model;
+using Snap.Hutao.Web.Hoyolab;
+using Snap.Hutao.Web.Hoyolab.Bbs.User;
+using Snap.Hutao.Web.Hoyolab.DynamicSecret;
+using Snap.Hutao.Web.Hoyolab.Passport;
+using Snap.Hutao.Web.Hoyolab.Takumi.Auth;
+using System.Text;
+
+namespace Snap.Hutao.Web.Bridge;
+
+///
+/// 调用桥
+///
+public class MiHoYoJSInterface
+{
+ private const string InitializeJsInterfaceScript2 = """
+ window.MiHoYoJSInterface = {
+ postMessage: function(arg) { chrome.webview.postMessage(arg) },
+ closePage: function() { this.postMessage('{"method":"closePage"}') },
+ };
+ """;
+
+ private const string HideScrollBarScript = """
+ let st = document.createElement('style');
+ st.innerHTML = '::-webkit-scrollbar{display:none}';
+ document.querySelector('body').appendChild(st);
+ """;
+
+ private readonly CoreWebView2 webView;
+ private readonly IServiceProvider serviceProvider;
+ private readonly ILogger logger;
+ private readonly JsonSerializerOptions options;
+
+ ///
+ /// 构造一个新的调用桥
+ ///
+ /// webview2
+ /// 服务提供器
+ protected MiHoYoJSInterface(CoreWebView2 webView, IServiceProvider serviceProvider)
+ {
+ this.webView = webView;
+ this.serviceProvider = serviceProvider;
+
+ logger = serviceProvider.GetRequiredService>();
+ options = serviceProvider.GetRequiredService();
+
+ webView.WebMessageReceived += OnWebMessageReceived;
+ webView.DOMContentLoaded += OnDOMContentLoaded;
+ webView.NavigationStarting += OnNavigationStarting;
+ }
+
+ ///
+ /// 获取ActionTicket
+ ///
+ /// 参数
+ /// 响应
+ [JsMethod("getActionTicket")]
+ public virtual async Task GetActionTicketAsync(JsParam jsParam)
+ {
+ User user = serviceProvider.GetRequiredService().Current!;
+ return await serviceProvider
+ .GetRequiredService()
+ .GetActionTicketWrapperByStokenAsync(jsParam.Payload!.ActionType, user.Entity)
+ .ConfigureAwait(false);
+ }
+
+ ///
+ /// 获取Http请求头
+ ///
+ /// 参数
+ /// Http请求头
+ [JsMethod("getHTTPRequestHeaders")]
+ public virtual JsResult> GetHttpRequestHeader(JsParam param)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// 异步获取账户信息
+ ///
+ /// 参数
+ /// 响应
+ [JsMethod("getCookieInfo")]
+ public virtual JsResult> GetCookieInfo(JsParam param)
+ {
+ User user = serviceProvider.GetRequiredService().Current!;
+
+ return new()
+ {
+ Data = new()
+ {
+ [Cookie.LTUID] = user.Ltoken![Cookie.LTUID],
+ [Cookie.LTOKEN] = user.Ltoken[Cookie.LTOKEN],
+ [Cookie.LOGIN_TICKET] = string.Empty,
+ },
+ };
+ }
+
+ ///
+ /// 获取1代动态密钥
+ ///
+ /// 参数
+ /// 响应
+ [JsMethod("getDS")]
+ public virtual JsResult> GetDynamicSecrectV1(JsParam param)
+ {
+ string salt = DynamicSecretHandler.DynamicSecrets[nameof(SaltType.LK2)];
+ long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
+ string r = GetRandomString();
+ string check = Md5Convert.ToHexString($"salt={salt}&t={t}&r={r}").ToLowerInvariant();
+
+ return new() { Data = new() { ["DS"] = $"{t},{r},{check}", }, };
+
+ static string GetRandomString()
+ {
+ const string RandomRange = "abcdefghijklmnopqrstuvwxyz1234567890";
+
+ StringBuilder sb = new(6);
+
+ for (int i = 0; i < 6; i++)
+ {
+ int pos = Random.Shared.Next(0, RandomRange.Length);
+ sb.Append(RandomRange[pos]);
+ }
+
+ return sb.ToString();
+ }
+ }
+
+ ///
+ /// 获取用户基本信息
+ ///
+ /// 参数
+ /// 响应
+ [JsMethod("getUserInfo")]
+ public virtual JsResult> GetUserInfo(JsParam param)
+ {
+ User user = serviceProvider.GetRequiredService().Current!;
+ UserInfo info = user.UserInfo!;
+
+ return new()
+ {
+ Data = new()
+ {
+ ["id"] = info.Uid,
+ ["gender"] = info.Gender,
+ ["nickname"] = info.Nickname,
+ ["introduce"] = info.Introduce,
+ ["avatar_url"] = info.AvatarUrl,
+ },
+ };
+ }
+
+ [JsMethod("getCookieToken")]
+ public virtual async Task>> GetCookieTokenAsync(JsParam param)
+ {
+ User user = serviceProvider.GetRequiredService().Current!;
+ string? cookieToken;
+ if (param.Payload!.ForceRefresh)
+ {
+ cookieToken = await Ioc.Default
+ .GetRequiredService()
+ .GetCookieAccountInfoBySTokenAsync(user.Entity, default)
+ .ConfigureAwait(false);
+
+ user.CookieToken![Cookie.COOKIE_TOKEN] = cookieToken!;
+ Ioc.Default.GetRequiredService().Users.UpdateAndSave(user.Entity);
+ }
+ else
+ {
+ cookieToken = user.CookieToken![Cookie.COOKIE_TOKEN];
+ }
+
+ return new() { Data = new() { [Cookie.COOKIE_TOKEN] = cookieToken! } };
+ }
+
+ [JsMethod("configure_share")]
+ public virtual Task ConfigureShare(JsParam param)
+ {
+ throw new NotImplementedException();
+ }
+
+ [JsMethod("showAlertDialog")]
+ public virtual Task ShowAlertDialogAsync(JsParam param)
+ {
+ return Task.FromException(new NotImplementedException());
+ }
+
+ [JsMethod("closePage")]
+ public virtual IJsResult? ClosePage(JsParam param)
+ {
+ throw new NotImplementedException();
+ }
+
+ [JsMethod("startRealPersonValidation")]
+ public virtual IJsResult? StartRealPersonValidation(JsParam param)
+ {
+ throw new NotImplementedException();
+ }
+
+ [JsMethod("startRealnameAuth")]
+ public virtual IJsResult? StartRealnameAuth(JsParam param)
+ {
+ throw new NotImplementedException();
+ }
+
+ [JsMethod("getDS2")]
+ public virtual IJsResult? GetDynamicSecrectV2(JsParam param)
+ {
+ throw new NotImplementedException();
+ }
+
+ [JsMethod("genAuthKey")]
+ public virtual IJsResult? GenAuthKey(JsParam param)
+ {
+ throw new NotImplementedException();
+ }
+
+ [JsMethod("genAppAuthKey")]
+ public virtual IJsResult? GenAppAuthKey(JsParam param)
+ {
+ throw new NotImplementedException();
+ }
+
+ [JsMethod("getStatusBarHeight")]
+ public virtual IJsResult? GetStatusBarHeight(JsParam param)
+ {
+ throw new NotImplementedException();
+ }
+
+ [JsMethod("pushPage")]
+ public virtual IJsResult? PushPage(JsParam param)
+ {
+ throw new NotImplementedException();
+ }
+
+ [JsMethod("openSystemBrowser")]
+ public virtual IJsResult? OpenSystemBrowser(JsParam param)
+ {
+ throw new NotImplementedException();
+ }
+
+ [JsMethod("saveLoginTicket")]
+ public virtual IJsResult? SaveLoginTicket(JsParam param)
+ {
+ throw new NotImplementedException();
+ }
+
+ [JsMethod("getNotificationSettings")]
+ public virtual Task GetNotificationSettingsAsync(JsParam param)
+ {
+ throw new NotImplementedException();
+ }
+
+ [JsMethod("showToast")]
+ public virtual Task ShowToast(JsParam param)
+ {
+ throw new NotImplementedException();
+ }
+
+ private async Task ExecuteCallbackScriptAsync(string callback, string? payload = null)
+ {
+ if (string.IsNullOrEmpty(callback))
+ {
+ // prevent executing this callback
+ return string.Empty;
+ }
+
+ string js = new StringBuilder()
+ .Append("javascript:mhyWebBridge(")
+ .Append('"')
+ .Append(callback)
+ .Append('"')
+ .AppendIf(payload != null, ',')
+ .Append(payload)
+ .Append(')')
+ .ToString();
+
+ logger?.LogInformation("[ExecuteScript] {js}", js);
+
+ await ThreadHelper.SwitchToMainThreadAsync();
+ return await webView.ExecuteScriptAsync(js);
+ }
+
+ [SuppressMessage("", "VSTHRD100")]
+ private async void OnWebMessageReceived(CoreWebView2 webView2, CoreWebView2WebMessageReceivedEventArgs args)
+ {
+ string message = args.TryGetWebMessageAsString();
+ logger?.LogInformation("[OnMessage] {message}", message);
+
+ JsParam param = JsonSerializer.Deserialize(message)!;
+
+ IJsResult? result = param.Method switch
+ {
+ "getActionTicket" => await GetActionTicketAsync(param).ConfigureAwait(false),
+ "getHTTPRequestHeaders" => GetHttpRequestHeader(param),
+ "getCookieInfo" => GetCookieInfo(param),
+ "getDS" => GetDynamicSecrectV1(param),
+ "getUserInfo" => GetUserInfo(param),
+ "getCookieToken" => await GetCookieTokenAsync(param).ConfigureAwait(false),
+ "configure_share" => null,
+ "login" => null,
+ _ => null,
+ };
+
+ if (result != null)
+ {
+ await ExecuteCallbackScriptAsync(param.Callback, result.ToString(options)).ConfigureAwait(false);
+ }
+ }
+
+ private void OnDOMContentLoaded(CoreWebView2 coreWebView2, CoreWebView2DOMContentLoadedEventArgs args)
+ {
+ coreWebView2.ExecuteScriptAsync(HideScrollBarScript).AsTask().SafeForget(logger);
+ }
+
+ private void OnNavigationStarting(CoreWebView2 coreWebView2, CoreWebView2NavigationStartingEventArgs args)
+ {
+ if (new Uri(args.Uri).Host.EndsWith("mihoyo.com"))
+ {
+ coreWebView2.ExecuteScriptAsync(InitializeJsInterfaceScript2).AsTask().SafeForget(logger);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJsBridge.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJsBridge.cs
deleted file mode 100644
index 703673fa33..0000000000
--- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJsBridge.cs
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright (c) DGP Studio. All rights reserved.
-// Licensed under the MIT license.
-
-using Microsoft.Web.WebView2.Core;
-using Snap.Hutao.Web.Bridge.Model;
-using Snap.Hutao.Web.Bridge.Model.Event;
-using System.Reflection;
-using System.Runtime.InteropServices;
-
-namespace Snap.Hutao.Web.Bridge;
-
-///
-/// 调用桥
-///
-[ComVisible(true)]
-[ClassInterface(ClassInterfaceType.None)]
-public sealed class MiHoYoJsBridge : IMiHoYoJsBridge
-{
- private readonly CoreWebView2 webView;
- private readonly ILogger? logger;
-
- private readonly Dictionary jsWebInvokeTypeCache = new();
- private readonly Dictionary> callbackHandlers = new();
-
- ///
- /// 构造一个新的调用桥
- ///
- /// webview2
- /// 日志器
- internal MiHoYoJsBridge(CoreWebView2 webView, ILogger? logger = null)
- {
- this.webView = webView;
- this.logger = logger;
- }
-
- ///
- /// 消息发生时调用
- ///
- /// 消息
- public void OnMessage(string message)
- {
- logger?.LogInformation("[OnMessage] {message}", message);
-
- JsParam p = JsonSerializer.Deserialize(message)!;
- p.Bridge = this;
-
- callbackHandlers.GetValueOrDefault(p.Method)?.Invoke(p);
- }
-
- ///
- /// 调用JS回调
- ///
- /// 回调名称
- /// 传输的数据
- /// 执行结果
- public Task InvokeJsCallbackAsync(string callbackName, string? payload = null)
- {
- if (string.IsNullOrEmpty(callbackName))
- {
- return Task.FromResult(string.Empty);
- }
-
- string dataStr = payload == null ? string.Empty : $", {payload}";
- string js = $"javascript:mhyWebBridge(\"{callbackName}\"{dataStr})";
- logger?.LogInformation("[InvokeJsCallback] {js}", js);
- return webView.ExecuteScriptAsync(js).AsTask();
- }
-
- ///
- /// 注册回调
- ///
- /// 回调类型
- /// 回调
- /// 桥
- public MiHoYoJsBridge Register(Action callback)
- where T : notnull
- {
- callbackHandlers[GetCallbackName()] = callback;
- return this;
- }
-
- ///
- /// 注册回调
- ///
- /// 回调类型
- /// 回调
- /// 桥
- public MiHoYoJsBridge Register(Action callback)
- where T : notnull
- {
- callbackHandlers[GetCallbackName()] = p => callback(p, p.Data.As());
- return this;
- }
-
- private string GetCallbackName()
- {
- Type type = typeof(T);
- string invokeName = type.GetCustomAttribute()?.Name
- ?? throw new ArgumentException("Type Callback not registered.");
-
- return jsWebInvokeTypeCache[type.Name] = invokeName;
- }
-}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/AccountInformation.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/AccountInformation.cs
new file mode 100644
index 0000000000..34732600ea
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/AccountInformation.cs
@@ -0,0 +1,77 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Web.Hoyolab.Passport;
+
+namespace Snap.Hutao.Web.Bridge.Model;
+
+///
+/// 账户信息
+///
+public class AccountInformation
+{
+ ///
+ /// 构造一个新的账户信息
+ ///
+ /// 用户信息
+ /// ltoken
+ public AccountInformation(UserInformation userInformation, string ltoken)
+ {
+ Aid = userInformation.Aid;
+ Mid = userInformation.Mid;
+ AccountName = userInformation.AccountName;
+ AreaCode = userInformation.AreaCode;
+ Mobile = userInformation.Mobile;
+ Email = userInformation.Email;
+ Token = ltoken;
+ TokenType = 2;
+ }
+
+ ///
+ /// 账户Id
+ ///
+ [JsonPropertyName("aid")]
+ public string Aid { get; set; } = default!;
+
+ ///
+ /// 米哈游Id
+ ///
+ [JsonPropertyName("mid")]
+ public string Mid { get; set; } = default!;
+
+ ///
+ /// 空
+ ///
+ [JsonPropertyName("accountName")]
+ public string AccountName { get; set; } = default!;
+
+ ///
+ /// 区域码 +86
+ ///
+ [JsonPropertyName("areaCode")]
+ public string AreaCode { get; set; } = default!;
+
+ ///
+ /// 手机号 111****1111
+ ///
+ [JsonPropertyName("mobile")]
+ public string Mobile { get; set; } = default!;
+
+ ///
+ /// 空
+ ///
+ [JsonPropertyName("email")]
+ public string Email { get; set; } = default!;
+
+ ///
+ /// Token
+ ///
+ [JsonPropertyName("tokenStr")]
+ public string Token { get; set; } = default!;
+
+ ///
+ /// Token 类型
+ ///
+ [JsonPropertyName("tokenType")]
+ public int TokenType { get; set; } = default!;
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/ActionTypePayload.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/ActionTypePayload.cs
new file mode 100644
index 0000000000..8219a9250e
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/ActionTypePayload.cs
@@ -0,0 +1,16 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Web.Bridge.Model;
+
+///
+/// 操作类型包装
+///
+public class ActionTypePayload
+{
+ ///
+ /// 操作类型
+ ///
+ [JsonPropertyName("action_type")]
+ public string ActionType { get; set; } = default!;
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/CookieTokenPayload.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/CookieTokenPayload.cs
new file mode 100644
index 0000000000..12414333c7
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/CookieTokenPayload.cs
@@ -0,0 +1,16 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Web.Bridge.Model;
+
+///
+/// 获取CookieToken的请求
+///
+public class CookieTokenPayload
+{
+ ///
+ /// 强制刷新
+ ///
+ [JsonPropertyName("forceRefresh")]
+ public bool ForceRefresh { get; set; }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/Event/WebInvokeAttribute.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/Event/WebInvokeAttribute.cs
deleted file mode 100644
index eb7a1d09c2..0000000000
--- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/Event/WebInvokeAttribute.cs
+++ /dev/null
@@ -1,196 +0,0 @@
-// Copyright (c) DGP Studio. All rights reserved.
-// Licensed under the MIT license.
-
-namespace Snap.Hutao.Web.Bridge.Model.Event;
-
-///
-/// Web 调用
-///
-[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
-public class WebInvokeAttribute : Attribute
-{
- ///
- /// 构造一个新的Web 调用特性
- ///
- /// 函数名称
- public WebInvokeAttribute(string name)
- {
- Name = name;
- }
-
- ///
- /// 调用函数名称
- ///
- public string Name { get; init; }
-}
-
-public class ButtonParam
-{
- [JsonPropertyName("title")]
- public string Title { get; set; } = default!;
-
- [JsonPropertyName("style")]
- public string Style { get; set; } = default!;
-}
-
-public abstract class GenAuthKeyBase
-{
- [JsonPropertyName("game_biz")]
- public string Biz { get; set; } = default!;
-
- [JsonPropertyName("auth_appid")]
- public string AppId { get; set; } = default!;
-
- [JsonPropertyName("game_uid")]
- public uint Uid { get; set; }
-
- [JsonPropertyName("region")]
- public string Region { get; set; } = default!;
-}
-
-[WebInvoke("closePage")]
-public struct JsEventClosePage
-{
-}
-
-[WebInvoke("configure_share")]
-public class JsEventConfigureShare
-{
- [JsonPropertyName("enable")]
- public bool Enable { get; set; }
-}
-
-[WebInvoke("genAppAuthKey")]
-public class JsEventGenAppAuthKey
- : GenAuthKeyBase
-{
-}
-
-[WebInvoke("genAuthKey")]
-public class JsEventGenAuthKey
- : GenAuthKeyBase
-{
-}
-
-[WebInvoke("getActionTicket")]
-public class JsEventGetActionTicket
-{
- [JsonPropertyName("action_type")]
- public string ActionType { get; set; } = default!;
-}
-
-[WebInvoke("getCookieToken")]
-public class JsEventGetCookieToken
-{
- [JsonPropertyName("forceRefresh")]
- public bool ForceRefresh { get; set; }
-}
-
-[WebInvoke("getDS")]
-public struct JsEventGetDynamicSecretV1
-{
-}
-
-[WebInvoke("getDS2")]
-public class JsEventGetDynamicSecretV2
-{
- [JsonPropertyName("query")]
- public Dictionary Query { get; set; } = new();
-
- [JsonPropertyName("body")]
- public string Body { get; set; } = default!;
-}
-
-[WebInvoke("getNotificationSettings")]
-public struct JsEventGetNotificationSettings
-{
-}
-
-[WebInvoke("startRealnameAuth")]
-public struct JsEventGetRealNameStatus
-{
- // guess
-}
-
-[WebInvoke("getHTTPRequestHeaders")]
-public struct JsEventGetRequestHeader
-{
-}
-
-[WebInvoke("getStatusBarHeight")]
-public struct JsEventGetStatusBarHeight
-{
- // just zero
-}
-
-[WebInvoke("getUserInfo")]
-public struct JsEventGetUserInfo
-{
-}
-
-[WebInvoke("getCookieInfo")]
-public struct JsEventGetWebLoginInfo
-{
-}
-
-[WebInvoke("openSystemBrowser")]
-public class JsEventOpenSystemBrowser
-{
- [JsonPropertyName("open_url")]
- public string PageUrl { get; set; } = default!;
-}
-
-[WebInvoke("pushPage")]
-public class JsEventPushPage
-{
- private string pageUrl = default!;
-
- [JsonPropertyName("page")]
- public string PageUrl
- {
- get => pageUrl;
- set => SetPageUrl(value);
- }
-
- private void SetPageUrl(string value)
- {
- pageUrl = value.StartsWith("mihoyobbs")
- ? value.Replace("mihoyobbs://", "https://bbs.mihoyo.com/dby/").Replace("topic", "topicDetail")
- : value;
- }
-}
-
-[WebInvoke("startRealPersonValidation")]
-public struct JsEventRealPersonValidation
-{
-}
-
-[WebInvoke("saveLoginTicket")]
-public class JsEventSaveLoginTicket
-{
- [JsonPropertyName("login_ticket")]
- public string LoginTicket { get; set; } = default!;
-}
-
-[WebInvoke("showAlertDialog")]
-public class JsEventShowAlertDialog
-{
- [JsonPropertyName("title")]
- public string Title { get; set; } = default!;
-
- [JsonPropertyName("message")]
- public string Message { get; set; } = default!;
-
- [JsonPropertyName("buttons")]
- public List Buttons { get; set; } = new();
-}
-
-[WebInvoke("showToast")]
-public class JsEventShowToast
-{
- [JsonPropertyName("toast")]
- public string Text { get; set; } = default!;
-
- [JsonPropertyName("type")]
- public string Type { get; set; } = default!;
-}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/IJsResult.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/IJsResult.cs
new file mode 100644
index 0000000000..cc633de89c
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/IJsResult.cs
@@ -0,0 +1,17 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Web.Bridge.Model;
+
+///
+/// 指示此为Js结果
+///
+public interface IJsResult
+{
+ ///
+ /// 转换到Json字符串表示
+ ///
+ /// 序列化参数
+ /// JSON字符串
+ string ToString(JsonSerializerOptions options);
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsParam.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsParam.cs
index 120ee5ec0e..9cb0affcdc 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsParam.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsParam.cs
@@ -4,6 +4,7 @@
namespace Snap.Hutao.Web.Bridge.Model;
///
+/// 由WebView向客户端传递的参数
/// Js 参数
///
public class JsParam
@@ -12,52 +13,54 @@ public class JsParam
/// 方法名称
///
[JsonPropertyName("method")]
- public string Method { get; set; } = string.Empty;
+ public string Method { get; set; } = default!;
///
- /// 数据
+ /// 数据 可以为空
///
[JsonPropertyName("payload")]
- public JsonElement Data { get; set; }
+ public JsonElement? Payload { get; set; }
///
- /// 回调名称
+ /// 回调的名称,调用 JavaScript:mhyWebBridge 时作为首个参数传入
///
[JsonPropertyName("callback")]
- public string CallbackName { get; set; } = string.Empty;
+ public string Callback { get; set; } = default!;
+}
+///
+/// 由WebView向客户端传递的参数
+/// Js 参数
+///
+/// Payload 类型
+[SuppressMessage("", "SA1402")]
+public class JsParam
+{
///
- /// 对应的调用桥
+ /// 方法名称
///
- internal MiHoYoJsBridge Bridge { get; set; } = null!;
+ [JsonPropertyName("method")]
+ public string Method { get; set; } = default!;
///
- /// 执行回调
+ /// 数据 可以为空
///
- /// 结果工厂
- public void Callback(Func? resultFactory = null)
- {
- JsResult? result = resultFactory?.Invoke() ?? new();
- Callback(result?.ToString());
- }
+ [JsonPropertyName("payload")]
+ public TPayload Payload { get; set; } = default!;
///
- /// 执行回调
+ /// 回调的名称,调用 JavaScript:mhyWebBridge 时作为首个参数传入
///
- /// 结果工厂
- public void Callback(Action resultModifier)
- {
- JsResult result = new();
- resultModifier(result);
- Callback(result?.ToString());
- }
+ [JsonPropertyName("callback")]
+ public string Callback { get; set; } = string.Empty;
- ///
- /// 执行回调
- ///
- /// 结果
- public void Callback(string? result = null)
+ public static implicit operator JsParam(JsParam jsParam)
{
- Bridge.InvokeJsCallbackAsync(CallbackName, result).GetAwaiter().GetResult();
+ return new JsParam()
+ {
+ Method = jsParam.Method,
+ Payload = jsParam.Payload.HasValue ? jsParam.Payload.Value.Deserialize() : default,
+ Callback = jsParam.Callback,
+ };
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsResult.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsResult.cs
index e2a23e1d95..44c2d52f66 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsResult.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsResult.cs
@@ -4,9 +4,11 @@
namespace Snap.Hutao.Web.Bridge.Model;
///
+/// 用于传回网页的参数
/// Js结果
///
-public class JsResult
+/// 内部数据类型
+public class JsResult : IJsResult
{
///
/// 代码
@@ -24,10 +26,10 @@ public class JsResult
/// 数据
///
[JsonPropertyName("data")]
- public Dictionary Data { get; set; } = new();
+ public TData Data { get; set; } = default!;
///
- public override string ToString()
+ string IJsResult.ToString(JsonSerializerOptions options)
{
return JsonSerializer.Serialize(this);
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsonElementExtension.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsonElementExtension.cs
deleted file mode 100644
index e62ec041d9..0000000000
--- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsonElementExtension.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) DGP Studio. All rights reserved.
-// Licensed under the MIT license.
-
-namespace Snap.Hutao.Web.Bridge.Model;
-
-///
-/// JsonElement 拓展
-///
-public static class JsonElementExtension
-{
- ///
- /// 序列化到对应类型
- ///
- /// 对应类型
- /// 元素
- /// 对应类型的实例
- public static T As(this JsonElement jsonElement)
- where T : notnull
- {
- return jsonElement.Deserialize()!;
- }
-}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJsInterface.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJsInterface.cs
new file mode 100644
index 0000000000..6e4b04df77
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJsInterface.cs
@@ -0,0 +1,33 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.Web.WebView2.Core;
+using Snap.Hutao.Web.Bridge.Model;
+
+namespace Snap.Hutao.Web.Bridge;
+
+///
+/// 签到页面JS桥
+///
+public class SignInJsInterface : MiHoYoJSInterface
+{
+ ///
+ public SignInJsInterface(CoreWebView2 webView, IServiceProvider serviceProvider)
+ : base(webView, serviceProvider)
+ {
+ }
+
+ ///
+ public override JsResult> GetHttpRequestHeader(JsParam param)
+ {
+ return new()
+ {
+ Data = new Dictionary()
+ {
+ { "x-rpc-client_type", "2" },
+ { "x-rpc-device_id", Core.CoreEnvironment.HoyolabDeviceId },
+ { "x-rpc-app_version", Core.CoreEnvironment.HoyolabXrpcVersion },
+ },
+ };
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.Constant.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.Constant.cs
index f6038cb0f7..c5e6a9cd7b 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.Constant.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.Constant.cs
@@ -10,6 +10,8 @@ namespace Snap.Hutao.Web.Hoyolab;
[SuppressMessage("", "SA1600")]
public partial class Cookie
{
+ public const string LOGIN_TICKET = "login_ticket";
+
public const string ACCOUNT_ID = "account_id";
public const string COOKIE_TOKEN = "cookie_token";
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretHandler.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretHandler.cs
index 1a671bc871..fbfdf00b83 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretHandler.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretHandler.cs
@@ -15,10 +15,11 @@ namespace Snap.Hutao.Web.Hoyolab.DynamicSecret;
[Injection(InjectAs.Transient)]
public class DynamicSecretHandler : DelegatingHandler
{
- private const string RandomRange = "abcdefghijklmnopqrstuvwxyz1234567890";
-
+ ///
+ /// 盐
+ ///
// https://github.com/UIGF-org/Hoyolab.Salt
- private static readonly ImmutableDictionary DynamicSecrets = new Dictionary()
+ public static readonly ImmutableDictionary DynamicSecrets = new Dictionary()
{
[nameof(SaltType.K2)] = "TsmyHpZg8gFAVKTtlPaL6YwMldzxZJxQ",
[nameof(SaltType.LK2)] = "osgT0DljLarYxgebPPHJFjdaxPfoiHGt",
@@ -27,6 +28,8 @@ public class DynamicSecretHandler : DelegatingHandler
[nameof(SaltType.PROD)] = "JwYDpKvLj6MrMqqYU6jTKF17KNO2PXoS",
}.ToImmutableDictionary();
+ private const string RandomRange = "abcdefghijklmnopqrstuvwxyz1234567890";
+
///
protected override async Task SendAsync(HttpRequestMessage request, CancellationToken token)
{
@@ -66,36 +69,6 @@ protected override async Task SendAsync(HttpRequestMessage
return await base.SendAsync(request, token).ConfigureAwait(false);
}
- ///
- /// 获取DS
- ///
- /// nameof
- /// nameof
- /// body
- /// query
- /// 是否需要字母
- /// DS
- public static string GetDynamicSecret(string saltType, string version, string? body = null, string? query = null, bool includeChars = true)
- {
- string salt = DynamicSecrets[saltType];
-
- long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
-
- string r = includeChars ? GetRandomStringWithChars() : GetRandomStringNoChars();
-
- string dsContent = $"salt={salt}&t={t}&r={r}";
-
- if (version == nameof(DynamicSecretVersion.Gen2))
- {
- string[] queries = Uri.UnescapeDataString(query!).Split('?', 2);
- string q = queries.Length == 2 ? string.Join('&', queries[1].Split('&').OrderBy(x => x)) : string.Empty;
-
- dsContent = $"{dsContent}&b={body}&q={q}";
- }
-
- return Md5Convert.ToHexString(dsContent).ToLowerInvariant();
- }
-
private static string GetRandomStringWithChars()
{
StringBuilder sb = new(6);
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Auth/AuthClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Auth/AuthClient.cs
index 098571ef98..9bf9c57b92 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Auth/AuthClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Auth/AuthClient.cs
@@ -58,6 +58,24 @@ public AuthClient(HttpClient httpClient, JsonSerializerOptions options, ILogger<
return null;
}
+ ///
+ /// 异步获取操作凭证
+ ///
+ /// 操作
+ /// 用户
+ /// 操作凭证
+ [ApiInformation(Cookie = CookieType.Stoken, Salt = SaltType.K2)]
+ public async Task?> GetActionTicketWrapperByStokenAsync(string action, User user)
+ {
+ Response? resp = await httpClient
+ .SetUser(user, CookieType.Stoken)
+ .UseDynamicSecret(DynamicSecretVersion.Gen1, SaltType.K2, true)
+ .TryCatchGetFromJsonAsync>(ApiEndpoints.AuthActionTicket(action, user.Stoken![Cookie.STOKEN], user.Aid!), options, logger)
+ .ConfigureAwait(false);
+
+ return resp;
+ }
+
///
/// 获取 MultiToken
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs b/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs
index 0dde9d7345..5315753a7f 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs
@@ -43,6 +43,11 @@ public enum KnownReturnCode : int
///
RET_NEED_AIGIS = -3101,
+ ///
+ /// 请在米游社App内打开~
+ ///
+ PleaseOpenInBbsApp = -1104,
+
///
/// 登录信息已失效,请重新登录
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs b/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs
index 846fd86d00..972264c8b3 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs
@@ -3,6 +3,7 @@
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Service.Abstraction;
+using Snap.Hutao.Web.Bridge.Model;
namespace Snap.Hutao.Web.Response;
@@ -60,7 +61,7 @@ public override string ToString()
///
/// 数据类型
[SuppressMessage("", "SA1402")]
-public class Response : Response
+public class Response : Response, IJsResult
{
///
/// 构造一个新的 Mihoyo 标准API响应
@@ -91,4 +92,10 @@ public bool IsOk()
{
return ReturnCode == 0;
}
+
+ ///
+ string IJsResult.ToString(JsonSerializerOptions options)
+ {
+ return JsonSerializer.Serialize(this);
+ }
}
\ No newline at end of file