mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
impl #870
This commit is contained in:
@@ -17,4 +17,5 @@
|
||||
<x:String x:Key="FontIconContentZipFolder"></x:String>
|
||||
<x:String x:Key="FontIconContentGridView"></x:String>
|
||||
<x:String x:Key="FontIconContentGiftboxOpen"></x:String>
|
||||
<x:String x:Key="FontIconContentQrCode"></x:String>
|
||||
</ResourceDictionary>
|
||||
@@ -19,4 +19,9 @@ internal static class Random
|
||||
{
|
||||
return new(System.Random.Shared.GetItems("0123456789abcdefghijklmnopqrstuvwxyz".AsSpan(), length));
|
||||
}
|
||||
|
||||
public static string GetLetterAndNumberString(int length)
|
||||
{
|
||||
return new(System.Random.Shared.GetItems("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".AsSpan(), length));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Factory.QrCode;
|
||||
|
||||
internal interface IQrCodeFactory
|
||||
{
|
||||
byte[] CreateByteArr(string source);
|
||||
}
|
||||
25
src/Snap.Hutao/Snap.Hutao/Factory/QrCode/QrCodeFactory.cs
Normal file
25
src/Snap.Hutao/Snap.Hutao/Factory/QrCode/QrCodeFactory.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using QRCoder;
|
||||
|
||||
namespace Snap.Hutao.Factory.QrCode;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Singleton, typeof(IQrCodeFactory))]
|
||||
internal class QrCodeFactory : IQrCodeFactory
|
||||
{
|
||||
public byte[] CreateByteArr(string source)
|
||||
{
|
||||
using (QRCodeGenerator generator = new())
|
||||
{
|
||||
using (QRCodeData data = generator.CreateQrCode(source, QRCodeGenerator.ECCLevel.Q))
|
||||
{
|
||||
using (BitmapByteQRCode code = new(data))
|
||||
{
|
||||
return code.GetGraphic(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1265,6 +1265,9 @@
|
||||
<data name="ViewDialogLaunchGamePackageConvertTitle" xml:space="preserve">
|
||||
<value>正在转换客户端</value>
|
||||
</data>
|
||||
<data name="ViewDialogQrCodeTitle" xml:space="preserve">
|
||||
<value>使用米游社扫描二维码</value>
|
||||
</data>
|
||||
<data name="ViewDialogSettingDeleteUserDataContent" xml:space="preserve">
|
||||
<value>该操作是不可逆的,所有用户登录状态会丢失</value>
|
||||
</data>
|
||||
@@ -2618,6 +2621,9 @@
|
||||
<data name="ViewUserCookieOperationLoginMihoyoUserAction" xml:space="preserve">
|
||||
<value>网页登录</value>
|
||||
</data>
|
||||
<data name="ViewUserCookieOperationLoginQrCodeAction" xml:space="preserve">
|
||||
<value>扫码登录</value>
|
||||
</data>
|
||||
<data name="ViewUserCookieOperationManualInputAction" xml:space="preserve">
|
||||
<value>手动输入</value>
|
||||
</data>
|
||||
|
||||
@@ -173,6 +173,7 @@
|
||||
<None Remove="View\Dialog\HutaoPassportUnregisterDialog.xaml" />
|
||||
<None Remove="View\Dialog\LaunchGameAccountNameDialog.xaml" />
|
||||
<None Remove="View\Dialog\LaunchGamePackageConvertDialog.xaml" />
|
||||
<None Remove="View\Dialog\QrCodeDialog.xaml" />
|
||||
<None Remove="View\Dialog\UserDialog.xaml" />
|
||||
<None Remove="View\Guide\GuideView.xaml" />
|
||||
<None Remove="View\InfoBarView.xaml" />
|
||||
@@ -301,6 +302,7 @@
|
||||
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.8.8" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231115000" />
|
||||
<PackageReference Include="QRCoder" Version="1.4.3" />
|
||||
<PackageReference Include="Snap.Discord.GameSDK" Version="1.5.0" />
|
||||
<PackageReference Include="Snap.Hutao.SourceGeneration" Version="1.0.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
@@ -329,6 +331,11 @@
|
||||
<ItemGroup>
|
||||
<None Include="..\.editorconfig" Link=".editorconfig" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Dialog\QrCodeDialog.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Card\Primitive\HorizontalCard.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
||||
21
src/Snap.Hutao/Snap.Hutao/View/Dialog/QrCodeDialog.xaml
Normal file
21
src/Snap.Hutao/Snap.Hutao/View/Dialog/QrCodeDialog.xaml
Normal file
@@ -0,0 +1,21 @@
|
||||
<ContentDialog
|
||||
x:Class="Snap.Hutao.View.Dialog.QrCodeDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||
xmlns:shvd="using:Snap.Hutao.View.Dialog"
|
||||
Title="{shcm:ResourceString Name=ViewDialogQrCodeTitle}"
|
||||
CloseButtonText="{shcm:ResourceString Name=ContentDialogCancelCloseButtonText}"
|
||||
DefaultButton="Close"
|
||||
Style="{StaticResource DefaultContentDialogStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<StackPanel>
|
||||
<Image
|
||||
x:Name="ImageView"
|
||||
Width="200"
|
||||
Height="200" />
|
||||
</StackPanel>
|
||||
</ContentDialog>
|
||||
107
src/Snap.Hutao/Snap.Hutao/View/Dialog/QrCodeDialog.xaml.cs
Normal file
107
src/Snap.Hutao/Snap.Hutao/View/Dialog/QrCodeDialog.xaml.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Snap.Hutao.Factory.QrCode;
|
||||
using Snap.Hutao.Web.Hoyolab.Hk4e.QrCode;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Snap.Hutao.View.Dialog;
|
||||
|
||||
/// <summary>
|
||||
/// 扫描二维码对话框
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal sealed partial class QrCodeDialog : ContentDialog
|
||||
{
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly QrCodeClient qrCodeClient;
|
||||
private readonly IQrCodeFactory qrCodeFactory;
|
||||
|
||||
private QrCodeAccount? account;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的扫描二维码对话框
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
public QrCodeDialog(IServiceProvider serviceProvider)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
||||
qrCodeClient = serviceProvider.GetRequiredService<QrCodeClient>();
|
||||
qrCodeFactory = serviceProvider.GetRequiredService<IQrCodeFactory>();
|
||||
|
||||
Initialize().SafeForget();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取登录的用户
|
||||
/// </summary>
|
||||
/// <returns>QrCodeAccount</returns>
|
||||
public async ValueTask<ValueResult<bool, QrCodeAccount>> GetAccountAsync()
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ContentDialogResult result = await ShowAsync();
|
||||
return new(account is not null, account!);
|
||||
}
|
||||
|
||||
private async ValueTask Initialize()
|
||||
{
|
||||
Response<QrCodeFetch> fetch = await qrCodeClient.PostQrCodeFetchAsync().ConfigureAwait(false);
|
||||
if (fetch.IsOk())
|
||||
{
|
||||
string url = Regex.Unescape(fetch.Data.Url);
|
||||
string ticket = url.ToUri().Query.Split('&').Last().Split('=').Last();
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
BitmapImage bitmap = new();
|
||||
await bitmap.SetSourceAsync(new MemoryStream(qrCodeFactory.CreateByteArr(url)).AsRandomAccessStream());
|
||||
|
||||
ImageView.Source = bitmap;
|
||||
if (bitmap is BitmapSource { PixelHeight: > 0, PixelWidth: > 0 })
|
||||
{
|
||||
VisualStateManager.GoToState(this, "Loaded", true);
|
||||
}
|
||||
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
|
||||
using (PeriodicTimer timer = new(TimeSpan.FromSeconds(3)))
|
||||
{
|
||||
while (await timer.WaitForNextTickAsync().ConfigureAwait(false))
|
||||
{
|
||||
Response<QrCodeQuery> query = await qrCodeClient.PostQrCodeQueryAsync(ticket).ConfigureAwait(false);
|
||||
if (query.IsOk(false))
|
||||
{
|
||||
switch (query.Data.Stat)
|
||||
{
|
||||
case QrCodeQueryStatus.INIT:
|
||||
case QrCodeQueryStatus.SCANNED:
|
||||
break; // @switch
|
||||
case QrCodeQueryStatus.CONFIRMED:
|
||||
if (query.Data.Payload.Proto == QrCodeQueryPayload.ACCOUNT)
|
||||
{
|
||||
account = JsonSerializer.Deserialize<QrCodeAccount>(query.Data.Payload.Raw);
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
break; // @switch
|
||||
}
|
||||
}
|
||||
else if (query.ReturnCode == (int)KnownReturnCode.QrCodeExpired)
|
||||
{
|
||||
Initialize().SafeForget();
|
||||
break; // @while
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,6 +154,13 @@
|
||||
Command="{Binding LoginMihoyoUserCommand}"
|
||||
Icon="{shcm:FontIcon Glyph={StaticResource FontIconContentWebsite}}"
|
||||
Label="{shcm:ResourceString Name=ViewUserCookieOperationLoginMihoyoUserAction}"/>
|
||||
<AppBarButton
|
||||
Width="{StaticResource LargeAppBarButtonWidth}"
|
||||
MaxWidth="{StaticResource LargeAppBarButtonWidth}"
|
||||
Margin="2,-4"
|
||||
Command="{Binding LoginQrCodeCommand}"
|
||||
Icon="{shcm:FontIcon Glyph={StaticResource FontIconContentQrCode}}"
|
||||
Label="{shcm:ResourceString Name=ViewUserCookieOperationLoginQrCodeAction}"/>
|
||||
<AppBarButton
|
||||
Width="{StaticResource LargeAppBarButtonWidth}"
|
||||
MaxWidth="{StaticResource LargeAppBarButtonWidth}"
|
||||
|
||||
@@ -15,6 +15,10 @@ using Snap.Hutao.Service.User;
|
||||
using Snap.Hutao.View.Dialog;
|
||||
using Snap.Hutao.View.Page;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Hk4e.QrCode;
|
||||
using Snap.Hutao.Web.Hoyolab.Passport;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Account;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text;
|
||||
using Windows.System;
|
||||
@@ -38,6 +42,7 @@ internal sealed partial class UserViewModel : ObservableObject
|
||||
private readonly ISignInService signInService;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly IUserService userService;
|
||||
private readonly SessionAppClient sessionAppClient;
|
||||
|
||||
private User? selectedUser;
|
||||
private ObservableCollection<User>? users;
|
||||
@@ -173,6 +178,29 @@ internal sealed partial class UserViewModel : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
[Command("LoginQrCodeCommand")]
|
||||
private async Task LoginQrCode()
|
||||
{
|
||||
// ContentDialog must be created by main thread.
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
|
||||
QrCodeDialog dialog = await contentDialogFactory.CreateInstanceAsync<QrCodeDialog>().ConfigureAwait(false);
|
||||
ValueResult<bool, QrCodeAccount> result = await dialog.GetAccountAsync().ConfigureAwait(false);
|
||||
|
||||
if (result.TryGetValue(out QrCodeAccount account))
|
||||
{
|
||||
Response<LoginResult> gameTokenResp = await sessionAppClient.PostSTokenByGameTokenAsync(account).ConfigureAwait(false);
|
||||
|
||||
if (gameTokenResp.IsOk())
|
||||
{
|
||||
Cookie stokenV2 = Cookie.FromLoginResult(gameTokenResp.Data);
|
||||
(UserOptionResult optionResult, string uid) = await userService.ProcessInputCookieAsync(stokenV2, false).ConfigureAwait(false);
|
||||
|
||||
await HandleUserOptionResultAsync(optionResult, uid).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Command("RemoveUserCommand")]
|
||||
private async Task RemoveUserAsync(User? user)
|
||||
{
|
||||
|
||||
@@ -16,6 +16,15 @@ namespace Snap.Hutao.Web;
|
||||
[SuppressMessage("", "SA1124")]
|
||||
internal static class ApiEndpoints
|
||||
{
|
||||
#region ApiTakumiAccountSessionApp
|
||||
|
||||
/// <summary>
|
||||
/// 通过 GameToken 获取 SToken (V2)
|
||||
/// </summary>
|
||||
public const string STokenByGameToken = $"{ApiTakumiAccountSessionApp}/getTokenByGameToken";
|
||||
|
||||
#endregion
|
||||
|
||||
#region ApiTakumiAuthApi
|
||||
|
||||
/// <summary>
|
||||
@@ -40,6 +49,7 @@ internal static class ApiEndpoints
|
||||
{
|
||||
return $"{ApiTakumiAuthApi}/getMultiTokenByLoginTicket?login_ticket={loginTicket}&uid={loginUid}&token_types=3";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ApiTaKumiBindingApi
|
||||
@@ -357,9 +367,18 @@ internal static class ApiEndpoints
|
||||
// https://sdk-static.mihoyo.com/hk4e_cn/mdk/launcher/api/content?key=eYd89JmJ&language=zh-cn&launcher_id=18
|
||||
#endregion
|
||||
|
||||
#region Hk4eSdk
|
||||
|
||||
public const string QrCodeFetch = $"{Hk4eSdk}/hk4e_cn/combo/panda/qrcode/fetch";
|
||||
|
||||
public const string QrCodeQuery = $"{Hk4eSdk}/hk4e_cn/combo/panda/qrcode/query";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Hosts | Queries
|
||||
private const string ApiTakumi = "https://api-takumi.mihoyo.com";
|
||||
private const string ApiTakumiAuthApi = $"{ApiTakumi}/auth/api";
|
||||
private const string ApiTakumiAccountSessionApp = $"{ApiTakumi}/account/ma-cn-session/app";
|
||||
private const string ApiTaKumiBindingApi = $"{ApiTakumi}/binding/api";
|
||||
|
||||
private const string ApiTakumiCardApi = $"{ApiTakumiRecord}/game_record/app/card/api";
|
||||
@@ -382,6 +401,8 @@ internal static class ApiEndpoints
|
||||
private const string Hk4eApi = "https://hk4e-api.mihoyo.com";
|
||||
private const string Hk4eApiAnnouncementApi = $"{Hk4eApi}/common/hk4e_cn/announcement/api";
|
||||
|
||||
private const string Hk4eSdk = "https://hk4e-sdk.mihoyo.com";
|
||||
|
||||
private const string PassportApi = "https://passport-api.mihoyo.com";
|
||||
private const string PassportApiAuthApi = $"{PassportApi}/account/auth/api";
|
||||
private const string PassportApiV4 = "https://passport-api-v4.mihoyo.com";
|
||||
@@ -402,4 +423,4 @@ internal static class ApiEndpoints
|
||||
|
||||
private const string AnnouncementQuery = "game=hk4e&game_biz=hk4e_cn&lang=zh-cn&bundle_id=hk4e_cn&platform=pc®ion=cn_gf01&level=55&uid=100000000";
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Hk4e.QrCode;
|
||||
|
||||
[HighQuality]
|
||||
internal sealed class QrCodeAccount
|
||||
{
|
||||
[JsonPropertyName("uid")]
|
||||
public string Stuid { get; set; } = default!;
|
||||
|
||||
[JsonPropertyName("token")]
|
||||
public string GameToken { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
using Snap.Hutao.Web.Request.Builder;
|
||||
using Snap.Hutao.Web.Request.Builder.Abstraction;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Hk4e.QrCode;
|
||||
|
||||
[HighQuality]
|
||||
[ConstructorGenerated(ResolveHttpClient = true)]
|
||||
[HttpClient(HttpClientConfiguration.Default)]
|
||||
internal sealed partial class QrCodeClient
|
||||
{
|
||||
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
|
||||
private readonly ILogger<QrCodeClient> logger;
|
||||
private readonly HttpClient httpClient;
|
||||
|
||||
private readonly string device = Core.Random.GetLetterAndNumberString(64);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取扫码链接
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>login url</returns>
|
||||
public async ValueTask<Response<QrCodeFetch>> PostQrCodeFetchAsync(CancellationToken token = default)
|
||||
{
|
||||
QrCodeFetchOptions options = new(4, device);
|
||||
|
||||
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
|
||||
.SetRequestUri(ApiEndpoints.QrCodeFetch)
|
||||
.PostJson(options);
|
||||
|
||||
Response<QrCodeFetch>? resp = await builder
|
||||
.TryCatchSendAsync<Response<QrCodeFetch>>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取扫码状态
|
||||
/// </summary>
|
||||
/// <param name="ticket">扫码链接中的ticket</param>
|
||||
/// <param name="token">取消令牌/param>
|
||||
/// <returns>扫码状态</returns>
|
||||
public async ValueTask<Response<QrCodeQuery>> PostQrCodeQueryAsync(string ticket, CancellationToken token = default)
|
||||
{
|
||||
QrCodeQueryOptions options = new(4, device, ticket);
|
||||
|
||||
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
|
||||
.SetRequestUri(ApiEndpoints.QrCodeQuery)
|
||||
.PostJson(options);
|
||||
|
||||
Response<QrCodeQuery>? resp = await builder
|
||||
.TryCatchSendAsync<Response<QrCodeQuery>>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Hk4e.QrCode;
|
||||
|
||||
[HighQuality]
|
||||
internal sealed class QrCodeFetch
|
||||
{
|
||||
[JsonPropertyName("url")]
|
||||
public string Url { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Hk4e.QrCode;
|
||||
|
||||
[HighQuality]
|
||||
internal sealed class QrCodeFetchOptions
|
||||
{
|
||||
public QrCodeFetchOptions(int appId, string device)
|
||||
{
|
||||
AppId = appId;
|
||||
Device = device;
|
||||
}
|
||||
|
||||
[JsonPropertyName("app_id")]
|
||||
public int AppId { get; set; }
|
||||
|
||||
[JsonPropertyName("device")]
|
||||
public string Device { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Hk4e.QrCode;
|
||||
|
||||
[HighQuality]
|
||||
internal sealed class QrCodeQuery
|
||||
{
|
||||
[JsonPropertyName("stat")]
|
||||
public string Stat { get; set; } = default!;
|
||||
|
||||
[JsonPropertyName("payload")]
|
||||
public QrCodeQueryPayload Payload { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Hk4e.QrCode;
|
||||
|
||||
[HighQuality]
|
||||
internal sealed class QrCodeQueryOptions
|
||||
{
|
||||
public QrCodeQueryOptions(int appId, string device, string ticket)
|
||||
{
|
||||
AppId = appId;
|
||||
Device = device;
|
||||
Ticket = ticket;
|
||||
}
|
||||
|
||||
[JsonPropertyName("app_id")]
|
||||
public int AppId { get; set; }
|
||||
|
||||
[JsonPropertyName("device")]
|
||||
public string Device { get; set; } = default!;
|
||||
|
||||
[JsonPropertyName("ticket")]
|
||||
public string Ticket { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Hk4e.QrCode;
|
||||
|
||||
/// <summary>
|
||||
/// Proto 常量
|
||||
/// </summary>
|
||||
internal sealed partial class QrCodeQueryPayload
|
||||
{
|
||||
public const string ACCOUNT = "Account";
|
||||
public const string RAW = "Raw";
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Hk4e.QrCode;
|
||||
|
||||
[HighQuality]
|
||||
internal sealed partial class QrCodeQueryPayload
|
||||
{
|
||||
[JsonPropertyName("proto")]
|
||||
public string Proto { get; set; } = default!;
|
||||
|
||||
[JsonPropertyName("raw")]
|
||||
public string Raw { get; set; } = default!;
|
||||
|
||||
[JsonPropertyName("ext")]
|
||||
public string Ext { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Hk4e.QrCode;
|
||||
|
||||
/// <summary>
|
||||
/// 扫码状态
|
||||
/// </summary>
|
||||
internal sealed partial class QrCodeQueryStatus
|
||||
{
|
||||
public const string INIT = "Init";
|
||||
public const string SCANNED = "Scanned";
|
||||
public const string CONFIRMED = "Confirmed";
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.Account;
|
||||
|
||||
internal sealed class GameTokenWrapper
|
||||
{
|
||||
[JsonPropertyName("account_id")]
|
||||
public int Stuid { get; set; } = default!;
|
||||
|
||||
[JsonPropertyName("game_token")]
|
||||
public string GameToken { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
using Snap.Hutao.Web.Hoyolab.Hk4e.QrCode;
|
||||
using Snap.Hutao.Web.Hoyolab.Passport;
|
||||
using Snap.Hutao.Web.Request.Builder;
|
||||
using Snap.Hutao.Web.Request.Builder.Abstraction;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.Account;
|
||||
|
||||
[HighQuality]
|
||||
[ConstructorGenerated(ResolveHttpClient = true)]
|
||||
[HttpClient(HttpClientConfiguration.XRpc2)]
|
||||
internal sealed partial class SessionAppClient
|
||||
{
|
||||
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
|
||||
private readonly ILogger<SessionAppClient> logger;
|
||||
private readonly HttpClient httpClient;
|
||||
|
||||
/// <summary>
|
||||
/// 通过 GameToken 获取 SToken (V2)
|
||||
/// </summary>
|
||||
/// <param name="account">扫码获得的账户信息</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>登录结果</returns>
|
||||
public async ValueTask<Response<LoginResult>> PostSTokenByGameTokenAsync(QrCodeAccount account, CancellationToken token = default)
|
||||
{
|
||||
GameTokenWrapper wrapper = new()
|
||||
{
|
||||
Stuid = int.Parse(account.Stuid, CultureInfo.CurrentCulture),
|
||||
GameToken = account.GameToken,
|
||||
};
|
||||
|
||||
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
|
||||
.SetRequestUri(ApiEndpoints.STokenByGameToken)
|
||||
.PostJson(wrapper);
|
||||
|
||||
Response<LoginResult>? resp = await builder
|
||||
.TryCatchSendAsync<Response<LoginResult>>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
}
|
||||
@@ -138,4 +138,9 @@ internal enum KnownReturnCode
|
||||
/// 实时便笺
|
||||
/// </summary>
|
||||
CODE10104 = 10104,
|
||||
|
||||
/// <summary>
|
||||
/// 二维码已过期
|
||||
/// </summary>
|
||||
QrCodeExpired = -106,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user