diff --git a/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs
index d7068aae..6f4ed18c 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs
@@ -4,6 +4,8 @@
using Microsoft.Win32;
using Snap.Hutao.Core.Convert;
using Snap.Hutao.Extension;
+using Snap.Hutao.Web.Hoyolab.DynamicSecret;
+using System.Collections.Immutable;
using System.Text.Encodings.Web;
using Windows.ApplicationModel;
@@ -36,6 +38,11 @@ internal static class CoreEnvironment
///
public const string DynamicSecretX6Salt = "t0qEgfub6cvueAPgR5m9aQWWVciEer7v";
+ ///
+ /// LoginApi的盐
+ ///
+ public const string DynamicSecretPRODSalt = "JwYDpKvLj6MrMqqYU6jTKF17KNO2PXoS";
+
///
/// 米游社请求UA
///
@@ -46,6 +53,18 @@ internal static class CoreEnvironment
///
public const string HoyolabXrpcVersion = "2.40.1";
+ ///
+ /// 动态密钥
+ ///
+ public static readonly ImmutableDictionary DynamicSecrets = new Dictionary()
+ {
+ [SaltType.K2] = "fdv0fY9My9eA7MR0NpjGP9RjueFvjUSQ",
+ [SaltType.LK2] = "jEpJb9rRARU2rXDA9qYbZ3selxkuct9a",
+ [SaltType.X4] = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs",
+ [SaltType.X6] = "t0qEgfub6cvueAPgR5m9aQWWVciEer7v",
+ [SaltType.PROD] = "JwYDpKvLj6MrMqqYU6jTKF17KNO2PXoS",
+ }.ToImmutableDictionary();
+
///
/// 标准UA
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ApiEndpoints.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ApiEndpoints.cs
index 8ade2c73..d4aba5ca 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ApiEndpoints.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ApiEndpoints.cs
@@ -181,6 +181,11 @@ internal static class ApiEndpoints
#region ma-cn-session
+ ///
+ /// 获取 CookieToken
+ ///
+ public const string AccountCookieAccountInfoBySToken = $"{PassportApi}/account/auth/api/getCookieAccountInfoBySToken";
+
///
/// 验证 Ltoken 有效性
///
@@ -213,6 +218,7 @@ 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 PassportApi = "passport-api.mihoyo.com";
private const string PassportApiV4 = "passport-api-v4.mihoyo.com";
private const string SdkStatic = "https://sdk-static.mihoyo.com";
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 2d0bc6a6..ba42f8fa 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider.cs
@@ -27,7 +27,7 @@ internal abstract class DynamicSecretProvider : Md5Convert
string r = GetRandomString();
- string salt = saltType == SaltType.K2 ? Core.CoreEnvironment.DynamicSecretK2Salt : Core.CoreEnvironment.DynamicSecretLK2Salt;
+ string salt = Core.CoreEnvironment.DynamicSecrets[saltType];
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 0a1d3ed2..fbd423cb 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider2.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider2.cs
@@ -20,7 +20,7 @@ internal abstract class DynamicSecretProvider2 : Md5Convert
/// 密钥
public static string Create(SaltType saltType, JsonSerializerOptions options, string queryUrl, object? postBody = null)
{
- Verify.Operation(saltType is SaltType.X6 or SaltType.X4, "SALT 值无效");
+ Verify.Operation(saltType is SaltType.X6 or SaltType.X4 or SaltType.PROD, "SALT 值无效");
// unix timestamp
long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
@@ -29,19 +29,29 @@ internal abstract class DynamicSecretProvider2 : Md5Convert
int r = GetRandom();
// body
- string b = postBody is null ? string.Empty : JsonSerializer.Serialize(postBody, options);
+ string b = postBody is null ? GetDefaultBody(saltType) : JsonSerializer.Serialize(postBody, options);
// query
string[] queries = queryUrl.Split('?', 2);
string q = queries.Length == 2 ? string.Join('&', queries[1].Split('&').OrderBy(x => x)) : string.Empty;
// check
- string salt = saltType == SaltType.X6 ? Core.CoreEnvironment.DynamicSecretX6Salt : Core.CoreEnvironment.DynamicSecretX4Salt;
+ string salt = Core.CoreEnvironment.DynamicSecrets[saltType];
string check = ToHexString($"salt={salt}&t={t}&r={r}&b={b}&q={q}").ToLowerInvariant();
return $"{t},{r},{check}";
}
+ private static string GetDefaultBody(SaltType saltType)
+ {
+ return saltType switch
+ {
+ SaltType.X4 or SaltType.X6 => string.Empty,
+ SaltType.PROD => "{}",
+ _ => throw Must.NeverHappen(((int)saltType).ToString()),
+ };
+ }
+
private static int GetRandom()
{
// 原汁原味
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/SaltType.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/SaltType.cs
index a8582f31..844d97f4 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/SaltType.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/SaltType.cs
@@ -23,6 +23,11 @@ public enum SaltType
///
X6,
+ ///
+ /// PROD
+ ///
+ PROD,
+
///
/// K2
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.cs
index 781bd814..e0930f32 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.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;
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs
new file mode 100644
index 00000000..0d059ef4
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs
@@ -0,0 +1,53 @@
+// 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.Hoyolab.DynamicSecret;
+using Snap.Hutao.Web.Response;
+using System.Net.Http;
+
+namespace Snap.Hutao.Web.Hoyolab.Passport;
+
+///
+/// 通行证客户端 XRPC 版
+///
+[HttpClient(HttpClientConfigration.XRpc)]
+internal class PassportClient2
+{
+ private readonly HttpClient httpClient;
+ private readonly JsonSerializerOptions options;
+ private readonly ILogger logger;
+
+ ///
+ /// 构造一个新的通行证客户端
+ ///
+ /// http客户端
+ /// json序列化选项
+ /// 日志器
+ public PassportClient2(HttpClient httpClient, JsonSerializerOptions options, ILogger logger)
+ {
+ this.httpClient = httpClient;
+ this.options = options;
+ this.logger = logger;
+ }
+
+ ///
+ /// 异步获取 CookieToken
+ ///
+ /// 用户
+ /// 取消令牌
+ /// uid 与 cookie token
+ [ApiInformation(Cookie = CookieType.Stoken, Salt = SaltType.PROD)]
+ public async Task GetCookieAccountInfoBySTokenAsync(User user, CancellationToken token)
+ {
+ Response? resp = await httpClient
+ .SetUser(user, CookieType.Stoken)
+ .UsingDynamicSecret2(SaltType.PROD, options, ApiEndpoints.AccountCookieAccountInfoBySToken)
+ .TryCatchGetFromJsonAsync>(logger, token)
+ .ConfigureAwait(false);
+
+ return resp?.Data;
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/UidCookieToken.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/UidCookieToken.cs
new file mode 100644
index 00000000..d6782b87
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/UidCookieToken.cs
@@ -0,0 +1,22 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Web.Hoyolab.Passport;
+
+///
+/// uid 与 cookie token
+///
+public class UidCookieToken
+{
+ ///
+ /// Uid
+ ///
+ [JsonPropertyName("uid")]
+ public string Uid { get; set; } = default!;
+
+ ///
+ /// CookieToken
+ ///
+ [JsonPropertyName("cookie_token")]
+ public string CookieToken { get; set; } = default!;
+}
\ No newline at end of file