mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
impl
This commit is contained in:
@@ -69,6 +69,8 @@ public class AppDbContext : DbContext
|
||||
/// <inheritdoc/>
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.ApplyConfiguration(new AvatarInfoConfiguration());
|
||||
modelBuilder
|
||||
.ApplyConfiguration(new AvatarInfoConfiguration())
|
||||
.ApplyConfiguration(new UserConfiguration());
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ internal class I18NExtension : MarkupExtension
|
||||
static I18NExtension()
|
||||
{
|
||||
string currentName = CultureInfo.CurrentUICulture.Name;
|
||||
Type languageType = EnumerableExtensions.GetValueOrDefault(TranslationMap, currentName, typeof(LanguagezhCN));
|
||||
Type languageType = TranslationMap.GetValueOrDefault(currentName, typeof(LanguagezhCN));
|
||||
Translation = (ITranslation)Activator.CreateInstance(languageType)!;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,83 @@ internal class DbCurrent<TEntity, TMessage>
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
|
||||
messenger.Send(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据库当前项
|
||||
/// 简化对数据库中选中项的管理
|
||||
/// </summary>
|
||||
/// <typeparam name="TObservable">绑定类型</typeparam>
|
||||
/// <typeparam name="TEntity">实体的类型</typeparam>
|
||||
/// <typeparam name="TMessage">消息的类型</typeparam>
|
||||
[SuppressMessage("", "SA1402")]
|
||||
internal class DbCurrent<TObservable, TEntity, TMessage>
|
||||
where TObservable : class
|
||||
where TEntity : class, ISelectable
|
||||
where TMessage : Message.ValueChangedMessage<TObservable>
|
||||
{
|
||||
private readonly DbContext dbContext;
|
||||
private readonly DbSet<TEntity> dbSet;
|
||||
private readonly IMessenger messenger;
|
||||
private readonly Func<TObservable, TEntity> selector;
|
||||
|
||||
private TObservable? current;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的数据库当前项
|
||||
/// </summary>
|
||||
/// <param name="dbContext">数据库上下文</param>
|
||||
/// <param name="dbSet">数据集</param>
|
||||
/// <param name="selector">选择器</param>
|
||||
/// <param name="messenger">消息器</param>
|
||||
public DbCurrent(DbContext dbContext, DbSet<TEntity> dbSet, Func<TObservable, TEntity> selector, IMessenger messenger)
|
||||
{
|
||||
this.dbContext = dbContext;
|
||||
this.dbSet = dbSet;
|
||||
this.selector = selector;
|
||||
this.messenger = messenger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前选中的项
|
||||
/// </summary>
|
||||
public TObservable? Current
|
||||
{
|
||||
get => current;
|
||||
set
|
||||
{
|
||||
// prevent useless sets
|
||||
if (current == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// only update when not processing a deletion
|
||||
if (value != null)
|
||||
{
|
||||
if (current != null)
|
||||
{
|
||||
TEntity entity = selector(current);
|
||||
entity.IsSelected = false;
|
||||
dbSet.Update(entity);
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
TMessage message = (TMessage)Activator.CreateInstance(typeof(TMessage), current, value)!;
|
||||
current = value;
|
||||
|
||||
if (current != null)
|
||||
{
|
||||
TEntity entity = selector(current);
|
||||
entity.IsSelected = true;
|
||||
dbSet.Update(entity);
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
|
||||
messenger.Send(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,4 +25,16 @@ internal static class Clipboard
|
||||
string json = await view.GetTextAsync();
|
||||
return JsonSerializer.Deserialize<T>(json, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置文本
|
||||
/// </summary>
|
||||
/// <param name="text">文本</param>
|
||||
public static void SetText(string text)
|
||||
{
|
||||
DataPackage content = new();
|
||||
content.SetText(text);
|
||||
Windows.ApplicationModel.DataTransfer.Clipboard.SetContent(content);
|
||||
Windows.ApplicationModel.DataTransfer.Clipboard.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
@@ -99,23 +100,35 @@ public static partial class EnumerableExtensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取值或默认值
|
||||
/// 增加计数
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <typeparam name="TValue">值类型</typeparam>
|
||||
/// <param name="dictionary">字典</param>
|
||||
/// <param name="dict">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>结果值</returns>
|
||||
public static TValue? GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue? defaultValue = default)
|
||||
public static void Increase<TKey>(this Dictionary<TKey, int> dict, TKey key)
|
||||
where TKey : notnull
|
||||
{
|
||||
if (dictionary.TryGetValue(key, out TValue? value))
|
||||
++CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加计数
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <param name="dict">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <returns>是否存在键值</returns>
|
||||
public static bool TryIncrease<TKey>(this Dictionary<TKey, int> dict, TKey key)
|
||||
where TKey : notnull
|
||||
{
|
||||
ref int value = ref CollectionsMarshal.GetValueRefOrNullRef(dict, key);
|
||||
if (!Unsafe.IsNullRef(ref value))
|
||||
{
|
||||
return value;
|
||||
++value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Bbs.User;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Auth;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using EntityUser = Snap.Hutao.Model.Entity.User;
|
||||
|
||||
@@ -56,7 +55,7 @@ public class User : Observable
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntityUser.Cookie"/>
|
||||
public string? Cookie
|
||||
public Cookie Cookie
|
||||
{
|
||||
get => inner.Cookie;
|
||||
set => inner.Cookie = value;
|
||||
@@ -72,27 +71,6 @@ public class User : Observable
|
||||
/// </summary>
|
||||
public bool IsInitialized { get => isInitialized; }
|
||||
|
||||
/// <summary>
|
||||
/// 将cookie的字符串形式转换为字典
|
||||
/// </summary>
|
||||
/// <param name="cookie">cookie的字符串形式</param>
|
||||
/// <returns>包含cookie信息的字典</returns>
|
||||
public static IDictionary<string, string> MapCookie(string cookie)
|
||||
{
|
||||
SortedDictionary<string, string> cookieDictionary = new();
|
||||
|
||||
string[] values = cookie.TrimEnd(';').Split(';');
|
||||
foreach (string[] parts in values.Select(c => c.Split('=', 2)))
|
||||
{
|
||||
string cookieName = parts[0].Trim();
|
||||
string cookieValue = parts.Length == 1 ? string.Empty : parts[1].Trim();
|
||||
|
||||
cookieDictionary.Add(cookieName, cookieValue);
|
||||
}
|
||||
|
||||
return cookieDictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库恢复用户
|
||||
/// </summary>
|
||||
@@ -108,114 +86,30 @@ public class User : Observable
|
||||
CancellationToken token = default)
|
||||
{
|
||||
User user = new(inner);
|
||||
bool successful = await user.ResumeInternalAsync(userClient, userGameRoleClient, token).ConfigureAwait(false);
|
||||
bool successful = await user.InitializeCoreAsync(userClient, userGameRoleClient, token).ConfigureAwait(false);
|
||||
return successful ? user : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化用户
|
||||
/// 创建并初始化用户
|
||||
/// </summary>
|
||||
/// <param name="cookie">cookie</param>
|
||||
/// <param name="userClient">用户客户端</param>
|
||||
/// <param name="userGameRoleClient">角色客户端</param>
|
||||
/// <param name="authClient">授权客户端</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>用户是否初始化完成,若Cookie失效会返回 <see langword="false"/> </returns>
|
||||
/// <returns>用户是否初始化完成,若Cookie失效会返回 <see langword="null"/> </returns>
|
||||
internal static async Task<User?> CreateAsync(
|
||||
IDictionary<string, string> cookie,
|
||||
Cookie cookie,
|
||||
UserClient userClient,
|
||||
BindingClient userGameRoleClient,
|
||||
AuthClient authClient,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
string simplifiedCookie = ToCookieString(cookie);
|
||||
EntityUser inner = EntityUser.Create(simplifiedCookie);
|
||||
User user = new(inner);
|
||||
bool successful = await user.CreateInternalAsync(cookie, userClient, userGameRoleClient, authClient, token).ConfigureAwait(false);
|
||||
User user = new(EntityUser.Create(cookie));
|
||||
bool successful = await user.InitializeCoreAsync(userClient, userGameRoleClient, token).ConfigureAwait(false);
|
||||
return successful ? user : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试升级到Stoken
|
||||
/// </summary>
|
||||
/// <param name="addition">额外的token</param>
|
||||
/// <param name="authClient">验证客户端</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>是否升级成功</returns>
|
||||
internal async Task<bool> TryUpgradeByLoginTicketAsync(IDictionary<string, string> addition, AuthClient authClient, CancellationToken token)
|
||||
{
|
||||
IDictionary<string, string> cookie = MapCookie(Cookie!);
|
||||
if (addition.TryGetValue(CookieKeys.LOGIN_TICKET, out string? loginTicket))
|
||||
{
|
||||
cookie[CookieKeys.LOGIN_TICKET] = loginTicket;
|
||||
}
|
||||
|
||||
if (addition.TryGetValue(CookieKeys.LOGIN_UID, out string? loginUid))
|
||||
{
|
||||
cookie[CookieKeys.LOGIN_UID] = loginUid;
|
||||
}
|
||||
|
||||
bool result = await TryRequestStokenAndAddToCookieAsync(cookie, authClient, token).ConfigureAwait(false);
|
||||
|
||||
if (result)
|
||||
{
|
||||
Cookie = ToCookieString(cookie);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加 Stoken
|
||||
/// </summary>
|
||||
/// <param name="addition">额外的cookie</param>
|
||||
internal void AddStoken(IDictionary<string, string> addition)
|
||||
{
|
||||
IDictionary<string, string> cookie = MapCookie(Cookie!);
|
||||
|
||||
if (addition.TryGetValue(CookieKeys.STOKEN, out string? stoken))
|
||||
{
|
||||
cookie[CookieKeys.STOKEN] = stoken;
|
||||
}
|
||||
|
||||
if (addition.TryGetValue(CookieKeys.STUID, out string? stuid))
|
||||
{
|
||||
cookie[CookieKeys.STUID] = stuid;
|
||||
}
|
||||
}
|
||||
|
||||
private static string ToCookieString(IDictionary<string, string> cookie)
|
||||
{
|
||||
return string.Join(';', cookie.Select(kvp => $"{kvp.Key}={kvp.Value}"));
|
||||
}
|
||||
|
||||
private static async Task<bool> TryRequestStokenAndAddToCookieAsync(IDictionary<string, string> cookie, AuthClient authClient, CancellationToken token)
|
||||
{
|
||||
if (cookie.TryGetValue(CookieKeys.LOGIN_TICKET, out string? loginTicket))
|
||||
{
|
||||
string? loginUid = cookie.GetValueOrDefault(CookieKeys.LOGIN_UID) ?? cookie.GetValueOrDefault(CookieKeys.LTUID);
|
||||
|
||||
if (loginUid != null)
|
||||
{
|
||||
Dictionary<string, string> stokens = await authClient
|
||||
.GetMultiTokenByLoginTicketAsync(loginTicket, loginUid, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (stokens.TryGetValue(CookieKeys.STOKEN, out string? stoken) && stokens.TryGetValue(CookieKeys.LTOKEN, out string? ltoken))
|
||||
{
|
||||
cookie[CookieKeys.STOKEN] = stoken;
|
||||
cookie[CookieKeys.LTOKEN] = ltoken;
|
||||
cookie[CookieKeys.STUID] = cookie[CookieKeys.LTUID];
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<bool> ResumeInternalAsync(
|
||||
private async Task<bool> InitializeCoreAsync(
|
||||
UserClient userClient,
|
||||
BindingClient userGameRoleClient,
|
||||
CancellationToken token = default)
|
||||
@@ -225,38 +119,14 @@ public class User : Observable
|
||||
return true;
|
||||
}
|
||||
|
||||
await PrepareUserInfoAndUserGameRolesAsync(userClient, userGameRoleClient, token).ConfigureAwait(false);
|
||||
await InitializeUserInfoAndUserGameRolesAsync(userClient, userGameRoleClient, token).ConfigureAwait(false);
|
||||
|
||||
isInitialized = true;
|
||||
|
||||
return UserInfo != null && UserGameRoles.Any();
|
||||
}
|
||||
|
||||
private async Task<bool> CreateInternalAsync(
|
||||
IDictionary<string, string> cookie,
|
||||
UserClient userClient,
|
||||
BindingClient userGameRoleClient,
|
||||
AuthClient authClient,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
if (isInitialized)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (await TryRequestStokenAndAddToCookieAsync(cookie, authClient, token).ConfigureAwait(false))
|
||||
{
|
||||
Cookie = ToCookieString(cookie);
|
||||
}
|
||||
|
||||
await PrepareUserInfoAndUserGameRolesAsync(userClient, userGameRoleClient, token).ConfigureAwait(false);
|
||||
|
||||
isInitialized = true;
|
||||
|
||||
return UserInfo != null && UserGameRoles.Any();
|
||||
}
|
||||
|
||||
private async Task PrepareUserInfoAndUserGameRolesAsync(UserClient userClient, BindingClient userGameRoleClient, CancellationToken token)
|
||||
private async Task InitializeUserInfoAndUserGameRolesAsync(UserClient userClient, BindingClient userGameRoleClient, CancellationToken token)
|
||||
{
|
||||
UserInfo = await userClient
|
||||
.GetUserFullInfoAsync(this, token)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
namespace Snap.Hutao.Model.Entity.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// 用户配置
|
||||
/// </summary>
|
||||
internal class UserConfiguration : IEntityTypeConfiguration<User>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public void Configure(EntityTypeBuilder<User> builder)
|
||||
{
|
||||
builder.Property(e => e.Cookie)
|
||||
.HasColumnType("TEXT")
|
||||
.HasConversion(
|
||||
e => e == null ? string.Empty : e.ToString(),
|
||||
e => Cookie.Parse(e));
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
@@ -28,14 +29,14 @@ public class User : ISelectable
|
||||
/// <summary>
|
||||
/// 用户的Cookie
|
||||
/// </summary>
|
||||
public string? Cookie { get; set; }
|
||||
public Cookie Cookie { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的用户
|
||||
/// </summary>
|
||||
/// <param name="cookie">cookie</param>
|
||||
/// <returns>新创建的用户</returns>
|
||||
public static User Create(string cookie)
|
||||
public static User Create(Cookie cookie)
|
||||
{
|
||||
return new() { Cookie = cookie };
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ public enum WeaponType
|
||||
WEAPON_SWORD_ONE_HAND = 1,
|
||||
|
||||
#region Not Used
|
||||
|
||||
/// <summary>
|
||||
/// ?
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Binding.Gacha;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Abstraction;
|
||||
|
||||
/// <summary>
|
||||
/// 指示该类为简述统计物品的源
|
||||
/// </summary>
|
||||
public interface ISummaryItemSource
|
||||
{
|
||||
/// <summary>
|
||||
/// 星级
|
||||
/// </summary>
|
||||
ItemQuality Quality { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 转换到简述统计物品
|
||||
/// </summary>
|
||||
/// <param name="lastPull">距上个五星</param>
|
||||
/// <param name="time">时间</param>
|
||||
/// <param name="isUp">是否为Up物品</param>
|
||||
/// <returns>简述统计物品</returns>
|
||||
SummaryItem ToSummaryItem(int lastPull, DateTimeOffset time, bool isUp);
|
||||
}
|
||||
@@ -12,7 +12,7 @@ namespace Snap.Hutao.Model.Metadata.Avatar;
|
||||
/// <summary>
|
||||
/// 角色
|
||||
/// </summary>
|
||||
public class Avatar : IStatisticsItemSource, INameQuality
|
||||
public class Avatar : IStatisticsItemSource, ISummaryItemSource, INameQuality
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Snap.Hutao.Model.Metadata.Weapon;
|
||||
/// <summary>
|
||||
/// 武器
|
||||
/// </summary>
|
||||
public class Weapon : IStatisticsItemSource, INameQuality
|
||||
public class Weapon : IStatisticsItemSource, ISummaryItemSource, INameQuality
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity
|
||||
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
|
||||
Publisher="CN=DGP Studio"
|
||||
Version="1.1.4.0" />
|
||||
Version="1.1.5.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>胡桃</DisplayName>
|
||||
|
||||
@@ -5,8 +5,9 @@ using Snap.Hutao.Model.Binding.Gacha;
|
||||
using Snap.Hutao.Model.Metadata.Abstraction;
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using Snap.Hutao.Model.Metadata.Weapon;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Snap.Hutao.Service.GachaLog.Factory;
|
||||
|
||||
@@ -34,35 +35,28 @@ public static class GachaStatisticsExtensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加计数
|
||||
/// 完成添加
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <param name="dict">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
public static void Increase<TKey>(this Dictionary<TKey, int> dict, TKey key)
|
||||
where TKey : notnull
|
||||
/// <param name="summaryItems">简述物品列表</param>
|
||||
public static void CompleteAdding(this List<SummaryItem> summaryItems)
|
||||
{
|
||||
++CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _);
|
||||
}
|
||||
// we can't trust first item's prev state.
|
||||
bool isPreviousUp = true;
|
||||
|
||||
/// <summary>
|
||||
/// 增加计数
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <param name="dict">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <returns>是否存在键值</returns>
|
||||
public static bool TryIncrease<TKey>(this Dictionary<TKey, int> dict, TKey key)
|
||||
where TKey : notnull
|
||||
{
|
||||
ref int value = ref CollectionsMarshal.GetValueRefOrNullRef(dict, key);
|
||||
if (!Unsafe.IsNullRef(ref value))
|
||||
// mark the IsGuarentee
|
||||
foreach (SummaryItem item in summaryItems)
|
||||
{
|
||||
++value;
|
||||
return true;
|
||||
if (item.IsUp && (!isPreviousUp))
|
||||
{
|
||||
item.IsGuarentee = true;
|
||||
}
|
||||
|
||||
isPreviousUp = item.IsUp;
|
||||
item.Color = GetColorByName(item.Name);
|
||||
}
|
||||
|
||||
return false;
|
||||
// reverse items
|
||||
summaryItems.Reverse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -103,4 +97,14 @@ public static class GachaStatisticsExtensions
|
||||
.OrderByDescending(item => item.Count)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static Color GetColorByName(string name)
|
||||
{
|
||||
byte[] codes = MD5.HashData(Encoding.UTF8.GetBytes(name));
|
||||
Span<byte> first = new(codes, 0, 5);
|
||||
Span<byte> second = new(codes, 5, 5);
|
||||
Span<byte> third = new(codes, 10, 5);
|
||||
Color color = Color.FromArgb(255, first.Average(), second.Average(), third.Average());
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,9 +89,9 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory
|
||||
break;
|
||||
}
|
||||
|
||||
permanentWishBuilder.TrackAvatar(item, avatar, isUp);
|
||||
avatarWishBuilder.TrackAvatar(item, avatar, isUp);
|
||||
weaponWishBuilder.TrackAvatar(item, avatar, isUp);
|
||||
permanentWishBuilder.Track(item, avatar, isUp);
|
||||
avatarWishBuilder.Track(item, avatar, isUp);
|
||||
weaponWishBuilder.Track(item, avatar, isUp);
|
||||
}
|
||||
|
||||
// It's a weapon
|
||||
@@ -116,9 +116,9 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory
|
||||
break;
|
||||
}
|
||||
|
||||
permanentWishBuilder.TrackWeapon(item, weapon, isUp);
|
||||
avatarWishBuilder.TrackWeapon(item, weapon, isUp);
|
||||
weaponWishBuilder.TrackWeapon(item, weapon, isUp);
|
||||
permanentWishBuilder.Track(item, weapon, isUp);
|
||||
avatarWishBuilder.Track(item, weapon, isUp);
|
||||
weaponWishBuilder.Track(item, weapon, isUp);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Model.Binding.Gacha;
|
||||
using Snap.Hutao.Model.Metadata;
|
||||
using Snap.Hutao.Model.Metadata.Abstraction;
|
||||
|
||||
@@ -5,12 +5,8 @@ using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Model.Binding.Gacha;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using Snap.Hutao.Model.Metadata.Weapon;
|
||||
using Snap.Hutao.Model.Metadata.Abstraction;
|
||||
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Snap.Hutao.Service.GachaLog.Factory;
|
||||
|
||||
@@ -75,9 +71,9 @@ internal class TypedWishSummaryBuilder
|
||||
/// 追踪物品
|
||||
/// </summary>
|
||||
/// <param name="item">祈愿物品</param>
|
||||
/// <param name="avatar">对应角色</param>
|
||||
/// <param name="source">对应武器</param>
|
||||
/// <param name="isUp">是否为Up物品</param>
|
||||
public void TrackAvatar(GachaItem item, Avatar avatar, bool isUp)
|
||||
public void Track(GachaItem item, ISummaryItemSource source, bool isUp)
|
||||
{
|
||||
if (typeEvaluator(item.GachaType))
|
||||
{
|
||||
@@ -89,7 +85,7 @@ internal class TypedWishSummaryBuilder
|
||||
++totalCountTracker;
|
||||
TrackFromToTime(item.Time);
|
||||
|
||||
switch (avatar.Quality)
|
||||
switch (source.Quality)
|
||||
{
|
||||
case ItemQuality.QUALITY_ORANGE:
|
||||
{
|
||||
@@ -102,55 +98,7 @@ internal class TypedWishSummaryBuilder
|
||||
lastUpOrangePullTracker = 0;
|
||||
}
|
||||
|
||||
summaryItemCache.Add(avatar.ToSummaryItem(lastOrangePullTracker, item.Time, isUp));
|
||||
|
||||
lastOrangePullTracker = 0;
|
||||
++totalOrangePullTracker;
|
||||
break;
|
||||
}
|
||||
|
||||
case ItemQuality.QUALITY_PURPLE:
|
||||
{
|
||||
lastPurplePullTracker = 0;
|
||||
++totalPurplePullTracker;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 追踪物品
|
||||
/// </summary>
|
||||
/// <param name="item">祈愿物品</param>
|
||||
/// <param name="weapon">对应武器</param>
|
||||
/// <param name="isUp">是否为Up物品</param>
|
||||
public void TrackWeapon(GachaItem item, Weapon weapon, bool isUp)
|
||||
{
|
||||
if (typeEvaluator(item.GachaType))
|
||||
{
|
||||
++lastOrangePullTracker;
|
||||
++lastPurplePullTracker;
|
||||
++lastUpOrangePullTracker;
|
||||
|
||||
// track total pulls
|
||||
++totalCountTracker;
|
||||
TrackFromToTime(item.Time);
|
||||
|
||||
switch (weapon.RankLevel)
|
||||
{
|
||||
case ItemQuality.QUALITY_ORANGE:
|
||||
{
|
||||
TrackMinMaxOrangePull(lastOrangePullTracker);
|
||||
averageOrangePullTracker.Add(lastOrangePullTracker);
|
||||
|
||||
if (isUp)
|
||||
{
|
||||
averageUpOrangePullTracker.Add(lastUpOrangePullTracker);
|
||||
lastUpOrangePullTracker = 0;
|
||||
}
|
||||
|
||||
summaryItemCache.Add(weapon.ToSummaryItem(lastOrangePullTracker, item.Time, isUp));
|
||||
summaryItemCache.Add(source.ToSummaryItem(lastOrangePullTracker, item.Time, isUp));
|
||||
|
||||
lastOrangePullTracker = 0;
|
||||
++totalOrangePullTracker;
|
||||
@@ -179,7 +127,7 @@ internal class TypedWishSummaryBuilder
|
||||
/// <returns>类型化祈愿统计信息</returns>
|
||||
public TypedWishSummary ToTypedWishSummary()
|
||||
{
|
||||
CompleteSummaryItems(summaryItemCache);
|
||||
summaryItemCache.CompleteAdding();
|
||||
double totalCountDouble = totalCountTracker;
|
||||
|
||||
return new()
|
||||
@@ -209,37 +157,6 @@ internal class TypedWishSummaryBuilder
|
||||
};
|
||||
}
|
||||
|
||||
private static void CompleteSummaryItems(List<SummaryItem> summaryItems)
|
||||
{
|
||||
// we can't trust first item's prev state.
|
||||
bool isPreviousUp = true;
|
||||
|
||||
// mark the IsGuarentee
|
||||
foreach (SummaryItem item in summaryItems)
|
||||
{
|
||||
if (item.IsUp && (!isPreviousUp))
|
||||
{
|
||||
item.IsGuarentee = true;
|
||||
}
|
||||
|
||||
isPreviousUp = item.IsUp;
|
||||
item.Color = GetColorByName(item.Name);
|
||||
}
|
||||
|
||||
// reverse items
|
||||
summaryItems.Reverse();
|
||||
}
|
||||
|
||||
private static Color GetColorByName(string name)
|
||||
{
|
||||
byte[] codes = MD5.HashData(Encoding.UTF8.GetBytes(name));
|
||||
Span<byte> first = new(codes, 0, 5);
|
||||
Span<byte> second = new(codes, 5, 5);
|
||||
Span<byte> third = new(codes, 10, 5);
|
||||
Color color = Color.FromArgb(255, first.Average()/*.HalfRange()*/, second.Average()/*.HalfRange()*/, third.Average()/*.HalfRange()*/);
|
||||
return color;
|
||||
}
|
||||
|
||||
private void TrackMinMaxOrangePull(int lastOrangePull)
|
||||
{
|
||||
if (lastOrangePull < minOrangePullTracker || minOrangePullTracker == 0)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.User;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
@@ -35,10 +35,10 @@ internal class GachaLogUrlStokenProvider : IGachaLogUrlProvider
|
||||
/// <inheritdoc/>
|
||||
public async Task<ValueResult<bool, string>> GetQueryAsync()
|
||||
{
|
||||
Model.Binding.User? user = userService.CurrentUser;
|
||||
Model.Binding.User? user = userService.Current;
|
||||
if (user != null)
|
||||
{
|
||||
if (user.Cookie!.Contains(CookieKeys.STOKEN) && user.SelectedUserGameRole != null)
|
||||
if (user.Cookie!.ContainsSToken() && user.SelectedUserGameRole != null)
|
||||
{
|
||||
PlayerUid uid = (PlayerUid)user.SelectedUserGameRole;
|
||||
GenAuthKeyData data = GenAuthKeyData.CreateForWebViewGacha(uid);
|
||||
@@ -51,6 +51,6 @@ internal class GachaLogUrlStokenProvider : IGachaLogUrlProvider
|
||||
}
|
||||
}
|
||||
|
||||
return new(false, null!);
|
||||
return new(false, "当前用户的Cookie不包含 Stoken");
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Automation.Provider;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Model.Binding;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using System.Collections.ObjectModel;
|
||||
using BindingUser = Snap.Hutao.Model.Binding.User;
|
||||
|
||||
namespace Snap.Hutao.Service.Abstraction;
|
||||
namespace Snap.Hutao.Service.User;
|
||||
|
||||
/// <summary>
|
||||
/// 用户服务
|
||||
@@ -16,52 +16,28 @@ public interface IUserService
|
||||
/// <summary>
|
||||
/// 获取或设置当前用户
|
||||
/// </summary>
|
||||
User? CurrentUser { get; set; }
|
||||
BindingUser? Current { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化用户服务及所有用户
|
||||
/// 异步获取同步的用户信息集合
|
||||
/// 对集合的操作应通过服务抽象完成
|
||||
/// 此操作不能取消
|
||||
/// </summary>
|
||||
/// <returns>准备完成的用户信息枚举</returns>
|
||||
Task<ObservableCollection<User>> GetUserCollectionAsync();
|
||||
/// <returns>准备完成的用户信息集合</returns>
|
||||
Task<ObservableCollection<BindingUser>> GetUserCollectionAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 异步添加用户
|
||||
/// 通常用户是未初始化的
|
||||
/// 尝试异步处理输入的Cookie
|
||||
/// </summary>
|
||||
/// <param name="user">待添加的用户</param>
|
||||
/// <param name="uid">用户的米游社UID,用于检查是否包含重复的用户</param>
|
||||
/// <returns>用户初始化是否成功</returns>
|
||||
Task<UserAddResult> TryAddUserAsync(User user, string uid);
|
||||
|
||||
/// <summary>
|
||||
/// 尝试使用 login_ticket 升级用户
|
||||
/// </summary>
|
||||
/// <param name="addiition">额外的Cookie</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>是否升级成功</returns>
|
||||
Task<ValueResult<bool, string>> TryUpgradeUserByLoginTicketAsync(IDictionary<string, string> addiition, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 尝试使用 Stoken 升级用户
|
||||
/// </summary>
|
||||
/// <param name="stoken">stoken</param>
|
||||
/// <returns>是否升级成功</returns>
|
||||
Task<ValueResult<bool, string>> TryUpgradeUserByStokenAsync(IDictionary<string, string> stoken);
|
||||
/// <param name="cookie">Cookie</param>
|
||||
/// <returns>处理的结果</returns>
|
||||
Task<ValueResult<UserOptionResult, string>> ProcessInputCookieAsync(Cookie cookie);
|
||||
|
||||
/// <summary>
|
||||
/// 异步移除用户
|
||||
/// </summary>
|
||||
/// <param name="user">待移除的用户</param>
|
||||
/// <returns>任务</returns>
|
||||
Task RemoveUserAsync(User user);
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的绑定用户
|
||||
/// 若存在 login_ticket 与 login_uid 则 自动获取 stoken
|
||||
/// </summary>
|
||||
/// <param name="cookie">cookie的字符串形式</param>
|
||||
/// <returns>新的绑定用户</returns>
|
||||
Task<User?> CreateUserAsync(IDictionary<string, string> cookie);
|
||||
}
|
||||
Task RemoveUserAsync(BindingUser user);
|
||||
}
|
||||
34
src/Snap.Hutao/Snap.Hutao/Service/User/UserHelper.cs
Normal file
34
src/Snap.Hutao/Snap.Hutao/Service/User/UserHelper.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Snap.Hutao.Context.Database;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Bbs.User;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Auth;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using System.Collections.ObjectModel;
|
||||
using BindingUser = Snap.Hutao.Model.Binding.User;
|
||||
|
||||
namespace Snap.Hutao.Service.User;
|
||||
|
||||
/// <summary>
|
||||
/// 用户帮助类
|
||||
/// </summary>
|
||||
internal static class UserHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 尝试获取用户
|
||||
/// </summary>
|
||||
/// <param name="users">待查找的用户集合</param>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="user">用户</param>
|
||||
/// <returns>是否存在用户</returns>
|
||||
public static bool TryGetUserByUid(ObservableCollection<BindingUser> users, string uid, [NotNullWhen(true)] out BindingUser? user)
|
||||
{
|
||||
user = users.SingleOrDefault(u => u.UserInfo!.Uid == uid);
|
||||
|
||||
return user != null;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,35 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.Abstraction;
|
||||
namespace Snap.Hutao.Service.User;
|
||||
|
||||
/// <summary>
|
||||
/// 用户添加操作结果
|
||||
/// </summary>
|
||||
public enum UserAddResult
|
||||
public enum UserOptionResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加成功
|
||||
/// </summary>
|
||||
Added,
|
||||
|
||||
/// <summary>
|
||||
/// Cookie不完整
|
||||
/// </summary>
|
||||
Incomplete,
|
||||
|
||||
/// <summary>
|
||||
/// Cookie信息已经失效
|
||||
/// </summary>
|
||||
Invalid,
|
||||
|
||||
/// <summary>
|
||||
/// 用户的Cookie成功更新
|
||||
/// </summary>
|
||||
Updated,
|
||||
|
||||
/// <summary>
|
||||
/// 已经存在该用户
|
||||
/// 升级到Stoken
|
||||
/// </summary>
|
||||
AlreadyExists,
|
||||
Upgraded,
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Snap.Hutao.Context.Database;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Bbs.User;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Auth;
|
||||
@@ -12,7 +11,7 @@ using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using System.Collections.ObjectModel;
|
||||
using BindingUser = Snap.Hutao.Model.Binding.User;
|
||||
|
||||
namespace Snap.Hutao.Service;
|
||||
namespace Snap.Hutao.Service.User;
|
||||
|
||||
/// <summary>
|
||||
/// 用户服务
|
||||
@@ -23,7 +22,7 @@ internal class UserService : IUserService
|
||||
{
|
||||
private readonly AppDbContext appDbContext;
|
||||
private readonly UserClient userClient;
|
||||
private readonly BindingClient userGameRoleClient;
|
||||
private readonly BindingClient bindingClient;
|
||||
private readonly AuthClient authClient;
|
||||
private readonly IMessenger messenger;
|
||||
|
||||
@@ -35,25 +34,25 @@ internal class UserService : IUserService
|
||||
/// </summary>
|
||||
/// <param name="appDbContext">应用程序数据库上下文</param>
|
||||
/// <param name="userClient">用户客户端</param>
|
||||
/// <param name="userGameRoleClient">角色客户端</param>
|
||||
/// <param name="bindingClient">角色客户端</param>
|
||||
/// <param name="authClient">验证客户端</param>
|
||||
/// <param name="messenger">消息器</param>
|
||||
public UserService(
|
||||
AppDbContext appDbContext,
|
||||
UserClient userClient,
|
||||
BindingClient userGameRoleClient,
|
||||
BindingClient bindingClient,
|
||||
AuthClient authClient,
|
||||
IMessenger messenger)
|
||||
{
|
||||
this.appDbContext = appDbContext;
|
||||
this.userClient = userClient;
|
||||
this.userGameRoleClient = userGameRoleClient;
|
||||
this.bindingClient = bindingClient;
|
||||
this.authClient = authClient;
|
||||
this.messenger = messenger;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public BindingUser? CurrentUser
|
||||
public BindingUser? Current
|
||||
{
|
||||
get => currentUser;
|
||||
set
|
||||
@@ -90,45 +89,6 @@ internal class UserService : IUserService
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<UserAddResult> TryAddUserAsync(BindingUser newUser, string uid)
|
||||
{
|
||||
Must.NotNull(userCollection!);
|
||||
|
||||
// 查找是否有相同的uid
|
||||
if (userCollection.SingleOrDefault(u => u.UserInfo!.Uid == uid) is BindingUser userWithSameUid)
|
||||
{
|
||||
// Prevent users from adding a completely same cookie.
|
||||
if (userWithSameUid.Cookie == newUser.Cookie)
|
||||
{
|
||||
return UserAddResult.AlreadyExists;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update user cookie here.
|
||||
userWithSameUid.Cookie = newUser.Cookie;
|
||||
appDbContext.Users.Update(userWithSameUid.Entity);
|
||||
await appDbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
return UserAddResult.Updated;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Verify.Operation(newUser.IsInitialized, "该用户尚未初始化");
|
||||
|
||||
// Sync cache
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
userCollection.Add(newUser);
|
||||
|
||||
// Sync database
|
||||
appDbContext.Users.Add(newUser.Entity);
|
||||
await appDbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
return UserAddResult.Added;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task RemoveUserAsync(BindingUser user)
|
||||
{
|
||||
@@ -152,7 +112,7 @@ internal class UserService : IUserService
|
||||
foreach (Model.Entity.User entity in appDbContext.Users)
|
||||
{
|
||||
BindingUser? initialized = await BindingUser
|
||||
.ResumeAsync(entity, userClient, userGameRoleClient)
|
||||
.ResumeAsync(entity, userClient, bindingClient)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (initialized != null)
|
||||
@@ -168,58 +128,94 @@ internal class UserService : IUserService
|
||||
}
|
||||
|
||||
userCollection = new(users);
|
||||
CurrentUser = users.SingleOrDefault(user => user.IsSelected);
|
||||
Current = users.SingleOrDefault(user => user.IsSelected);
|
||||
}
|
||||
|
||||
return userCollection;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<BindingUser?> CreateUserAsync(IDictionary<string, string> cookie)
|
||||
{
|
||||
return BindingUser.CreateAsync(cookie, userClient, userGameRoleClient, authClient);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ValueResult<bool, string>> TryUpgradeUserByLoginTicketAsync(IDictionary<string, string> addition, CancellationToken token = default)
|
||||
public async Task<ValueResult<UserOptionResult, string>> ProcessInputCookieAsync(Cookie cookie)
|
||||
{
|
||||
Must.NotNull(userCollection!);
|
||||
if (addition.TryGetValue(CookieKeys.LOGIN_UID, out string? uid))
|
||||
|
||||
// 检查 uid 是否存在
|
||||
if (cookie.TryGetUid(out string? uid))
|
||||
{
|
||||
// 查找是否有相同的uid
|
||||
if (userCollection.SingleOrDefault(u => u.UserInfo!.Uid == uid) is BindingUser userWithSameUid)
|
||||
// 检查 login ticket 是否存在
|
||||
// 若存在则尝试升级至 stoken
|
||||
await TryAddMultiTokenAsync(cookie, uid).ConfigureAwait(false);
|
||||
|
||||
// 检查 uid 对应用户是否存在
|
||||
if (UserHelper.TryGetUserByUid(userCollection, uid, out BindingUser? userWithSameUid))
|
||||
{
|
||||
// Update user cookie here.
|
||||
if (await userWithSameUid.TryUpgradeByLoginTicketAsync(addition, authClient, token))
|
||||
// 检查 stoken 是否存在
|
||||
if (cookie.ContainsSToken())
|
||||
{
|
||||
appDbContext.Users.Update(userWithSameUid.Entity);
|
||||
await appDbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
return new(true, userWithSameUid.UserInfo?.Nickname ?? string.Empty);
|
||||
// insert stoken directly
|
||||
userWithSameUid.Cookie!.InsertSToken(uid, cookie);
|
||||
return new(UserOptionResult.Upgraded, uid);
|
||||
}
|
||||
|
||||
if (cookie.ContainsLTokenAndCookieToken())
|
||||
{
|
||||
UpdateUserCookie(cookie, userWithSameUid);
|
||||
return new(UserOptionResult.Updated, uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new(false, string.Empty);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ValueResult<bool, string>> TryUpgradeUserByStokenAsync(IDictionary<string, string> stoken)
|
||||
{
|
||||
Must.NotNull(userCollection!);
|
||||
if (stoken.TryGetValue(CookieKeys.STUID, out string? uid))
|
||||
{
|
||||
// 查找是否有相同的uid
|
||||
if (userCollection.SingleOrDefault(u => u.UserInfo!.Uid == uid) is BindingUser userWithSameUid)
|
||||
else if (cookie.ContainsLTokenAndCookieToken())
|
||||
{
|
||||
// Update user cookie here.
|
||||
userWithSameUid.AddStoken(stoken);
|
||||
|
||||
appDbContext.Users.Update(userWithSameUid.Entity);
|
||||
await appDbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
return new(true, userWithSameUid.UserInfo?.Nickname ?? string.Empty);
|
||||
return await TryCreateUserAndAddAsync(userCollection, cookie).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return new(false, string.Empty);
|
||||
return new(UserOptionResult.Incomplete, null!);
|
||||
}
|
||||
|
||||
private async Task TryAddMultiTokenAsync(Cookie cookie, string uid)
|
||||
{
|
||||
if (cookie.TryGetLoginTicket(out string? loginTicket))
|
||||
{
|
||||
// get multitoken
|
||||
Dictionary<string, string> multiToken = await authClient
|
||||
.GetMultiTokenByLoginTicketAsync(loginTicket, uid, default)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (multiToken.Count >= 2)
|
||||
{
|
||||
cookie.InsertMultiToken(uid, multiToken);
|
||||
cookie.RemoveLoginTicket();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateUserCookie(Cookie cookie, BindingUser user)
|
||||
{
|
||||
user.Cookie = cookie;
|
||||
|
||||
appDbContext.Users.Update(user.Entity);
|
||||
appDbContext.SaveChanges();
|
||||
}
|
||||
|
||||
private async Task<ValueResult<UserOptionResult, string>> TryCreateUserAndAddAsync(ObservableCollection<BindingUser> users, Cookie cookie)
|
||||
{
|
||||
cookie.Trim();
|
||||
BindingUser? newUser = await BindingUser.CreateAsync(cookie, userClient, bindingClient).ConfigureAwait(false);
|
||||
if (newUser != null)
|
||||
{
|
||||
// Sync cache
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
users.Add(newUser);
|
||||
|
||||
// Sync database
|
||||
appDbContext.Users.Add(newUser.Entity);
|
||||
await appDbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
return new(UserOptionResult.Added, newUser.UserInfo!.Uid);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new(UserOptionResult.Invalid, null!);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,8 +44,6 @@ public sealed partial class AchievementImportDialog : ContentDialog
|
||||
/// <returns>导入选项</returns>
|
||||
public async Task<ValueResult<bool, ImportStrategy>> GetImportStrategyAsync()
|
||||
{
|
||||
//await ThreadHelper.SwitchToMainThreadAsync();
|
||||
|
||||
ContentDialogResult result = await ShowAsync();
|
||||
ImportStrategy strategy = (ImportStrategy)ImportModeSelector.SelectedIndex;
|
||||
|
||||
|
||||
@@ -42,7 +42,6 @@ public sealed partial class GachaLogImportDialog : ContentDialog
|
||||
/// <returns>是否导入</returns>
|
||||
public async Task<bool> GetShouldImportAsync()
|
||||
{
|
||||
//await ThreadHelper.SwitchToMainThreadAsync();
|
||||
return await ShowAsync() == ContentDialogResult.Primary;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
namespace Snap.Hutao.View.Dialog;
|
||||
|
||||
@@ -13,7 +14,7 @@ namespace Snap.Hutao.View.Dialog;
|
||||
/// </summary>
|
||||
public sealed partial class UserAutoCookieDialog : ContentDialog
|
||||
{
|
||||
private IDictionary<string, string>? cookie;
|
||||
private Cookie? cookie;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的用户自动Cookie对话框
|
||||
@@ -29,7 +30,7 @@ public sealed partial class UserAutoCookieDialog : ContentDialog
|
||||
/// 获取输入的Cookie
|
||||
/// </summary>
|
||||
/// <returns>输入的结果</returns>
|
||||
public async Task<ValueResult<bool, IDictionary<string, string>>> GetInputCookieAsync()
|
||||
public async Task<ValueResult<bool, Cookie>> GetInputCookieAsync()
|
||||
{
|
||||
ContentDialogResult result = await ShowAsync();
|
||||
return new(result == ContentDialogResult.Primary && cookie != null, cookie!);
|
||||
@@ -58,16 +59,10 @@ public sealed partial class UserAutoCookieDialog : ContentDialog
|
||||
{
|
||||
if (sender.Source.ToString() == "https://user.mihoyo.com/#/account/home")
|
||||
{
|
||||
try
|
||||
{
|
||||
CoreWebView2CookieManager manager = WebView.CoreWebView2.CookieManager;
|
||||
IReadOnlyList<CoreWebView2Cookie> cookies = await manager.GetCookiesAsync("https://user.mihoyo.com");
|
||||
cookie = cookies.ToDictionary(c => c.Name, c => c.Value);
|
||||
WebView.CoreWebView2.SourceChanged -= OnCoreWebView2SourceChanged;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
CoreWebView2CookieManager manager = WebView.CoreWebView2.CookieManager;
|
||||
IReadOnlyList<CoreWebView2Cookie> cookies = await manager.GetCookiesAsync("https://user.mihoyo.com");
|
||||
cookie = Cookie.FromCoreWebView2Cookies(cookies);
|
||||
WebView.CoreWebView2.SourceChanged -= OnCoreWebView2SourceChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,15 @@ public sealed partial class TitleView : UserControl
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标题
|
||||
/// </summary>
|
||||
[SuppressMessage("", "CA1822")]
|
||||
public string Title
|
||||
{
|
||||
get => $"胡桃 {Core.CoreEnvironment.Version}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取可拖动区域
|
||||
/// </summary>
|
||||
@@ -26,9 +35,4 @@ public sealed partial class TitleView : UserControl
|
||||
{
|
||||
get => DragableGrid;
|
||||
}
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => $"胡桃 {Core.CoreEnvironment.Version}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Snap.Hutao.Core.IO.DataTransfer;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Model.Binding;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.User;
|
||||
using Snap.Hutao.View.Dialog;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using System.Collections.ObjectModel;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
|
||||
namespace Snap.Hutao.ViewModel;
|
||||
|
||||
@@ -54,7 +55,7 @@ internal class UserViewModel : ObservableObject
|
||||
{
|
||||
if (SetProperty(ref selectedUser, value))
|
||||
{
|
||||
userService.CurrentUser = value;
|
||||
userService.Current = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,7 +76,7 @@ internal class UserViewModel : ObservableObject
|
||||
public ICommand AddUserCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 升级到Stoken命令
|
||||
/// 登录米哈游通行证升级到Stoken命令
|
||||
/// </summary>
|
||||
public ICommand UpgradeToStokenCommand { get; }
|
||||
|
||||
@@ -89,51 +90,10 @@ internal class UserViewModel : ObservableObject
|
||||
/// </summary>
|
||||
public ICommand CopyCookieCommand { get; }
|
||||
|
||||
private static (bool Valid, bool Upgrade) TryValidateCookie(IDictionary<string, string> map, out IDictionary<string, string> cookie)
|
||||
{
|
||||
int validFlag = 4;
|
||||
int stokenFlag = 2;
|
||||
|
||||
cookie = new SortedDictionary<string, string>();
|
||||
|
||||
foreach ((string key, string value) in map)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case CookieKeys.COOKIE_TOKEN:
|
||||
case CookieKeys.ACCOUNT_ID:
|
||||
case CookieKeys.LTOKEN:
|
||||
case CookieKeys.LTUID:
|
||||
{
|
||||
validFlag--;
|
||||
cookie.Add(key, value);
|
||||
break;
|
||||
}
|
||||
|
||||
case CookieKeys.STOKEN:
|
||||
case CookieKeys.STUID:
|
||||
{
|
||||
stokenFlag--;
|
||||
cookie.Add(key, value);
|
||||
break;
|
||||
}
|
||||
|
||||
case CookieKeys.LOGIN_TICKET:
|
||||
case CookieKeys.LOGIN_UID:
|
||||
{
|
||||
cookie.Add(key, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (validFlag == 0, stokenFlag == 0);
|
||||
}
|
||||
|
||||
private async Task OpenUIAsync()
|
||||
{
|
||||
Users = await userService.GetUserCollectionAsync().ConfigureAwait(true);
|
||||
SelectedUser = userService.CurrentUser;
|
||||
SelectedUser = userService.Current;
|
||||
}
|
||||
|
||||
private async Task AddUserAsync()
|
||||
@@ -145,50 +105,29 @@ internal class UserViewModel : ObservableObject
|
||||
// User confirms the input
|
||||
if (result.IsOk)
|
||||
{
|
||||
(bool valid, bool upgradable) = TryValidateCookie(User.MapCookie(result.Value), out IDictionary<string, string> cookie);
|
||||
if (valid)
|
||||
{
|
||||
if (await userService.CreateUserAsync(cookie).ConfigureAwait(false) is User user)
|
||||
{
|
||||
switch (await userService.TryAddUserAsync(user, cookie[CookieKeys.ACCOUNT_ID]).ConfigureAwait(false))
|
||||
{
|
||||
case UserAddResult.Added:
|
||||
infoBarService.Success($"用户 [{user.UserInfo!.Nickname}] 添加成功");
|
||||
break;
|
||||
case UserAddResult.Updated:
|
||||
infoBarService.Success($"用户 [{user.UserInfo!.Nickname}] 更新成功");
|
||||
break;
|
||||
case UserAddResult.AlreadyExists:
|
||||
infoBarService.Information($"用户 [{user.UserInfo!.Nickname}] 已经存在");
|
||||
break;
|
||||
default:
|
||||
throw Must.NeverHappen();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning("此 Cookie 无法获取用户信息,请重新输入");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (upgradable)
|
||||
{
|
||||
(bool success, string nickname) = await userService.TryUpgradeUserByStokenAsync(cookie).ConfigureAwait(false);
|
||||
Cookie cookie = Cookie.Parse(result.Value);
|
||||
|
||||
if (success)
|
||||
{
|
||||
infoBarService.Information($"用户 [{nickname}] 的 Stoken 更新成功");
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning($"未找到匹配的可升级用户");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning("提供的文本不是正确的 Cookie ,请重新输入");
|
||||
}
|
||||
(UserOptionResult optionResult, string uid) = await userService.ProcessInputCookieAsync(cookie).ConfigureAwait(false);
|
||||
|
||||
switch (optionResult)
|
||||
{
|
||||
case UserOptionResult.Added:
|
||||
infoBarService.Success($"用户 [{uid}] 添加成功");
|
||||
break;
|
||||
case UserOptionResult.Incomplete:
|
||||
infoBarService.Information($"此 Cookie 不完整,操作失败");
|
||||
break;
|
||||
case UserOptionResult.Invalid:
|
||||
infoBarService.Information($"此 Cookie 无法,操作失败");
|
||||
break;
|
||||
case UserOptionResult.Updated:
|
||||
infoBarService.Success($"用户 [{uid}] 更新成功");
|
||||
break;
|
||||
case UserOptionResult.Upgraded:
|
||||
infoBarService.Information($"用户 [{uid}] 升级成功");
|
||||
break;
|
||||
default:
|
||||
throw Must.NeverHappen();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,15 +136,16 @@ internal class UserViewModel : ObservableObject
|
||||
{
|
||||
// Get cookie from user input
|
||||
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
(bool isOk, IDictionary<string, string> addition) = await new UserAutoCookieDialog(mainWindow).GetInputCookieAsync().ConfigureAwait(false);
|
||||
(bool isOk, Cookie addition) = await new UserAutoCookieDialog(mainWindow).GetInputCookieAsync().ConfigureAwait(false);
|
||||
|
||||
// User confirms the input
|
||||
if (isOk)
|
||||
{
|
||||
(bool isUpgraded, string nickname) = await userService.TryUpgradeUserByLoginTicketAsync(addition).ConfigureAwait(false);
|
||||
if (isUpgraded)
|
||||
(UserOptionResult result, string nickname) = await userService.ProcessInputCookieAsync(addition).ConfigureAwait(false);
|
||||
|
||||
if (result == UserOptionResult.Upgraded)
|
||||
{
|
||||
infoBarService.Information($"用户 [{nickname}] 的 Cookie 已成功添加 Stoken");
|
||||
infoBarService.Information($"用户 [{nickname}] 的 Cookie 升级成功");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -228,10 +168,7 @@ internal class UserViewModel : ObservableObject
|
||||
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
||||
try
|
||||
{
|
||||
DataPackage content = new();
|
||||
content.SetText(Must.NotNull(user.Cookie!));
|
||||
Clipboard.SetContent(content);
|
||||
Clipboard.Flush();
|
||||
Clipboard.SetText(user.Cookie?.ToString() ?? string.Empty);
|
||||
infoBarService.Success($"{user.UserInfo!.Nickname} 的 Cookie 复制成功");
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
216
src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs
Normal file
216
src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs
Normal file
@@ -0,0 +1,216 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Snap.Hutao.Extension;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
/// <summary>
|
||||
/// 封装了米哈游的Cookie
|
||||
/// </summary>
|
||||
public partial class Cookie
|
||||
{
|
||||
private readonly SortedDictionary<string, string> inner;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个空白的Cookie
|
||||
/// </summary>
|
||||
public Cookie()
|
||||
: this(new())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的Cookie
|
||||
/// </summary>
|
||||
/// <param name="dict">源</param>
|
||||
private Cookie(SortedDictionary<string, string> dict)
|
||||
{
|
||||
inner = dict;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析Cookie字符串
|
||||
/// </summary>
|
||||
/// <param name="cookieString">cookie字符串</param>
|
||||
/// <returns>新的Cookie对象</returns>
|
||||
public static Cookie Parse(string cookieString)
|
||||
{
|
||||
SortedDictionary<string, string> cookieMap = new();
|
||||
|
||||
string[] values = cookieString.TrimEnd(';').Split(';');
|
||||
foreach (string[] parts in values.Select(c => c.Split('=', 2)))
|
||||
{
|
||||
string name = parts[0].Trim();
|
||||
string value = parts.Length == 1 ? string.Empty : parts[1].Trim();
|
||||
|
||||
cookieMap.Add(name, value);
|
||||
}
|
||||
|
||||
return new(cookieMap);
|
||||
}
|
||||
|
||||
public static Cookie FromCoreWebView2Cookies(IReadOnlyList<CoreWebView2Cookie> webView2Cookies)
|
||||
{
|
||||
SortedDictionary<string, string> cookieMap = new();
|
||||
|
||||
foreach (CoreWebView2Cookie cookie in webView2Cookies)
|
||||
{
|
||||
cookieMap.Add(cookie.Name, cookie.Value);
|
||||
}
|
||||
|
||||
return new(cookieMap);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 存在 LoginTicket
|
||||
/// </summary>
|
||||
/// <returns>是否存在</returns>
|
||||
public bool ContainsLoginTicket()
|
||||
{
|
||||
return inner.ContainsKey(LOGIN_TICKET);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 存在 LToken 与 CookieToken
|
||||
/// </summary>
|
||||
/// <returns>是否存在</returns>
|
||||
public bool ContainsLTokenAndCookieToken()
|
||||
{
|
||||
return inner.ContainsKey(LTOKEN) && inner.ContainsKey(COOKIE_TOKEN);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 存在 SToken
|
||||
/// </summary>
|
||||
/// <returns>是否存在</returns>
|
||||
public bool ContainsSToken()
|
||||
{
|
||||
return inner.ContainsKey(STOKEN);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插入Stoken
|
||||
/// </summary>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="multiToken">tokens</param>
|
||||
public void InsertMultiToken(string uid, Dictionary<string, string> multiToken)
|
||||
{
|
||||
inner[STUID] = uid;
|
||||
inner[STOKEN] = multiToken[STOKEN];
|
||||
|
||||
inner[LTUID] = uid;
|
||||
inner[LTOKEN] = multiToken[LTOKEN];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插入 Stoken
|
||||
/// </summary>
|
||||
/// <param name="stuid">stuid</param>
|
||||
/// <param name="cookie">cookie</param>
|
||||
public void InsertSToken(string stuid, Cookie cookie)
|
||||
{
|
||||
inner[STUID] = stuid;
|
||||
inner[STOKEN] = cookie.inner[STOKEN];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除 LoginTicket
|
||||
/// </summary>
|
||||
public void RemoveLoginTicket()
|
||||
{
|
||||
inner.Remove(LOGIN_TICKET);
|
||||
inner.Remove(LOGIN_UID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除无效的键
|
||||
/// </summary>
|
||||
public void Trim()
|
||||
{
|
||||
foreach (string key in inner.Keys.ToList())
|
||||
{
|
||||
if (key == ACCOUNT_ID
|
||||
|| key == COOKIE_TOKEN
|
||||
|| key == LOGIN_UID
|
||||
|| key == LOGIN_TICKET
|
||||
|| key == LTUID
|
||||
|| key == LTOKEN
|
||||
|| key == STUID
|
||||
|| key == STOKEN)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
inner.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Dictionary2{TKey, TValue}.TryGetValue(TKey, out TValue)"/>
|
||||
public bool TryGetValue(string key, [NotNullWhen(true)] out string? value)
|
||||
{
|
||||
return inner.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
public bool TryGetLoginTicket([NotNullWhen(true)] out string? loginTicket)
|
||||
{
|
||||
return inner.TryGetValue(LOGIN_TICKET, out loginTicket);
|
||||
}
|
||||
|
||||
public bool TryGetUid([NotNullWhen(true)] out string? uid)
|
||||
{
|
||||
Dictionary<string, int> uidCounter = new();
|
||||
|
||||
foreach ((string key, string value) in inner)
|
||||
{
|
||||
if (key is ACCOUNT_ID or LOGIN_UID or LTUID or STUID)
|
||||
{
|
||||
uidCounter.Increase(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (uidCounter.Count > 0)
|
||||
{
|
||||
uid = uidCounter.MaxBy(kvp => kvp.Value).Key;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
uid = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为Cookie的字符串表示
|
||||
/// </summary>
|
||||
/// <returns>Cookie的字符串表示</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Join(';', inner.Select(kvp => $"{kvp.Key}={kvp.Value}"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 键部分
|
||||
/// </summary>
|
||||
[SuppressMessage("", "SA1310")]
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public partial class Cookie
|
||||
{
|
||||
public const string COOKIE_TOKEN = "cookie_token";
|
||||
public const string ACCOUNT_ID = "account_id";
|
||||
|
||||
public const string LOGIN_TICKET = "login_ticket";
|
||||
public const string LOGIN_UID = "login_uid";
|
||||
|
||||
public const string LTOKEN = "ltoken";
|
||||
public const string LTUID = "ltuid";
|
||||
|
||||
public const string STOKEN = "stoken";
|
||||
public const string STUID = "stuid";
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
/// <summary>
|
||||
/// Cookie的键
|
||||
/// </summary>
|
||||
[SuppressMessage("", "SA1310")]
|
||||
[SuppressMessage("", "SA1600")]
|
||||
internal static class CookieKeys
|
||||
{
|
||||
public const string ACCOUNT_ID = "account_id";
|
||||
public const string COOKIE_TOKEN = "cookie_token";
|
||||
public const string LOGIN_TICKET = "login_ticket";
|
||||
public const string LOGIN_UID = "login_uid";
|
||||
public const string LTOKEN = "ltoken";
|
||||
public const string LTUID = "ltuid";
|
||||
public const string STOKEN = "stoken";
|
||||
public const string STUID = "stuid";
|
||||
}
|
||||
@@ -53,7 +53,7 @@ internal static class HttpClientExtensions
|
||||
/// <returns>客户端</returns>
|
||||
internal static HttpClient SetUser(this HttpClient httpClient, User user)
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Set("Cookie", user.Cookie);
|
||||
httpClient.DefaultRequestHeaders.Set("Cookie", user.Cookie?.ToString() ?? string.Empty);
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.Net.Http;
|
||||
@@ -46,7 +47,10 @@ internal class AuthClient
|
||||
|
||||
if (resp?.Data != null)
|
||||
{
|
||||
return resp.Data.List.ToDictionary(n => n.Name, n => n.Token);
|
||||
Dictionary<string, string> dict = resp.Data.List.ToDictionary(n => n.Name, n => n.Token);
|
||||
Must.Argument(dict.ContainsKey(Cookie.LTOKEN), "MultiToken 应该包含 ltoken");
|
||||
Must.Argument(dict.ContainsKey(Cookie.STOKEN), "MultiToken 应该包含 stoken");
|
||||
return dict;
|
||||
}
|
||||
|
||||
return new();
|
||||
|
||||
@@ -26,6 +26,7 @@ internal class GameRecordClient
|
||||
/// </summary>
|
||||
/// <param name="httpClient">请求器</param>
|
||||
/// <param name="options">json序列化选项</param>
|
||||
/// <param name="logger">日志器</param>
|
||||
public GameRecordClient(HttpClient httpClient, JsonSerializerOptions options, ILogger<GameRecordClient> logger)
|
||||
{
|
||||
this.httpClient = httpClient;
|
||||
|
||||
31
src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/UidToken.cs
Normal file
31
src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/UidToken.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
/// <summary>
|
||||
/// Uid Token 对
|
||||
/// </summary>
|
||||
public struct UidToken
|
||||
{
|
||||
/// <summary>
|
||||
/// Uid
|
||||
/// </summary>
|
||||
public string Uid;
|
||||
|
||||
/// <summary>
|
||||
/// Token
|
||||
/// </summary>
|
||||
public string Token;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的 Uid Token 对
|
||||
/// </summary>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="token">token</param>
|
||||
public UidToken(string uid, string token)
|
||||
{
|
||||
Uid = uid;
|
||||
Token = token;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user