From 58b34ea60a82ecbd88bb00778ab21c75f354c50b Mon Sep 17 00:00:00 2001
From: DismissedLight <1686188646@qq.com>
Date: Sun, 13 Nov 2022 18:04:08 +0800
Subject: [PATCH] support verifyLtoken for #207
---
.../Snap.Hutao/Core/CoreEnvironment.cs | 18 ++-
.../Annotation/ApiInformationAttribute.cs | 6 +
.../Snap.Hutao/Web/Hoyolab/ApiEndpoints.cs | 10 ++
.../Web/Hoyolab/App/GameRecord/CardClient.cs | 8 +-
.../Snap.Hutao/Web/Hoyolab/Cookie.Constant.cs | 8 +-
.../DynamicSecret/DynamicSecretProvider.cs | 8 +-
.../DynamicSecret/DynamicSecretProvider2.cs | 8 +-
.../Http/DynamicSecretHttpClient.cs | 41 ++++++-
.../Http/IDynamicSecretHttpClient.cs | 9 ++
.../HttpClientDynamicSecretExtensions.cs | 21 ++--
.../Web/Hoyolab/DynamicSecret/SaltType.cs | 35 ++++++
.../Web/Hoyolab/HttpClientExtensions.cs | 9 +-
.../Snap.Hutao/Web/Hoyolab/Passport/Link.cs | 32 ++++++
.../Web/Hoyolab/Passport/PassportClient.cs | 62 +++++++++++
.../Web/Hoyolab/Passport/VerifyInformation.cs | 104 ++++++++++++++++++
.../Hoyolab/Takumi/Binding/BindingClient2.cs | 2 +-
.../Takumi/GameRecord/GameRecordClient.cs | 10 +-
17 files changed, 355 insertions(+), 36 deletions(-)
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/SaltType.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/Link.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/VerifyInformation.cs
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs
index 738242ee..d7068aae 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs
@@ -17,14 +17,24 @@ internal static class CoreEnvironment
// 计算过程:https://github.com/UIGF-org/Hoyolab.Salt
///
- /// 动态密钥1的盐
+ /// 动态密钥1的K2盐
///
- public const string DynamicSecret1Salt = "jEpJb9rRARU2rXDA9qYbZ3selxkuct9a";
+ public const string DynamicSecretK2Salt = "fdv0fY9My9eA7MR0NpjGP9RjueFvjUSQ";
///
- /// 动态密钥2的盐
+ /// 动态密钥1的LK2盐
///
- public const string DynamicSecret2Salt = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs";
+ public const string DynamicSecretLK2Salt = "jEpJb9rRARU2rXDA9qYbZ3selxkuct9a";
+
+ ///
+ /// 动态密钥2的X4盐
+ ///
+ public const string DynamicSecretX4Salt = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs";
+
+ ///
+ /// 动态密钥2的X6盐
+ ///
+ public const string DynamicSecretX6Salt = "t0qEgfub6cvueAPgR5m9aQWWVciEer7v";
///
/// 米游社请求UA
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Annotation/ApiInformationAttribute.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Annotation/ApiInformationAttribute.cs
index e531a250..2f0f58f5 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Annotation/ApiInformationAttribute.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Annotation/ApiInformationAttribute.cs
@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
+using Snap.Hutao.Web.Hoyolab.DynamicSecret;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -20,4 +21,9 @@ internal class ApiInformationAttribute : Attribute
/// Cookie类型
///
public CookieType Cookie { get; set; }
+
+ ///
+ /// SALT
+ ///
+ public SaltType Salt { get; set; }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ApiEndpoints.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ApiEndpoints.cs
index 41286916..8595699c 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ApiEndpoints.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ApiEndpoints.cs
@@ -145,6 +145,14 @@ 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 ma-cn-session
+
+ ///
+ /// 验证 Ltoken 有效性
+ ///
+ public const string AccountVerifyLtoken = $"{PassportApiV4}/account/ma-cn-session/web/verifyLtoken";
+ #endregion
+
#region App
///
@@ -171,6 +179,8 @@ internal static class ApiEndpoints
private const string Hk4eApiAnnouncementApi = $"{Hk4eApi}/common/hk4e_cn/announcement/api";
private const string Hk4eApiGachaInfoApi = $"{Hk4eApi}/event/gacha_info/api";
+ private const string PassportApiV4 = "passport-api-v4.mihoyo.com";
+
private const string SdkStatic = "https://sdk-static.mihoyo.com";
private const string SdkStaticLauncherApi = $"{SdkStatic}/hk4e_cn/mdk/launcher/api";
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/App/GameRecord/CardClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/App/GameRecord/CardClient.cs
index 200fafd3..dced6c50 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/App/GameRecord/CardClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/App/GameRecord/CardClient.cs
@@ -4,6 +4,7 @@
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Web.Hoyolab.Annotation;
+using Snap.Hutao.Web.Hoyolab.DynamicSecret;
using Snap.Hutao.Web.Response;
using System.Net.Http;
@@ -23,7 +24,7 @@ internal class CardClient
/// 构造一个新的桌面卡片客户端
///
/// http客户端
- /// 选项
+ /// json序列化选项
/// 日志器
public CardClient(HttpClient httpClient, JsonSerializerOptions options, ILogger logger)
{
@@ -38,12 +39,13 @@ internal class CardClient
/// 用户
/// 取消令牌
/// 桌面小组件数据
- [ApiInformation(Cookie = CookieType.Stoken)]
+ [ApiInformation(Cookie = CookieType.Stoken, Salt = SaltType.X6)]
public async Task GetWidgetDataAsync(User user, CancellationToken token)
{
Response>? resp = await httpClient
.SetUser(user, CookieType.Stoken)
- .TryCatchGetFromJsonAsync>>(ApiEndpoints.UserFullInfo, options, logger, token)
+ .UsingDynamicSecret2(SaltType.X6, options, ApiEndpoints.AppWidgetData)
+ .TryCatchGetFromJsonAsync>>(logger, token)
.ConfigureAwait(false);
return resp?.Data?.Data;
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.Constant.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.Constant.cs
index bdbae465..29438b66 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.Constant.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.Constant.cs
@@ -10,8 +10,10 @@ namespace Snap.Hutao.Web.Hoyolab;
[SuppressMessage("", "SA1600")]
public partial class Cookie
{
- public const string COOKIE_TOKEN = "cookie_token";
public const string ACCOUNT_ID = "account_id";
+ public const string COOKIE_TOKEN = "cookie_token";
+
+ public const string E_HK4E_TOKEN = "e_hk4e_token";
public const string LOGIN_TICKET = "login_ticket";
public const string LOGIN_UID = "login_uid";
@@ -19,8 +21,8 @@ public partial class Cookie
public const string LTOKEN = "ltoken";
public const string LTUID = "ltuid";
+ public const string MID = "mid";
+
public const string STOKEN = "stoken";
public const string STUID = "stuid";
-
- public const string MID = "mid";
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider.cs
index aad9294f..2d0bc6a6 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider.cs
@@ -16,15 +16,19 @@ internal abstract class DynamicSecretProvider : Md5Convert
///
/// 创建动态密钥
///
+ /// SALT 类型
/// 密钥
- public static string Create()
+ public static string Create(SaltType saltType)
{
+ Verify.Operation(saltType is SaltType.K2 or SaltType.LK2, "SALT 值无效");
+
// unix timestamp
long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
string r = GetRandomString();
- string check = ToHexString($"salt={Core.CoreEnvironment.DynamicSecret1Salt}&t={t}&r={r}").ToLowerInvariant();
+ string salt = saltType == SaltType.K2 ? Core.CoreEnvironment.DynamicSecretK2Salt : Core.CoreEnvironment.DynamicSecretLK2Salt;
+ string check = ToHexString($"salt={salt}&t={t}&r={r}").ToLowerInvariant();
return $"{t},{r},{check}";
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider2.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider2.cs
index b71e760b..0a1d3ed2 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider2.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider2.cs
@@ -13,12 +13,15 @@ internal abstract class DynamicSecretProvider2 : Md5Convert
///
/// 创建动态密钥
///
+ /// SALT 类型
/// json格式化器
/// 查询url
/// 请求体
/// 密钥
- public static string Create(JsonSerializerOptions options, string queryUrl, object? postBody = null)
+ public static string Create(SaltType saltType, JsonSerializerOptions options, string queryUrl, object? postBody = null)
{
+ Verify.Operation(saltType is SaltType.X6 or SaltType.X4, "SALT 值无效");
+
// unix timestamp
long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
@@ -33,7 +36,8 @@ internal abstract class DynamicSecretProvider2 : Md5Convert
string q = queries.Length == 2 ? string.Join('&', queries[1].Split('&').OrderBy(x => x)) : string.Empty;
// check
- string check = ToHexString($"salt={Core.CoreEnvironment.DynamicSecret2Salt}&t={t}&r={r}&b={b}&q={q}").ToLowerInvariant();
+ string salt = saltType == SaltType.X6 ? Core.CoreEnvironment.DynamicSecretX6Salt : Core.CoreEnvironment.DynamicSecretX4Salt;
+ string check = ToHexString($"salt={salt}&t={t}&r={r}&b={b}&q={q}").ToLowerInvariant();
return $"{t},{r},{check}";
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/Http/DynamicSecretHttpClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/Http/DynamicSecretHttpClient.cs
index badbe5c1..31fe098b 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/Http/DynamicSecretHttpClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/Http/DynamicSecretHttpClient.cs
@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
+using Snap.Hutao.Core.Logging;
using Snap.Hutao.Web.Request;
using System.Net.Http;
using System.Net.Http.Json;
@@ -14,6 +15,7 @@ namespace Snap.Hutao.Web.Hoyolab.DynamicSecret.Http;
internal class DynamicSecretHttpClient : IDynamicSecretHttpClient
{
private readonly HttpClient httpClient;
+ private readonly SaltType saltType;
private readonly JsonSerializerOptions options;
private readonly string url;
@@ -21,12 +23,13 @@ internal class DynamicSecretHttpClient : IDynamicSecretHttpClient
/// 构造一个新的使用动态密钥2的Http客户端默认实现的实例
///
/// 请求使用的客户端
+ /// SALT 类型
/// Json序列化选项
/// url
- /// 请求的数据
- public DynamicSecretHttpClient(HttpClient httpClient, JsonSerializerOptions options, string url)
+ public DynamicSecretHttpClient(HttpClient httpClient, SaltType saltType, JsonSerializerOptions options, string url)
{
this.httpClient = httpClient;
+ this.saltType = saltType;
this.options = options;
this.url = url;
@@ -38,6 +41,35 @@ internal class DynamicSecretHttpClient : IDynamicSecretHttpClient
{
return httpClient.GetFromJsonAsync(url, options, token);
}
+
+ ///
+ public async Task TryCatchGetFromJsonAsync(CancellationToken token = default)
+ where T : class
+ {
+ try
+ {
+ return await httpClient.GetFromJsonAsync(url, options, token).ConfigureAwait(false);
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ ///
+ public async Task TryCatchGetFromJsonAsync(ILogger logger, CancellationToken token = default)
+ where T : class
+ {
+ try
+ {
+ return await httpClient.GetFromJsonAsync(url, options, token).ConfigureAwait(false);
+ }
+ catch (HttpRequestException ex)
+ {
+ logger.LogWarning(EventIds.HttpException, ex, "请求异常已忽略");
+ return null;
+ }
+ }
}
///
@@ -49,6 +81,7 @@ internal class DynamicSecretHttpClient : IDynamicSecretHttpClient : IDynamicSecretHttpClient
/// 请求使用的客户端
+ /// SALT 类型
/// Json序列化选项
/// url
/// 请求的数据
- public DynamicSecretHttpClient(HttpClient httpClient, JsonSerializerOptions options, string url, TValue data)
+ public DynamicSecretHttpClient(HttpClient httpClient, SaltType saltType, JsonSerializerOptions options, string url, TValue data)
{
this.httpClient = httpClient;
+ this.saltType = saltType;
this.options = options;
this.url = url;
this.data = data;
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/Http/IDynamicSecretHttpClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/Http/IDynamicSecretHttpClient.cs
index ed42a578..9d67ede1 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/Http/IDynamicSecretHttpClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/Http/IDynamicSecretHttpClient.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using System.Net.Http;
+using System.Net.Http.Json;
namespace Snap.Hutao.Web.Hoyolab.DynamicSecret.Http;
@@ -17,6 +18,14 @@ internal interface IDynamicSecretHttpClient
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
/// The task object representing the asynchronous operation.
Task GetFromJsonAsync(CancellationToken token);
+
+ ///
+ Task TryCatchGetFromJsonAsync(CancellationToken token = default)
+ where T : class;
+
+ ///
+ Task TryCatchGetFromJsonAsync(ILogger logger, CancellationToken token = default(CancellationToken))
+ where T : class;
}
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/HttpClientDynamicSecretExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/HttpClientDynamicSecretExtensions.cs
index a255b28b..e1c2bd02 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/HttpClientDynamicSecretExtensions.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/HttpClientDynamicSecretExtensions.cs
@@ -13,40 +13,43 @@ namespace Snap.Hutao.Web.Hoyolab.DynamicSecret;
internal static class HttpClientDynamicSecretExtensions
{
///
- /// 使用一代动态密钥执行 GET 操作
+ /// 使用一代动态密钥执行 GET/POST 操作
///
/// 请求器
+ /// SALT 类型
/// 响应
- public static HttpClient UsingDynamicSecret(this HttpClient httpClient)
+ public static HttpClient UsingDynamicSecret1(this HttpClient httpClient, SaltType type)
{
- httpClient.DefaultRequestHeaders.Set("DS", DynamicSecretProvider.Create());
+ httpClient.DefaultRequestHeaders.Set("DS", DynamicSecretProvider.Create(type));
return httpClient;
}
///
- /// 使用二代动态密钥执行 GET 操作
+ /// 使用二代动态密钥执行 GET/POST 操作
///
/// 请求器
+ /// SALT 类型
/// 选项
/// 地址
/// 响应
- public static IDynamicSecretHttpClient UsingDynamicSecret(this HttpClient httpClient, JsonSerializerOptions options, string url)
+ public static IDynamicSecretHttpClient UsingDynamicSecret2(this HttpClient httpClient, SaltType type, JsonSerializerOptions options, string url)
{
- return new DynamicSecretHttpClient(httpClient, options, url);
+ return new DynamicSecretHttpClient(httpClient, type, options, url);
}
///
- /// 使用二代动态密钥执行 GET 操作
+ /// 使用二代动态密钥执行 GET/POST 操作
///
/// 请求数据的类型
/// 请求器
+ /// SALT 类型
/// 选项
/// 地址
/// post数据
/// 响应
- public static IDynamicSecretHttpClient UsingDynamicSecret(this HttpClient httpClient, JsonSerializerOptions options, string url, TValue data)
+ public static IDynamicSecretHttpClient UsingDynamicSecret2(this HttpClient httpClient, SaltType type, JsonSerializerOptions options, string url, TValue data)
where TValue : class
{
- return new DynamicSecretHttpClient(httpClient, options, url, data);
+ return new DynamicSecretHttpClient(httpClient, type, options, url, data);
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/SaltType.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/SaltType.cs
new file mode 100644
index 00000000..a8582f31
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/SaltType.cs
@@ -0,0 +1,35 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Web.Hoyolab.DynamicSecret;
+
+///
+/// Salt's type
+///
+public enum SaltType
+{
+ ///
+ /// 不需要 SALT
+ ///
+ None,
+
+ ///
+ /// X4
+ ///
+ X4,
+
+ ///
+ /// X6
+ ///
+ X6,
+
+ ///
+ /// K2
+ ///
+ K2,
+
+ ///
+ /// LK2
+ ///
+ LK2,
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs
index 4bf2b8c0..795977a3 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs
@@ -3,6 +3,7 @@
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Model.Binding.User;
+using Snap.Hutao.Web.Hoyolab.DynamicSecret;
using Snap.Hutao.Web.Request;
using System.Net.Http;
using System.Net.Http.Json;
@@ -66,7 +67,7 @@ internal static class HttpClientExtensions
/// http客户端
/// 绑定用户
/// 客户端
- [Obsolete("请使用带有 type 的重载")]
+ [Obsolete("请使用带有 cookie 的重载")]
internal static HttpClient SetUser(this HttpClient httpClient, User user)
{
httpClient.DefaultRequestHeaders.Set("Cookie", user.Cookie!.ToString());
@@ -78,11 +79,11 @@ internal static class HttpClientExtensions
///
/// http客户端
/// 实体用户
- /// Cookie类型
+ /// Cookie类型
/// 客户端
- internal static HttpClient SetUser(this HttpClient httpClient, Model.Entity.User user, CookieType type = CookieType.All)
+ internal static HttpClient SetUser(this HttpClient httpClient, Model.Entity.User user, CookieType cookie = CookieType.All)
{
- httpClient.DefaultRequestHeaders.Set("Cookie", user.Cookie!.ToString(type));
+ httpClient.DefaultRequestHeaders.Set("Cookie", user.Cookie!.ToString(cookie));
return httpClient;
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/Link.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/Link.cs
new file mode 100644
index 00000000..bf321f30
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/Link.cs
@@ -0,0 +1,32 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
+using Snap.Hutao.Web.Response;
+using System.Net.Http;
+
+namespace Snap.Hutao.Web.Hoyolab.Passport;
+
+///
+/// 链接
+///
+public class Link
+{
+ ///
+ /// 第三方 sn
+ ///
+ [JsonPropertyName("thirdparty")]
+ public string Thirdparty { get; set; } = default!;
+
+ ///
+ /// Union Id
+ ///
+ [JsonPropertyName("union_id")]
+ public string UnionId { get; set; } = default!;
+
+ ///
+ /// 昵称
+ ///
+ [JsonPropertyName("nickname")]
+ public string Nickname { get; set; } = default!;
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.cs
new file mode 100644
index 00000000..95d01174
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.cs
@@ -0,0 +1,62 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
+using Snap.Hutao.Model.Entity;
+using Snap.Hutao.Web.Hoyolab.Annotation;
+using Snap.Hutao.Web.Response;
+using System.Net.Http;
+
+namespace Snap.Hutao.Web.Hoyolab.Passport;
+
+///
+/// 通行证客户端
+///
+[HttpClient(HttpClientConfigration.Default)]
+internal class PassportClient
+{
+ private readonly HttpClient httpClient;
+ private readonly JsonSerializerOptions options;
+ private readonly ILogger logger;
+
+ ///
+ /// 构造一个新的通行证客户端
+ ///
+ /// http客户端
+ /// json序列化选项
+ /// 日志器
+ public PassportClient(HttpClient httpClient, JsonSerializerOptions options, ILogger logger)
+ {
+ this.httpClient = httpClient;
+ this.options = options;
+ this.logger = logger;
+ }
+
+ ///
+ /// 异步验证Ltoken
+ ///
+ /// 用户
+ /// 取消令牌
+ /// 验证信息
+ [ApiInformation(Cookie = CookieType.All)]
+ public async Task VerifyLtokenAsync(User user, CancellationToken token)
+ {
+ Response? response = await httpClient
+ .SetUser(user, CookieType.All)
+ .TryCatchPostAsJsonAsync>(ApiEndpoints.AccountVerifyLtoken, new(), options, logger, token)
+ .ConfigureAwait(false);
+
+ return response?.Data;
+ }
+
+ private class Timestamp
+ {
+ public Timestamp()
+ {
+ T = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+ }
+
+ [JsonPropertyName("t")]
+ public long T { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/VerifyInformation.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/VerifyInformation.cs
new file mode 100644
index 00000000..46a170b1
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/VerifyInformation.cs
@@ -0,0 +1,104 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
+using Snap.Hutao.Web.Response;
+using System.Net.Http;
+
+namespace Snap.Hutao.Web.Hoyolab.Passport;
+
+///
+/// 验证返回信息
+///
+public class VerifyInformation
+{
+ ///
+ /// 账户Id
+ ///
+ [JsonPropertyName("aid")]
+ public string Aid { get; set; } = default!;
+
+ ///
+ /// 米哈游Id
+ ///
+ [JsonPropertyName("mid")]
+ public string Mid { get; set; } = default!;
+
+ ///
+ /// 空
+ ///
+ [JsonPropertyName("account_name")]
+ public string AccountName { get; set; } = default!;
+
+ ///
+ /// 空
+ ///
+ [JsonPropertyName("email")]
+ public string Email { get; set; } = default!;
+
+ ///
+ /// 是否为邮箱验证
+ ///
+ [JsonPropertyName("is_email_verify")]
+ public int IsEmailVerify { get; set; } = default!;
+
+ ///
+ /// 区域码 +86
+ ///
+ [JsonPropertyName("area_code")]
+ public string AreaCode { get; set; } = default!;
+
+ ///
+ /// 手机号 111****1111
+ ///
+ [JsonPropertyName("mobile")]
+ public string Mobile { get; set; } = default!;
+
+ ///
+ /// 安全手机区域码 +86
+ ///
+ [JsonPropertyName("safe_area_code")]
+ public string SafeAreaCode { get; set; } = default!;
+
+ ///
+ /// 安全手机号 111****1111
+ ///
+ [JsonPropertyName("safe_mobile")]
+ public string SafeMobile { get; set; } = default!;
+
+ ///
+ /// 真名 **某
+ ///
+ [JsonPropertyName("realname")]
+ public string Realname { get; set; } = default!;
+
+ ///
+ /// 身份证号 111************111
+ ///
+ [JsonPropertyName("identity_code")]
+ public string IdentityCode { get; set; } = default!;
+
+ ///
+ /// 重新绑定区域码
+ ///
+ [JsonPropertyName("rebind_area_code")]
+ public string RebindAreaCode { get; set; } = default!;
+
+ ///
+ /// 重新绑定的手机号
+ ///
+ [JsonPropertyName("rebind_mobile")]
+ public string RebindMobile { get; set; } = default!;
+
+ ///
+ /// 重新绑定时间 "0"
+ ///
+ [JsonPropertyName("rebind_mobile_time")]
+ public string RebindMobileTime { get; set; } = default!;
+
+ ///
+ /// 链接
+ ///
+ [JsonPropertyName("links")]
+ public List Links { get; set; } = default!;
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient2.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient2.cs
index 22d73332..85f7b96a 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient2.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient2.cs
@@ -45,7 +45,7 @@ internal class BindingClient2
Response? resp = await httpClient
.SetUser(user)
.SetReferer("https://app.mihoyo.com")
- .UsingDynamicSecret()
+ .UsingDynamicSecret1()
.TryCatchPostAsJsonAsync>(ApiEndpoints.GenAuthKey, data, options, 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 90bdcd29..8f074bf6 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
@@ -45,7 +45,7 @@ internal class GameRecordClient
{
Response? resp = await httpClient
.SetUser(user)
- .UsingDynamicSecret(options, ApiEndpoints.GameRecordDailyNote(uid.Value, uid.Region))
+ .UsingDynamicSecret2(options, ApiEndpoints.GameRecordDailyNote(uid.Value, uid.Region))
.GetFromJsonAsync>(token)
.ConfigureAwait(false);
@@ -63,7 +63,7 @@ internal class GameRecordClient
{
Response? resp = await httpClient
.SetUser(user)
- .UsingDynamicSecret(options, ApiEndpoints.GameRecordDailyNote(uid.Value, uid.Region))
+ .UsingDynamicSecret2(options, ApiEndpoints.GameRecordDailyNote(uid.Value, uid.Region))
.GetFromJsonAsync>(token)
.ConfigureAwait(false);
@@ -93,7 +93,7 @@ internal class GameRecordClient
{
Response? resp = await httpClient
.SetUser(user)
- .UsingDynamicSecret(options, ApiEndpoints.GameRecordIndex(uid.Value, uid.Region))
+ .UsingDynamicSecret2(options, ApiEndpoints.GameRecordIndex(uid.Value, uid.Region))
.GetFromJsonAsync>(token)
.ConfigureAwait(false);
@@ -125,7 +125,7 @@ internal class GameRecordClient
{
Response? resp = await httpClient
.SetUser(user)
- .UsingDynamicSecret(options, ApiEndpoints.GameRecordSpiralAbyss(schedule, uid))
+ .UsingDynamicSecret2(options, ApiEndpoints.GameRecordSpiralAbyss(schedule, uid))
.GetFromJsonAsync>(token)
.ConfigureAwait(false);
@@ -159,7 +159,7 @@ internal class GameRecordClient
Response? resp = await httpClient
.SetUser(user)
- .UsingDynamicSecret(options, ApiEndpoints.GameRecordCharacter, data)
+ .UsingDynamicSecret2(options, ApiEndpoints.GameRecordCharacter, data)
.TryCatchPostAsJsonAsync>(logger, token)
.ConfigureAwait(false);