From 9f793670feaa7da3d0c8f1c6d819d2e8fca7159f Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Thu, 14 Dec 2023 22:47:17 +0800 Subject: [PATCH] failed attempt: fight with device_fp --- .../BaseClassLibrary/JsonSerializeTest.cs | 3 +- .../Core/Abstraction/IDeconstruct.cs | 16 +-- .../DependencyInjection.cs | 2 +- .../EnumerableExtension.Dictionary.cs | 25 ---- .../Extension/EnumerableExtension.cs | 61 ++++++++- .../Model/Entity/Extension/UserExtension.cs | 19 +++ .../Snap.Hutao/Service/AppOptions.cs | 2 - .../Snap.Hutao/Service/AppOptionsExtension.cs | 3 - .../Game/PathAbstraction/GamePathService.cs | 1 - .../Service/User/IUserCollectionService.cs | 3 +- .../User/IUserInitializationService.cs | 4 +- .../Snap.Hutao/Service/User/IUserService.cs | 9 +- .../Snap.Hutao/Service/User/InputCookie.cs | 48 +++++++ .../Service/User/UserCollectionService.cs | 10 +- .../Service/User/UserFingerprintService.cs | 6 +- .../Service/User/UserInitializationService.cs | 5 +- .../Snap.Hutao/Service/User/UserService.cs | 15 +- src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj | 1 + .../View/Page/ISupportLoginByWebView.cs | 5 +- .../View/Page/LoginHoyoverseUserPage.xaml.cs | 3 +- .../View/Page/LoginMihoyoUserPage.xaml.cs | 4 +- .../ViewModel/Setting/SettingViewModel.cs | 1 - .../Snap.Hutao/ViewModel/User/User.cs | 4 +- .../ViewModel/User/UserExtension.cs | 14 ++ .../ViewModel/User/UserViewModel.cs | 4 +- .../Snap.Hutao/Web/Hoyolab/Cookie.cs | 128 +----------------- .../Snap.Hutao/Web/Hoyolab/CookieExtension.cs | 68 ++++++++++ .../Web/Hoyolab/Passport/PassportClient2.cs | 1 - 28 files changed, 255 insertions(+), 210 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Entity/Extension/UserExtension.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/User/InputCookie.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserExtension.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/CookieExtension.cs diff --git a/src/Snap.Hutao/Snap.Hutao.Test/BaseClassLibrary/JsonSerializeTest.cs b/src/Snap.Hutao/Snap.Hutao.Test/BaseClassLibrary/JsonSerializeTest.cs index 42b0ca1d..309ef2bb 100644 --- a/src/Snap.Hutao/Snap.Hutao.Test/BaseClassLibrary/JsonSerializeTest.cs +++ b/src/Snap.Hutao/Snap.Hutao.Test/BaseClassLibrary/JsonSerializeTest.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Abstraction/IDeconstruct.cs b/src/Snap.Hutao/Snap.Hutao/Core/Abstraction/IDeconstruct.cs index be6f3181..a0052c17 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Abstraction/IDeconstruct.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Abstraction/IDeconstruct.cs @@ -3,18 +3,12 @@ namespace Snap.Hutao.Core.Abstraction; -/// -/// 指示该类可以解构为元组 -/// -/// 元组的第一个类型 -/// 元组的第二个类型 -[HighQuality] internal interface IDeconstruct { - /// - /// 解构 - /// - /// 第一个元素 - /// 第二个元素 void Deconstruct(out T1 t1, out T2 t2); +} + +internal interface IDeconstruct +{ + void Deconstruct(out T1 t1, out T2 t2, out T3 t3); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs index 1f515abd..7799061a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs @@ -24,7 +24,7 @@ internal static class DependencyInjection ServiceProvider serviceProvider = new ServiceCollection() // Microsoft extension - .AddLogging(builder => builder.AddConsoleWindow()) + .AddLogging(builder => builder.AddDebug().AddConsoleWindow()) .AddMemoryCache() // Hutao extensions diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.Dictionary.cs b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.Dictionary.cs index c6897d7f..bfd640f5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.Dictionary.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.Dictionary.cs @@ -23,13 +23,6 @@ internal static partial class EnumerableExtension return true; } - /// - /// 增加计数 - /// - /// 键类型 - /// 值类型 - /// 字典 - /// 键 public static void IncreaseOne(this Dictionary dict, TKey key) where TKey : notnull where TValue : struct, IIncrementOperators @@ -37,14 +30,6 @@ internal static partial class EnumerableExtension ++CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _); } - /// - /// 增加计数 - /// - /// 键类型 - /// 值类型 - /// 字典 - /// 键 - /// 增加的值 public static void IncreaseValue(this Dictionary dict, TKey key, TValue value) where TKey : notnull where TValue : struct, IAdditionOperators @@ -54,14 +39,6 @@ internal static partial class EnumerableExtension current += value; } - /// - /// 增加计数 - /// - /// 键类型 - /// 值类型 - /// 字典 - /// 键 - /// 是否存在键值 public static bool TryIncreaseOne(this Dictionary dict, TKey key) where TKey : notnull where TValue : struct, IIncrementOperators @@ -76,7 +53,6 @@ internal static partial class EnumerableExtension return false; } - /// public static Dictionary ToDictionaryIgnoringDuplicateKeys(this IEnumerable source, Func keySelector) where TKey : notnull { @@ -90,7 +66,6 @@ internal static partial class EnumerableExtension return dictionary; } - /// public static Dictionary ToDictionaryIgnoringDuplicateKeys(this IEnumerable source, Func keySelector, Func elementSelector) where TKey : notnull { diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs index 3ff7c070..a9a99041 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs @@ -3,6 +3,7 @@ using System.Collections.ObjectModel; using System.Runtime.CompilerServices; +using System.Text; namespace Snap.Hutao.Extension; @@ -43,6 +44,63 @@ internal static partial class EnumerableExtension return first; } + public static string JoinToString(this IEnumerable source, char separator, Action selector) + { + StringBuilder resultBuilder = new(); + + IEnumerator enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return string.Empty; + } + + T first = enumerator.Current; + selector(resultBuilder, first); + + if (!enumerator.MoveNext()) + { + return resultBuilder.ToString(); + } + + do + { + resultBuilder.Append(separator); + selector(resultBuilder, enumerator.Current); + } + while (enumerator.MoveNext()); + + return resultBuilder.ToString(); + } + + public static string JoinToString(this IEnumerable> source, char separator, Action selector) + { + StringBuilder resultBuilder = new(); + + IEnumerator> enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return string.Empty; + } + + KeyValuePair first = enumerator.Current; + selector(resultBuilder, first.Key, first.Value); + + if (!enumerator.MoveNext()) + { + return resultBuilder.ToString(); + } + + do + { + resultBuilder.Append(separator); + KeyValuePair current = enumerator.Current; + selector(resultBuilder, current.Key, current.Value); + } + while (enumerator.MoveNext()); + + return resultBuilder.ToString(); + } + /// /// 转换到 /// @@ -64,7 +122,6 @@ internal static partial class EnumerableExtension /// Converted collection into string. public static string ToString(this IEnumerable collection, char separator) { - string result = string.Join(separator, collection); - return result.Length > 0 ? result : string.Empty; + return string.Join(separator, collection); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/Extension/UserExtension.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Extension/UserExtension.cs new file mode 100644 index 00000000..66f2df90 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Extension/UserExtension.cs @@ -0,0 +1,19 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Model.Entity.Extension; + +internal static class UserExtension +{ + public static bool TryUpdateFingerprint(this User user, string? deviceFp) + { + if (string.IsNullOrEmpty(deviceFp)) + { + return false; + } + + user.Fingerprint = deviceFp; + user.FingerprintLastUpdateTime = DateTimeOffset.UtcNow; + return true; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs index 8c9adcfa..aeb46ccc 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs @@ -6,8 +6,6 @@ using Snap.Hutao.Core.Windowing; using Snap.Hutao.Model; using Snap.Hutao.Model.Entity; using Snap.Hutao.Service.Abstraction; -using Snap.Hutao.Service.Game.PathAbstraction; -using System.Collections.Immutable; using System.Globalization; using System.IO; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppOptionsExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppOptionsExtension.cs index 34c731f1..d05ac848 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AppOptionsExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppOptionsExtension.cs @@ -2,10 +2,7 @@ // Licensed under the MIT license. using Snap.Hutao.Model; -using Snap.Hutao.Service.Game.PathAbstraction; -using System.Collections.Immutable; using System.Globalization; -using System.IO; namespace Snap.Hutao.Service; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathService.cs index c1c9eff4..a9294f1d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathService.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. using Snap.Hutao.Service.Game.Locator; -using Snap.Hutao.Service.Game.PathAbstraction; namespace Snap.Hutao.Service.Game.PathAbstraction; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserCollectionService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserCollectionService.cs index 9d914db6..b34c8e67 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserCollectionService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserCollectionService.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. using Snap.Hutao.ViewModel.User; -using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab.Takumi.Binding; using System.Collections.ObjectModel; using BindingUser = Snap.Hutao.ViewModel.User.User; @@ -21,7 +20,7 @@ internal interface IUserCollectionService ValueTask RemoveUserAsync(BindingUser user); - ValueTask> TryCreateAndAddUserFromCookieAsync(Cookie cookie, bool isOversea); + ValueTask> TryCreateAndAddUserFromInputCookieAsync(InputCookie inputCookie); bool TryGetUserByMid(string mid, [NotNullWhen(true)] out BindingUser? user); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserInitializationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserInitializationService.cs index 03f62a30..442bd68b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserInitializationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserInitializationService.cs @@ -1,13 +1,11 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Snap.Hutao.Web.Hoyolab; - namespace Snap.Hutao.Service.User; internal interface IUserInitializationService { - ValueTask CreateUserFromCookieOrDefaultAsync(Cookie cookie, bool isOversea, CancellationToken token = default(CancellationToken)); + ValueTask CreateUserFromInputCookieOrDefaultAsync(InputCookie inputCookie, CancellationToken token = default(CancellationToken)); ValueTask ResumeUserAsync(Model.Entity.User inner, CancellationToken token = default(CancellationToken)); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs index e89d0494..9174bb80 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. using Snap.Hutao.ViewModel.User; -using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab.Takumi.Binding; using System.Collections.ObjectModel; using BindingUser = Snap.Hutao.ViewModel.User.User; @@ -42,13 +41,7 @@ internal interface IUserService /// 对应的角色信息 UserGameRole? GetUserGameRoleByUid(string uid); - /// - /// 尝试异步处理输入的Cookie - /// - /// Cookie - /// 是否为国际服 - /// 处理的结果 - ValueTask> ProcessInputCookieAsync(Cookie cookie, bool isOversea); + ValueTask> ProcessInputCookieAsync(InputCookie inputCookie); ValueTask RefreshCookieTokenAsync(Model.Entity.User user); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/InputCookie.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/InputCookie.cs new file mode 100644 index 00000000..c09ba478 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/InputCookie.cs @@ -0,0 +1,48 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core.Abstraction; +using Snap.Hutao.Web.Hoyolab; + +namespace Snap.Hutao.Service.User; + +internal sealed class InputCookie : IDeconstruct +{ + private InputCookie(Cookie cookie, bool isOversea) + { + Cookie = cookie; + IsOversea = isOversea; + cookie.TryGetDeviceFp(out string? deviceFp); + DeviceFp = deviceFp; + } + + private InputCookie(Cookie cookie, bool isOversea, string? deviceFp) + { + Cookie = cookie; + IsOversea = isOversea; + DeviceFp = deviceFp; + } + + public Cookie Cookie { get; } + + public bool IsOversea { get; } + + public string? DeviceFp { get; } + + public static InputCookie Create(Cookie cookie, bool isOversea, string? deviceFp) + { + return new InputCookie(cookie, isOversea, deviceFp); + } + + public static InputCookie CreateWithDeviceFpInference(Cookie cookie, bool isOversea) + { + return new InputCookie(cookie, isOversea); + } + + public void Deconstruct(out Cookie cookie, out bool isOversea, out string? deviceFp) + { + cookie = Cookie; + isOversea = IsOversea; + deviceFp = DeviceFp; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserCollectionService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserCollectionService.cs index 8c8fb108..6ea7d8ff 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserCollectionService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserCollectionService.cs @@ -6,7 +6,6 @@ using Snap.Hutao.Core.Database; using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Message; using Snap.Hutao.ViewModel.User; -using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab.Takumi.Binding; using System.Collections.ObjectModel; using BindingUser = Snap.Hutao.ViewModel.User.User; @@ -113,6 +112,11 @@ internal sealed partial class UserCollectionService : IUserCollectionService midUserMap?.Remove(user.Entity.Mid); } + foreach (UserGameRole role in user.UserGameRoles) + { + uidUserGameRoleMap?.Remove(role.GameUid); + } + // Sync database await taskContext.SwitchToBackgroundAsync(); await userDbService.DeleteUserByIdAsync(user.Entity.InnerId).ConfigureAwait(false); @@ -146,10 +150,10 @@ internal sealed partial class UserCollectionService : IUserCollectionService return midUserMap.TryGetValue(mid, out user); } - public async ValueTask> TryCreateAndAddUserFromCookieAsync(Cookie cookie, bool isOversea) + public async ValueTask> TryCreateAndAddUserFromInputCookieAsync(InputCookie inputCookie) { await taskContext.SwitchToBackgroundAsync(); - BindingUser? newUser = await userInitializationService.CreateUserFromCookieOrDefaultAsync(cookie, isOversea).ConfigureAwait(false); + BindingUser? newUser = await userInitializationService.CreateUserFromInputCookieOrDefaultAsync(inputCookie).ConfigureAwait(false); if (newUser is null) { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserFingerprintService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserFingerprintService.cs index a8a1f59e..f119309d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserFingerprintService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserFingerprintService.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.ViewModel.User; using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab.PublicData.DeviceFp; using Snap.Hutao.Web.Response; @@ -21,7 +22,7 @@ internal sealed partial class UserFingerprintService : IUserFingerprintService return; } - if (user.Entity.FingerprintLastUpdateTime >= DateTimeOffset.UtcNow - TimeSpan.FromDays(3)) + if (user.Entity.FingerprintLastUpdateTime >= DateTimeOffset.UtcNow - TimeSpan.FromDays(7)) { if (!string.IsNullOrEmpty(user.Fingerprint)) { @@ -79,9 +80,8 @@ internal sealed partial class UserFingerprintService : IUserFingerprintService }; Response response = await deviceFpClient.GetFingerprintAsync(data, token).ConfigureAwait(false); - user.Fingerprint = response.IsOk() ? response.Data.DeviceFp : string.Empty; + user.TryUpdateFingerprint(response.IsOk() ? response.Data.DeviceFp : string.Empty); - user.Entity.FingerprintLastUpdateTime = DateTimeOffset.UtcNow; user.NeedDbUpdateAfterResume = true; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs index 08fc3785..02fce7b6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Snap.Hutao.Core.DependencyInjection.Abstraction; +using Snap.Hutao.Model.Entity.Extension; using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab.Bbs.User; using Snap.Hutao.Web.Hoyolab.Passport; @@ -30,14 +31,16 @@ internal sealed partial class UserInitializationService : IUserInitializationSer return user; } - public async ValueTask CreateUserFromCookieOrDefaultAsync(Cookie cookie, bool isOversea, CancellationToken token = default) + public async ValueTask CreateUserFromInputCookieOrDefaultAsync(InputCookie inputCookie, CancellationToken token = default) { // 这里只负责创建实体用户,稍后在用户服务中保存到数据库 + (Cookie cookie, bool isOversea, string? deviceFp) = inputCookie; Model.Entity.User entity = Model.Entity.User.From(cookie, isOversea); entity.Aid = cookie.GetValueOrDefault(Cookie.STUID); entity.Mid = isOversea ? entity.Aid : cookie.GetValueOrDefault(Cookie.MID); entity.IsOversea = isOversea; + entity.TryUpdateFingerprint(deviceFp); if (entity.Aid is not null && entity.Mid is not null) { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs index 43b5e233..b42f23ab 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs @@ -56,20 +56,22 @@ internal sealed partial class UserService : IUserService, IUserServiceUnsafe return userCollectionService.GetUserGameRoleByUid(uid); } - public async ValueTask> ProcessInputCookieAsync(Cookie cookie, bool isOversea) + public async ValueTask> ProcessInputCookieAsync(InputCookie inputCookie) { await taskContext.SwitchToBackgroundAsync(); - string? mid = cookie.GetValueOrDefault(isOversea ? Cookie.STUID : Cookie.MID); + (Cookie cookie, bool isOversea, string? deviceFp) = inputCookie; - if (string.IsNullOrEmpty(mid)) + string? midOrAid = cookie.GetValueOrDefault(isOversea ? Cookie.STUID : Cookie.MID); + + if (string.IsNullOrEmpty(midOrAid)) { return new(UserOptionResult.CookieInvalid, SH.ServiceUserProcessCookieNoMid); } // 检查 mid 对应用户是否存在 - if (!userCollectionService.TryGetUserByMid(mid, out BindingUser? user)) + if (!userCollectionService.TryGetUserByMid(midOrAid, out BindingUser? user)) { - return await userCollectionService.TryCreateAndAddUserFromCookieAsync(cookie, isOversea).ConfigureAwait(false); + return await userCollectionService.TryCreateAndAddUserFromInputCookieAsync(inputCookie).ConfigureAwait(false); } if (!cookie.TryGetSToken(isOversea, out Cookie? stoken)) @@ -80,9 +82,10 @@ internal sealed partial class UserService : IUserService, IUserServiceUnsafe user.SToken = stoken; user.LToken = cookie.TryGetLToken(out Cookie? ltoken) ? ltoken : user.LToken; user.CookieToken = cookie.TryGetCookieToken(out Cookie? cookieToken) ? cookieToken : user.CookieToken; + user.TryUpdateFingerprint(deviceFp); await userDbService.UpdateUserAsync(user.Entity).ConfigureAwait(false); - return new(UserOptionResult.CookieUpdated, mid); + return new(UserOptionResult.CookieUpdated, midOrAid); } public async ValueTask RefreshCookieTokenAsync(Model.Entity.User user) diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj index 615e07e0..36d2ffad 100644 --- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj +++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj @@ -299,6 +299,7 @@ + diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/ISupportLoginByWebView.cs b/src/Snap.Hutao/Snap.Hutao/View/Page/ISupportLoginByWebView.cs index a6101a73..0a560cbd 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/ISupportLoginByWebView.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/ISupportLoginByWebView.cs @@ -7,7 +7,6 @@ using Snap.Hutao.Service.Navigation; using Snap.Hutao.Service.Notification; using Snap.Hutao.Service.User; using Snap.Hutao.Web.Bridge; -using Snap.Hutao.Web.Hoyolab; namespace Snap.Hutao.View.Page; @@ -30,11 +29,11 @@ internal interface ISupportLoginByWebView } } - static async ValueTask PostHandleCurrentCookieAsync(IServiceProvider serviceProvider, Cookie cookie, bool isOversea) + static async ValueTask PostHandleCurrentCookieAsync(IServiceProvider serviceProvider, InputCookie inputCookie) { (UserOptionResult result, string nickname) = await serviceProvider .GetRequiredService() - .ProcessInputCookieAsync(cookie, isOversea) + .ProcessInputCookieAsync(inputCookie) .ConfigureAwait(false); serviceProvider.GetRequiredService().GoBack(); diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/LoginHoyoverseUserPage.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Page/LoginHoyoverseUserPage.xaml.cs index 0ec71bf1..ef5deb5a 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/LoginHoyoverseUserPage.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/LoginHoyoverseUserPage.xaml.cs @@ -4,6 +4,7 @@ using Microsoft.UI.Xaml; using Microsoft.Web.WebView2.Core; using Snap.Hutao.Service.Notification; +using Snap.Hutao.Service.User; using Snap.Hutao.Web; using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab.Takumi.Auth; @@ -88,7 +89,7 @@ internal sealed partial class LoginHoyoverseUserPage : Microsoft.UI.Xaml.Control Cookie stokenV1 = Cookie.FromSToken(uid, multiTokenMap[Cookie.STOKEN]); await ISupportLoginByWebView - .PostHandleCurrentCookieAsync(serviceProvider, stokenV1, true) + .PostHandleCurrentCookieAsync(serviceProvider, InputCookie.Create(stokenV1, true, default)) .ConfigureAwait(false); } diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoUserPage.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoUserPage.xaml.cs index c32a41d0..61529721 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoUserPage.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoUserPage.xaml.cs @@ -4,6 +4,7 @@ using Microsoft.UI.Xaml; using Microsoft.Web.WebView2.Core; using Snap.Hutao.Service.Notification; +using Snap.Hutao.Service.User; using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab.Passport; using Snap.Hutao.Web.Hoyolab.Takumi.Auth; @@ -73,9 +74,10 @@ internal sealed partial class LoginMihoyoUserPage : Microsoft.UI.Xaml.Controls.P } Cookie stokenV2 = Cookie.FromLoginResult(loginResultResponse.Data); + webCookie.TryGetDeviceFp(out string? deviceFp); await ISupportLoginByWebView - .PostHandleCurrentCookieAsync(serviceProvider, stokenV2, false) + .PostHandleCurrentCookieAsync(serviceProvider, InputCookie.Create(stokenV2, false, deviceFp)) .ConfigureAwait(false); } } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs index d72538c3..31f1defe 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs @@ -1,7 +1,6 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Microsoft.Data.Sqlite; using Microsoft.UI.Xaml.Controls; using Microsoft.Windows.AppLifecycle; using Snap.Hutao.Core; diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/User.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/User.cs index 1d16a59a..06f3ba83 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/User.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/User.cs @@ -55,7 +55,7 @@ internal sealed class User : ObservableObject, IEntityOnly, IMapping set => SetSelectedUserGameRole(value); } - public string? Fingerprint { get => inner.Fingerprint; set => inner.Fingerprint = value; } + public string? Fingerprint { get => inner.Fingerprint; } public Guid InnerId { get => inner.InnerId; } @@ -111,4 +111,4 @@ internal sealed class User : ObservableObject, IEntityOnly, IMapping messenger.Send(Message.UserChangedMessage.CreateOnlyRoleChanged(this)); } } -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserExtension.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserExtension.cs new file mode 100644 index 00000000..13dce0a8 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserExtension.cs @@ -0,0 +1,14 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Entity.Extension; + +namespace Snap.Hutao.ViewModel.User; + +internal static class UserExtension +{ + public static bool TryUpdateFingerprint(this User user, string? deviceFp) + { + return user.Entity.TryUpdateFingerprint(deviceFp); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs index 129b7ff6..b0e46bc1 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs @@ -143,7 +143,7 @@ internal sealed partial class UserViewModel : ObservableObject if (result.TryGetValue(out string rawCookie)) { Cookie cookie = Cookie.Parse(rawCookie); - (UserOptionResult optionResult, string uid) = await userService.ProcessInputCookieAsync(cookie, isOversea).ConfigureAwait(false); + (UserOptionResult optionResult, string uid) = await userService.ProcessInputCookieAsync(InputCookie.CreateWithDeviceFpInference(cookie, isOversea)).ConfigureAwait(false); await HandleUserOptionResultAsync(optionResult, uid).ConfigureAwait(false); } } @@ -192,7 +192,7 @@ internal sealed partial class UserViewModel : ObservableObject if (sTokenResponse.IsOk()) { Cookie stokenV2 = Cookie.FromLoginResult(sTokenResponse.Data); - (UserOptionResult optionResult, string uid) = await userService.ProcessInputCookieAsync(stokenV2, false).ConfigureAwait(false); + (UserOptionResult optionResult, string uid) = await userService.ProcessInputCookieAsync(InputCookie.CreateWithDeviceFpInference(stokenV2, false)).ConfigureAwait(false); await HandleUserOptionResultAsync(optionResult, uid).ConfigureAwait(false); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs index 64309812..b3c94b14 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs @@ -14,19 +14,12 @@ internal sealed partial class Cookie { private readonly SortedDictionary inner; - /// - /// 构造一个空白的Cookie - /// public Cookie() : this([]) { } - /// - /// 构造一个新的Cookie - /// - /// 源 - private Cookie(SortedDictionary dict) + public Cookie(SortedDictionary dict) { inner = dict; } @@ -37,11 +30,6 @@ internal sealed partial class Cookie set => inner[key] = value; } - /// - /// 解析Cookie字符串 - /// - /// cookie字符串 - /// 新的Cookie对象 public static Cookie Parse(string cookieString) { SortedDictionary cookieMap = []; @@ -69,11 +57,6 @@ internal sealed partial class Cookie return new(cookieMap); } - /// - /// 从登录结果创建 Cookie - /// - /// 登录结果 - /// Cookie public static Cookie FromLoginResult(LoginResult? loginResult) { if (loginResult is null) @@ -91,12 +74,6 @@ internal sealed partial class Cookie return new(cookieMap); } - /// - /// 从 SToken 创建 Cookie - /// - /// stuid - /// stoken - /// Cookie public static Cookie FromSToken(string stuid, string stoken) { SortedDictionary cookieMap = new() @@ -113,119 +90,18 @@ internal sealed partial class Cookie return inner.Count <= 0; } - public bool TryGetLoginTicket([NotNullWhen(true)] out Cookie? cookie) - { - if (TryGetValue(LOGIN_TICKET, out string? loginTicket) && TryGetValue(LOGIN_UID, out string? loginUid)) - { - cookie = new Cookie(new() - { - [LOGIN_TICKET] = loginTicket, - [LOGIN_UID] = loginUid, - }); - - return true; - } - - cookie = null; - return false; - } - - public bool TryGetSToken(bool isOversea, [NotNullWhen(true)] out Cookie? cookie) - { - return isOversea ? TryGetLegacySToken(out cookie) : TryGetSToken(out cookie); - } - - public bool TryGetLToken([NotNullWhen(true)] out Cookie? cookie) - { - if (TryGetValue(LTOKEN, out string? ltoken) && TryGetValue(LTUID, out string? ltuid)) - { - cookie = new Cookie(new() - { - [LTOKEN] = ltoken, - [LTUID] = ltuid, - }); - - return true; - } - - cookie = null; - return false; - } - - public bool TryGetCookieToken([NotNullWhen(true)] out Cookie? cookie) - { - if (TryGetValue(ACCOUNT_ID, out string? accountId) && TryGetValue(COOKIE_TOKEN, out string? cookieToken)) - { - cookie = new Cookie(new() - { - [ACCOUNT_ID] = accountId, - [COOKIE_TOKEN] = cookieToken, - }); - - return true; - } - - cookie = null; - return false; - } - - /// public bool TryGetValue(string key, [NotNullWhen(true)] out string? value) { return inner.TryGetValue(key, out value); } - /// - /// 获取值 - /// - /// 键 - /// 值或默认值 public string? GetValueOrDefault(string key) { return inner.GetValueOrDefault(key); } - /// - /// 转换为Cookie的字符串表示 - /// - /// Cookie的字符串表示 public override string ToString() { - return string.Join(';', inner.Select(kvp => $"{kvp.Key}={kvp.Value}")); - } - - private bool TryGetSToken([NotNullWhen(true)] out Cookie? cookie) - { - if (TryGetValue(MID, out string? mid) && TryGetValue(STOKEN, out string? stoken) && TryGetValue(STUID, out string? stuid)) - { - cookie = new Cookie(new() - { - [MID] = mid, - [STOKEN] = stoken, - [STUID] = stuid, - }); - - return true; - } - - cookie = null; - return false; - } - - private bool TryGetLegacySToken([NotNullWhen(true)] out Cookie? cookie) - { - if (TryGetValue(STOKEN, out string? stoken) && TryGetValue(STUID, out string? stuid)) - { - cookie = new Cookie(new() - { - [STOKEN] = stoken, - [STUID] = stuid, - }); - - return true; - } - - cookie = null; - return false; + return inner.JoinToString(';', (builder, key, value) => builder.Append(key).Append('=').Append(value)); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/CookieExtension.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/CookieExtension.cs new file mode 100644 index 00000000..1e37f57e --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/CookieExtension.cs @@ -0,0 +1,68 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Web.Hoyolab; + +[SuppressMessage("", "SA1310")] +internal static class CookieExtension +{ + private const string LOGIN_TICKET = "login_ticket"; + private const string LOGIN_UID = "login_uid"; + private const string ACCOUNT_ID = "account_id"; + private const string COOKIE_TOKEN = "cookie_token"; + private const string LTOKEN = "ltoken"; + private const string LTUID = "ltuid"; + private const string MID = "mid"; + private const string STOKEN = "stoken"; + private const string STUID = "stuid"; + private const string DEVICEFP = "DEVICEFP"; + + public static bool TryGetLoginTicket(this Cookie source, [NotNullWhen(true)] out Cookie? cookie) + { + return source.TryGetValuesToCookie([LOGIN_TICKET, LOGIN_UID], out cookie); + } + + public static bool TryGetSToken(this Cookie source, bool isOversea, [NotNullWhen(true)] out Cookie? cookie) + { + return isOversea + ? source.TryGetValuesToCookie([STOKEN, STUID], out cookie) + : source.TryGetValuesToCookie([MID, STOKEN, STUID], out cookie); + } + + public static bool TryGetLToken(this Cookie source, [NotNullWhen(true)] out Cookie? cookie) + { + return source.TryGetValuesToCookie([LTOKEN, LTUID], out cookie); + } + + public static bool TryGetCookieToken(this Cookie source, [NotNullWhen(true)] out Cookie? cookie) + { + return source.TryGetValuesToCookie([ACCOUNT_ID, COOKIE_TOKEN], out cookie); + } + + public static bool TryGetDeviceFp(this Cookie source, [NotNullWhen(true)] out string? deviceFp) + { + return source.TryGetValue(DEVICEFP, out deviceFp); + } + + private static bool TryGetValuesToCookie(this Cookie source, in ReadOnlySpan keys, [NotNullWhen(true)] out Cookie? cookie) + { + Must.Range(keys.Length > 0, "Empty keys is not supported"); + SortedDictionary cookieMap = []; + + foreach (string key in keys) + { + if (source.TryGetValue(key, out string? value)) + { + cookieMap.TryAdd(key, value); + } + else + { + cookie = default; + return false; + } + } + + cookie = new(cookieMap); + return true; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs index be1ad870..e581f2a1 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs @@ -5,7 +5,6 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.Model.Entity; using Snap.Hutao.Web.Hoyolab.Annotation; using Snap.Hutao.Web.Hoyolab.DataSigning; -using Snap.Hutao.Web.Hoyolab.Hk4e.Sdk.Combo; using Snap.Hutao.Web.Request.Builder; using Snap.Hutao.Web.Request.Builder.Abstraction; using Snap.Hutao.Web.Response;