fix avatarInfo def value

This commit is contained in:
DismissedLight
2022-11-20 13:28:54 +08:00
parent efcf0620d7
commit c0d670c5b6
23 changed files with 204 additions and 60 deletions

6
desktop.ini Normal file
View File

@@ -0,0 +1,6 @@
[.ShellClassInfo]
IconResource=D:\Develop\Projects\Snap.Hutao\src\Snap.Hutao\Snap.Hutao\Assets\Logo.ico,0
[ViewState]
Mode=
Vid=
FolderType=Generic

View File

@@ -23,6 +23,13 @@ internal class JsonTextEncoder : JavaScriptEncoder
/// <inheritdoc/>
public override unsafe bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten)
{
// " => \"
if (unicodeScalar == '"')
{
numberOfCharactersWritten = 2;
return "\\\"".AsSpan().TryCopyTo(new Span<char>(buffer, bufferLength));
}
string encoded = $"\\u{(uint)unicodeScalar:x4}";
numberOfCharactersWritten = (encoded.Length <= (uint)bufferLength) ? encoded.Length : 0;
return encoded.AsSpan().TryCopyTo(new Span<char>(buffer, bufferLength));
@@ -31,6 +38,6 @@ internal class JsonTextEncoder : JavaScriptEncoder
/// <inheritdoc/>
public override bool WillEncode(int unicodeScalar)
{
return true;
return unicodeScalar == '=';
}
}

View File

@@ -41,12 +41,12 @@ public class User : ObservableObject
public List<UserGameRole> UserGameRoles { get; private set; } = default!;
/// <summary>
/// 用户信息
/// 用户信息, 请勿访问set
/// </summary>
public UserGameRole? SelectedUserGameRole
{
get => selectedUserGameRole;
private set => SetProperty(ref selectedUserGameRole, value);
set => SetProperty(ref selectedUserGameRole, value);
}
/// <inheritdoc cref="EntityUser.IsSelected"/>

View File

@@ -12,7 +12,7 @@
<Identity
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
Publisher="CN=DGP Studio"
Version="1.2.0.0" />
Version="1.2.2.0" />
<Properties>
<DisplayName>胡桃</DisplayName>

View File

@@ -118,7 +118,7 @@ internal static class SummaryHelper
double def = fightPropMap.GetValueOrDefault2(FightProperty.FIGHT_PROP_DEFENSE); // 8
double defPercent = fightPropMap.GetValueOrDefault2(FightProperty.FIGHT_PROP_DEFENSE_PERCENT); // 9
double defAdd = def + (baseDef * defPercent);
double maxDef = baseDef + defPercent;
double maxDef = baseDef + defAdd;
Pair2<string, string, string?> defPair2 = PropertyInfoDescriptor.FormatIntegerPair2("防御力", FormatMethod.Integer, maxDef, defAdd);
double em = fightPropMap.GetValueOrDefault2(FightProperty.FIGHT_PROP_ELEMENT_MASTERY); // 28

View File

@@ -90,7 +90,7 @@ internal class UserService : IUserService
// Sync cache
userCollection!.Remove(user);
roleCollection!.RemoveWhere(r => r.User.InnerId == user.Entity.InnerId);
roleCollection?.RemoveWhere(r => r.User.InnerId == user.Entity.InnerId);
// Sync database
using (IServiceScope scope = scopeFactory.CreateScope())
@@ -191,8 +191,7 @@ internal class UserService : IUserService
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// 检查 stoken 是否存在
if (user.HasStoken)
if (cookie.IsStoken())
{
// update stoken
await ThreadHelper.SwitchToMainThreadAsync();
@@ -201,14 +200,15 @@ internal class UserService : IUserService
return new(UserOptionResult.Upgraded, mid);
}
await ThreadHelper.SwitchToMainThreadAsync();
else
{
user.Cookie = cookie;
appDbContext.Users.UpdateAndSave(user.Entity);
return new(UserOptionResult.Updated, mid);
}
}
}
else
{
return await TryCreateUserAndAddAsync(cookie).ConfigureAwait(false);

View File

@@ -20,7 +20,7 @@
<AppxPackageSigningEnabled>True</AppxPackageSigningEnabled>
<PackageCertificateThumbprint>F8C2255969BEA4A681CED102771BF807856AEC02</PackageCertificateThumbprint>
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
<AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
<AppxAutoIncrementPackageRevision>True</AppxAutoIncrementPackageRevision>
<AppxSymbolPackageEnabled>True</AppxSymbolPackageEnabled>
<GenerateTestArtifacts>True</GenerateTestArtifacts>
<AppxBundle>Never</AppxBundle>

View File

@@ -16,7 +16,7 @@ public sealed partial class LoginMihoyoBBSDialog : ContentDialog
/// <20><><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>µĵ<C2B5>¼<EFBFBD><C2BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ի<EFBFBD><D4BB><EFBFBD>
/// </summary>
/// <param name="window"><3E><><EFBFBD><EFBFBD></param>
public LoginMihoyoBBSDialog(Window window)
public LoginMihoyoBBSDialog(MainWindow window)
{
InitializeComponent();
XamlRoot = window.Content.XamlRoot;

View File

@@ -206,8 +206,8 @@
Height="40"
Width="40"
Background="{StaticResource CardBackgroundFillColorDefaultBrush}"
Value="{Binding DailyNote.CurrentResin,Mode=OneWay}"
Maximum="{Binding DailyNote.MaxResin,Mode=OneWay}"
Value="{Binding DailyNote.CurrentResin,Mode=OneWay}"
IsIndeterminate="False"/>
</Grid>
<TextBlock
@@ -288,8 +288,8 @@
Height="40"
Width="40"
Background="{StaticResource CardBackgroundFillColorDefaultBrush}"
Value="{Binding DailyNote.FinishedTaskNum,Mode=OneWay}"
Maximum="{Binding DailyNote.TotalTaskNum,Mode=OneWay}"
Value="{Binding DailyNote.FinishedTaskNum,Mode=OneWay}"
IsIndeterminate="False"/>
</Grid>
<TextBlock
@@ -329,8 +329,8 @@
Height="40"
Width="40"
Background="{StaticResource CardBackgroundFillColorDefaultBrush}"
Value="{Binding DailyNote.ResinDiscountUsedNum,Mode=OneWay}"
Maximum="{Binding DailyNote.ResinDiscountNumLimit,Mode=OneWay}"
Value="{Binding DailyNote.ResinDiscountUsedNum,Mode=OneWay}"
IsIndeterminate="False"/>
</Grid>
<TextBlock
@@ -370,8 +370,8 @@
Height="40"
Width="40"
Background="{StaticResource CardBackgroundFillColorDefaultBrush}"
Value="{Binding DailyNote.Transformer.RecoveryTime.TotalSeconds,Mode=OneWay}"
Maximum="604800"
Value="{Binding DailyNote.Transformer.RecoveryTime.TotalSeconds,Mode=OneWay}"
IsIndeterminate="False"/>
</Grid>
<TextBlock

View File

@@ -100,7 +100,18 @@ internal class DailyNoteViewModel : ObservableObject, ISupportCancellation
/// <summary>
/// 提醒式通知
/// </summary>
public bool IsReminderNotification { get => isReminderNotification; set => SetProperty(ref isReminderNotification, value); }
public bool IsReminderNotification
{
get => isReminderNotification;
set
{
if (SetProperty(ref isReminderNotification, value))
{
reminderNotifyEntry.SetBoolean(value);
appDbContext.Settings.UpdateAndSave(reminderNotifyEntry!);
}
}
}
/// <summary>
/// 用户与角色集合

View File

@@ -2,8 +2,8 @@
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.DependencyInjection;
using Snap.Hutao.Context.Database;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.Windowing;
@@ -12,7 +12,11 @@ using Snap.Hutao.Model;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Game;
using Snap.Hutao.Service.Game.Locator;
using Snap.Hutao.Service.User;
using Snap.Hutao.View.Dialog;
using Snap.Hutao.Web.Hoyolab;
using Snap.Hutao.Web.Hoyolab.Passport;
using Snap.Hutao.Web.Response;
namespace Snap.Hutao.ViewModel;
@@ -44,7 +48,11 @@ internal class SettingViewModel : ObservableObject
/// <param name="gameService">游戏服务</param>
/// <param name="asyncRelayCommandFactory">异步命令工厂</param>
/// <param name="experimental">实验性功能</param>
public SettingViewModel(AppDbContext appDbContext, IGameService gameService, IAsyncRelayCommandFactory asyncRelayCommandFactory, ExperimentalFeaturesViewModel experimental)
public SettingViewModel(
AppDbContext appDbContext,
IGameService gameService,
IAsyncRelayCommandFactory asyncRelayCommandFactory,
ExperimentalFeaturesViewModel experimental)
{
this.appDbContext = appDbContext;
this.gameService = gameService;
@@ -64,7 +72,7 @@ internal class SettingViewModel : ObservableObject
GamePath = gameService.GetGamePathSkipLocator();
SetGamePathCommand = asyncRelayCommandFactory.Create(SetGamePathAsync);
DebugExceptionCommand = new RelayCommand(DebugThrowException);
DebugExceptionCommand = asyncRelayCommandFactory.Create(DebugThrowExceptionAsync);
}
/// <summary>
@@ -154,13 +162,35 @@ internal class SettingViewModel : ObservableObject
}
}
private async void DebugThrowException()
private async Task DebugThrowExceptionAsync()
{
#if DEBUG
PassportClient2 passportClient2 = Ioc.Default.GetRequiredService<PassportClient2>();
LoginResult? data = await passportClient2.LoginByPasswordAsync("phoneNunmber", "password", CancellationToken.None).ConfigureAwait(false);
LoginMihoyoBBSDialog dialog = ActivatorUtilities.CreateInstance<LoginMihoyoBBSDialog>(Ioc.Default);
(bool isOk, Dictionary<string, string>? data) = await dialog.GetInputAccountPasswordAsync().ConfigureAwait(false);
_ = data;
if (isOk)
{
(Response<LoginResult>? resp, Aigis? aigis) = await Ioc.Default
.GetRequiredService<PassportClient2>()
.LoginByPasswordAsync(data, CancellationToken.None)
.ConfigureAwait(false);
if (resp != null)
{
if (resp.IsOk())
{
Cookie cookie = Cookie.FromLoginResult(resp.Data!);
await Ioc.Default
.GetRequiredService<IUserService>()
.ProcessInputCookieAsync(cookie)
.ConfigureAwait(false);
}
if (resp.ReturnCode == (int)KnownReturnCode.RET_NEED_AIGIS)
{
}
}
}
#endif
}
}

View File

@@ -10,6 +10,30 @@ namespace Snap.Hutao.Web.Hoyolab;
[SuppressMessage("", "SA1124")]
internal static class ApiEndpoints
{
#region
/// <summary>
/// 获取GT码
/// </summary>
/// <param name="gt">gt</param>
/// <returns>GT码Url</returns>
public static string GeetestGetType(string gt)
{
return $"{ApiGeetest}/gettype.php?gt={gt}";
}
/// <summary>
/// 验证接口
/// </summary>
/// <param name="gt">gt</param>
/// <param name="challenge">challenge流水号</param>
/// <returns>验证接口Url</returns>
public static string GeetestAjax(string gt, string challenge)
{
return $"{ApiV6Geetest}/ajax.php?gt={gt}&challenge={challenge}&lang=zh-cn&pt=3&client_type=web_mobile";
}
#endregion
#region ApiTakumiAuthApi
/// <summary>
@@ -217,6 +241,9 @@ internal static class ApiEndpoints
#endregion
// consts
private const string ApiGeetest = "https://api.geetest.com";
private const string ApiV6Geetest = "https://apiv6.geetest.com";
private const string ApiTakumi = "https://api-takumi.mihoyo.com";
private const string ApiTakumiAuthApi = $"{ApiTakumi}/auth/api";
private const string ApiTaKumiBindingApi = $"{ApiTakumi}/binding/api";

View File

@@ -42,7 +42,7 @@ internal class UserClient
{
Response<UserFullInfoWrapper>? resp = await httpClient
.SetUser(user, CookieType.Cookie)
.SetReferer(ApiEndpoints.BbsReferer)
.SetReferer(ApiEndpoints.BbsReferer) // Otherwise HTTP 403
.TryCatchGetFromJsonAsync<Response<UserFullInfoWrapper>>(ApiEndpoints.UserFullInfo, options, logger, token)
.ConfigureAwait(false);

View File

@@ -45,7 +45,7 @@ internal class UserClient2
{
Response<UserFullInfoWrapper>? resp = await httpClient
.SetUser(user, CookieType.Stoken)
//.SetReferer(ApiEndpoints.BbsReferer)
.SetReferer(ApiEndpoints.BbsReferer)
.UseDynamicSecret(DynamicSecretVersion.Gen1, SaltType.K2, true)
.TryCatchGetFromJsonAsync<Response<UserFullInfoWrapper>>(ApiEndpoints.UserFullInfoQuery(uid), options, logger, token)
.ConfigureAwait(false);

View File

@@ -65,6 +65,23 @@ public partial class Cookie
return new(cookieMap);
}
/// <summary>
/// 从登录结果创建Cookie
/// </summary>
/// <param name="loginResult">登录结果</param>
/// <returns>Cookie</returns>
public static Cookie FromLoginResult(LoginResult loginResult)
{
SortedDictionary<string, string> cookieMap = new()
{
[STUID] = loginResult.UserInfo.Aid,
[STOKEN] = loginResult.Token.Token,
[MID] = loginResult.UserInfo.Mid,
};
return new(cookieMap);
}
/// <summary>
/// 此 Cookie 是 SToken
/// </summary>

View File

@@ -14,7 +14,7 @@ public enum CookieType
None,
/// <summary>
/// 需要 Ltoken &amp; CookieToken &amp; e_hk4e_token
/// 需要 Ltoken &amp; CookieToken
/// </summary>
Cookie,
@@ -22,9 +22,4 @@ public enum CookieType
/// 需要 Stoken
/// </summary>
Stoken,
/// <summary>
/// 全部
/// </summary>
All,
}

View File

@@ -5,6 +5,7 @@ using Snap.Hutao.Core.Convert;
using Snap.Hutao.Web.Request;
using System.Collections.Immutable;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
namespace Snap.Hutao.Web.Hoyolab.DynamicSecret;
@@ -28,26 +29,28 @@ public class DynamicSecretHandler : DelegatingHandler
}.ToImmutableDictionary();
/// <inheritdoc/>
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken token)
{
if (request.Headers.TryGetValues("DS-Option", out IEnumerable<string>? values))
{
string[] definations = values.Single().Split('|');
string version = definations[0];
string saltType = definations[1];
bool includeCharsKey = definations[2] == "true";
bool includeChars = definations[2] == "true";
string salt = DynamicSecrets[saltType];
long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
string r = includeCharsKey ? GetRandomStringWithChars() : GetRandomStringNoChars();
string r = includeChars ? GetRandomStringWithChars() : GetRandomStringNoChars();
string dsContent = $"salt={salt}&t={t}&r={r}";
if (version == nameof(DynamicSecretVersion.Gen2))
{
string b = request.Content?.ToString() ?? (saltType == nameof(SaltType.PROD) ? "{}" : string.Empty);
string b = request.Content != null
? await request.Content.ReadAsStringAsync(token).ConfigureAwait(false)
: (saltType == nameof(SaltType.PROD) ? "{}" : string.Empty);
string[] queries = Uri.UnescapeDataString(request.RequestUri!.Query).Split('?', 2);
string q = queries.Length == 2 ? string.Join('&', queries[1].Split('&').OrderBy(x => x)) : string.Empty;
@@ -61,7 +64,7 @@ public class DynamicSecretHandler : DelegatingHandler
request.Headers.Set("DS", $"{t},{r},{check}");
}
return base.SendAsync(request, cancellationToken);
return await base.SendAsync(request, token).ConfigureAwait(false);
}
private static string GetRandomStringWithChars()

View File

@@ -0,0 +1,28 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hoyolab.Passport;
/// <summary>
/// 极验
/// </summary>
public class Aigis
{
/// <summary>
/// 极验会话Id
/// </summary>
[JsonPropertyName("session_id")]
public string SessionId { get; set; } = default!;
/// <summary>
/// 验证类型 1
/// </summary>
[JsonPropertyName("mmt_type")]
public int MmtType { get; set; }
/// <summary>
/// 数据
/// </summary>
[JsonPropertyName("data")]
public string Data { get; set; } = default!;
}

View File

@@ -7,12 +7,14 @@ using Snap.Hutao.Web.Hoyolab.Annotation;
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
using Snap.Hutao.Web.Response;
using System.Net.Http;
using System.Net.Http.Json;
namespace Snap.Hutao.Web.Hoyolab.Passport;
/// <summary>
/// 通行证客户端 XRPC 版
/// </summary>
[UseDynamicSecret]
[HttpClient(HttpClientConfigration.XRpc2)]
internal class PassportClient2
{
@@ -36,25 +38,23 @@ internal class PassportClient2
/// <summary>
/// 异步账密登录
/// </summary>
/// <param name="account">用户</param>
/// <param name="password">密码</param>
/// <param name="data">账密数据</param>
/// <param name="token">取消令牌</param>
/// <returns>登录数据</returns>
[ApiInformation(Salt = SaltType.PROD)]
public async Task<LoginResult?> LoginByPasswordAsync(string account, string password, CancellationToken token)
public async Task<ValueResult<Response<LoginResult>?, Aigis?>> LoginByPasswordAsync(Dictionary<string, string> data, CancellationToken token)
{
Dictionary<string, string> data = new()
{
{ "account", RSAEncryptedString.Encrypt(account) },
{ "password", RSAEncryptedString.Encrypt(password) },
};
Response<LoginResult>? resp = await httpClient
HttpResponseMessage resp = await httpClient
.UseDynamicSecret(DynamicSecretVersion.Gen2, SaltType.PROD, true)
.TryCatchPostAsJsonAsync<Dictionary<string, string>, Response<LoginResult>>(ApiEndpoints.AccountLoginByPassword, data, options, logger, token)
.PostAsJsonAsync(ApiEndpoints.AccountLoginByPassword, data, options, token)
.ConfigureAwait(false);
return resp?.Data;
_ = resp.Headers.TryGetValues("X-Rpc-Aigis", out IEnumerable<string>? values);
Aigis? aigis = values != null && values.Any() ? JsonSerializer.Deserialize<Aigis>(values.Single(), options) : null;
Response<LoginResult>? body = await resp.Content.ReadFromJsonAsync<Response<LoginResult>>(options, token).ConfigureAwait(false);
return new(body, aigis);
}
/// <summary>

View File

@@ -61,13 +61,13 @@ internal class BindingClient2
/// <param name="data">提交数据</param>
/// <param name="token">取消令牌</param>
/// <returns>用户角色信息</returns>
[ApiInformation(Cookie = CookieType.Stoken, Salt = SaltType.K2)]
[ApiInformation(Cookie = CookieType.Stoken, Salt = SaltType.LK2)]
public async Task<GameAuthKey?> GenerateAuthenticationKeyAsync(User user, GenAuthKeyData data, CancellationToken token = default)
{
Response<GameAuthKey>? resp = await httpClient
.SetUser(user, CookieType.Stoken)
.SetReferer("https://app.mihoyo.com")
.UseDynamicSecret(DynamicSecretVersion.Gen1, SaltType.K2, true)
.UseDynamicSecret(DynamicSecretVersion.Gen1, SaltType.LK2, true)
.TryCatchPostAsJsonAsync<GenAuthKeyData, Response<GameAuthKey>>(ApiEndpoints.BindingGenAuthKey, data, options, logger, token)
.ConfigureAwait(false);

View File

@@ -119,12 +119,13 @@ internal class GameRecordClient
/// </summary>
/// <param name="user">用户</param>
/// <param name="uid">uid</param>
/// <param name="playerInfo">玩家的基础信息</param>
/// <param name="token">取消令牌</param>
/// <returns>角色列表</returns>
[ApiInformation(Cookie = CookieType.Cookie, Salt = SaltType.X4)]
public async Task<List<Character>> GetCharactersAsync(User user, PlayerUid uid, CancellationToken token = default)
public async Task<List<Character>> GetCharactersAsync(User user, PlayerUid uid, PlayerInfo playerInfo, CancellationToken token = default)
{
CharacterData data = new(uid);
CharacterData data = new(uid, playerInfo.Avatars.Select(x => x.Id));
Response<CharacterWrapper>? resp = await httpClient
.SetUser(user, CookieType.Cookie)
@@ -155,12 +156,16 @@ internal class GameRecordClient
private class CharacterData
{
public CharacterData(PlayerUid uid)
public CharacterData(PlayerUid uid, IEnumerable<int> characterIds)
{
CharacterIds = characterIds;
Uid = uid.Value;
Server = uid.Region;
}
[JsonPropertyName("character_ids")]
public IEnumerable<int> CharacterIds { get; }
[JsonPropertyName("role_id")]
public string Uid { get; }

View File

@@ -177,8 +177,13 @@ internal class HomaClient
{
Must.NotNull(user.SelectedUserGameRole!);
PlayerInfo? playerInfo = await gameRecordClient
.GetPlayerInfoAsync(user.Entity, user.SelectedUserGameRole, token)
.ConfigureAwait(false);
Must.NotNull(playerInfo!);
List<Character> characters = await gameRecordClient
.GetCharactersAsync(user.Entity, user.SelectedUserGameRole, token)
.GetCharactersAsync(user.Entity, user.SelectedUserGameRole, playerInfo, token)
.ConfigureAwait(false);
SpiralAbyss? spiralAbyssInfo = await gameRecordClient

View File

@@ -19,7 +19,7 @@ public enum KnownReturnCode : int
AlreadySignedIn = -5003,
/// <summary>
/// 需要风险验证
/// 需要风险验证(闪验)
/// </summary>
RET_NEED_RISK_VERIFY = -3235,
@@ -88,8 +88,18 @@ public enum KnownReturnCode : int
/// </summary>
CODE1034 = 1034,
/// <summary>
/// 请登录
/// </summary>
PleaseLogin = 10001,
/// <summary>
/// 数据未公开
/// </summary>
DataIsNotPublicForTheUser = 10102,
/// <summary>
/// 实时便笺
/// </summary>
CODE10104 = 10104,
}