diff --git a/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs b/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs
index bde6842b..c0ffb9fd 100644
--- a/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs
@@ -69,6 +69,8 @@ public class AppDbContext : DbContext
///
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
- modelBuilder.ApplyConfiguration(new AvatarInfoConfiguration());
+ modelBuilder
+ .ApplyConfiguration(new AvatarInfoConfiguration())
+ .ApplyConfiguration(new UserConfiguration());
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Markup/I18NExtension.cs b/src/Snap.Hutao/Snap.Hutao/Control/Markup/I18NExtension.cs
index 653c4d2e..0a8d0a09 100644
--- a/src/Snap.Hutao/Snap.Hutao/Control/Markup/I18NExtension.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Markup/I18NExtension.cs
@@ -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)!;
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Database/DbCurrent.cs b/src/Snap.Hutao/Snap.Hutao/Core/Database/DbCurrent.cs
index 3d7fc8c1..4fde20be 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Database/DbCurrent.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Database/DbCurrent.cs
@@ -70,6 +70,83 @@ internal class DbCurrent
dbContext.SaveChanges();
}
+ messenger.Send(message);
+ }
+ }
+}
+
+///
+/// 数据库当前项
+/// 简化对数据库中选中项的管理
+///
+/// 绑定类型
+/// 实体的类型
+/// 消息的类型
+[SuppressMessage("", "SA1402")]
+internal class DbCurrent
+ where TObservable : class
+ where TEntity : class, ISelectable
+ where TMessage : Message.ValueChangedMessage
+{
+ private readonly DbContext dbContext;
+ private readonly DbSet dbSet;
+ private readonly IMessenger messenger;
+ private readonly Func selector;
+
+ private TObservable? current;
+
+ ///
+ /// 构造一个新的数据库当前项
+ ///
+ /// 数据库上下文
+ /// 数据集
+ /// 选择器
+ /// 消息器
+ public DbCurrent(DbContext dbContext, DbSet dbSet, Func selector, IMessenger messenger)
+ {
+ this.dbContext = dbContext;
+ this.dbSet = dbSet;
+ this.selector = selector;
+ this.messenger = messenger;
+ }
+
+ ///
+ /// 当前选中的项
+ ///
+ 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);
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/Clipboard.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/Clipboard.cs
index fa9ba271..bd7bf0a7 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/Clipboard.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/Clipboard.cs
@@ -25,4 +25,16 @@ internal static class Clipboard
string json = await view.GetTextAsync();
return JsonSerializer.Deserialize(json, options);
}
+
+ ///
+ /// 设置文本
+ ///
+ /// 文本
+ public static void SetText(string text)
+ {
+ DataPackage content = new();
+ content.SetText(text);
+ Windows.ApplicationModel.DataTransfer.Clipboard.SetContent(content);
+ Windows.ApplicationModel.DataTransfer.Clipboard.Flush();
+ }
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtensions.cs
index 93ea94fc..e4795d9b 100644
--- a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtensions.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtensions.cs
@@ -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
}
///
- /// 获取值或默认值
+ /// 增加计数
///
/// 键类型
- /// 值类型
- /// 字典
+ /// 字典
/// 键
- /// 默认值
- /// 结果值
- public static TValue? GetValueOrDefault(this IDictionary dictionary, TKey key, TValue? defaultValue = default)
+ public static void Increase(this Dictionary dict, TKey key)
where TKey : notnull
{
- if (dictionary.TryGetValue(key, out TValue? value))
+ ++CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _);
+ }
+
+ ///
+ /// 增加计数
+ ///
+ /// 键类型
+ /// 字典
+ /// 键
+ /// 是否存在键值
+ public static bool TryIncrease(this Dictionary 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;
}
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/User.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/User.cs
index 2a51b012..5fcd09d8 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Binding/User.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/User.cs
@@ -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
}
///
- public string? Cookie
+ public Cookie Cookie
{
get => inner.Cookie;
set => inner.Cookie = value;
@@ -72,27 +71,6 @@ public class User : Observable
///
public bool IsInitialized { get => isInitialized; }
- ///
- /// 将cookie的字符串形式转换为字典
- ///
- /// cookie的字符串形式
- /// 包含cookie信息的字典
- public static IDictionary MapCookie(string cookie)
- {
- SortedDictionary 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;
- }
-
///
/// 从数据库恢复用户
///
@@ -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;
}
///
- /// 初始化用户
+ /// 创建并初始化用户
///
/// cookie
/// 用户客户端
/// 角色客户端
- /// 授权客户端
/// 取消令牌
- /// 用户是否初始化完成,若Cookie失效会返回
+ /// 用户是否初始化完成,若Cookie失效会返回
internal static async Task CreateAsync(
- IDictionary 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;
}
- ///
- /// 尝试升级到Stoken
- ///
- /// 额外的token
- /// 验证客户端
- /// 取消令牌
- /// 是否升级成功
- internal async Task TryUpgradeByLoginTicketAsync(IDictionary addition, AuthClient authClient, CancellationToken token)
- {
- IDictionary 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;
- }
-
- ///
- /// 添加 Stoken
- ///
- /// 额外的cookie
- internal void AddStoken(IDictionary addition)
- {
- IDictionary 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 cookie)
- {
- return string.Join(';', cookie.Select(kvp => $"{kvp.Key}={kvp.Value}"));
- }
-
- private static async Task TryRequestStokenAndAddToCookieAsync(IDictionary 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 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 ResumeInternalAsync(
+ private async Task 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 CreateInternalAsync(
- IDictionary 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)
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/Configuration/UserConfiguration.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Configuration/UserConfiguration.cs
new file mode 100644
index 00000000..69964c1e
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Configuration/UserConfiguration.cs
@@ -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;
+
+///
+/// 用户配置
+///
+internal class UserConfiguration : IEntityTypeConfiguration
+{
+ ///
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.Property(e => e.Cookie)
+ .HasColumnType("TEXT")
+ .HasConversion(
+ e => e == null ? string.Empty : e.ToString(),
+ e => Cookie.Parse(e));
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/User.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/User.cs
index 8542f0dc..ca000fda 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/User.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/User.cs
@@ -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
///
/// 用户的Cookie
///
- public string? Cookie { get; set; }
+ public Cookie Cookie { get; set; } = default!;
///
/// 创建一个新的用户
///
/// cookie
/// 新创建的用户
- public static User Create(string cookie)
+ public static User Create(Cookie cookie)
{
return new() { Cookie = cookie };
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/WeaponType.cs b/src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/WeaponType.cs
index ebe6fd8c..0f9b2a3b 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/WeaponType.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/WeaponType.cs
@@ -21,6 +21,7 @@ public enum WeaponType
WEAPON_SWORD_ONE_HAND = 1,
#region Not Used
+
///
/// ?
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Abstraction/ISummaryItemSource.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Abstraction/ISummaryItemSource.cs
new file mode 100644
index 00000000..b843309a
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Abstraction/ISummaryItemSource.cs
@@ -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;
+
+///
+/// 指示该类为简述统计物品的源
+///
+public interface ISummaryItemSource
+{
+ ///
+ /// 星级
+ ///
+ ItemQuality Quality { get; }
+
+ ///
+ /// 转换到简述统计物品
+ ///
+ /// 距上个五星
+ /// 时间
+ /// 是否为Up物品
+ /// 简述统计物品
+ SummaryItem ToSummaryItem(int lastPull, DateTimeOffset time, bool isUp);
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/Avatar.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/Avatar.cs
index fc23883c..843c571a 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/Avatar.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/Avatar.cs
@@ -12,7 +12,7 @@ namespace Snap.Hutao.Model.Metadata.Avatar;
///
/// 角色
///
-public class Avatar : IStatisticsItemSource, INameQuality
+public class Avatar : IStatisticsItemSource, ISummaryItemSource, INameQuality
{
///
/// Id
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Weapon/Weapon.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Weapon/Weapon.cs
index 87f070bb..46f613d0 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Weapon/Weapon.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Weapon/Weapon.cs
@@ -12,7 +12,7 @@ namespace Snap.Hutao.Model.Metadata.Weapon;
///
/// 武器
///
-public class Weapon : IStatisticsItemSource, INameQuality
+public class Weapon : IStatisticsItemSource, ISummaryItemSource, INameQuality
{
///
/// Id
diff --git a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
index a4cf5ef8..2c0b5fda 100644
--- a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
+++ b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
@@ -9,7 +9,7 @@
+ Version="1.1.5.0" />
胡桃
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsExtensions.cs
index f9de6fcf..44d5c75c 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsExtensions.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsExtensions.cs
@@ -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
}
///
- /// 增加计数
+ /// 完成添加
///
- /// 键类型
- /// 字典
- /// 键
- public static void Increase(this Dictionary dict, TKey key)
- where TKey : notnull
+ /// 简述物品列表
+ public static void CompleteAdding(this List summaryItems)
{
- ++CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _);
- }
+ // we can't trust first item's prev state.
+ bool isPreviousUp = true;
- ///
- /// 增加计数
- ///
- /// 键类型
- /// 字典
- /// 键
- /// 是否存在键值
- public static bool TryIncrease(this Dictionary 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();
}
///
@@ -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 first = new(codes, 0, 5);
+ Span second = new(codes, 5, 5);
+ Span third = new(codes, 10, 5);
+ Color color = Color.FromArgb(255, first.Average(), second.Average(), third.Average());
+ return color;
+ }
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs
index f1852372..90c5f8d7 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs
@@ -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
{
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HistoryWishBuilder.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HistoryWishBuilder.cs
index 3337865e..c8e53cfe 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HistoryWishBuilder.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HistoryWishBuilder.cs
@@ -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;
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/TypedWishSummaryBuilder.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/TypedWishSummaryBuilder.cs
index cc3c41f2..bbccd898 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/TypedWishSummaryBuilder.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/TypedWishSummaryBuilder.cs
@@ -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
/// 追踪物品
///
/// 祈愿物品
- /// 对应角色
+ /// 对应武器
/// 是否为Up物品
- 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;
- }
- }
- }
- }
-
- ///
- /// 追踪物品
- ///
- /// 祈愿物品
- /// 对应武器
- /// 是否为Up物品
- 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
/// 类型化祈愿统计信息
public TypedWishSummary ToTypedWishSummary()
{
- CompleteSummaryItems(summaryItemCache);
+ summaryItemCache.CompleteAdding();
double totalCountDouble = totalCountTracker;
return new()
@@ -209,37 +157,6 @@ internal class TypedWishSummaryBuilder
};
}
- private static void CompleteSummaryItems(List 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 first = new(codes, 0, 5);
- Span second = new(codes, 5, 5);
- Span 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)
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlStokenProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlStokenProvider.cs
index 88674a7d..9994545f 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlStokenProvider.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlStokenProvider.cs
@@ -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
///
public async Task> 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");
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs
index 9ada459a..bd189307 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs
@@ -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;
///
/// 用户服务
@@ -16,52 +16,28 @@ public interface IUserService
///
/// 获取或设置当前用户
///
- User? CurrentUser { get; set; }
+ BindingUser? Current { get; set; }
///
+ /// 初始化用户服务及所有用户
/// 异步获取同步的用户信息集合
/// 对集合的操作应通过服务抽象完成
/// 此操作不能取消
///
- /// 准备完成的用户信息枚举
- Task> GetUserCollectionAsync();
+ /// 准备完成的用户信息集合
+ Task> GetUserCollectionAsync();
///
- /// 异步添加用户
- /// 通常用户是未初始化的
+ /// 尝试异步处理输入的Cookie
///
- /// 待添加的用户
- /// 用户的米游社UID,用于检查是否包含重复的用户
- /// 用户初始化是否成功
- Task TryAddUserAsync(User user, string uid);
-
- ///
- /// 尝试使用 login_ticket 升级用户
- ///
- /// 额外的Cookie
- /// 取消令牌
- /// 是否升级成功
- Task> TryUpgradeUserByLoginTicketAsync(IDictionary addiition, CancellationToken token = default);
-
- ///
- /// 尝试使用 Stoken 升级用户
- ///
- /// stoken
- /// 是否升级成功
- Task> TryUpgradeUserByStokenAsync(IDictionary stoken);
+ /// Cookie
+ /// 处理的结果
+ Task> ProcessInputCookieAsync(Cookie cookie);
///
/// 异步移除用户
///
/// 待移除的用户
/// 任务
- Task RemoveUserAsync(User user);
-
- ///
- /// 创建一个新的绑定用户
- /// 若存在 login_ticket 与 login_uid 则 自动获取 stoken
- ///
- /// cookie的字符串形式
- /// 新的绑定用户
- Task CreateUserAsync(IDictionary cookie);
-}
+ Task RemoveUserAsync(BindingUser user);
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserHelper.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserHelper.cs
new file mode 100644
index 00000000..f416ea16
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserHelper.cs
@@ -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;
+
+///
+/// 用户帮助类
+///
+internal static class UserHelper
+{
+ ///
+ /// 尝试获取用户
+ ///
+ /// 待查找的用户集合
+ /// uid
+ /// 用户
+ /// 是否存在用户
+ public static bool TryGetUserByUid(ObservableCollection users, string uid, [NotNullWhen(true)] out BindingUser? user)
+ {
+ user = users.SingleOrDefault(u => u.UserInfo!.Uid == uid);
+
+ return user != null;
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserAddResult.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserOptionResult.cs
similarity index 56%
rename from src/Snap.Hutao/Snap.Hutao/Service/User/UserAddResult.cs
rename to src/Snap.Hutao/Snap.Hutao/Service/User/UserOptionResult.cs
index 715d3108..c96f7ca5 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserAddResult.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserOptionResult.cs
@@ -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;
///
/// 用户添加操作结果
///
-public enum UserAddResult
+public enum UserOptionResult
{
///
/// 添加成功
///
Added,
+ ///
+ /// Cookie不完整
+ ///
+ Incomplete,
+
+ ///
+ /// Cookie信息已经失效
+ ///
+ Invalid,
+
///
/// 用户的Cookie成功更新
///
Updated,
///
- /// 已经存在该用户
+ /// 升级到Stoken
///
- AlreadyExists,
+ Upgraded,
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs
index b423a7b1..b0b09917 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs
@@ -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;
///
/// 用户服务
@@ -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
///
/// 应用程序数据库上下文
/// 用户客户端
- /// 角色客户端
+ /// 角色客户端
/// 验证客户端
/// 消息器
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;
}
///
- public BindingUser? CurrentUser
+ public BindingUser? Current
{
get => currentUser;
set
@@ -90,45 +89,6 @@ internal class UserService : IUserService
}
}
- ///
- public async Task 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;
- }
- }
-
///
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;
}
///
- public Task CreateUserAsync(IDictionary cookie)
- {
- return BindingUser.CreateAsync(cookie, userClient, userGameRoleClient, authClient);
- }
-
- ///
- public async Task> TryUpgradeUserByLoginTicketAsync(IDictionary addition, CancellationToken token = default)
+ public async Task> 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);
- }
-
- ///
- public async Task> TryUpgradeUserByStokenAsync(IDictionary 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 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> TryCreateUserAndAddAsync(ObservableCollection 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!);
+ }
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/AchievementImportDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/AchievementImportDialog.xaml.cs
index 421ff476..1398547d 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/AchievementImportDialog.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/AchievementImportDialog.xaml.cs
@@ -44,8 +44,6 @@ public sealed partial class AchievementImportDialog : ContentDialog
/// 导入选项
public async Task> GetImportStrategyAsync()
{
- //await ThreadHelper.SwitchToMainThreadAsync();
-
ContentDialogResult result = await ShowAsync();
ImportStrategy strategy = (ImportStrategy)ImportModeSelector.SelectedIndex;
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogImportDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogImportDialog.xaml.cs
index 841bf276..fa209f5a 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogImportDialog.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogImportDialog.xaml.cs
@@ -42,7 +42,6 @@ public sealed partial class GachaLogImportDialog : ContentDialog
/// 是否导入
public async Task GetShouldImportAsync()
{
- //await ThreadHelper.SwitchToMainThreadAsync();
return await ShowAsync() == ContentDialogResult.Primary;
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserAutoCookieDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserAutoCookieDialog.xaml.cs
index 947da7e4..4b12b40d 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserAutoCookieDialog.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserAutoCookieDialog.xaml.cs
@@ -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;
///
public sealed partial class UserAutoCookieDialog : ContentDialog
{
- private IDictionary? cookie;
+ private Cookie? cookie;
///
/// 构造一个新的用户自动Cookie对话框
@@ -29,7 +30,7 @@ public sealed partial class UserAutoCookieDialog : ContentDialog
/// 获取输入的Cookie
///
/// 输入的结果
- public async Task>> GetInputCookieAsync()
+ public async Task> 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 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 cookies = await manager.GetCookiesAsync("https://user.mihoyo.com");
+ cookie = Cookie.FromCoreWebView2Cookies(cookies);
+ WebView.CoreWebView2.SourceChanged -= OnCoreWebView2SourceChanged;
}
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/View/TitleView.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/TitleView.xaml.cs
index d81f8243..2a35e584 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/TitleView.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/TitleView.xaml.cs
@@ -19,6 +19,15 @@ public sealed partial class TitleView : UserControl
InitializeComponent();
}
+ ///
+ /// 标题
+ ///
+ [SuppressMessage("", "CA1822")]
+ public string Title
+ {
+ get => $"胡桃 {Core.CoreEnvironment.Version}";
+ }
+
///
/// 获取可拖动区域
///
@@ -26,9 +35,4 @@ public sealed partial class TitleView : UserControl
{
get => DragableGrid;
}
-
- public string Title
- {
- get => $"胡桃 {Core.CoreEnvironment.Version}";
- }
}
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs
index df2e2851..7145c62b 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs
@@ -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; }
///
- /// 升级到Stoken命令
+ /// 登录米哈游通行证升级到Stoken命令
///
public ICommand UpgradeToStokenCommand { get; }
@@ -89,51 +90,10 @@ internal class UserViewModel : ObservableObject
///
public ICommand CopyCookieCommand { get; }
- private static (bool Valid, bool Upgrade) TryValidateCookie(IDictionary map, out IDictionary cookie)
- {
- int validFlag = 4;
- int stokenFlag = 2;
-
- cookie = new SortedDictionary();
-
- 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 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();
- (bool isOk, IDictionary 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();
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)
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs
new file mode 100644
index 00000000..d80bc00b
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs
@@ -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;
+
+///
+/// 封装了米哈游的Cookie
+///
+public partial class Cookie
+{
+ private readonly SortedDictionary inner;
+
+ ///
+ /// 构造一个空白的Cookie
+ ///
+ public Cookie()
+ : this(new())
+ {
+ }
+
+ ///
+ /// 构造一个新的Cookie
+ ///
+ /// 源
+ private Cookie(SortedDictionary dict)
+ {
+ inner = dict;
+ }
+
+ ///
+ /// 解析Cookie字符串
+ ///
+ /// cookie字符串
+ /// 新的Cookie对象
+ public static Cookie Parse(string cookieString)
+ {
+ SortedDictionary 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 webView2Cookies)
+ {
+ SortedDictionary cookieMap = new();
+
+ foreach (CoreWebView2Cookie cookie in webView2Cookies)
+ {
+ cookieMap.Add(cookie.Name, cookie.Value);
+ }
+
+ return new(cookieMap);
+ }
+
+ ///
+ /// 存在 LoginTicket
+ ///
+ /// 是否存在
+ public bool ContainsLoginTicket()
+ {
+ return inner.ContainsKey(LOGIN_TICKET);
+ }
+
+ ///
+ /// 存在 LToken 与 CookieToken
+ ///
+ /// 是否存在
+ public bool ContainsLTokenAndCookieToken()
+ {
+ return inner.ContainsKey(LTOKEN) && inner.ContainsKey(COOKIE_TOKEN);
+ }
+
+ ///
+ /// 存在 SToken
+ ///
+ /// 是否存在
+ public bool ContainsSToken()
+ {
+ return inner.ContainsKey(STOKEN);
+ }
+
+ ///
+ /// 插入Stoken
+ ///
+ /// uid
+ /// tokens
+ public void InsertMultiToken(string uid, Dictionary multiToken)
+ {
+ inner[STUID] = uid;
+ inner[STOKEN] = multiToken[STOKEN];
+
+ inner[LTUID] = uid;
+ inner[LTOKEN] = multiToken[LTOKEN];
+ }
+
+ ///
+ /// 插入 Stoken
+ ///
+ /// stuid
+ /// cookie
+ public void InsertSToken(string stuid, Cookie cookie)
+ {
+ inner[STUID] = stuid;
+ inner[STOKEN] = cookie.inner[STOKEN];
+ }
+
+ ///
+ /// 移除 LoginTicket
+ ///
+ public void RemoveLoginTicket()
+ {
+ inner.Remove(LOGIN_TICKET);
+ inner.Remove(LOGIN_UID);
+ }
+
+ ///
+ /// 移除无效的键
+ ///
+ 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);
+ }
+ }
+ }
+
+ ///
+ 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 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;
+ }
+ }
+
+ ///
+ /// 转换为Cookie的字符串表示
+ ///
+ /// Cookie的字符串表示
+ public override string ToString()
+ {
+ return string.Join(';', inner.Select(kvp => $"{kvp.Key}={kvp.Value}"));
+ }
+}
+
+///
+/// 键部分
+///
+[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";
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/CookieKeys.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/CookieKeys.cs
deleted file mode 100644
index 563c4b84..00000000
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/CookieKeys.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) DGP Studio. All rights reserved.
-// Licensed under the MIT license.
-
-namespace Snap.Hutao.Web.Hoyolab;
-
-///
-/// Cookie的键
-///
-[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";
-}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs
index 7832256d..9f5ac6b3 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs
@@ -53,7 +53,7 @@ internal static class HttpClientExtensions
/// 客户端
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;
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Auth/AuthClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Auth/AuthClient.cs
index 32e085dd..5dc01ba2 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Auth/AuthClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Auth/AuthClient.cs
@@ -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 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();
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs
index 1542935f..36c902b2 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs
@@ -26,6 +26,7 @@ internal class GameRecordClient
///
/// 请求器
/// json序列化选项
+ /// 日志器
public GameRecordClient(HttpClient httpClient, JsonSerializerOptions options, ILogger logger)
{
this.httpClient = httpClient;
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/UidToken.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/UidToken.cs
new file mode 100644
index 00000000..c4bd216f
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/UidToken.cs
@@ -0,0 +1,31 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Web.Hoyolab;
+
+///
+/// Uid Token 对
+///
+public struct UidToken
+{
+ ///
+ /// Uid
+ ///
+ public string Uid;
+
+ ///
+ /// Token
+ ///
+ public string Token;
+
+ ///
+ /// 构造一个新的 Uid Token 对
+ ///
+ /// uid
+ /// token
+ public UidToken(string uid, string token)
+ {
+ Uid = uid;
+ Token = token;
+ }
+}
\ No newline at end of file