Skip to content

Commit

Permalink
Merge pull request #2067 from DGP-Studio/feat/1616
Browse files Browse the repository at this point in the history
  • Loading branch information
Lightczx authored Oct 19, 2024
2 parents 2d4440d + 8a0284e commit c9acac5
Show file tree
Hide file tree
Showing 19 changed files with 255 additions and 355 deletions.
19 changes: 19 additions & 0 deletions src/Snap.Hutao/Snap.Hutao/Service/Geetest/AigisData.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
16 changes: 16 additions & 0 deletions src/Snap.Hutao/Snap.Hutao/Service/Geetest/AigisSession.cs
Original file line number Diff line number Diff line change
@@ -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!;
}
109 changes: 109 additions & 0 deletions src/Snap.Hutao/Snap.Hutao/Service/Geetest/GeetestService.cs
Original file line number Diff line number Diff line change
@@ -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<GeetestData?> 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<GeetestWebResponse>(result);
ArgumentNullException.ThrowIfNull(webResponse);

return new GeetestData()
{
Gt = gt,
Challenge = webResponse.Challenge,
Validate = webResponse.Validate,
};
}

public async ValueTask<string?> TryVerifyXrpcChallengeAsync(Model.Entity.User user, CardVerifiationHeaders headers, CancellationToken token = default)
{
Response<VerificationRegistration> 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<VerificationResult> 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<bool> TryVerifyAigisSessionAsync(IAigisProvider provider, string? rawSession, bool isOversea, CancellationToken token = default)
{
if (string.IsNullOrEmpty(rawSession))
{
return false;
}

AigisSession? session = JsonSerializer.Deserialize<AigisSession>(rawSession);
ArgumentNullException.ThrowIfNull(session);

AigisData? sessionData = JsonSerializer.Deserialize<AigisData>(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<string?> 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);
}
}
16 changes: 16 additions & 0 deletions src/Snap.Hutao/Snap.Hutao/Service/Geetest/GeetestWebResponse.cs
Original file line number Diff line number Diff line change
@@ -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!;
}
17 changes: 17 additions & 0 deletions src/Snap.Hutao/Snap.Hutao/Service/Geetest/IGeetestService.cs
Original file line number Diff line number Diff line change
@@ -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<GeetestData?> TryVerifyGtChallengeAsync(string gt, string challenge, CancellationToken token = default);

ValueTask<string?> TryVerifyXrpcChallengeAsync(Model.Entity.User user, CardVerifiationHeaders headers, CancellationToken token = default);

ValueTask<bool> TryVerifyAigisSessionAsync(IAigisProvider provider, string? rawSession, bool isOversea, CancellationToken token = default);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<TProvider>(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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,24 @@
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;

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<ITaskContext>();
InitializeComponent();
}

public string? Account { get => account; set => SetProperty(ref account, value); }

public string? Password
Expand Down Expand Up @@ -63,26 +59,19 @@ public string? Password
ArgumentNullException.ThrowIfNull(Account);
ArgumentNullException.ThrowIfNull(Password);

string? rawSession;
Response<LoginResult> response;

using (IServiceScope scope = serviceProvider.CreateScope())
{
IHoyoPlayPassportClient hoyoPlayPassportClient = scope.ServiceProvider.GetRequiredService<IOverseaSupportFactory<IHoyoPlayPassportClient>>().Create(isOversea);
(rawSession, response) = await hoyoPlayPassportClient.LoginByPasswordAsync(this).ConfigureAwait(false);
}
(string? rawSession, Response<LoginResult> 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<IOverseaSupportFactory<IHoyoPlayPassportClient>>().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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<ITaskContext>();
InitializeComponent();
}

public string? Mobile
{
get => mobile;
Expand Down Expand Up @@ -67,27 +63,20 @@ public async Task SendMobileCaptchaAsync()
{
ArgumentNullException.ThrowIfNull(Mobile);

string? rawSession;
Response<MobileCaptcha> response;

using (IServiceScope scope = serviceProvider.CreateScope())
{
IPassportClient passportClient = scope.ServiceProvider.GetRequiredService<IOverseaSupportFactory<IPassportClient>>().Create(false);
(rawSession, response) = await passportClient.CreateLoginCaptchaAsync(Mobile, null).ConfigureAwait(false);
}
(string? rawSession, Response<MobileCaptcha> 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<IOverseaSupportFactory<IPassportClient>>().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
Expand Down
2 changes: 0 additions & 2 deletions src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/UserView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -429,13 +429,11 @@
</mxi:Interaction.Behaviors>
</AppBarButton>
<AppBarButton
x:Name="SignInRewardButton"
Width="{StaticResource LargeAppBarButtonWidth}"
MaxWidth="{StaticResource LargeAppBarButtonWidth}"
Margin="2,-4"
AllowFocusOnInteraction="True"
Command="{Binding ClaimSignInRewardCommand}"
CommandParameter="{Binding ElementName=SignInRewardButton}"
Icon="{shuxm:FontIcon Glyph={StaticResource FontIconContentGiftboxOpen}}"
Label="{shuxm:ResourceString Name=ViewUserCookieOperationSignInRewardAction}"
Style="{StaticResource DefaultAppBarButtonStyle}"/>
Expand Down
5 changes: 1 addition & 4 deletions src/Snap.Hutao/Snap.Hutao/ViewModel/TitleViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<UpdateLogContentProvider>(currentXamlWindowReference.GetXamlRoot());
}
}

Expand Down
Loading

0 comments on commit c9acac5

Please sign in to comment.