failed attempt: fight with device_fp

This commit is contained in:
DismissedLight
2023-12-14 22:47:17 +08:00
parent 414e0715a5
commit 9f793670fe
28 changed files with 255 additions and 210 deletions

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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
{

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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));
}

View File

@@ -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);

View 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;
}
}

View File

@@ -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)
{

View File

@@ -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;
}
}

View File

@@ -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)
{

View File

@@ -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)

View File

@@ -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" />

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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));
}
}
}
}

View 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);
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View 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;
}
}

View File

@@ -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;