code style (?)

This commit is contained in:
qhy040404
2023-12-11 18:47:41 +08:00
parent a8d4dc84a1
commit 2fb6cd3441
24 changed files with 209 additions and 204 deletions

View File

@@ -5,5 +5,5 @@ namespace Snap.Hutao.Factory.QrCode;
internal interface IQrCodeFactory
{
byte[] CreateByteArr(string source);
byte[] CreateQrCodeByteArray(string source);
}

View File

@@ -9,7 +9,7 @@ namespace Snap.Hutao.Factory.QrCode;
[Injection(InjectAs.Singleton, typeof(IQrCodeFactory))]
internal class QrCodeFactory : IQrCodeFactory
{
public byte[] CreateByteArr(string source)
public byte[] CreateQrCodeByteArray(string source)
{
using (QRCodeGenerator generator = new())
{

View File

@@ -5,7 +5,6 @@
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"

View File

@@ -3,10 +3,10 @@
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.Hoyolab.Passport;
using Snap.Hutao.Web.Response;
using System.IO;
using System.Text.RegularExpressions;
@@ -20,10 +20,10 @@ namespace Snap.Hutao.View.Dialog;
internal sealed partial class QrCodeDialog : ContentDialog
{
private readonly ITaskContext taskContext;
private readonly QrCodeClient qrCodeClient;
private readonly PassportClient2 passportClient2;
private readonly IQrCodeFactory qrCodeFactory;
private QrCodeAccount? account;
private UidGameToken? account;
/// <summary>
/// 构造一个新的扫描二维码对话框
@@ -34,26 +34,27 @@ internal sealed partial class QrCodeDialog : ContentDialog
InitializeComponent();
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
qrCodeClient = serviceProvider.GetRequiredService<QrCodeClient>();
passportClient2 = serviceProvider.GetRequiredService<PassportClient2>();
qrCodeFactory = serviceProvider.GetRequiredService<IQrCodeFactory>();
Initialize().SafeForget();
FetchQrCodeAsync().SafeForget();
}
/// <summary>
/// 获取登录的用户
/// </summary>
/// <returns>QrCodeAccount</returns>
public async ValueTask<ValueResult<bool, QrCodeAccount>> GetAccountAsync()
[SuppressMessage("", "SH007")]
public async ValueTask<ValueResult<bool, UidGameToken>> GetAccountAsync()
{
await taskContext.SwitchToMainThreadAsync();
ContentDialogResult result = await ShowAsync();
return new(account is not null, account!);
}
private async ValueTask Initialize()
private async ValueTask FetchQrCodeAsync()
{
Response<QrCodeFetch> fetch = await qrCodeClient.PostQrCodeFetchAsync().ConfigureAwait(false);
Response<GameLoginRequestResult> fetch = await passportClient2.PostQrCodeFetchAsync().ConfigureAwait(false);
if (fetch.IsOk())
{
string url = Regex.Unescape(fetch.Data.Url);
@@ -61,7 +62,7 @@ internal sealed partial class QrCodeDialog : ContentDialog
await taskContext.SwitchToMainThreadAsync();
BitmapImage bitmap = new();
await bitmap.SetSourceAsync(new MemoryStream(qrCodeFactory.CreateByteArr(url)).AsRandomAccessStream());
await bitmap.SetSourceAsync(new MemoryStream(qrCodeFactory.CreateQrCodeByteArray(url)).AsRandomAccessStream());
ImageView.Source = bitmap;
if (bitmap is BitmapSource { PixelHeight: > 0, PixelWidth: > 0 })
@@ -70,37 +71,41 @@ internal sealed partial class QrCodeDialog : ContentDialog
}
await taskContext.SwitchToBackgroundAsync();
await CheckStatusAsync(ticket).ConfigureAwait(false);
}
}
using (PeriodicTimer timer = new(TimeSpan.FromSeconds(3)))
private async ValueTask CheckStatusAsync(string ticket)
{
using (PeriodicTimer timer = new(TimeSpan.FromSeconds(3)))
{
while (await timer.WaitForNextTickAsync().ConfigureAwait(false))
{
while (await timer.WaitForNextTickAsync().ConfigureAwait(false))
Response<GameLoginResult> query = await passportClient2.PostQrCodeQueryAsync(ticket).ConfigureAwait(false);
if (query.IsOk(false))
{
Response<QrCodeQuery> query = await qrCodeClient.PostQrCodeQueryAsync(ticket).ConfigureAwait(false);
if (query.IsOk(false))
switch (query.Data.Stat)
{
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;
}
case GameLoginResultStatus.Init:
case GameLoginResultStatus.Scanned:
break; // @switch
case GameLoginResultStatus.Confirmed:
if (query.Data.Payload.Proto == GameLoginResultPayload.ACCOUNT)
{
account = JsonSerializer.Deserialize<UidGameToken>(query.Data.Payload.Raw);
await taskContext.SwitchToMainThreadAsync();
Hide();
return; // Stop timer
}
break; // @switch
}
}
else if (query.ReturnCode == (int)KnownReturnCode.QrCodeExpired)
{
Initialize().SafeForget();
break; // @while
break; // @switch
}
}
else if (query.ReturnCode == (int)KnownReturnCode.QrCodeExpired)
{
FetchQrCodeAsync().SafeForget();
break; // @while
}
}
}
}

View File

@@ -158,7 +158,7 @@
Width="{StaticResource LargeAppBarButtonWidth}"
MaxWidth="{StaticResource LargeAppBarButtonWidth}"
Margin="2,-4"
Command="{Binding LoginQrCodeCommand}"
Command="{Binding LoginByQrCodeCommand}"
Icon="{shcm:FontIcon Glyph={StaticResource FontIconContentQrCode}}"
Label="{shcm:ResourceString Name=ViewUserCookieOperationLoginQrCodeAction}"/>
<AppBarButton

View File

@@ -15,7 +15,6 @@ 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;
@@ -178,16 +177,16 @@ internal sealed partial class UserViewModel : ObservableObject
}
}
[Command("LoginQrCodeCommand")]
private async Task LoginQrCode()
[Command("LoginByQrCodeCommand")]
private async Task LoginByQrCode()
{
// 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);
ValueResult<bool, UidGameToken> result = await dialog.GetAccountAsync().ConfigureAwait(false);
if (result.TryGetValue(out QrCodeAccount account))
if (result.TryGetValue(out UidGameToken account))
{
Response<LoginResult> gameTokenResp = await sessionAppClient.PostSTokenByGameTokenAsync(account).ConfigureAwait(false);

View File

@@ -16,15 +16,6 @@ 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>
@@ -304,6 +295,11 @@ internal static class ApiEndpoints
/// </summary>
public const string AccountGetLTokenBySToken = $"{PassportApiAuthApi}/getLTokenBySToken";
/// <summary>
/// 通过GameToken获取V2SToken
/// </summary>
public const string AccountGetSTokenByGameToken = $"{PassportApi}/account/ma-cn-session/app/getTokenByGameToken";
/// <summary>
/// 获取V2SToken
/// </summary>
@@ -378,7 +374,6 @@ internal static class ApiEndpoints
#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";

View File

@@ -1,63 +0,0 @@
// 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);
}
}

View File

@@ -1,20 +0,0 @@
// 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!;
}

View File

@@ -1,14 +0,0 @@
// 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!;
}

View File

@@ -1,24 +0,0 @@
// 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!;
}

View File

@@ -1,14 +0,0 @@
// 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";
}

View File

@@ -37,6 +37,11 @@ internal sealed class HoyolabOptions : IOptions<HoyolabOptions>
/// </summary>
public static string DeviceId { get; } = Guid.NewGuid().ToString();
/// <summary>
/// 扫码登录设备Id
/// </summary>
public static string Device { get; } = Core.Random.GetLetterAndNumberString(64);
/// <summary>
/// 盐
/// </summary>

View File

@@ -0,0 +1,26 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hoyolab.Passport;
/// <summary>
/// 扫码登录请求配置
/// </summary>
[HighQuality]
internal sealed class GameLoginRequestOptions
{
[JsonPropertyName("app_id")]
public int AppId { get; set; }
[JsonPropertyName("device")]
public string Device { get; set; } = default!;
public static GameLoginRequestOptions Create(int appId, string device)
{
return new GameLoginRequestOptions
{
AppId = appId,
Device = device,
};
}
}

View File

@@ -1,10 +1,13 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hoyolab.Hk4e.QrCode;
namespace Snap.Hutao.Web.Hoyolab.Passport;
/// <summary>
/// 扫码登录请求结果
/// </summary>
[HighQuality]
internal sealed class QrCodeFetch
internal sealed class GameLoginRequestResult
{
[JsonPropertyName("url")]
public string Url { get; set; } = default!;

View File

@@ -0,0 +1,21 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Json.Annotation;
using Snap.Hutao.Web.Hoyolab.Hk4e.QrCode;
namespace Snap.Hutao.Web.Hoyolab.Passport;
/// <summary>
/// 扫码登录结果
/// </summary>
[HighQuality]
internal sealed class GameLoginResult
{
[JsonPropertyName("stat")]
[JsonEnum(JsonSerializeType.String)]
public GameLoginResultStatus Stat { get; set; } = default!;
[JsonPropertyName("payload")]
public GameLoginResultPayload Payload { get; set; } = default!;
}

View File

@@ -0,0 +1,30 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hoyolab.Passport;
/// <summary>
/// 扫码登录结果请求配置
/// </summary>
[HighQuality]
internal sealed class GameLoginResultOptions
{
[JsonPropertyName("app_id")]
public int AppId { get; set; }
[JsonPropertyName("device")]
public string Device { get; set; } = default!;
[JsonPropertyName("ticket")]
public string Ticket { get; set; } = default!;
public static GameLoginResultOptions Create(int appId, string device, string ticket)
{
return new GameLoginResultOptions
{
AppId = appId,
Device = device,
Ticket = ticket,
};
}
}

View File

@@ -4,9 +4,9 @@
namespace Snap.Hutao.Web.Hoyolab.Hk4e.QrCode;
/// <summary>
/// Proto 常量
/// Proto 类型常量
/// </summary>
internal sealed partial class QrCodeQueryPayload
internal sealed partial class GameLoginResultPayload
{
public const string ACCOUNT = "Account";
public const string RAW = "Raw";

View File

@@ -3,8 +3,11 @@
namespace Snap.Hutao.Web.Hoyolab.Hk4e.QrCode;
/// <summary>
/// 扫码登录结果Payload
/// </summary>
[HighQuality]
internal sealed partial class QrCodeQueryPayload
internal sealed partial class GameLoginResultPayload
{
[JsonPropertyName("proto")]
public string Proto { get; set; } = default!;

View File

@@ -0,0 +1,14 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hoyolab.Passport;
/// <summary>
/// 扫码状态
/// </summary>
internal enum GameLoginResultStatus
{
Init,
Scanned,
Confirmed,
}

View File

@@ -68,6 +68,47 @@ internal sealed partial class PassportClient2
return Response.Response.DefaultIfNull(resp);
}
/// <summary>
/// 异步获取扫码链接
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>二维码原始链接</returns>
public async ValueTask<Response<GameLoginRequestResult>> PostQrCodeFetchAsync(CancellationToken token = default)
{
GameLoginRequestOptions options = GameLoginRequestOptions.Create(4, HoyolabOptions.Device);
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(ApiEndpoints.QrCodeFetch)
.PostJson(options);
Response<GameLoginRequestResult>? resp = await builder
.TryCatchSendAsync<Response<GameLoginRequestResult>>(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<GameLoginResult>> PostQrCodeQueryAsync(string ticket, CancellationToken token = default)
{
GameLoginResultOptions options = GameLoginResultOptions.Create(4, HoyolabOptions.Device, ticket);
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(ApiEndpoints.QrCodeQuery)
.PostJson(options);
Response<GameLoginResult>? resp = await builder
.TryCatchSendAsync<Response<GameLoginResult>>(httpClient, logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);
}
private class Timestamp
{
[JsonPropertyName("t")]

View File

@@ -1,13 +1,13 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hoyolab.Hk4e.QrCode;
namespace Snap.Hutao.Web.Hoyolab.Passport;
[HighQuality]
internal sealed class QrCodeAccount
internal sealed class UidGameToken
{
[JsonPropertyName("uid")]
public string Stuid { get; set; } = default!;
public string Uid { get; set; } = default!;
[JsonPropertyName("token")]
public string GameToken { get; set; } = default!;

View File

@@ -2,7 +2,6 @@
// 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;
@@ -27,16 +26,16 @@ internal sealed partial class SessionAppClient
/// <param name="account">扫码获得的账户信息</param>
/// <param name="token">取消令牌</param>
/// <returns>登录结果</returns>
public async ValueTask<Response<LoginResult>> PostSTokenByGameTokenAsync(QrCodeAccount account, CancellationToken token = default)
public async ValueTask<Response<LoginResult>> PostSTokenByGameTokenAsync(UidGameToken account, CancellationToken token = default)
{
GameTokenWrapper wrapper = new()
{
Stuid = int.Parse(account.Stuid, CultureInfo.CurrentCulture),
Stuid = int.Parse(account.Uid, CultureInfo.CurrentCulture),
GameToken = account.GameToken,
};
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(ApiEndpoints.STokenByGameToken)
.SetRequestUri(ApiEndpoints.AccountGetSTokenByGameToken)
.PostJson(wrapper);
Response<LoginResult>? resp = await builder

View File

@@ -84,6 +84,11 @@ internal enum KnownReturnCode
/// </summary>
AppIdError = -109,
/// <summary>
/// 二维码已过期
/// </summary>
QrCodeExpired = -106,
/// <summary>
/// 验证密钥过期
/// </summary>
@@ -138,9 +143,4 @@ internal enum KnownReturnCode
/// 实时便笺
/// </summary>
CODE10104 = 10104,
/// <summary>
/// 二维码已过期
/// </summary>
QrCodeExpired = -106,
}