mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
failed attempt: fight with device_fp
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
|
||||
@@ -3,18 +3,12 @@
|
||||
|
||||
namespace Snap.Hutao.Core.Abstraction;
|
||||
|
||||
/// <summary>
|
||||
/// 指示该类可以解构为元组
|
||||
/// </summary>
|
||||
/// <typeparam name="T1">元组的第一个类型</typeparam>
|
||||
/// <typeparam name="T2">元组的第二个类型</typeparam>
|
||||
[HighQuality]
|
||||
internal interface IDeconstruct<T1, T2>
|
||||
{
|
||||
/// <summary>
|
||||
/// 解构
|
||||
/// </summary>
|
||||
/// <param name="t1">第一个元素</param>
|
||||
/// <param name="t2">第二个元素</param>
|
||||
void Deconstruct(out T1 t1, out T2 t2);
|
||||
}
|
||||
|
||||
internal interface IDeconstruct<T1, T2, T3>
|
||||
{
|
||||
void Deconstruct(out T1 t1, out T2 t2, out T3 t3);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -23,13 +23,6 @@ internal static partial class EnumerableExtension
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加计数
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <typeparam name="TValue">值类型</typeparam>
|
||||
/// <param name="dict">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
public static void IncreaseOne<TKey, TValue>(this Dictionary<TKey, TValue> dict, TKey key)
|
||||
where TKey : notnull
|
||||
where TValue : struct, IIncrementOperators<TValue>
|
||||
@@ -37,14 +30,6 @@ internal static partial class EnumerableExtension
|
||||
++CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加计数
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <typeparam name="TValue">值类型</typeparam>
|
||||
/// <param name="dict">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">增加的值</param>
|
||||
public static void IncreaseValue<TKey, TValue>(this Dictionary<TKey, TValue> dict, TKey key, TValue value)
|
||||
where TKey : notnull
|
||||
where TValue : struct, IAdditionOperators<TValue, TValue, TValue>
|
||||
@@ -54,14 +39,6 @@ internal static partial class EnumerableExtension
|
||||
current += value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加计数
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <typeparam name="TValue">值类型</typeparam>
|
||||
/// <param name="dict">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <returns>是否存在键值</returns>
|
||||
public static bool TryIncreaseOne<TKey, TValue>(this Dictionary<TKey, TValue> dict, TKey key)
|
||||
where TKey : notnull
|
||||
where TValue : struct, IIncrementOperators<TValue>
|
||||
@@ -76,7 +53,6 @@ internal static partial class EnumerableExtension
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Enumerable.ToDictionary{TSource, TKey}(IEnumerable{TSource}, Func{TSource, TKey})"/>
|
||||
public static Dictionary<TKey, TSource> ToDictionaryIgnoringDuplicateKeys<TKey, TSource>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
|
||||
where TKey : notnull
|
||||
{
|
||||
@@ -90,7 +66,6 @@ internal static partial class EnumerableExtension
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Enumerable.ToDictionary{TSource, TKey, TElement}(IEnumerable{TSource}, Func{TSource, TKey}, Func{TSource, TElement})"/>
|
||||
public static Dictionary<TKey, TValue> ToDictionaryIgnoringDuplicateKeys<TKey, TValue, TSource>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TValue> elementSelector)
|
||||
where TKey : notnull
|
||||
{
|
||||
|
||||
@@ -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<T>(this IEnumerable<T> source, char separator, Action<StringBuilder, T> selector)
|
||||
{
|
||||
StringBuilder resultBuilder = new();
|
||||
|
||||
IEnumerator<T> 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<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> source, char separator, Action<StringBuilder, TKey, TValue> selector)
|
||||
{
|
||||
StringBuilder resultBuilder = new();
|
||||
|
||||
IEnumerator<KeyValuePair<TKey, TValue>> enumerator = source.GetEnumerator();
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
KeyValuePair<TKey, TValue> first = enumerator.Current;
|
||||
selector(resultBuilder, first.Key, first.Value);
|
||||
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
return resultBuilder.ToString();
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
resultBuilder.Append(separator);
|
||||
KeyValuePair<TKey, TValue> current = enumerator.Current;
|
||||
selector(resultBuilder, current.Key, current.Value);
|
||||
}
|
||||
while (enumerator.MoveNext());
|
||||
|
||||
return resultBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换到 <see cref="ObservableCollection{T}"/>
|
||||
/// </summary>
|
||||
@@ -64,7 +122,6 @@ internal static partial class EnumerableExtension
|
||||
/// <returns>Converted collection into string.</returns>
|
||||
public static string ToString<T>(this IEnumerable<T> collection, char separator)
|
||||
{
|
||||
string result = string.Join(separator, collection);
|
||||
return result.Length > 0 ? result : string.Empty;
|
||||
return string.Join(separator, collection);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<ValueResult<UserOptionResult, string>> TryCreateAndAddUserFromCookieAsync(Cookie cookie, bool isOversea);
|
||||
ValueTask<ValueResult<UserOptionResult, string>> TryCreateAndAddUserFromInputCookieAsync(InputCookie inputCookie);
|
||||
|
||||
bool TryGetUserByMid(string mid, [NotNullWhen(true)] out BindingUser? user);
|
||||
}
|
||||
@@ -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<ViewModel.User.User?> CreateUserFromCookieOrDefaultAsync(Cookie cookie, bool isOversea, CancellationToken token = default(CancellationToken));
|
||||
ValueTask<ViewModel.User.User?> CreateUserFromInputCookieOrDefaultAsync(InputCookie inputCookie, CancellationToken token = default(CancellationToken));
|
||||
|
||||
ValueTask<ViewModel.User.User> ResumeUserAsync(Model.Entity.User inner, CancellationToken token = default(CancellationToken));
|
||||
}
|
||||
@@ -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
|
||||
/// <returns>对应的角色信息</returns>
|
||||
UserGameRole? GetUserGameRoleByUid(string uid);
|
||||
|
||||
/// <summary>
|
||||
/// 尝试异步处理输入的Cookie
|
||||
/// </summary>
|
||||
/// <param name="cookie">Cookie</param>
|
||||
/// <param name="isOversea">是否为国际服</param>
|
||||
/// <returns>处理的结果</returns>
|
||||
ValueTask<ValueResult<UserOptionResult, string>> ProcessInputCookieAsync(Cookie cookie, bool isOversea);
|
||||
ValueTask<ValueResult<UserOptionResult, string>> ProcessInputCookieAsync(InputCookie inputCookie);
|
||||
|
||||
ValueTask<bool> RefreshCookieTokenAsync(Model.Entity.User user);
|
||||
|
||||
|
||||
48
src/Snap.Hutao/Snap.Hutao/Service/User/InputCookie.cs
Normal file
48
src/Snap.Hutao/Snap.Hutao/Service/User/InputCookie.cs
Normal file
@@ -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<Cookie, bool, string?>
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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<ValueResult<UserOptionResult, string>> TryCreateAndAddUserFromCookieAsync(Cookie cookie, bool isOversea)
|
||||
public async ValueTask<ValueResult<UserOptionResult, string>> 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)
|
||||
{
|
||||
|
||||
@@ -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<DeviceFpWrapper> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<ViewModel.User.User?> CreateUserFromCookieOrDefaultAsync(Cookie cookie, bool isOversea, CancellationToken token = default)
|
||||
public async ValueTask<ViewModel.User.User?> 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)
|
||||
{
|
||||
|
||||
@@ -56,20 +56,22 @@ internal sealed partial class UserService : IUserService, IUserServiceUnsafe
|
||||
return userCollectionService.GetUserGameRoleByUid(uid);
|
||||
}
|
||||
|
||||
public async ValueTask<ValueResult<UserOptionResult, string>> ProcessInputCookieAsync(Cookie cookie, bool isOversea)
|
||||
public async ValueTask<ValueResult<UserOptionResult, string>> 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<bool> RefreshCookieTokenAsync(Model.Entity.User user)
|
||||
|
||||
@@ -299,6 +299,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.1.1" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.8.8" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
|
||||
|
||||
@@ -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<IUserService>()
|
||||
.ProcessInputCookieAsync(cookie, isOversea)
|
||||
.ProcessInputCookieAsync(inputCookie)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
serviceProvider.GetRequiredService<INavigationService>().GoBack();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -55,7 +55,7 @@ internal sealed class User : ObservableObject, IEntityOnly<EntityUser>, 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<EntityUser>, IMapping
|
||||
messenger.Send(Message.UserChangedMessage.CreateOnlyRoleChanged(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserExtension.cs
Normal file
14
src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserExtension.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,19 +14,12 @@ internal sealed partial class Cookie
|
||||
{
|
||||
private readonly SortedDictionary<string, string> inner;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个空白的Cookie
|
||||
/// </summary>
|
||||
public Cookie()
|
||||
: this([])
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的Cookie
|
||||
/// </summary>
|
||||
/// <param name="dict">源</param>
|
||||
private Cookie(SortedDictionary<string, string> dict)
|
||||
public Cookie(SortedDictionary<string, string> dict)
|
||||
{
|
||||
inner = dict;
|
||||
}
|
||||
@@ -37,11 +30,6 @@ internal sealed partial class Cookie
|
||||
set => inner[key] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析Cookie字符串
|
||||
/// </summary>
|
||||
/// <param name="cookieString">cookie字符串</param>
|
||||
/// <returns>新的Cookie对象</returns>
|
||||
public static Cookie Parse(string cookieString)
|
||||
{
|
||||
SortedDictionary<string, string> cookieMap = [];
|
||||
@@ -69,11 +57,6 @@ internal sealed partial class Cookie
|
||||
return new(cookieMap);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从登录结果创建 Cookie
|
||||
/// </summary>
|
||||
/// <param name="loginResult">登录结果</param>
|
||||
/// <returns>Cookie</returns>
|
||||
public static Cookie FromLoginResult(LoginResult? loginResult)
|
||||
{
|
||||
if (loginResult is null)
|
||||
@@ -91,12 +74,6 @@ internal sealed partial class Cookie
|
||||
return new(cookieMap);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 SToken 创建 Cookie
|
||||
/// </summary>
|
||||
/// <param name="stuid">stuid</param>
|
||||
/// <param name="stoken">stoken</param>
|
||||
/// <returns>Cookie</returns>
|
||||
public static Cookie FromSToken(string stuid, string stoken)
|
||||
{
|
||||
SortedDictionary<string, string> 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;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Dictionary{TKey, TValue}.TryGetValue(TKey, out TValue)"/>
|
||||
public bool TryGetValue(string key, [NotNullWhen(true)] out string? value)
|
||||
{
|
||||
return inner.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取值
|
||||
/// </summary>
|
||||
/// <param name="key">键</param>
|
||||
/// <returns>值或默认值</returns>
|
||||
public string? GetValueOrDefault(string key)
|
||||
{
|
||||
return inner.GetValueOrDefault(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为Cookie的字符串表示
|
||||
/// </summary>
|
||||
/// <returns>Cookie的字符串表示</returns>
|
||||
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));
|
||||
}
|
||||
}
|
||||
68
src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/CookieExtension.cs
Normal file
68
src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/CookieExtension.cs
Normal file
@@ -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<string> keys, [NotNullWhen(true)] out Cookie? cookie)
|
||||
{
|
||||
Must.Range(keys.Length > 0, "Empty keys is not supported");
|
||||
SortedDictionary<string, string> 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user