diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Geetest/AigisData.cs b/src/Snap.Hutao/Snap.Hutao/Service/Geetest/AigisData.cs new file mode 100644 index 0000000000..5904898b7c --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Geetest/AigisData.cs @@ -0,0 +1,19 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.Geetest; + +internal sealed class AigisData +{ + [JsonPropertyName("success")] + public int Success { get; set; } + + [JsonPropertyName("gt")] + public string GT { get; set; } = default!; + + [JsonPropertyName("challenge")] + public string Challenge { get; set; } = default!; + + [JsonPropertyName("new_captcha")] + public int NewCaptcha { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Geetest/AigisSession.cs b/src/Snap.Hutao/Snap.Hutao/Service/Geetest/AigisSession.cs new file mode 100644 index 0000000000..a762b54d2a --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Geetest/AigisSession.cs @@ -0,0 +1,16 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.Geetest; + +internal sealed class AigisSession +{ + [JsonPropertyName("session_id")] + public string SessionId { get; set; } = default!; + + [JsonPropertyName("mmt_type")] + public int MmtType { get; set; } + + [JsonPropertyName("data")] + public string Data { get; set; } = default!; +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Geetest/GeetestService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Geetest/GeetestService.cs new file mode 100644 index 0000000000..dbf387826e --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Geetest/GeetestService.cs @@ -0,0 +1,109 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core.LifeCycle; +using Snap.Hutao.Service.Notification; +using Snap.Hutao.UI.Xaml.Behavior.Action; +using Snap.Hutao.UI.Xaml.View.Window.WebView2; +using Snap.Hutao.Web.Hoyolab.Passport; +using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord; +using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Verification; +using Snap.Hutao.Web.Hutao.Geetest; +using Snap.Hutao.Web.Response; +using System.Text; + +namespace Snap.Hutao.Service.Geetest; + +[ConstructorGenerated] +[Injection(InjectAs.Transient, typeof(IGeetestService))] +internal sealed partial class GeetestService : IGeetestService +{ + private readonly ICurrentXamlWindowReference currentXamlWindowReference; + private readonly CustomGeetestClient customGeetestClient; + private readonly IInfoBarService infoBarService; + private readonly ITaskContext taskContext; + private readonly CardClient cardClient; + + public async ValueTask TryVerifyGtChallengeAsync(string gt, string challenge, CancellationToken token = default) + { + GeetestResponse response = await customGeetestClient.VerifyAsync(gt, challenge, token).ConfigureAwait(false); + + if (response is { Code: 0, Data: { } data }) + { + return data; + } + + string? result = await PrivateVerifyByWebViewAsync(gt, challenge, false, token).ConfigureAwait(false); + if (string.IsNullOrEmpty(result)) + { + return default; + } + + GeetestWebResponse? webResponse = JsonSerializer.Deserialize(result); + ArgumentNullException.ThrowIfNull(webResponse); + + return new GeetestData() + { + Gt = gt, + Challenge = webResponse.Challenge, + Validate = webResponse.Validate, + }; + } + + public async ValueTask TryVerifyXrpcChallengeAsync(Model.Entity.User user, CardVerifiationHeaders headers, CancellationToken token = default) + { + Response registrationResponse = await cardClient.CreateVerificationAsync(user, headers, token).ConfigureAwait(false); + if (!ResponseValidator.TryValidate(registrationResponse, infoBarService, out VerificationRegistration? registration)) + { + return default; + } + + if (await TryVerifyGtChallengeAsync(registration.Gt, registration.Challenge, token).ConfigureAwait(false) is not { } data) + { + return default; + } + + Response verifyResponse = await cardClient.VerifyVerificationAsync(user, headers, registration.Challenge, data.Validate, token).ConfigureAwait(false); + if (!ResponseValidator.TryValidate(verifyResponse, infoBarService, out VerificationResult? result)) + { + return default; + } + + return result.Challenge; + } + + public async ValueTask TryVerifyAigisSessionAsync(IAigisProvider provider, string? rawSession, bool isOversea, CancellationToken token = default) + { + if (string.IsNullOrEmpty(rawSession)) + { + return false; + } + + AigisSession? session = JsonSerializer.Deserialize(rawSession); + ArgumentNullException.ThrowIfNull(session); + + AigisData? sessionData = JsonSerializer.Deserialize(session.Data); + ArgumentNullException.ThrowIfNull(sessionData); + + string? result = await PrivateVerifyByWebViewAsync(sessionData.GT, sessionData.Challenge, isOversea, token).ConfigureAwait(false); + + if (string.IsNullOrEmpty(result)) + { + // User closed the window without completing the verification + return false; + } + + provider.Aigis = $"{session.SessionId};{Convert.ToBase64String(Encoding.UTF8.GetBytes(result))}"; + return true; + } + + private async ValueTask PrivateVerifyByWebViewAsync(string gt, string challenge, bool isOversea, CancellationToken token) + { + await taskContext.SwitchToMainThreadAsync(); + GeetestWebView2ContentProvider contentProvider = new(gt, challenge, isOversea); + ShowWebView2WindowAction.Show(contentProvider, currentXamlWindowReference.GetXamlRoot()); + + await taskContext.SwitchToBackgroundAsync(); + return await contentProvider.GetResultAsync().ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Geetest/GeetestWebResponse.cs b/src/Snap.Hutao/Snap.Hutao/Service/Geetest/GeetestWebResponse.cs new file mode 100644 index 0000000000..429f5b7c7f --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Geetest/GeetestWebResponse.cs @@ -0,0 +1,16 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.Geetest; + +internal sealed class GeetestWebResponse +{ + [JsonPropertyName("geetest_challenge")] + public string Challenge { get; set; } = default!; + + [JsonPropertyName("geetest_validate")] + public string Validate { get; set; } = default!; + + [JsonPropertyName("geetest_seccode")] + public string Seccode { get; set; } = default!; +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Geetest/IGeetestService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Geetest/IGeetestService.cs new file mode 100644 index 0000000000..87ea025637 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Geetest/IGeetestService.cs @@ -0,0 +1,17 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Web.Hoyolab.Passport; +using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord; +using Snap.Hutao.Web.Hutao.Geetest; + +namespace Snap.Hutao.Service.Geetest; + +internal interface IGeetestService +{ + ValueTask TryVerifyGtChallengeAsync(string gt, string challenge, CancellationToken token = default); + + ValueTask TryVerifyXrpcChallengeAsync(Model.Entity.User user, CardVerifiationHeaders headers, CancellationToken token = default); + + ValueTask TryVerifyAigisSessionAsync(IAigisProvider provider, string? rawSession, bool isOversea, CancellationToken token = default); +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/Behavior/Action/ShowWebView2WindowAction.cs b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/Behavior/Action/ShowWebView2WindowAction.cs index 9d79b77c04..eec0105225 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/Behavior/Action/ShowWebView2WindowAction.cs +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/Behavior/Action/ShowWebView2WindowAction.cs @@ -10,6 +10,23 @@ namespace Snap.Hutao.UI.Xaml.Behavior.Action; [DependencyProperty("ContentProvider", typeof(IWebView2ContentProvider))] internal sealed partial class ShowWebView2WindowAction : DependencyObject, IAction { + public static ShowWebView2WindowAction Show(XamlRoot xamlRoot) + where TProvider : IWebView2ContentProvider, new() + { + return Show(new TProvider(), xamlRoot); + } + + public static ShowWebView2WindowAction Show(IWebView2ContentProvider contentProvider, XamlRoot xamlRoot) + { + ShowWebView2WindowAction action = new() + { + ContentProvider = contentProvider, + }; + + action.ShowAt(xamlRoot); + return action; + } + public object? Execute(object sender, object parameter) { ShowAt(((FrameworkElement)sender).XamlRoot); diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/UserAccountPasswordDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/UserAccountPasswordDialog.xaml.cs index 285fe19a2c..91032ce5ba 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/UserAccountPasswordDialog.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/UserAccountPasswordDialog.xaml.cs @@ -5,6 +5,7 @@ using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Input; using Snap.Hutao.Core.DependencyInjection.Abstraction; +using Snap.Hutao.Service.Geetest; using Snap.Hutao.Web.Hoyolab.Passport; using Snap.Hutao.Web.Response; using Windows.System; @@ -12,21 +13,16 @@ namespace Snap.Hutao.UI.Xaml.View.Dialog; [INotifyPropertyChanged] +[ConstructorGenerated(InitializeComponent = true)] internal sealed partial class UserAccountPasswordDialog : ContentDialog, IPassportPasswordProvider { private readonly IServiceProvider serviceProvider; + private readonly IGeetestService geetestService; private readonly ITaskContext taskContext; private string? account; private string? password; - public UserAccountPasswordDialog(IServiceProvider serviceProvider) - { - this.serviceProvider = serviceProvider; - taskContext = serviceProvider.GetRequiredService(); - InitializeComponent(); - } - public string? Account { get => account; set => SetProperty(ref account, value); } public string? Password @@ -63,26 +59,19 @@ public string? Password ArgumentNullException.ThrowIfNull(Account); ArgumentNullException.ThrowIfNull(Password); - string? rawSession; - Response response; - using (IServiceScope scope = serviceProvider.CreateScope()) { IHoyoPlayPassportClient hoyoPlayPassportClient = scope.ServiceProvider.GetRequiredService>().Create(isOversea); - (rawSession, response) = await hoyoPlayPassportClient.LoginByPasswordAsync(this).ConfigureAwait(false); - } + (string? rawSession, Response response) = await hoyoPlayPassportClient.LoginByPasswordAsync(this).ConfigureAwait(false); - if (await this.TryResolveAigisAsync(rawSession, isOversea, taskContext).ConfigureAwait(false)) - { - using (IServiceScope scope = serviceProvider.CreateScope()) + if (await geetestService.TryVerifyAigisSessionAsync(this, rawSession, isOversea).ConfigureAwait(false)) { - IHoyoPlayPassportClient hoyoPlayPassportClient = scope.ServiceProvider.GetRequiredService>().Create(isOversea); - (rawSession, response) = await hoyoPlayPassportClient.LoginByPasswordAsync(this).ConfigureAwait(false); + (_, response) = await hoyoPlayPassportClient.LoginByPasswordAsync(this).ConfigureAwait(false); } - } - bool ok = ResponseValidator.TryValidate(response, serviceProvider, out LoginResult? result); - return new(ok, result); + bool ok = ResponseValidator.TryValidate(response, serviceProvider, out LoginResult? result); + return new(ok, result); + } } private void OnTextKeyDown(object sender, KeyRoutedEventArgs e) diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/UserMobileCaptchaDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/UserMobileCaptchaDialog.xaml.cs index 60c87da932..79e9faefe4 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/UserMobileCaptchaDialog.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/UserMobileCaptchaDialog.xaml.cs @@ -5,6 +5,7 @@ using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Input; using Snap.Hutao.Core.DependencyInjection.Abstraction; +using Snap.Hutao.Service.Geetest; using Snap.Hutao.Web.Hoyolab.Passport; using Snap.Hutao.Web.Response; using System.Text.RegularExpressions; @@ -13,21 +14,16 @@ namespace Snap.Hutao.UI.Xaml.View.Dialog; [INotifyPropertyChanged] +[ConstructorGenerated(InitializeComponent = true)] internal sealed partial class UserMobileCaptchaDialog : ContentDialog, IPassportMobileCaptchaProvider { private readonly IServiceProvider serviceProvider; + private readonly IGeetestService geetestService; private readonly ITaskContext taskContext; private string? mobile; private string? captcha; - public UserMobileCaptchaDialog(IServiceProvider serviceProvider) - { - this.serviceProvider = serviceProvider; - taskContext = serviceProvider.GetRequiredService(); - InitializeComponent(); - } - public string? Mobile { get => mobile; @@ -67,27 +63,20 @@ public async Task SendMobileCaptchaAsync() { ArgumentNullException.ThrowIfNull(Mobile); - string? rawSession; - Response response; - using (IServiceScope scope = serviceProvider.CreateScope()) { IPassportClient passportClient = scope.ServiceProvider.GetRequiredService>().Create(false); - (rawSession, response) = await passportClient.CreateLoginCaptchaAsync(Mobile, null).ConfigureAwait(false); - } + (string? rawSession, Response response) = await passportClient.CreateLoginCaptchaAsync(Mobile, null).ConfigureAwait(false); - if (await this.TryResolveAigisAsync(rawSession, false, taskContext).ConfigureAwait(false)) - { - using (IServiceScope scope = serviceProvider.CreateScope()) + if (await geetestService.TryVerifyAigisSessionAsync(this, rawSession, false).ConfigureAwait(false)) { - IPassportClient passportClient = scope.ServiceProvider.GetRequiredService>().Create(false); - (rawSession, response) = await passportClient.CreateLoginCaptchaAsync(Mobile, Aigis).ConfigureAwait(false); + (_, response) = await passportClient.CreateLoginCaptchaAsync(Mobile, Aigis).ConfigureAwait(false); } - } - if (ResponseValidator.TryValidate(response, serviceProvider, out MobileCaptcha? mobileCaptcha)) - { - ActionType = mobileCaptcha.ActionType; + if (ResponseValidator.TryValidate(response, serviceProvider, out MobileCaptcha? mobileCaptcha)) + { + ActionType = mobileCaptcha.ActionType; + } } // Prevent re-enable too soon, and user might not receive the short message diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/UserView.xaml b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/UserView.xaml index bcc27cb141..bb22f34ded 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/UserView.xaml +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/UserView.xaml @@ -429,13 +429,11 @@ diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/TitleViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/TitleViewModel.cs index 9dcd056a2a..54b7e4b852 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/TitleViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/TitleViewModel.cs @@ -75,10 +75,7 @@ private void ShowUpdateLogWindowAfterUpdate() if (LocalSetting.Get(SettingKeys.AlwaysIsFirstRunAfterUpdate, false) || XamlApplicationLifetime.IsFirstRunAfterUpdate) { XamlApplicationLifetime.IsFirstRunAfterUpdate = false; - new ShowWebView2WindowAction - { - ContentProvider = new UpdateLogContentProvider(), - }.ShowAt(currentXamlWindowReference.GetXamlRoot()); + ShowWebView2WindowAction.Show(currentXamlWindowReference.GetXamlRoot()); } } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs index 72a7f5a498..bce6ae1f43 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs @@ -128,11 +128,7 @@ private async Task LoginByThirdPartyOverseaAsync(string kind) await taskContext.SwitchToMainThreadAsync(); OverseaThirdPartyLoginWebView2ContentProvider contentProvider = new(thirdPartyKind, cultureOptions.LanguageCode); - - new ShowWebView2WindowAction - { - ContentProvider = contentProvider, - }.ShowAt(currentXamlWindowReference.GetXamlRoot()); + ShowWebView2WindowAction.Show(contentProvider, currentXamlWindowReference.GetXamlRoot()); await taskContext.SwitchToBackgroundAsync(); ThirdPartyToken? token = await contentProvider.GetResultAsync().ConfigureAwait(false); @@ -307,7 +303,7 @@ private async Task RefreshCookieTokenAsync() } [Command("ClaimSignInRewardCommand")] - private async Task ClaimSignInRewardAsync(AppBarButton? appBarButton) + private async Task ClaimSignInRewardAsync() { if (await userService.GetCurrentUserAndUidAsync().ConfigureAwait(false) is not { } userAndUid) { @@ -325,20 +321,14 @@ private async Task ClaimSignInRewardAsync(AppBarButton? appBarButton) infoBarService.Warning(message); - if (appBarButton is null) - { - return; - } - // Manual webview await taskContext.SwitchToMainThreadAsync(); - new ShowWebView2WindowAction + MiHoYoJSBridgeWebView2ContentProvider provider = new() { - ContentProvider = new MiHoYoJSBridgeWebView2ContentProvider - { - SourceProvider = new SignInJSBridgeUriSourceProvider(), - }, - }.ShowAt(appBarButton.XamlRoot); + SourceProvider = new SignInJSBridgeUriSourceProvider(), + }; + + ShowWebView2WindowAction.Show(provider, currentXamlWindowReference.GetXamlRoot()); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/IAigisProvider.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/IAigisProvider.cs index ae59a92ad6..b92b0a7f9a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/IAigisProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/IAigisProvider.cs @@ -1,13 +1,9 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Microsoft.UI.Xaml; - namespace Snap.Hutao.Web.Hoyolab.Passport; internal interface IAigisProvider { string? Aigis { get; set; } - - XamlRoot XamlRoot { get; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/IAigisProviderExtension.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/IAigisProviderExtension.cs deleted file mode 100644 index 8ac4239d0b..0000000000 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/IAigisProviderExtension.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Snap.Hutao.UI.Xaml.Behavior.Action; -using Snap.Hutao.UI.Xaml.View.Window.WebView2; -using System.Text; - -namespace Snap.Hutao.Web.Hoyolab.Passport; - -internal static class IAigisProviderExtension -{ - public static async ValueTask TryResolveAigisAsync(this IAigisProvider provider, string? rawSession, bool isOversea, ITaskContext taskContext) - { - if (string.IsNullOrEmpty(rawSession)) - { - return false; - } - - AigisObject? session = JsonSerializer.Deserialize(rawSession); - ArgumentNullException.ThrowIfNull(session); - AigisData? sessionData = JsonSerializer.Deserialize(session.Data); - ArgumentNullException.ThrowIfNull(sessionData); - - await taskContext.SwitchToMainThreadAsync(); - GeetestWebView2ContentProvider contentProvider = new(sessionData.GT, sessionData.Challenge, isOversea); - - new ShowWebView2WindowAction - { - ContentProvider = contentProvider, - }.ShowAt(provider.XamlRoot); - - await taskContext.SwitchToBackgroundAsync(); - string? result = await contentProvider.GetResultAsync().ConfigureAwait(false); - - if (string.IsNullOrEmpty(result)) - { - // User closed the window without completing the verification - return false; - } - - provider.Aigis = $"{session.SessionId};{Convert.ToBase64String(Encoding.UTF8.GetBytes(result))}"; - return true; - } - - private sealed class AigisObject - { - [JsonPropertyName("session_id")] - public string SessionId { get; set; } = default!; - - [JsonPropertyName("mmt_type")] - public int MmtType { get; set; } - - [JsonPropertyName("data")] - public string Data { get; set; } = default!; - } - - private sealed class AigisData - { - [JsonPropertyName("success")] - public int Success { get; set; } - - [JsonPropertyName("gt")] - public string GT { get; set; } = default!; - - [JsonPropertyName("challenge")] - public string Challenge { get; set; } = default!; - - [JsonPropertyName("new_captcha")] - public int NewCaptcha { get; set; } - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInClient.cs index 1cbff8e94a..617737b034 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInClient.cs @@ -3,10 +3,10 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.Service; +using Snap.Hutao.Service.Geetest; using Snap.Hutao.ViewModel.User; using Snap.Hutao.Web.Endpoint.Hoyolab; using Snap.Hutao.Web.Hoyolab.DataSigning; -using Snap.Hutao.Web.Hutao.Geetest; using Snap.Hutao.Web.Request.Builder; using Snap.Hutao.Web.Request.Builder.Abstraction; using Snap.Hutao.Web.Response; @@ -20,7 +20,7 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward; internal sealed partial class SignInClient : ISignInClient { private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory; - private readonly HomaGeetestClient homaGeetestClient; + private readonly IGeetestService geetestService; private readonly CultureOptions cultureOptions; private readonly ILogger logger; [FromKeyed(ApiEndpointsKind.Chinese)] @@ -109,20 +109,16 @@ public async ValueTask> SignAsync(UserAndUid userAndUid, if (resp is { Data: { Success: 1, Gt: { } gt, Challenge: { } originChallenge } }) { - GeetestResponse verifyResponse = await homaGeetestClient.VerifyAsync(gt, originChallenge, token).ConfigureAwait(false); - - if (verifyResponse is { Code: 0, Data: { Validate: { } validate, Challenge: { } challenge } }) + if (await geetestService.TryVerifyGtChallengeAsync(gt, originChallenge, token).ConfigureAwait(false) is { } data) { - HttpRequestMessageBuilder verifiedBuilder = httpRequestMessageBuilderFactory.Create() - .SetRequestUri(apiEndpoints.LunaSolSign()) - .SetUserCookieAndFpHeader(userAndUid, CookieType.CookieToken) + builder + .Resurrect() .SetHeader("x-rpc-signgame", "hk4e") - .SetXrpcChallenge(challenge, validate) - .PostJson(new SignInData(apiEndpoints, userAndUid.Uid)); + .SetXrpcChallenge(data.Challenge, data.Validate); - await verifiedBuilder.SignDataAsync(DataSignAlgorithmVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false); - resp = await verifiedBuilder + resp = await builder .SendAsync>(httpClient, logger, token) .ConfigureAwait(false); } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs index 1071733e53..76151865c3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs @@ -3,11 +3,11 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.Model.Primitive; +using Snap.Hutao.Service.Geetest; using Snap.Hutao.ViewModel.User; using Snap.Hutao.Web.Endpoint.Hoyolab; using Snap.Hutao.Web.Hoyolab.DataSigning; using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar; -using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Verification; using Snap.Hutao.Web.Request.Builder; using Snap.Hutao.Web.Request.Builder.Abstraction; using Snap.Hutao.Web.Response; @@ -42,34 +42,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient .SendAsync>(httpClient, logger, token) .ConfigureAwait(false); - // We have a verification procedure to handle - if (resp?.ReturnCode is (int)KnownReturnCode.CODE1034) - { - // Replace message - resp.Message = SH.WebDailyNoteVerificationFailed; - - IGeetestCardVerifier verifier = serviceProvider.GetRequiredKeyedService(GeetestCardVerifierType.Custom); - CardVerifiationHeaders headers = CardVerifiationHeaders.CreateForDailyNote(apiEndpoints); - - if (await verifier.TryValidateXrpcChallengeAsync(userAndUid.User, headers, token).ConfigureAwait(false) is { } challenge) - { - HttpRequestMessageBuilder verifiedbuilder = httpRequestMessageBuilderFactory.Create() - .SetRequestUri(apiEndpoints.GameRecordDailyNote(userAndUid.Uid)) - .SetUserCookieAndFpHeader(userAndUid, CookieType.Cookie) - .SetReferer(apiEndpoints.WebStaticReferer()) - .SetHeader("x-rpc-tool_verison", "v5.0.1-ys") - .SetXrpcChallenge(challenge) - .Get(); - - await verifiedbuilder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); - - resp = await verifiedbuilder - .SendAsync>(httpClient, logger, token) - .ConfigureAwait(false); - } - } - - return Response.Response.DefaultIfNull(resp); + return await RetryIf1034Async(builder, userAndUid, resp, SH.WebDailyNoteVerificationFailed, CardVerifiationHeaders.CreateForDailyNote, token).ConfigureAwait(false); } public async ValueTask> GetPlayerInfoAsync(UserAndUid userAndUid, CancellationToken token = default) @@ -86,33 +59,7 @@ public async ValueTask> GetPlayerInfoAsync(UserAndUid userA .SendAsync>(httpClient, logger, token) .ConfigureAwait(false); - // We have a verification procedure to handle - if (resp?.ReturnCode == (int)KnownReturnCode.CODE1034) - { - // Replace message - resp.Message = SH.WebIndexOrSpiralAbyssVerificationFailed; - - IGeetestCardVerifier verifier = serviceProvider.GetRequiredKeyedService(GeetestCardVerifierType.Custom); - CardVerifiationHeaders headers = CardVerifiationHeaders.CreateForIndex(apiEndpoints); - - if (await verifier.TryValidateXrpcChallengeAsync(userAndUid.User, headers, token).ConfigureAwait(false) is { } challenge) - { - HttpRequestMessageBuilder verifiedbuilder = httpRequestMessageBuilderFactory.Create() - .SetRequestUri(apiEndpoints.GameRecordIndex(userAndUid.Uid)) - .SetUserCookieAndFpHeader(userAndUid, CookieType.Cookie) - .SetReferer(apiEndpoints.WebStaticReferer()) - .SetXrpcChallenge(challenge) - .Get(); - - await verifiedbuilder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); - - resp = await verifiedbuilder - .SendAsync>(httpClient, logger, token) - .ConfigureAwait(false); - } - } - - return Response.Response.DefaultIfNull(resp); + return await RetryIf1034Async(builder, userAndUid, resp, SH.WebIndexOrSpiralAbyssVerificationFailed, CardVerifiationHeaders.CreateForIndex, token).ConfigureAwait(false); } public async ValueTask> GetSpiralAbyssAsync(UserAndUid userAndUid, ScheduleType schedule, CancellationToken token = default) @@ -130,28 +77,19 @@ public async ValueTask> GetPlayerInfoAsync(UserAndUid userA .ConfigureAwait(false); // We have a verification procedure to handle - if (resp?.ReturnCode == (int)KnownReturnCode.CODE1034) + if (resp?.ReturnCode is (int)KnownReturnCode.CODE1034) { // Replace message resp.Message = SH.WebIndexOrSpiralAbyssVerificationFailed; - IGeetestCardVerifier verifier = serviceProvider.GetRequiredKeyedService(GeetestCardVerifierType.Custom); + IGeetestService geetestService = serviceProvider.GetRequiredService(); CardVerifiationHeaders headers = CardVerifiationHeaders.CreateForSpiralAbyss(apiEndpoints); - if (await verifier.TryValidateXrpcChallengeAsync(userAndUid.User, headers, token).ConfigureAwait(false) is { } challenge) + if (await geetestService.TryVerifyXrpcChallengeAsync(userAndUid.User, headers, token).ConfigureAwait(false) is { } challenge) { - HttpRequestMessageBuilder verifiedbuilder = httpRequestMessageBuilderFactory.Create() - .SetRequestUri(apiEndpoints.GameRecordSpiralAbyss(schedule, userAndUid.Uid)) - .SetUserCookieAndFpHeader(userAndUid, CookieType.Cookie) - .SetReferer(apiEndpoints.WebStaticReferer()) - .SetXrpcChallenge(challenge) - .Get(); - - await verifiedbuilder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); - - resp = await verifiedbuilder - .SendAsync>(httpClient, logger, token) - .ConfigureAwait(false); + builder.Resurrect().SetXrpcChallenge(challenge); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); + resp = await builder.SendAsync>(httpClient, logger, token).ConfigureAwait(false); } } @@ -189,33 +127,7 @@ public async ValueTask>> GetCharacterListAsync(U .SendAsync>>(httpClient, logger, token) .ConfigureAwait(false); - // We have a verification procedure to handle - if (resp?.ReturnCode is (int)KnownReturnCode.CODE1034) - { - // Replace message - resp.Message = SH.WebIndexOrSpiralAbyssVerificationFailed; - - IGeetestCardVerifier verifier = serviceProvider.GetRequiredKeyedService(GeetestCardVerifierType.Custom); - CardVerifiationHeaders headers = CardVerifiationHeaders.CreateForCharacterAll(apiEndpoints); - - if (await verifier.TryValidateXrpcChallengeAsync(userAndUid.User, headers, token).ConfigureAwait(false) is { } challenge) - { - HttpRequestMessageBuilder verifiedBuilder = httpRequestMessageBuilderFactory.Create() - .SetRequestUri(apiEndpoints.GameRecordCharacterList()) - .SetUserCookieAndFpHeader(userAndUid, CookieType.Cookie) - .SetReferer(apiEndpoints.WebStaticReferer()) - .SetXrpcChallenge(challenge) - .PostJson(new CharacterData(userAndUid.Uid)); - - await verifiedBuilder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); - - resp = await verifiedBuilder - .SendAsync>>(httpClient, logger, token) - .ConfigureAwait(false); - } - } - - return Response.Response.DefaultIfNull(resp); + return await RetryIf1034Async(builder, userAndUid, resp, SH.WebIndexOrSpiralAbyssVerificationFailed, CardVerifiationHeaders.CreateForCharacterAll, token).ConfigureAwait(false); } public async ValueTask>> GetCharacterDetailAsync(UserAndUid userAndUid, List characterIds, CancellationToken token = default) @@ -232,33 +144,7 @@ public async ValueTask>> GetCharacterDet .SendAsync>>(httpClient, logger, token) .ConfigureAwait(false); - // We have a verification procedure to handle - if (resp?.ReturnCode is (int)KnownReturnCode.CODE1034) - { - // Replace message - resp.Message = SH.WebIndexOrSpiralAbyssVerificationFailed; - - IGeetestCardVerifier verifier = serviceProvider.GetRequiredKeyedService(GeetestCardVerifierType.Custom); - CardVerifiationHeaders headers = CardVerifiationHeaders.CreateForCharacterDetail(apiEndpoints); - - if (await verifier.TryValidateXrpcChallengeAsync(userAndUid.User, headers, token).ConfigureAwait(false) is { } challenge) - { - HttpRequestMessageBuilder verifiedBuilder = httpRequestMessageBuilderFactory.Create() - .SetRequestUri(apiEndpoints.GameRecordCharacterDetail()) - .SetUserCookieAndFpHeader(userAndUid, CookieType.Cookie) - .SetReferer(apiEndpoints.WebStaticReferer()) - .SetXrpcChallenge(challenge) - .PostJson(new CharacterData(userAndUid.Uid, characterIds)); - - await verifiedBuilder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); - - resp = await verifiedBuilder - .SendAsync>>(httpClient, logger, token) - .ConfigureAwait(false); - } - } - - return Response.Response.DefaultIfNull(resp); + return await RetryIf1034Async(builder, userAndUid, resp, SH.WebIndexOrSpiralAbyssVerificationFailed, CardVerifiationHeaders.CreateForCharacterDetail, token).ConfigureAwait(false); } public async ValueTask> GetRoleCombatAsync(UserAndUid userAndUid, CancellationToken token = default) @@ -275,26 +161,29 @@ public async ValueTask>> GetCharacterDet .SendAsync>(httpClient, logger, token) .ConfigureAwait(false); + return await RetryIf1034Async(builder, userAndUid, resp, SH.WebIndexOrSpiralAbyssVerificationFailed, CardVerifiationHeaders.CreateForRoleCombat, token).ConfigureAwait(false); + } + + private async ValueTask RetryIf1034Async(HttpRequestMessageBuilder builder, UserAndUid userAndUid, TResponse? response, string message, Func headersFactory, CancellationToken token = default) + where TResponse : class, ICommonResponse + { // We have a verification procedure to handle - if (resp?.ReturnCode is (int)KnownReturnCode.CODE1034) + if (response?.ReturnCode is (int)KnownReturnCode.CODE1034) { // Replace message - resp.Message = SH.WebIndexOrSpiralAbyssVerificationFailed; + response.Message = message; - IGeetestCardVerifier verifier = serviceProvider.GetRequiredKeyedService(GeetestCardVerifierType.Custom); - CardVerifiationHeaders headers = CardVerifiationHeaders.CreateForRoleCombat(apiEndpoints); + IGeetestService geetestService = serviceProvider.GetRequiredService(); + CardVerifiationHeaders headers = headersFactory(apiEndpoints); - if (await verifier.TryValidateXrpcChallengeAsync(userAndUid.User, headers, token).ConfigureAwait(false) is { } challenge) + if (await geetestService.TryVerifyXrpcChallengeAsync(userAndUid.User, headers, token).ConfigureAwait(false) is { } challenge) { builder.Resurrect().SetXrpcChallenge(challenge); await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); - - resp = await builder - .SendAsync>(httpClient, logger, token) - .ConfigureAwait(false); + response = await builder.SendAsync(httpClient, logger, token).ConfigureAwait(false); } } - return Response.Response.DefaultIfNull(resp); + return Response.Response.DefaultIfNull(response); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Verification/GeetestCardVerifierType.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Verification/GeetestCardVerifierType.cs deleted file mode 100644 index f8dedec3b2..0000000000 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Verification/GeetestCardVerifierType.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Verification; - -internal enum GeetestCardVerifierType -{ - Custom, -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Verification/HomaGeetestCardVerifier.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Verification/HomaGeetestCardVerifier.cs deleted file mode 100644 index 779304b892..0000000000 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Verification/HomaGeetestCardVerifier.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Snap.Hutao.Model.Entity; -using Snap.Hutao.Service.Notification; -using Snap.Hutao.Web.Hutao.Geetest; -using Snap.Hutao.Web.Response; - -namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Verification; - -[ConstructorGenerated] -[Injection(InjectAs.Transient, typeof(IGeetestCardVerifier), Key = GeetestCardVerifierType.Custom)] -internal sealed partial class HomaGeetestCardVerifier : IGeetestCardVerifier -{ - private readonly HomaGeetestClient homaGeetestClient; - private readonly IInfoBarService infoBarService; - private readonly CardClient cardClient; - - public async ValueTask TryValidateXrpcChallengeAsync(User user, CardVerifiationHeaders headers, CancellationToken token) - { - Response registrationResponse = await cardClient.CreateVerificationAsync(user, headers, token).ConfigureAwait(false); - if (!ResponseValidator.TryValidate(registrationResponse, infoBarService, out VerificationRegistration? registration)) - { - return default; - } - - GeetestResponse response = await homaGeetestClient.VerifyAsync(registration.Gt, registration.Challenge, token).ConfigureAwait(false); - - if (response is not { Code: 0, Data.Validate: { } validate }) - { - return default; - } - - Response verifyResponse = await cardClient.VerifyVerificationAsync(user, headers, registration.Challenge, validate, token).ConfigureAwait(false); - if (!ResponseValidator.TryValidate(verifyResponse, infoBarService, out VerificationResult? result)) - { - return default; - } - - if (result.Challenge is not null) - { - return result.Challenge; - } - - return default; - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Verification/IGeetestCardVerifier.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Verification/IGeetestCardVerifier.cs deleted file mode 100644 index 13126c4561..0000000000 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Verification/IGeetestCardVerifier.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Snap.Hutao.Model.Entity; - -namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Verification; - -internal interface IGeetestCardVerifier -{ - ValueTask TryValidateXrpcChallengeAsync(User user, CardVerifiationHeaders headers, CancellationToken token); -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Geetest/HomaGeetestClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Geetest/CustomGeetestClient.cs similarity index 93% rename from src/Snap.Hutao/Snap.Hutao/Web/Hutao/Geetest/HomaGeetestClient.cs rename to src/Snap.Hutao/Snap.Hutao/Web/Hutao/Geetest/CustomGeetestClient.cs index f53ede69c8..199e72cac7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Geetest/HomaGeetestClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Geetest/CustomGeetestClient.cs @@ -12,10 +12,10 @@ namespace Snap.Hutao.Web.Hutao.Geetest; [ConstructorGenerated(ResolveHttpClient = true)] [HttpClient(HttpClientConfiguration.Default)] -internal sealed partial class HomaGeetestClient +internal sealed partial class CustomGeetestClient { private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory; - private readonly ILogger logger; + private readonly ILogger logger; private readonly AppOptions appOptions; private readonly HttpClient httpClient;