mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
support sign in bbs
This commit is contained in:
@@ -32,4 +32,4 @@ internal class InvokeCommandOnUnloadedBehavior : BehaviorBase<UIElement>
|
||||
|
||||
base.OnDetaching();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,11 @@ internal static class CoreEnvironment
|
||||
/// </summary>
|
||||
public const string HoyolabUA = $"Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBS/{HoyolabXrpcVersion}";
|
||||
|
||||
/// <summary>
|
||||
/// 米游社移动端请求UA
|
||||
/// </summary>
|
||||
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}";
|
||||
|
||||
/// <summary>
|
||||
/// 米游社 Rpc 版本
|
||||
/// </summary>
|
||||
|
||||
@@ -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 @@ internal static class TaskSchedulerHelper
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,19 +38,6 @@ public static class Must
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 任务异常
|
||||
/// </summary>
|
||||
/// <typeparam name="T">任务结果类型</typeparam>
|
||||
/// <param name="message">异常消息</param>
|
||||
/// <returns>异常的任务</returns>
|
||||
[SuppressMessage("", "VSTHRD200")]
|
||||
public static Task<T> Fault<T>(string message)
|
||||
{
|
||||
InvalidOperationException exception = new(message);
|
||||
return Task.FromException<T>(exception);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unconditionally throws an <see cref="NotSupportedException"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<Identity
|
||||
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
|
||||
Publisher="CN=DGP Studio"
|
||||
Version="1.2.4.0" />
|
||||
Version="1.2.5.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>胡桃</DisplayName>
|
||||
|
||||
@@ -109,7 +109,7 @@ internal class DailyNoteService : IDailyNoteService, IRecipient<UserRemovedMessa
|
||||
|
||||
// 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)
|
||||
{
|
||||
|
||||
@@ -22,5 +22,6 @@ internal interface IGameLocator : INamed
|
||||
/// 路径应当包含启动器文件名称
|
||||
/// </summary>
|
||||
/// <returns>游戏启动器位置</returns>
|
||||
[Obsolete("不应定位启动器位置")]
|
||||
Task<ValueResult<bool, string>> LocateLauncherPathAsync();
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
<ContentDialog
|
||||
<ContentDialog
|
||||
x:Class="Snap.Hutao.View.Dialog.SignInWebViewDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Style="{StaticResource DefaultContentDialogStyle}">
|
||||
Closed="OnContentDialogClosed"
|
||||
Style="{StaticResource DefaultContentDialogStyle}"
|
||||
Title="米游社每日签到"
|
||||
PrimaryButtonText="完成"
|
||||
DefaultButton="Primary">
|
||||
|
||||
<Grid Loaded="OnGridLoaded" Height="600" Width="380">
|
||||
<WebView2 Name="WebView"/>
|
||||
<Grid Loaded="OnGridLoaded">
|
||||
<WebView2 Name="WebView" Height="456" Width="380"/>
|
||||
</Grid>
|
||||
</ContentDialog>
|
||||
|
||||
@@ -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;
|
||||
/// </summary>
|
||||
public sealed partial class SignInWebViewDialog : ContentDialog
|
||||
{
|
||||
private readonly IServiceScope scope;
|
||||
[SuppressMessage("", "IDE0052")]
|
||||
private SignInJsInterface? signInJsInterface;
|
||||
|
||||
/// <summary>
|
||||
/// <20><><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>µ<EFBFBD>ǩ<EFBFBD><C7A9><EFBFBD><EFBFBD>ҳ<EFBFBD><D2B3>ͼ<EFBFBD>Ի<EFBFBD><D4BB><EFBFBD>
|
||||
/// </summary>
|
||||
@@ -27,6 +27,7 @@ public sealed partial class SignInWebViewDialog : ContentDialog
|
||||
{
|
||||
InitializeComponent();
|
||||
XamlRoot = window.Content.XamlRoot;
|
||||
scope = Ioc.Default.CreateScope();
|
||||
}
|
||||
|
||||
private void OnGridLoaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
@@ -38,31 +39,27 @@ public sealed partial class SignInWebViewDialog : ContentDialog
|
||||
{
|
||||
await WebView.EnsureCoreWebView2Async();
|
||||
CoreWebView2 coreWebView2 = WebView.CoreWebView2;
|
||||
IUserService userService = Ioc.Default.GetRequiredService<IUserService>();
|
||||
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
||||
ILogger<MiHoYoJsBridge> logger = Ioc.Default.GetRequiredService<ILogger<MiHoYoJsBridge>>();
|
||||
IUserService userService = scope.ServiceProvider.GetRequiredService<IUserService>();
|
||||
|
||||
User? user = userService.Current;
|
||||
|
||||
coreWebView2.SetCookie(user?.CookieToken, user?.Ltoken);
|
||||
coreWebView2.SetMobileUserAgent();
|
||||
coreWebView2.InitializeBridge(logger, false)
|
||||
.Register<JsEventClosePage>(e => Hide())
|
||||
.Register<JsEventRealPersonValidation>(e => infoBarService.Information("<22><EFBFBD>ʹ<EFBFBD>ô˹<C3B4><CBB9><EFBFBD>", "<22><>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʵ<EFBFBD><CAB5><EFBFBD><EFBFBD>֤<EFBFBD><D6A4><EFBFBD><EFBFBD><EFBFBD><EFBFBD>"))
|
||||
.Register<JsEventGetStatusBarHeight>(s => s.Callback(result => result.Data["statusBarHeight"] = 0))
|
||||
.Register<JsEventGetDynamicSecretV1>(s => s.Callback(result =>
|
||||
{
|
||||
result.Data["DS"] = DynamicSecretHandler.GetDynamicSecret(nameof(SaltType.K2), nameof(DynamicSecretVersion.Gen1), includeChars: true);
|
||||
}))
|
||||
.Register<JsEventGetUserInfo>(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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,16 @@
|
||||
Icon=""
|
||||
Header="胡桃"
|
||||
Description="{Binding AppVersion}"/>
|
||||
<sc:Setting
|
||||
Icon=""
|
||||
Header="设备ID">
|
||||
<sc:Setting.Description>
|
||||
<TextBlock
|
||||
IsTextSelectionEnabled="True"
|
||||
Text="{Binding DeviceId}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
</sc:Setting.Description>
|
||||
</sc:Setting>
|
||||
<sc:Setting
|
||||
Icon=""
|
||||
Header="反馈"
|
||||
@@ -159,14 +169,23 @@
|
||||
</sc:Setting>
|
||||
|
||||
<sc:Setting
|
||||
Icon=""
|
||||
Header="删除所有用户"
|
||||
Description="直接删除用户表的所有记录,用于修复特定的账号冲突问题"
|
||||
Background="#80800000">
|
||||
Icon=""
|
||||
Header="米游社每日签到">
|
||||
<sc:Setting.Description>
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="对当前选中的账号进行签到"/>
|
||||
<TextBlock
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Foreground="Yellow"
|
||||
Text="可能需要重启应用以对其他账号签到"/>
|
||||
</StackPanel>
|
||||
</sc:Setting.Description>
|
||||
<sc:Setting.ActionContent>
|
||||
<Button
|
||||
Content="执行"
|
||||
Command="{Binding Experimental.DeleteUsersCommand}"/>
|
||||
Content="打开签到对话框"
|
||||
Command="{Binding ShowSignInWebViewDialogCommand}"/>
|
||||
</sc:Setting.ActionContent>
|
||||
</sc:Setting>
|
||||
|
||||
@@ -179,6 +198,20 @@
|
||||
</sc:Setting.ActionContent>
|
||||
</sc:Setting>
|
||||
</sc:SettingsGroup>
|
||||
|
||||
<sc:SettingsGroup Header="危险功能" Foreground="#FF800000">
|
||||
<sc:Setting
|
||||
Icon=""
|
||||
Header="删除所有用户"
|
||||
Description="直接删除用户表的所有记录,用于修复特定的账号冲突问题"
|
||||
Background="#80800000">
|
||||
<sc:Setting.ActionContent>
|
||||
<Button
|
||||
Content="执行"
|
||||
Command="{Binding Experimental.DeleteUsersCommand}"/>
|
||||
</sc:Setting.ActionContent>
|
||||
</sc:Setting>
|
||||
</sc:SettingsGroup>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
@@ -194,26 +194,12 @@
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
<TextBlock
|
||||
Margin="10,6,0,6"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="Webview"/>
|
||||
<CommandBar DefaultLabelPosition="Right">
|
||||
<AppBarButton Label="米游社签到" Icon="{shcm:FontIcon Glyph=}" Command="{Binding ShowSignInWebViewDialogCommand}"/>
|
||||
</CommandBar>
|
||||
<TextBlock
|
||||
Margin="10,6,0,6"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="Cookie 操作"/>
|
||||
<CommandBar DefaultLabelPosition="Right">
|
||||
<AppBarButton Label="登录米哈游通行证" Icon="{shcm:FontIcon Glyph=}" Command="{Binding LoginMihoyoUserCommand}">
|
||||
<!--<AppBarButton.Flyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyoutItem Icon="{shcm:FontIcon Glyph=}" Text="网页米游社" Command="{Binding LoginMihoyoBBSCommand}"/>
|
||||
<MenuFlyoutItem Icon="{shcm:FontIcon Glyph=}" Text="米游社" Command="{Binding LoginMihoyoBBS2Command}"/>
|
||||
</MenuFlyout>
|
||||
</AppBarButton.Flyout>-->
|
||||
</AppBarButton>
|
||||
<AppBarButton Label="登录米哈游通行证" Icon="{shcm:FontIcon Glyph=}" Command="{Binding LoginMihoyoUserCommand}"/>
|
||||
<AppBarButton
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Label="手动输入"
|
||||
|
||||
@@ -81,6 +81,7 @@ internal class SettingViewModel : ObservableObject
|
||||
SetGamePathCommand = asyncRelayCommandFactory.Create(SetGamePathAsync);
|
||||
DebugExceptionCommand = asyncRelayCommandFactory.Create(DebugThrowExceptionAsync);
|
||||
DeleteGameWebCacheCommand = new RelayCommand(DeleteGameWebCache);
|
||||
ShowSignInWebViewDialogCommand = asyncRelayCommandFactory.Create(ShowSignInWebViewDialogAsync);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -92,6 +93,15 @@ internal class SettingViewModel : ObservableObject
|
||||
get => Core.CoreEnvironment.Version.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设备Id
|
||||
/// </summary>
|
||||
[SuppressMessage("", "CA1822")]
|
||||
public string DeviceId
|
||||
{
|
||||
get => Core.CoreEnvironment.HutaoDeviceId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 空的历史卡池是否可见
|
||||
/// </summary>
|
||||
@@ -161,6 +171,11 @@ internal class SettingViewModel : ObservableObject
|
||||
/// </summary>
|
||||
public ICommand DeleteGameWebCacheCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 签到对话框命令
|
||||
/// </summary>
|
||||
public ICommand ShowSignInWebViewDialogCommand { get; }
|
||||
|
||||
private async Task SetGamePathAsync()
|
||||
{
|
||||
IGameLocator locator = Ioc.Default.GetRequiredService<IEnumerable<IGameLocator>>()
|
||||
@@ -188,6 +203,12 @@ internal class SettingViewModel : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowSignInWebViewDialogAsync()
|
||||
{
|
||||
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
await new SignInWebViewDialog(mainWindow).ShowAsync();
|
||||
}
|
||||
|
||||
private async Task DebugThrowExceptionAsync()
|
||||
{
|
||||
#if DEBUG
|
||||
|
||||
@@ -46,7 +46,6 @@ internal class UserViewModel : ObservableObject
|
||||
LoginMihoyoUserCommand = new RelayCommand(LoginMihoyoUser);
|
||||
RemoveUserCommand = asyncRelayCommandFactory.Create<User>(RemoveUserAsync);
|
||||
CopyCookieCommand = new RelayCommand<User>(CopyCookie);
|
||||
ShowSignInWebViewDialogCommand = asyncRelayCommandFactory.Create(ShowSignInWebViewDialogAsync);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -84,8 +83,6 @@ internal class UserViewModel : ObservableObject
|
||||
/// </summary>
|
||||
public ICommand LoginMihoyoUserCommand { get; }
|
||||
|
||||
public ICommand ShowSignInWebViewDialogCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 移除用户命令
|
||||
/// </summary>
|
||||
@@ -168,10 +165,4 @@ internal class UserViewModel : ObservableObject
|
||||
infoBarService.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowSignInWebViewDialogAsync()
|
||||
{
|
||||
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
await new SignInWebViewDialog(mainWindow).ShowAsync();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Bridge 拓展
|
||||
/// </summary>
|
||||
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);
|
||||
""";
|
||||
|
||||
/// <summary>
|
||||
/// 设置 移动端UA
|
||||
/// </summary>
|
||||
/// <param name="webView">webview2</param>
|
||||
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";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化调用桥
|
||||
/// </summary>
|
||||
/// <param name="webView">webview2</param>
|
||||
/// <param name="logger">日志器</param>
|
||||
/// <param name="checkHost">检查主机</param>
|
||||
/// <returns>初始化后的调用桥</returns>
|
||||
public static MiHoYoJsBridge InitializeBridge(this CoreWebView2 webView, ILogger<MiHoYoJsBridge> logger, bool checkHost = true)
|
||||
{
|
||||
MiHoYoJsBridge bridge = new(webView, logger);
|
||||
var result = webView.As<ICoreWebView2Interop>().AddHostObjectToScript("MiHoYoJsBridge", bridge);
|
||||
|
||||
webView.DOMContentLoaded += OnDOMContentLoaded;
|
||||
webView.NavigationStarting += (coreWebView2, args) => OnWebViewNavigationStarting(coreWebView2, args, checkHost);
|
||||
|
||||
return bridge;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置WebView2的Cookie
|
||||
/// </summary>
|
||||
/// <param name="webView">webview2</param>
|
||||
/// <param name="cookieToken">CookieToken</param>
|
||||
/// <param name="ltoken">Ltoken</param>
|
||||
/// <param name="stoken">Stoken</param>
|
||||
/// <returns>链式调用的WebView2</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Bridge 拓展
|
||||
/// </summary>
|
||||
public static class CoreWebView2Extension
|
||||
{
|
||||
/// <summary>
|
||||
/// 设置 移动端UA
|
||||
/// </summary>
|
||||
/// <param name="webView">webview2</param>
|
||||
/// <returns>链式调用的WebView2</returns>
|
||||
public static CoreWebView2 SetMobileUserAgent(this CoreWebView2 webView)
|
||||
{
|
||||
webView.Settings.UserAgent = Core.CoreEnvironment.HoyolabMobileUA;
|
||||
return webView;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置WebView2的Cookie
|
||||
/// </summary>
|
||||
/// <param name="webView">webview2</param>
|
||||
/// <param name="cookieToken">CookieToken</param>
|
||||
/// <param name="ltoken">Ltoken</param>
|
||||
/// <param name="stoken">Stoken</param>
|
||||
/// <returns>链式调用的WebView2</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// ICoreWebView2Interop
|
||||
/// </summary>
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("912b34a7-d10b-49c4-af18-7cb7e604e01a")]
|
||||
public interface ICoreWebView2Interop
|
||||
{
|
||||
/// <summary>
|
||||
/// Add the provided host object to script running in the WebView with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="name">名称</param>
|
||||
/// <param name="obj">对象</param>
|
||||
/// <returns>结果</returns>
|
||||
HRESULT AddHostObjectToScript([In] string name, [In] ref object obj);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 调用桥暴露的COM接口
|
||||
/// </summary>
|
||||
[ComVisible(true)]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
|
||||
public interface IMiHoYoJsBridge
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息发生时调用
|
||||
/// </summary>
|
||||
/// <param name="str">消息</param>
|
||||
void OnMessage(string str);
|
||||
}
|
||||
25
src/Snap.Hutao/Snap.Hutao/Web/Bridge/JsMethodAttribute.cs
Normal file
25
src/Snap.Hutao/Snap.Hutao/Web/Bridge/JsMethodAttribute.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Bridge;
|
||||
|
||||
/// <summary>
|
||||
/// Web 调用
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public class JsMethodAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的Web 调用特性
|
||||
/// </summary>
|
||||
/// <param name="name">函数名称</param>
|
||||
public JsMethodAttribute(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调用函数名称
|
||||
/// </summary>
|
||||
public string Name { get; init; }
|
||||
}
|
||||
335
src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs
Normal file
335
src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs
Normal file
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 调用桥
|
||||
/// </summary>
|
||||
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<MiHoYoJSInterface> logger;
|
||||
private readonly JsonSerializerOptions options;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的调用桥
|
||||
/// </summary>
|
||||
/// <param name="webView">webview2</param>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
protected MiHoYoJSInterface(CoreWebView2 webView, IServiceProvider serviceProvider)
|
||||
{
|
||||
this.webView = webView;
|
||||
this.serviceProvider = serviceProvider;
|
||||
|
||||
logger = serviceProvider.GetRequiredService<ILogger<MiHoYoJSInterface>>();
|
||||
options = serviceProvider.GetRequiredService<JsonSerializerOptions>();
|
||||
|
||||
webView.WebMessageReceived += OnWebMessageReceived;
|
||||
webView.DOMContentLoaded += OnDOMContentLoaded;
|
||||
webView.NavigationStarting += OnNavigationStarting;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取ActionTicket
|
||||
/// </summary>
|
||||
/// <param name="jsParam">参数</param>
|
||||
/// <returns>响应</returns>
|
||||
[JsMethod("getActionTicket")]
|
||||
public virtual async Task<IJsResult?> GetActionTicketAsync(JsParam<ActionTypePayload> jsParam)
|
||||
{
|
||||
User user = serviceProvider.GetRequiredService<IUserService>().Current!;
|
||||
return await serviceProvider
|
||||
.GetRequiredService<AuthClient>()
|
||||
.GetActionTicketWrapperByStokenAsync(jsParam.Payload!.ActionType, user.Entity)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Http请求头
|
||||
/// </summary>
|
||||
/// <param name="param">参数</param>
|
||||
/// <returns>Http请求头</returns>
|
||||
[JsMethod("getHTTPRequestHeaders")]
|
||||
public virtual JsResult<Dictionary<string, string>> GetHttpRequestHeader(JsParam param)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取账户信息
|
||||
/// </summary>
|
||||
/// <param name="param">参数</param>
|
||||
/// <returns>响应</returns>
|
||||
[JsMethod("getCookieInfo")]
|
||||
public virtual JsResult<Dictionary<string, string>> GetCookieInfo(JsParam param)
|
||||
{
|
||||
User user = serviceProvider.GetRequiredService<IUserService>().Current!;
|
||||
|
||||
return new()
|
||||
{
|
||||
Data = new()
|
||||
{
|
||||
[Cookie.LTUID] = user.Ltoken![Cookie.LTUID],
|
||||
[Cookie.LTOKEN] = user.Ltoken[Cookie.LTOKEN],
|
||||
[Cookie.LOGIN_TICKET] = string.Empty,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取1代动态密钥
|
||||
/// </summary>
|
||||
/// <param name="param">参数</param>
|
||||
/// <returns>响应</returns>
|
||||
[JsMethod("getDS")]
|
||||
public virtual JsResult<Dictionary<string, string>> 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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户基本信息
|
||||
/// </summary>
|
||||
/// <param name="param">参数</param>
|
||||
/// <returns>响应</returns>
|
||||
[JsMethod("getUserInfo")]
|
||||
public virtual JsResult<Dictionary<string, object>> GetUserInfo(JsParam param)
|
||||
{
|
||||
User user = serviceProvider.GetRequiredService<IUserService>().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<JsResult<Dictionary<string, string>>> GetCookieTokenAsync(JsParam<CookieTokenPayload> param)
|
||||
{
|
||||
User user = serviceProvider.GetRequiredService<IUserService>().Current!;
|
||||
string? cookieToken;
|
||||
if (param.Payload!.ForceRefresh)
|
||||
{
|
||||
cookieToken = await Ioc.Default
|
||||
.GetRequiredService<PassportClient2>()
|
||||
.GetCookieAccountInfoBySTokenAsync(user.Entity, default)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
user.CookieToken![Cookie.COOKIE_TOKEN] = cookieToken!;
|
||||
Ioc.Default.GetRequiredService<AppDbContext>().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<IJsResult?> ConfigureShare(JsParam param)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
[JsMethod("showAlertDialog")]
|
||||
public virtual Task<IJsResult?> ShowAlertDialogAsync(JsParam param)
|
||||
{
|
||||
return Task.FromException<IJsResult?>(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<IJsResult?> GetNotificationSettingsAsync(JsParam param)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
[JsMethod("showToast")]
|
||||
public virtual Task<IJsResult?> ShowToast(JsParam param)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private async Task<string> 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<JsParam>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 调用桥
|
||||
/// </summary>
|
||||
[ComVisible(true)]
|
||||
[ClassInterface(ClassInterfaceType.None)]
|
||||
public sealed class MiHoYoJsBridge : IMiHoYoJsBridge
|
||||
{
|
||||
private readonly CoreWebView2 webView;
|
||||
private readonly ILogger<MiHoYoJsBridge>? logger;
|
||||
|
||||
private readonly Dictionary<string, string> jsWebInvokeTypeCache = new();
|
||||
private readonly Dictionary<string, Action<JsParam>> callbackHandlers = new();
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的调用桥
|
||||
/// </summary>
|
||||
/// <param name="webView">webview2</param>
|
||||
/// <param name="logger">日志器</param>
|
||||
internal MiHoYoJsBridge(CoreWebView2 webView, ILogger<MiHoYoJsBridge>? logger = null)
|
||||
{
|
||||
this.webView = webView;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 消息发生时调用
|
||||
/// </summary>
|
||||
/// <param name="message">消息</param>
|
||||
public void OnMessage(string message)
|
||||
{
|
||||
logger?.LogInformation("[OnMessage] {message}", message);
|
||||
|
||||
JsParam p = JsonSerializer.Deserialize<JsParam>(message)!;
|
||||
p.Bridge = this;
|
||||
|
||||
callbackHandlers.GetValueOrDefault(p.Method)?.Invoke(p);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调用JS回调
|
||||
/// </summary>
|
||||
/// <param name="callbackName">回调名称</param>
|
||||
/// <param name="payload">传输的数据</param>
|
||||
/// <returns>执行结果</returns>
|
||||
public Task<string> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册回调
|
||||
/// </summary>
|
||||
/// <typeparam name="T">回调类型</typeparam>
|
||||
/// <param name="callback">回调</param>
|
||||
/// <returns>桥</returns>
|
||||
public MiHoYoJsBridge Register<T>(Action<JsParam> callback)
|
||||
where T : notnull
|
||||
{
|
||||
callbackHandlers[GetCallbackName<T>()] = callback;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册回调
|
||||
/// </summary>
|
||||
/// <typeparam name="T">回调类型</typeparam>
|
||||
/// <param name="callback">回调</param>
|
||||
/// <returns>桥</returns>
|
||||
public MiHoYoJsBridge Register<T>(Action<JsParam, T> callback)
|
||||
where T : notnull
|
||||
{
|
||||
callbackHandlers[GetCallbackName<T>()] = p => callback(p, p.Data.As<T>());
|
||||
return this;
|
||||
}
|
||||
|
||||
private string GetCallbackName<T>()
|
||||
{
|
||||
Type type = typeof(T);
|
||||
string invokeName = type.GetCustomAttribute<WebInvokeAttribute>()?.Name
|
||||
?? throw new ArgumentException("Type Callback not registered.");
|
||||
|
||||
return jsWebInvokeTypeCache[type.Name] = invokeName;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 账户信息
|
||||
/// </summary>
|
||||
public class AccountInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的账户信息
|
||||
/// </summary>
|
||||
/// <param name="userInformation">用户信息</param>
|
||||
/// <param name="ltoken">ltoken</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 账户Id
|
||||
/// </summary>
|
||||
[JsonPropertyName("aid")]
|
||||
public string Aid { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 米哈游Id
|
||||
/// </summary>
|
||||
[JsonPropertyName("mid")]
|
||||
public string Mid { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 空
|
||||
/// </summary>
|
||||
[JsonPropertyName("accountName")]
|
||||
public string AccountName { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 区域码 +86
|
||||
/// </summary>
|
||||
[JsonPropertyName("areaCode")]
|
||||
public string AreaCode { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 手机号 111****1111
|
||||
/// </summary>
|
||||
[JsonPropertyName("mobile")]
|
||||
public string Mobile { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 空
|
||||
/// </summary>
|
||||
[JsonPropertyName("email")]
|
||||
public string Email { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Token
|
||||
/// </summary>
|
||||
[JsonPropertyName("tokenStr")]
|
||||
public string Token { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Token 类型
|
||||
/// </summary>
|
||||
[JsonPropertyName("tokenType")]
|
||||
public int TokenType { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Bridge.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 操作类型包装
|
||||
/// </summary>
|
||||
public class ActionTypePayload
|
||||
{
|
||||
/// <summary>
|
||||
/// 操作类型
|
||||
/// </summary>
|
||||
[JsonPropertyName("action_type")]
|
||||
public string ActionType { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Bridge.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 获取CookieToken的请求
|
||||
/// </summary>
|
||||
public class CookieTokenPayload
|
||||
{
|
||||
/// <summary>
|
||||
/// 强制刷新
|
||||
/// </summary>
|
||||
[JsonPropertyName("forceRefresh")]
|
||||
public bool ForceRefresh { get; set; }
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Bridge.Model.Event;
|
||||
|
||||
/// <summary>
|
||||
/// Web 调用
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
|
||||
public class WebInvokeAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的Web 调用特性
|
||||
/// </summary>
|
||||
/// <param name="name">函数名称</param>
|
||||
public WebInvokeAttribute(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调用函数名称
|
||||
/// </summary>
|
||||
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<string, string> 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<ButtonParam> 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!;
|
||||
}
|
||||
17
src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/IJsResult.cs
Normal file
17
src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/IJsResult.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Bridge.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 指示此为Js结果
|
||||
/// </summary>
|
||||
public interface IJsResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 转换到Json字符串表示
|
||||
/// </summary>
|
||||
/// <param name="options">序列化参数</param>
|
||||
/// <returns>JSON字符串</returns>
|
||||
string ToString(JsonSerializerOptions options);
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
namespace Snap.Hutao.Web.Bridge.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 由WebView向客户端传递的参数
|
||||
/// Js 参数
|
||||
/// </summary>
|
||||
public class JsParam
|
||||
@@ -12,52 +13,54 @@ public class JsParam
|
||||
/// 方法名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("method")]
|
||||
public string Method { get; set; } = string.Empty;
|
||||
public string Method { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 数据
|
||||
/// 数据 可以为空
|
||||
/// </summary>
|
||||
[JsonPropertyName("payload")]
|
||||
public JsonElement Data { get; set; }
|
||||
public JsonElement? Payload { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 回调名称
|
||||
/// 回调的名称,调用 JavaScript:mhyWebBridge 时作为首个参数传入
|
||||
/// </summary>
|
||||
[JsonPropertyName("callback")]
|
||||
public string CallbackName { get; set; } = string.Empty;
|
||||
public string Callback { get; set; } = default!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 由WebView向客户端传递的参数
|
||||
/// Js 参数
|
||||
/// </summary>
|
||||
/// <typeparam name="TPayload">Payload 类型</typeparam>
|
||||
[SuppressMessage("", "SA1402")]
|
||||
public class JsParam<TPayload>
|
||||
{
|
||||
/// <summary>
|
||||
/// 方法名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("method")]
|
||||
public string Method { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 对应的调用桥
|
||||
/// 数据 可以为空
|
||||
/// </summary>
|
||||
internal MiHoYoJsBridge Bridge { get; set; } = null!;
|
||||
[JsonPropertyName("payload")]
|
||||
public TPayload Payload { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 执行回调
|
||||
/// 回调的名称,调用 JavaScript:mhyWebBridge 时作为首个参数传入
|
||||
/// </summary>
|
||||
/// <param name="resultFactory">结果工厂</param>
|
||||
public void Callback(Func<JsResult>? resultFactory = null)
|
||||
[JsonPropertyName("callback")]
|
||||
public string Callback { get; set; } = string.Empty;
|
||||
|
||||
public static implicit operator JsParam<TPayload>(JsParam jsParam)
|
||||
{
|
||||
JsResult? result = resultFactory?.Invoke() ?? new();
|
||||
Callback(result?.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行回调
|
||||
/// </summary>
|
||||
/// <param name="resultModifier">结果工厂</param>
|
||||
public void Callback(Action<JsResult> resultModifier)
|
||||
{
|
||||
JsResult result = new();
|
||||
resultModifier(result);
|
||||
Callback(result?.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行回调
|
||||
/// </summary>
|
||||
/// <param name="result">结果</param>
|
||||
public void Callback(string? result = null)
|
||||
{
|
||||
Bridge.InvokeJsCallbackAsync(CallbackName, result).GetAwaiter().GetResult();
|
||||
return new JsParam<TPayload>()
|
||||
{
|
||||
Method = jsParam.Method,
|
||||
Payload = jsParam.Payload.HasValue ? jsParam.Payload.Value.Deserialize<TPayload>() : default,
|
||||
Callback = jsParam.Callback,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,11 @@
|
||||
namespace Snap.Hutao.Web.Bridge.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 用于传回网页的参数
|
||||
/// Js结果
|
||||
/// </summary>
|
||||
public class JsResult
|
||||
/// <typeparam name="TData">内部数据类型</typeparam>
|
||||
public class JsResult<TData> : IJsResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 代码
|
||||
@@ -24,10 +26,10 @@ public class JsResult
|
||||
/// 数据
|
||||
/// </summary>
|
||||
[JsonPropertyName("data")]
|
||||
public Dictionary<string, object> Data { get; set; } = new();
|
||||
public TData Data { get; set; } = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
string IJsResult.ToString(JsonSerializerOptions options)
|
||||
{
|
||||
return JsonSerializer.Serialize(this);
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Bridge.Model;
|
||||
|
||||
/// <summary>
|
||||
/// JsonElement 拓展
|
||||
/// </summary>
|
||||
public static class JsonElementExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 序列化到对应类型
|
||||
/// </summary>
|
||||
/// <typeparam name="T">对应类型</typeparam>
|
||||
/// <param name="jsonElement">元素</param>
|
||||
/// <returns>对应类型的实例</returns>
|
||||
public static T As<T>(this JsonElement jsonElement)
|
||||
where T : notnull
|
||||
{
|
||||
return jsonElement.Deserialize<T>()!;
|
||||
}
|
||||
}
|
||||
33
src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJsInterface.cs
Normal file
33
src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJsInterface.cs
Normal file
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 签到页面JS桥
|
||||
/// </summary>
|
||||
public class SignInJsInterface : MiHoYoJSInterface
|
||||
{
|
||||
/// <inheritdoc cref="MiHoYoJSInterface(CoreWebView2, IServiceProvider)"/>
|
||||
public SignInJsInterface(CoreWebView2 webView, IServiceProvider serviceProvider)
|
||||
: base(webView, serviceProvider)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override JsResult<Dictionary<string, string>> GetHttpRequestHeader(JsParam param)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Data = new Dictionary<string, string>()
|
||||
{
|
||||
{ "x-rpc-client_type", "2" },
|
||||
{ "x-rpc-device_id", Core.CoreEnvironment.HoyolabDeviceId },
|
||||
{ "x-rpc-app_version", Core.CoreEnvironment.HoyolabXrpcVersion },
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -15,10 +15,11 @@ namespace Snap.Hutao.Web.Hoyolab.DynamicSecret;
|
||||
[Injection(InjectAs.Transient)]
|
||||
public class DynamicSecretHandler : DelegatingHandler
|
||||
{
|
||||
private const string RandomRange = "abcdefghijklmnopqrstuvwxyz1234567890";
|
||||
|
||||
/// <summary>
|
||||
/// 盐
|
||||
/// </summary>
|
||||
// https://github.com/UIGF-org/Hoyolab.Salt
|
||||
private static readonly ImmutableDictionary<string, string> DynamicSecrets = new Dictionary<string, string>()
|
||||
public static readonly ImmutableDictionary<string, string> DynamicSecrets = new Dictionary<string, string>()
|
||||
{
|
||||
[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";
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken token)
|
||||
{
|
||||
@@ -66,36 +69,6 @@ public class DynamicSecretHandler : DelegatingHandler
|
||||
return await base.SendAsync(request, token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取DS
|
||||
/// </summary>
|
||||
/// <param name="saltType">nameof <see cref="SaltType"/></param>
|
||||
/// <param name="version">nameof <see cref="DynamicSecretVersion"/></param>
|
||||
/// <param name="body">body</param>
|
||||
/// <param name="query">query</param>
|
||||
/// <param name="includeChars">是否需要字母</param>
|
||||
/// <returns>DS</returns>
|
||||
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);
|
||||
|
||||
@@ -58,6 +58,24 @@ internal class AuthClient
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取操作凭证
|
||||
/// </summary>
|
||||
/// <param name="action">操作</param>
|
||||
/// <param name="user">用户</param>
|
||||
/// <returns>操作凭证</returns>
|
||||
[ApiInformation(Cookie = CookieType.Stoken, Salt = SaltType.K2)]
|
||||
public async Task<Response<ActionTicketWrapper>?> GetActionTicketWrapperByStokenAsync(string action, User user)
|
||||
{
|
||||
Response<ActionTicketWrapper>? resp = await httpClient
|
||||
.SetUser(user, CookieType.Stoken)
|
||||
.UseDynamicSecret(DynamicSecretVersion.Gen1, SaltType.K2, true)
|
||||
.TryCatchGetFromJsonAsync<Response<ActionTicketWrapper>>(ApiEndpoints.AuthActionTicket(action, user.Stoken![Cookie.STOKEN], user.Aid!), options, logger)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 MultiToken
|
||||
/// </summary>
|
||||
|
||||
@@ -43,6 +43,11 @@ public enum KnownReturnCode : int
|
||||
/// </summary>
|
||||
RET_NEED_AIGIS = -3101,
|
||||
|
||||
/// <summary>
|
||||
/// 请在米游社App内打开~
|
||||
/// </summary>
|
||||
PleaseOpenInBbsApp = -1104,
|
||||
|
||||
/// <summary>
|
||||
/// 登录信息已失效,请重新登录
|
||||
/// </summary>
|
||||
|
||||
@@ -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 class Response : ISupportValidation
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">数据类型</typeparam>
|
||||
[SuppressMessage("", "SA1402")]
|
||||
public class Response<TData> : Response
|
||||
public class Response<TData> : Response, IJsResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的 Mihoyo 标准API响应
|
||||
@@ -91,4 +92,10 @@ public class Response<TData> : Response
|
||||
{
|
||||
return ReturnCode == 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
string IJsResult.ToString(JsonSerializerOptions options)
|
||||
{
|
||||
return JsonSerializer.Serialize(this);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user