mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
login by password
This commit is contained in:
@@ -3,10 +3,12 @@
|
||||
|
||||
using Microsoft.Win32;
|
||||
using Snap.Hutao.Core.Convert;
|
||||
using Snap.Hutao.Core.Json;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Unicode;
|
||||
using Windows.ApplicationModel;
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
@@ -70,7 +72,7 @@ internal static class CoreEnvironment
|
||||
public static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
Encoder = new InboxTextEncoder(), //JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
WriteIndented = true,
|
||||
};
|
||||
|
||||
49
src/Snap.Hutao/Snap.Hutao/Core/Json/InboxTextEncoder.cs
Normal file
49
src/Snap.Hutao/Snap.Hutao/Core/Json/InboxTextEncoder.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Snap.Hutao.Core.Json;
|
||||
|
||||
/// <summary>
|
||||
/// 替换 =
|
||||
/// </summary>
|
||||
internal class InboxTextEncoder : JavaScriptEncoder
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override int MaxOutputCharactersPerInputCharacter { get => 6; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override unsafe int FindFirstCharacterToEncode(char* text, int textLength)
|
||||
{
|
||||
for (int i = 0; i < textLength; i++)
|
||||
{
|
||||
char c = text[i];
|
||||
if (c == '=')
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override unsafe bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten)
|
||||
{
|
||||
string encoded = FormattableString.Invariant($"\\u{(uint)unicodeScalar:x4}");
|
||||
numberOfCharactersWritten = (encoded.Length <= (uint)bufferLength) ? encoded.Length : 0;
|
||||
return encoded.AsSpan().TryCopyTo(new Span<char>(buffer, bufferLength));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool WillEncode(int unicodeScalar)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -12,11 +12,12 @@ using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service.Game;
|
||||
using Snap.Hutao.Service.Game.Locator;
|
||||
using Snap.Hutao.Web.Hoyolab.Passport;
|
||||
|
||||
namespace Snap.Hutao.ViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// 测试视图模型
|
||||
/// 设置视图模型
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal class SettingViewModel : ObservableObject
|
||||
@@ -37,7 +38,7 @@ internal class SettingViewModel : ObservableObject
|
||||
private NamedValue<BackdropType> selectedBackdropType;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的测试视图模型
|
||||
/// 构造一个新的设置视图模型
|
||||
/// </summary>
|
||||
/// <param name="appDbContext">数据库上下文</param>
|
||||
/// <param name="gameService">游戏服务</param>
|
||||
@@ -153,10 +154,13 @@ internal class SettingViewModel : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
private void DebugThrowException()
|
||||
private async void DebugThrowException()
|
||||
{
|
||||
#if DEBUG
|
||||
throw new InvalidOperationException("测试用异常");
|
||||
PassportClient2 passportClient2 = Ioc.Default.GetRequiredService<PassportClient2>();
|
||||
LoginResult? data = await passportClient2.LoginByPasswordAsync("phoneNunmber", "password", CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
_ = data;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -189,6 +189,11 @@ internal static class ApiEndpoints
|
||||
/// </summary>
|
||||
public const string AccountCookieAccountInfoBySToken = $"{PassportApi}/account/auth/api/getCookieAccountInfoBySToken";
|
||||
|
||||
/// <summary>
|
||||
/// 验证 Ltoken 有效性
|
||||
/// </summary>
|
||||
public const string AccountLoginByPassword = $"{PassportApi}/account/ma-cn-passport/app/loginByPassword";
|
||||
|
||||
/// <summary>
|
||||
/// 验证 Ltoken 有效性
|
||||
/// </summary>
|
||||
@@ -244,8 +249,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 PassportApi = "passport-api.mihoyo.com";
|
||||
private const string PassportApiV4 = "passport-api-v4.mihoyo.com";
|
||||
private const string PassportApi = "https://passport-api.mihoyo.com";
|
||||
private const string PassportApiV4 = "https://passport-api-v4.mihoyo.com";
|
||||
|
||||
private const string SdkStatic = "https://sdk-static.mihoyo.com";
|
||||
private const string SdkStaticLauncherApi = $"{SdkStatic}/hk4e_cn/mdk/launcher/api";
|
||||
|
||||
@@ -98,4 +98,28 @@ internal static class HttpClientExtensions
|
||||
httpClient.DefaultRequestHeaders.Set("Referer", referer);
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置AppId
|
||||
/// </summary>
|
||||
/// <param name="httpClient">http客户端</param>
|
||||
/// <returns>客户端</returns>
|
||||
internal static HttpClient SetAppId(this HttpClient httpClient)
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Set("x-rpc-app_id", "bll8iq97cem8");
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置头
|
||||
/// </summary>
|
||||
/// <param name="httpClient">http客户端</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <returns>客户端</returns>
|
||||
internal static HttpClient SetHeader(this HttpClient httpClient, string key, string value)
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Set(key, value);
|
||||
return httpClient;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,11 +40,11 @@ internal class PassportClient
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>验证信息</returns>
|
||||
[ApiInformation(Cookie = CookieType.All)]
|
||||
public async Task<VerifyInformation?> VerifyLtokenAsync(User user, CancellationToken token)
|
||||
public async Task<UserInformation?> VerifyLtokenAsync(User user, CancellationToken token)
|
||||
{
|
||||
Response<VerifyInformation>? response = await httpClient
|
||||
Response<UserInformation>? response = await httpClient
|
||||
.SetUser(user, CookieType.All)
|
||||
.TryCatchPostAsJsonAsync<Timestamp, Response<VerifyInformation>>(ApiEndpoints.AccountVerifyLtoken, new(), options, logger, token)
|
||||
.TryCatchPostAsJsonAsync<Timestamp, Response<UserInformation>>(ApiEndpoints.AccountVerifyLtoken, new(), options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return response?.Data;
|
||||
|
||||
@@ -33,6 +33,34 @@ internal class PassportClient2
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步账密登录
|
||||
/// </summary>
|
||||
/// <param name="account">用户</param>
|
||||
/// <param name="password">密码</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>登录数据</returns>
|
||||
public async Task<LoginResult?> LoginByPasswordAsync(string account, string password, CancellationToken token)
|
||||
{
|
||||
Dictionary<string, string> data = new()
|
||||
{
|
||||
{ "account", RSAEncryptedString.Encrypt(account) },
|
||||
{ "password", RSAEncryptedString.Encrypt(password) },
|
||||
};
|
||||
|
||||
Response<LoginResult>? resp = await httpClient
|
||||
.SetAppId()
|
||||
.SetHeader("x-rpc-aigis", string.Empty)
|
||||
.SetHeader("x-rpc-client_type", "2")
|
||||
.SetHeader("x-rpc-game_biz", "bbs_cn")
|
||||
.SetHeader("x-rpc-sdk_version", "1.3.1.2")
|
||||
.UsingDynamicSecret2(SaltType.PROD, options, ApiEndpoints.AccountLoginByPassword, data)
|
||||
.TryCatchPostAsJsonAsync<Response<LoginResult>>(logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取 CookieToken
|
||||
/// </summary>
|
||||
@@ -50,4 +78,38 @@ internal class PassportClient2
|
||||
|
||||
return resp?.Data;
|
||||
}
|
||||
}
|
||||
|
||||
public class LoginResult
|
||||
{
|
||||
[JsonPropertyName("token")]
|
||||
public TokenWrapper Token { get; set; }
|
||||
|
||||
[JsonPropertyName("user_info")]
|
||||
public UserInformation UserInfo { get; set; }
|
||||
|
||||
[JsonPropertyName("reactivate_info")]
|
||||
public ReactivateInfo ReactivateInfo { get; set; }
|
||||
|
||||
[JsonPropertyName("login_ticket")]
|
||||
public string LoginTicket { get; set; }
|
||||
}
|
||||
|
||||
public class ReactivateInfo
|
||||
{
|
||||
|
||||
[JsonPropertyName("required")]
|
||||
public bool Required { get; set; }
|
||||
|
||||
[JsonPropertyName("ticket")]
|
||||
public string Ticket { get; set; }
|
||||
}
|
||||
|
||||
public class TokenWrapper
|
||||
{
|
||||
[JsonPropertyName("token_type")]
|
||||
public int TokenType { get; set; }
|
||||
|
||||
[JsonPropertyName("token")]
|
||||
public string Token { get; set; }
|
||||
}
|
||||
@@ -9,20 +9,34 @@ namespace Snap.Hutao.Web.Hoyolab.Passport;
|
||||
/// <summary>
|
||||
/// 支持RSA转换
|
||||
/// </summary>
|
||||
internal static class RSAUtil
|
||||
internal class RSAEncryptedString
|
||||
{
|
||||
private const string RsaPublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDvekdPMHN3AYhm/vktJT+YJr7cI5DcsNKqdsx5DZX0gDuWFuIjzdwButrIYPNmRJ1G8ybDIF7oDW2eEpm5sMbL9zs\n9ExXCdvqrn51qELbqj0XxtMTIpaCHFSI50PfPpTFV9Xt/hmyVwokoOXFlAEgCn+Q\nCgGs52bFoYMtyi+xEQIDAQAB\n";
|
||||
|
||||
private readonly string encryptedSource;
|
||||
|
||||
private RSAEncryptedString(string encryptedSource)
|
||||
{
|
||||
this.encryptedSource = encryptedSource;
|
||||
}
|
||||
|
||||
public static implicit operator string(RSAEncryptedString encryptedString)
|
||||
{
|
||||
return encryptedString.encryptedSource.Replace("=", @"\u003d");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加密
|
||||
/// </summary>
|
||||
/// <param name="source">源</param>
|
||||
/// <returns>字节数组</returns>
|
||||
public static string Encrypt(string source)
|
||||
public static RSAEncryptedString Encrypt(string source)
|
||||
{
|
||||
RSA rsa = RSA.Create();
|
||||
rsa.ImportFromPem($"-----BEGIN PUBLIC KEY-----\n{RsaPublicKey}\n-----END PUBLIC KEY-----");
|
||||
rsa.ImportFromPem($"-----BEGIN PUBLIC KEY-----\n{RsaPublicKey}-----END PUBLIC KEY-----");
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(source);
|
||||
return Convert.ToBase64String(rsa.Encrypt(bytes, RSAEncryptionPadding.Pkcs1));
|
||||
string encrypted = Convert.ToBase64String(rsa.Encrypt(bytes, RSAEncryptionPadding.Pkcs1));
|
||||
|
||||
return new(encrypted);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ namespace Snap.Hutao.Web.Hoyolab.Passport;
|
||||
/// <summary>
|
||||
/// 验证返回信息
|
||||
/// </summary>
|
||||
public class VerifyInformation
|
||||
public class UserInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// 账户Id
|
||||
Reference in New Issue
Block a user