From 912895549e3f3c9d7f8070410f760905dfb138d1 Mon Sep 17 00:00:00 2001
From: DismissedLight <1686188646@qq.com>
Date: Sat, 4 Jun 2022 18:53:57 +0800
Subject: [PATCH] add hutao api client impl
---
src/Snap.Hutao/.editorconfig | 3 +
src/Snap.Hutao/Snap.Hutao/App.xaml.cs | 1 +
.../Context/Database/AppDbContext.cs | 6 +
.../Control/Animation/ImageZoomInAnimation.cs | 33 ++
.../Animation/ImageZoomOutAnimation.cs | 33 ++
.../Control/Behavior/AutoHeightBehavior.cs | 10 +-
.../Core/Abstraction/IAsyncInitializable.cs | 22 ++
src/Snap.Hutao/Snap.Hutao/Core/Browser.cs | 2 +-
.../Snap.Hutao/Core/Converting/Md5Convert.cs | 25 ++
.../Snap.Hutao/Core/CoreEnvironment.cs | 7 +-
src/Snap.Hutao/Snap.Hutao/Core/HttpJson.cs | 39 --
src/Snap.Hutao/Snap.Hutao/Core/Json.cs | 133 -------
.../SeparatorCommaInt32EnumerableConverter.cs | 29 ++
src/Snap.Hutao/Snap.Hutao/Core/Observable.cs | 43 ---
.../Snap.Hutao/Core/Setting/LocalSetting.cs | 2 +-
.../Snap.Hutao/Core/Validation/Must.cs | 2 +-
.../Snap.Hutao/Core/WebView2Helper.cs | 2 +-
.../Extension/EnumerableExtensions.cs | 69 ++++
src/Snap.Hutao/Snap.Hutao/GlobalUsing.cs | 1 +
src/Snap.Hutao/Snap.Hutao/IocConfiguration.cs | 12 +-
.../Snap.Hutao/Model/Entity/SettingEntry.cs | 36 ++
.../Snap.Hutao/Model/Entity/User.cs | 19 +
.../Service/Abstraction/INavigationService.cs | 2 +-
.../Service/Abstraction/IUserService.cs | 17 +
.../Snap.Hutao/Service/AnnouncementService.cs | 31 +-
src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj | 13 +-
.../Snap.Hutao/View/MainView.xaml.cs | 3 +-
.../View/Page/AnnouncementPage.xaml | 20 +-
.../Snap.Hutao/Web/Hoyolab/ApiEndpoints.cs | 25 +-
.../DynamicSecret/DynamicSecretProvider2.cs | 77 ++++
.../DynamicSecret/RequesterExtensions.cs | 55 +++
.../Hk4e/Common/Announcement/Announcement.cs | 10 +-
.../Announcement/AnnouncementContent.cs | 10 +-
.../Announcement/AnnouncementListWrapper.cs | 2 +-
.../Announcement/AnnouncementProvider.cs | 7 +-
.../Common/Announcement/AnnouncementType.cs | 4 +-
.../Announcement/AnnouncementWrapper.cs | 4 +-
.../Hoyolab/Takumi/Binding/UserGameRole.cs | 75 ++++
.../Takumi/Binding/UserGameRoleProvider.cs | 48 +++
.../Takumi/GameRecord/Avatar/Avatar.cs | 60 +++
.../Takumi/GameRecord/Avatar/Character.cs | 50 +++
.../GameRecord/Avatar/CharacterWrapper.cs | 19 +
.../Takumi/GameRecord/Avatar/Constellation.cs | 49 +++
.../Takumi/GameRecord/Avatar/Costume.cs | 32 ++
.../Takumi/GameRecord/Avatar/Rarity.cs | 35 ++
.../Takumi/GameRecord/Avatar/Reliquary.cs | 61 ++++
.../GameRecord/Avatar/ReliquaryAffix.cs | 25 ++
.../GameRecord/Avatar/ReliquaryPosition.cs | 35 ++
.../Takumi/GameRecord/Avatar/ReliquarySet.cs | 31 ++
.../Takumi/GameRecord/Avatar/Weapon.cs | 63 ++++
.../Takumi/GameRecord/Avatar/WeaponType.cs | 35 ++
.../Takumi/GameRecord/GameRecordProvider.cs | 118 ++++++
.../Web/Hoyolab/Takumi/GameRecord/Home.cs | 60 +++
.../Web/Hoyolab/Takumi/GameRecord/Offering.cs | 30 ++
.../Hoyolab/Takumi/GameRecord/PlayerInfo.cs | 43 +++
.../Hoyolab/Takumi/GameRecord/PlayerStats.cs | 96 +++++
.../Takumi/GameRecord/SpiralAbyss/Avatar.cs | 37 ++
.../Takumi/GameRecord/SpiralAbyss/Battle.cs | 42 +++
.../Takumi/GameRecord/SpiralAbyss/Floor.cs | 55 +++
.../Takumi/GameRecord/SpiralAbyss/Level.cs | 37 ++
.../Takumi/GameRecord/SpiralAbyss/Rank.cs | 36 ++
.../GameRecord/SpiralAbyss/SpiralAbyss.cs | 103 ++++++
.../Takumi/GameRecord/SpiralAbyssSchedule.cs | 20 +
.../Takumi/GameRecord/WorldExploration.cs | 118 ++++++
.../Web/Hoyolab/Takumi/PlayerUid.cs | 59 +++
.../Snap.Hutao/Web/Hutao/HutaoAPIProvider.cs | 341 ++++++++++++++++++
.../Web/Hutao/Model/AvatarConstellationNum.cs | 27 ++
.../Web/Hutao/Model/AvatarParticipation.cs | 22 ++
.../Web/Hutao/Model/AvatarReliquaryUsage.cs | 22 ++
.../Web/Hutao/Model/AvatarWeaponUsage.cs | 22 ++
.../Model/Converter/ReliquarySetsConverter.cs | 37 ++
.../Snap.Hutao/Web/Hutao/Model/Item.cs | 41 +++
.../Snap.Hutao/Web/Hutao/Model/LevelInfo.cs | 20 +
.../Snap.Hutao/Web/Hutao/Model/Overview.cs | 25 ++
.../Hutao/Model/Post/AvatarReliquarySet.cs | 42 +++
.../Web/Hutao/Model/Post/AvatarWeapon.cs | 40 ++
.../Hutao/Model/Post/GenshinItemWrapper.cs | 40 ++
.../Web/Hutao/Model/Post/IndexedLevel.cs | 33 ++
.../Web/Hutao/Model/Post/PlayerAvatar.cs | 56 +++
.../Web/Hutao/Model/Post/PlayerRecord.cs | 64 ++++
.../Model/Post/PlayerSpiralAbyssBattle.cs | 34 ++
.../Model/Post/PlayerSpiralAbyssLevel.cs | 46 +++
.../Snap.Hutao/Web/Hutao/Model/Rate{T}.cs | 21 ++
.../Web/Hutao/Model/ReliquarySet.cs | 38 ++
.../Web/Hutao/Model/ReliquarySets.cs | 24 ++
.../Snap.Hutao/Web/Hutao/Model/Team.cs | 26 ++
.../Web/Hutao/Model/TeamCollocation.cs | 22 ++
.../Web/Hutao/Model/TeamCombination.cs | 22 ++
.../Web/Hutao/Model/UploadStatus.cs | 15 +
.../Snap.Hutao/Web/Request/RequestHeaders.cs | 41 +++
.../Snap.Hutao/Web/Request/RequestOptions.cs | 19 +-
.../Snap.Hutao/Web/Request/Requester.cs | 81 ++++-
.../Web/Response/KnownReturnCode.cs | 5 +
.../Snap.Hutao/Web/Response/ListWrapper.cs | 2 +-
.../Snap.Hutao/Web/Response/Response.cs | 2 +-
.../Web/Response/Response{TData}.cs | 14 +
96 files changed, 3189 insertions(+), 336 deletions(-)
create mode 100644 src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomInAnimation.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomOutAnimation.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Abstraction/IAsyncInitializable.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Converting/Md5Convert.cs
delete mode 100644 src/Snap.Hutao/Snap.Hutao/Core/HttpJson.cs
delete mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Json.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/SeparatorCommaInt32EnumerableConverter.cs
delete mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Observable.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtensions.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Entity/User.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IUserService.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider2.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/RequesterExtensions.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRole.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRoleProvider.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Avatar.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Character.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/CharacterWrapper.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Constellation.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Costume.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Rarity.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Reliquary.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/ReliquaryAffix.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/ReliquaryPosition.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/ReliquarySet.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Weapon.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/WeaponType.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordProvider.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Home.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Offering.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/PlayerInfo.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/PlayerStats.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Avatar.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Battle.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Floor.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Level.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Rank.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/SpiralAbyss.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyssSchedule.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/WorldExploration.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/PlayerUid.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/HutaoAPIProvider.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/AvatarConstellationNum.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/AvatarParticipation.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/AvatarReliquaryUsage.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/AvatarWeaponUsage.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Converter/ReliquarySetsConverter.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Item.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/LevelInfo.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Overview.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/AvatarReliquarySet.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/AvatarWeapon.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/GenshinItemWrapper.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/IndexedLevel.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/PlayerAvatar.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/PlayerRecord.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/PlayerSpiralAbyssBattle.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/PlayerSpiralAbyssLevel.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Rate{T}.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/ReliquarySet.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/ReliquarySets.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Team.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/TeamCollocation.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/TeamCombination.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/UploadStatus.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Request/RequestHeaders.cs
diff --git a/src/Snap.Hutao/.editorconfig b/src/Snap.Hutao/.editorconfig
index ab8f1de7..b1697969 100644
--- a/src/Snap.Hutao/.editorconfig
+++ b/src/Snap.Hutao/.editorconfig
@@ -91,6 +91,9 @@ dotnet_diagnostic.SA1623.severity = none
# SA1636: File header copyright text should match
dotnet_diagnostic.SA1636.severity = none
+# SA1414: Tuple types in signatures should have element names
+dotnet_diagnostic.SA1414.severity = none
+
[*.vb]
#### 命名样式 ####
diff --git a/src/Snap.Hutao/Snap.Hutao/App.xaml.cs b/src/Snap.Hutao/Snap.Hutao/App.xaml.cs
index 73af19f7..3469d497 100644
--- a/src/Snap.Hutao/Snap.Hutao/App.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/App.xaml.cs
@@ -42,6 +42,7 @@ public partial class App : Application
{
IServiceProvider services = new ServiceCollection()
.AddLogging(builder => builder.AddDebug())
+ .AddDatebase()
.AddHttpClients()
.AddDefaultJsonSerializerOptions()
.AddInjections(typeof(App))
diff --git a/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs b/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs
index c604c7de..d307f41c 100644
--- a/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
+using Snap.Hutao.Model.Entity;
namespace Snap.Hutao.Context.Database;
@@ -18,4 +19,9 @@ internal class AppDbContext : DbContext
: base(options)
{
}
+
+ ///
+ /// 设置项
+ ///
+ public DbSet Settings { get; set; } = default!;
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomInAnimation.cs b/src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomInAnimation.cs
new file mode 100644
index 00000000..e26f9f82
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomInAnimation.cs
@@ -0,0 +1,33 @@
+using CommunityToolkit.WinUI.UI;
+using CommunityToolkit.WinUI.UI.Animations;
+using Microsoft.UI.Composition;
+using Microsoft.UI.Xaml.Media.Animation;
+using System.Numerics;
+
+namespace Snap.Hutao.Control.Animation;
+
+///
+/// 图片放大动画
+///
+internal class ImageZoomInAnimation : ImplicitAnimation
+{
+ ///
+ /// 构造一个新的图片放大动画
+ ///
+ public ImageZoomInAnimation()
+ {
+ EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseOut;
+ EasingType = CommunityToolkit.WinUI.UI.Animations.EasingType.Circle;
+ To = "1.1";
+ Duration = TimeSpan.FromSeconds(0.5);
+ }
+
+ ///
+ protected override string ExplicitTarget => nameof(Visual.Scale);
+
+ ///
+ protected override (Vector3?, Vector3?) GetParsedValues()
+ {
+ return (To?.ToVector3(), From?.ToVector3());
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomOutAnimation.cs b/src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomOutAnimation.cs
new file mode 100644
index 00000000..68e312fa
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomOutAnimation.cs
@@ -0,0 +1,33 @@
+using CommunityToolkit.WinUI.UI;
+using CommunityToolkit.WinUI.UI.Animations;
+using Microsoft.UI.Composition;
+using Microsoft.UI.Xaml.Media.Animation;
+using System.Numerics;
+
+namespace Snap.Hutao.Control.Animation;
+
+///
+/// 图片缩小动画
+///
+internal class ImageZoomOutAnimation : ImplicitAnimation
+{
+ ///
+ /// 构造一个新的图片缩小动画
+ ///
+ public ImageZoomOutAnimation()
+ {
+ EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseOut;
+ EasingType = CommunityToolkit.WinUI.UI.Animations.EasingType.Circle;
+ To = "1";
+ Duration = TimeSpan.FromSeconds(0.5);
+ }
+
+ ///
+ protected override string ExplicitTarget => nameof(Visual.Scale);
+
+ ///
+ protected override (Vector3?, Vector3?) GetParsedValues()
+ {
+ return (To?.ToVector3(), From?.ToVector3());
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Behavior/AutoHeightBehavior.cs b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/AutoHeightBehavior.cs
index 6c1a895b..a9aeb669 100644
--- a/src/Snap.Hutao/Snap.Hutao/Control/Behavior/AutoHeightBehavior.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/AutoHeightBehavior.cs
@@ -40,16 +40,22 @@ internal class AutoHeightBehavior : BehaviorBase
protected override void OnAssociatedObjectLoaded()
{
AssociatedObject.SizeChanged += OnSizeChanged;
+ UpdateElementHeight();
}
///
- protected override void OnAssociatedObjectUnloaded()
+ protected override void OnDetaching()
{
AssociatedObject.SizeChanged -= OnSizeChanged;
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
- AssociatedObject.Height = (double)((FrameworkElement)sender).ActualWidth * (TargetHeight / TargetWidth);
+ UpdateElementHeight();
+ }
+
+ private void UpdateElementHeight()
+ {
+ AssociatedObject.Height = (double)AssociatedObject.ActualWidth * (TargetHeight / TargetWidth);
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Abstraction/IAsyncInitializable.cs b/src/Snap.Hutao/Snap.Hutao/Core/Abstraction/IAsyncInitializable.cs
new file mode 100644
index 00000000..a1e36db2
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Abstraction/IAsyncInitializable.cs
@@ -0,0 +1,22 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Core.Abstraction;
+
+///
+/// 可异步初始化
+///
+internal interface IAsyncInitializable
+{
+ ///
+ /// 是否已经初始化完成
+ ///
+ public bool IsInitialized { get; }
+
+ ///
+ /// 异步初始化
+ ///
+ /// 取消令牌
+ /// 初始化任务
+ Task InitializeAsync(CancellationToken cancellationToken = default);
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Browser.cs b/src/Snap.Hutao/Snap.Hutao/Core/Browser.cs
index 2d5fd681..d3a3da91 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Browser.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Browser.cs
@@ -41,4 +41,4 @@ public static class Browser
failAction?.Invoke(ex);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Converting/Md5Convert.cs b/src/Snap.Hutao/Snap.Hutao/Core/Converting/Md5Convert.cs
new file mode 100644
index 00000000..699803ea
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Converting/Md5Convert.cs
@@ -0,0 +1,25 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Snap.Hutao.Core.Converting;
+
+///
+/// 支持Md5转换
+///
+internal abstract class Md5Convert
+{
+ ///
+ /// 获取字符串的MD5计算结果
+ ///
+ /// 源字符串
+ /// 计算的结果
+ public static string ToHexString(string source)
+ {
+ byte[] bytes = Encoding.UTF8.GetBytes(source);
+ byte[] hash = MD5.HashData(bytes);
+ return Convert.ToHexString(hash);
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs
index 2853a4df..eae06975 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs
@@ -2,9 +2,8 @@
// Licensed under the MIT license.
using Microsoft.Win32;
+using Snap.Hutao.Core.Converting;
using Snap.Hutao.Extension;
-using System.Security.Cryptography;
-using System.Text;
using Windows.ApplicationModel;
namespace Snap.Hutao.Core;
@@ -41,8 +40,6 @@ internal static class CoreEnvironment
{
string userName = Environment.UserName;
object? machineGuid = Registry.GetValue(CryptographyKey, MachineGuidValue, userName);
- byte[] bytes = Encoding.UTF8.GetBytes($"{userName}{machineGuid}");
- byte[] hash = MD5.HashData(bytes);
- return Convert.ToHexString(hash);
+ return Md5Convert.ToHexString($"{userName}{machineGuid}");
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/HttpJson.cs b/src/Snap.Hutao/Snap.Hutao/Core/HttpJson.cs
deleted file mode 100644
index b0df8fb2..00000000
--- a/src/Snap.Hutao/Snap.Hutao/Core/HttpJson.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (c) DGP Studio. All rights reserved.
-// Licensed under the MIT license.
-
-using System.Net.Http;
-
-namespace Snap.Hutao.Core;
-
-///
-/// Http Json 处理
-///
-public class HttpJson
-{
- private readonly Json json;
- private readonly HttpClient httpClient;
-
- ///
- /// 初始化一个新的 Http Json 处理 实例
- ///
- /// Json 处理器
- /// http 客户端
- public HttpJson(Json json, HttpClient httpClient)
- {
- this.json = json;
- this.httpClient = httpClient;
- }
-
- ///
- /// 从网站上下载json并转换为对象
- ///
- /// 对象的类型
- /// 链接
- /// 取消令牌
- /// Json字符串中的反序列化对象, 如果反序列化失败会抛出异常
- public async Task FromWebsiteAsync(string url, CancellationToken cancellationToken = default)
- {
- string response = await httpClient.GetStringAsync(url, cancellationToken);
- return json.ToObject(response);
- }
-}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Json.cs b/src/Snap.Hutao/Snap.Hutao/Core/Json.cs
deleted file mode 100644
index eb470f2a..00000000
--- a/src/Snap.Hutao/Snap.Hutao/Core/Json.cs
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright (c) DGP Studio. All rights reserved.
-// Licensed under the MIT license.
-
-using Newtonsoft.Json;
-using System.IO;
-
-namespace Snap.Hutao.Core;
-
-///
-/// Json操作
-///
-[Injection(InjectAs.Transient)]
-public class Json
-{
- private readonly ILogger logger;
-
- private readonly JsonSerializerSettings jsonSerializerSettings = new()
- {
- DateFormatString = "yyyy'-'MM'-'dd' 'HH':'mm':'ss.FFFFFFFK",
- Formatting = Formatting.Indented,
- };
-
- ///
- /// 初始化一个新的 Json操作 实例
- ///
- /// 日志器
- public Json(ILogger logger)
- {
- this.logger = logger;
- }
-
- ///
- /// 将JSON反序列化为指定的.NET类型
- ///
- /// 要反序列化的对象的类型
- /// 要反序列化的JSON
- /// Json字符串中的反序列化对象, 如果反序列化失败会抛出异常
- public T? ToObject(string value)
- {
- try
- {
- return JsonConvert.DeserializeObject(value);
- }
- catch (Exception ex)
- {
- logger.LogError("反序列化Json时遇到问题:{ex}", ex);
- }
-
- return default;
- }
-
- ///
- /// 将JSON反序列化为指定的.NET类型
- /// 若为null则返回一个新建的实例
- ///
- /// 指定的类型
- /// 字符串
- /// Json字符串中的反序列化对象, 如果反序列化失败会抛出异常
- public T ToObjectOrNew(string value)
- where T : new()
- {
- return ToObject(value) ?? new T();
- }
-
- ///
- /// 将指定的对象序列化为JSON字符串
- ///
- /// 要序列化的对象
- /// 对象的JSON字符串表示形式
- public string Stringify(object? value)
- {
- return JsonConvert.SerializeObject(value, jsonSerializerSettings);
- }
-
- ///
- /// 使用 , 和 从文件中读取后转化为实体类
- ///
- /// 要反序列化的对象的类型
- /// 存放JSON数据的文件路径
- /// JSON字符串中的反序列化对象, 如果反序列化失败则抛出异常,若文件不存在则返回
- public T? FromFile(string fileName)
- {
- if (File.Exists(fileName))
- {
- // FileShare.Read is important to read some file
- using (StreamReader sr = new(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)))
- {
- return ToObject(sr.ReadToEnd());
- }
- }
- else
- {
- return default;
- }
- }
-
- ///
- /// 使用 , 和 从文件中读取后转化为实体类
- /// 若为null则返回一个新建的实例
- ///
- /// 要反序列化的对象的类型
- /// 存放JSON数据的文件路径
- /// JSON字符串中的反序列化对象
- public T FromFileOrNew(string fileName)
- where T : new()
- {
- return FromFile(fileName) ?? new T();
- }
-
- ///
- /// 从文件中读取后转化为实体类
- ///
- /// 要反序列化的对象的类型
- /// 存放JSON数据的文件
- /// JSON字符串中的反序列化对象
- public T? FromFile(FileInfo file)
- {
- using (StreamReader sr = file.OpenText())
- {
- return ToObject(sr.ReadToEnd());
- }
- }
-
- ///
- /// 将对象保存到文件
- ///
- /// 文件名称
- /// 对象
- public void ToFile(string fileName, object? value)
- {
- File.WriteAllText(fileName, Stringify(value));
- }
-}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/SeparatorCommaInt32EnumerableConverter.cs b/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/SeparatorCommaInt32EnumerableConverter.cs
new file mode 100644
index 00000000..6c2cd69b
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/SeparatorCommaInt32EnumerableConverter.cs
@@ -0,0 +1,29 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Core.Json.Converter;
+
+///
+/// 逗号分隔列表转换器
+///
+internal class SeparatorCommaInt32EnumerableConverter : JsonConverter>
+{
+ ///
+ public override IEnumerable Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ string? team = reader.GetString();
+ IEnumerable? ids = team?.Split(',').Select(x => int.Parse(x));
+ return ids ?? Enumerable.Empty();
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, IEnumerable value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue(string.Join(',', value));
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Observable.cs b/src/Snap.Hutao/Snap.Hutao/Core/Observable.cs
deleted file mode 100644
index 9dc24c49..00000000
--- a/src/Snap.Hutao/Snap.Hutao/Core/Observable.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (c) DGP Studio. All rights reserved.
-// Licensed under the MIT license.
-
-using System.ComponentModel;
-using System.Runtime.CompilerServices;
-
-namespace Snap.Hutao.Core;
-
-///
-/// 简单的实现了 接口
-///
-public class Observable : INotifyPropertyChanged
-{
- ///
- public event PropertyChangedEventHandler? PropertyChanged;
-
- ///
- /// 设置字段的值
- ///
- /// 字段类型
- /// 现有值
- /// 新的值
- /// 属性名称
- protected void Set([NotNullIfNotNull("value")] ref T storage, T value, [CallerMemberName] string propertyName = default!)
- {
- if (Equals(storage, value))
- {
- return;
- }
-
- storage = value;
- OnPropertyChanged(propertyName);
- }
-
- ///
- /// 触发
- ///
- /// 属性名称
- protected void OnPropertyChanged(string propertyName)
- {
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
- }
-}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Setting/LocalSetting.cs b/src/Snap.Hutao/Snap.Hutao/Core/Setting/LocalSetting.cs
index 80c64e09..553e7997 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Setting/LocalSetting.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Setting/LocalSetting.cs
@@ -68,7 +68,7 @@ internal static class LocalSetting
}
else
{
- Set(key, defaultValue);
+ SetValueType(key, defaultValue);
return defaultValue;
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs b/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs
index fd24a47c..7abe5d52 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs
@@ -13,7 +13,7 @@ namespace Snap.Hutao.Core.Validation;
public static class Must
{
///
- /// Throws an exception if the specified parameter's value is null.
+ /// Throws an if the specified parameter's value is null.
///
/// The type of the parameter.
/// The value of the argument.
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/WebView2Helper.cs b/src/Snap.Hutao/Snap.Hutao/Core/WebView2Helper.cs
index 83c948d3..624b44cf 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/WebView2Helper.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/WebView2Helper.cs
@@ -9,7 +9,7 @@ namespace Snap.Hutao.Core;
/// 检测 WebView2运行时 是否存在
/// 不再使用注册表检查方式
///
-internal class WebView2Helper
+internal static class WebView2Helper
{
private static bool hasEverDetected = false;
private static bool isSupported = false;
diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtensions.cs
new file mode 100644
index 00000000..e4abc2f9
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtensions.cs
@@ -0,0 +1,69 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Snap.Hutao.Extension;
+
+///
+/// 扩展
+///
+public static class EnumerableExtensions
+{
+ ///
+ /// 将二维可枚举对象一维化
+ ///
+ /// 源类型
+ /// 源
+ /// 扁平的对象
+ public static IEnumerable Flatten(this IEnumerable> source)
+ {
+ return source.SelectMany(x => x);
+ }
+
+ ///
+ /// 计数
+ ///
+ /// 源类型
+ /// 计数的键类型
+ /// 源
+ /// 键选择器
+ /// 计数表
+ public static IEnumerable> CountBy(this IEnumerable source, Func keySelector)
+ where TKey : notnull, IEquatable
+ {
+ CounterInt32 counter = new();
+ foreach (TSource item in source)
+ {
+ counter.Increase(keySelector(item));
+ }
+
+ return counter;
+ }
+
+ ///
+ /// 表示一个对 类型的计数器
+ ///
+ /// 待计数的类型
+ private class CounterInt32 : Dictionary
+ where TItem : notnull, IEquatable
+ {
+ ///
+ /// 增加计数器
+ ///
+ /// 物品
+ public void Increase(TItem? item)
+ {
+ if (item != null)
+ {
+ if (!ContainsKey(item))
+ {
+ this[item] = 0;
+ }
+
+ this[item] += 1;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/GlobalUsing.cs b/src/Snap.Hutao/Snap.Hutao/GlobalUsing.cs
index 27a15f43..94696f57 100644
--- a/src/Snap.Hutao/Snap.Hutao/GlobalUsing.cs
+++ b/src/Snap.Hutao/Snap.Hutao/GlobalUsing.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license.
global using CommunityToolkit.Mvvm.DependencyInjection;
+global using Microsoft;
global using Microsoft.Extensions.Logging;
global using Snap.Hutao.Core.DependencyInjection;
global using Snap.Hutao.Core.DependencyInjection.Annotation;
diff --git a/src/Snap.Hutao/Snap.Hutao/IocConfiguration.cs b/src/Snap.Hutao/Snap.Hutao/IocConfiguration.cs
index 44ca25e9..9b2167dc 100644
--- a/src/Snap.Hutao/Snap.Hutao/IocConfiguration.cs
+++ b/src/Snap.Hutao/Snap.Hutao/IocConfiguration.cs
@@ -76,11 +76,7 @@ internal static class IocConfiguration
bool shouldMigrate = false;
- if (!myDocument.FileExists(dbFile))
- {
- shouldMigrate = true;
- }
- else
+ if (myDocument.FileExists(dbFile))
{
string? versionString = LocalSetting.Get(SettingKeys.LastAppVersion);
if (Version.TryParse(versionString, out Version? lastVersion))
@@ -91,6 +87,10 @@ internal static class IocConfiguration
}
}
}
+ else
+ {
+ shouldMigrate = true;
+ }
if (shouldMigrate)
{
@@ -104,6 +104,6 @@ internal static class IocConfiguration
LocalSetting.Set(SettingKeys.LastAppVersion, CoreEnvironment.Version.ToString());
return services
- .AddPooledDbContextFactory(builder => builder.UseSqlite(sqlConnectionString));
+ .AddDbContextPool(builder => builder.UseSqlite(sqlConnectionString));
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs
new file mode 100644
index 00000000..3fb45900
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs
@@ -0,0 +1,36 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace Snap.Hutao.Model.Entity;
+
+///
+/// 设置入口
+///
+[Table("settings")]
+public class SettingEntry
+{
+ ///
+ /// 构造一个新的设置入口
+ ///
+ /// 键
+ /// 值
+ public SettingEntry(string key, string? value)
+ {
+ Key = key;
+ Value = value;
+ }
+
+ ///
+ /// 键
+ ///
+ [Key]
+ public string Key { get; set; }
+
+ ///
+ /// 值
+ ///
+ public string? Value { get; set; }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/User.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/User.cs
new file mode 100644
index 00000000..754def2f
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/User.cs
@@ -0,0 +1,19 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace Snap.Hutao.Model.Entity;
+
+///
+/// 用户
+///
+[Table("users")]
+public class User
+{
+ ///
+ /// 用户的Cookie
+ ///
+ public string? Cookie { get; set; }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/INavigationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/INavigationService.cs
index 2e5416e7..b21c8509 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/INavigationService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/INavigationService.cs
@@ -62,4 +62,4 @@ public interface INavigationService
/// 同步的页面类型
/// 是否同步成功
bool SyncSelectedNavigationViewItemWith(Type pageType);
-}
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IUserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IUserService.cs
new file mode 100644
index 00000000..413512bc
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IUserService.cs
@@ -0,0 +1,17 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Model.Entity;
+
+namespace Snap.Hutao.Service.Abstraction;
+
+///
+/// 用户服务
+///
+public interface IUserService
+{
+ ///
+ /// 获取当前用户信息
+ ///
+ User CurrentUser { get; }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AnnouncementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/AnnouncementService.cs
index 5654cb8e..142db6f6 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/AnnouncementService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/AnnouncementService.cs
@@ -28,7 +28,7 @@ internal class AnnouncementService : IAnnouncementService
///
public async Task GetAnnouncementsAsync(ICommand openAnnouncementUICommand, CancellationToken cancellationToken = default)
{
- AnnouncementWrapper? wrapper = await announcementProvider.GetAnnouncementWrapperAsync(cancellationToken);
+ AnnouncementWrapper? wrapper = await announcementProvider.GetAnnouncementsAsync(cancellationToken);
List contents = await announcementProvider.GetAnnouncementContentsAsync(cancellationToken);
Dictionary contentMap = contents
@@ -42,12 +42,6 @@ internal class AnnouncementService : IAnnouncementService
// 将公告内容联入公告列表
JoinAnnouncements(openAnnouncementUICommand, contentMap, announcementListWrappers);
- // we only cares about activities
- if (announcementListWrappers[0].List is List activities)
- {
- AdjustActivitiesTime(ref activities);
- }
-
return wrapper;
}
@@ -64,7 +58,6 @@ internal class AnnouncementService : IAnnouncementService
{
listWrapper.List?.ForEach(item =>
{
- // fix key issue
if (contentMap.TryGetValue(item.AnnId, out string? rawContent))
{
// remove tag
@@ -76,26 +69,4 @@ internal class AnnouncementService : IAnnouncementService
});
});
}
-
- private void AdjustActivitiesTime(ref List activities)
- {
- // Match yyyy/MM/dd HH:mm:ss time format
- Regex dateTimeRegex = new(@"(\d+\/\d+\/\d+\s\d+:\d+:\d+)", RegexOptions.IgnoreCase | RegexOptions.Multiline);
- activities.ForEach(item =>
- {
- Match matched = dateTimeRegex.Match(item.Content ?? string.Empty);
- if (matched.Success && DateTime.TryParse(matched.Value, out DateTime time))
- {
- if (time > item.StartTime && time < item.EndTime)
- {
- item.StartTime = time;
- }
- }
- });
-
- activities = activities
- .OrderBy(i => i.StartTime)
- .ThenBy(i => i.EndTime)
- .ToList();
- }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
index d7cc05da..7336b6b7 100644
--- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
+++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
@@ -13,8 +13,7 @@
enable
true
zh-CN
- zh-cn
-
+ zh-CN
False
True
F8C2255969BEA4A681CED102771BF807856AEC02
@@ -53,15 +52,11 @@
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs
index b19adabe..3ed10d9f 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs
@@ -26,7 +26,6 @@ public sealed partial class MainView : UserControl
navigationService = Ioc.Default.GetRequiredService();
navigationService.Initialize(NavView, ContentFrame);
-
- navigationService.Navigate();
+ navigationService.Navigate(true);
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml
index 53c5cd4d..2d3351d5 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml
@@ -13,7 +13,7 @@
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
xmlns:shcc="using:Snap.Hutao.Control.Cancellable"
- xmlns:shvc="using:Snap.Hutao.View.Converter"
+ xmlns:shvc="using:Snap.Hutao.View.Converter" xmlns:shca="using:Snap.Hutao.Control.Animation"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
@@ -82,18 +82,10 @@
-
+
-
+
@@ -105,12 +97,6 @@
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Visibility="{Binding ShouldShowTimeDescription,Converter={StaticResource BoolToVisibilityConverter}}">
-
/// 公告列表
///
- public static readonly string AnnList = $"{Hk4eApi}/common/hk4e_cn/announcement/api/getAnnList?{AnnouncementQuery}";
+ public const string AnnList = $"{Hk4eApi}/common/hk4e_cn/announcement/api/getAnnList?{AnnouncementQuery}";
///
/// 公告内容
///
- public static readonly string AnnContent = $"{Hk4eApi}/common/hk4e_cn/announcement/api/getAnnContent?{AnnouncementQuery}";
+ public const string AnnContent = $"{Hk4eApi}/common/hk4e_cn/announcement/api/getAnnContent?{AnnouncementQuery}";
+ ///
+ /// 游戏记录主页
+ ///
+ public const string GameRecordIndex = $"{ApiTakumiRecordApi}/index?role_id={{0}}&server={{1}}";
+
+ ///
+ /// 深渊信息
+ ///
+ public const string SpiralAbyss = $"{ApiTakumiRecordApi}/spiralAbyss?schedule_type={{0}}&role_id={{1}}&server={{2}}";
+
+ ///
+ /// 角色信息
+ ///
+ public const string Character = $"{ApiTakumiRecordApi}/character";
+
+ public const string UserGameRoles = $"{ApiTakumi}/binding/api/getUserGameRolesByCookie?game_biz=hk4e_cn";
+
+ private const string ApiTakumi = "https://api-takumi.mihoyo.com";
+ private const string ApiTakumiRecord = "https://api-takumi-record.mihoyo.com";
+ private const string ApiTakumiRecordApi = $"{ApiTakumiRecord}/game_record/app/genshin/api";
private const string Hk4eApi = "https://hk4e-api.mihoyo.com";
+
private const string AnnouncementQuery = "game=hk4e&game_biz=hk4e_cn&lang=zh-cn&bundle_id=hk4e_cn&platform=pc®ion=cn_gf01&level=55&uid=100000000";
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider2.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider2.cs
new file mode 100644
index 00000000..ba258ced
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider2.cs
@@ -0,0 +1,77 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Core.Converting;
+using Snap.Hutao.Core.Json;
+using Snap.Hutao.Web.Response;
+using System.Linq;
+
+namespace Snap.Hutao.Web.Hoyolab.DynamicSecret;
+
+///
+/// 为MiHoYo接口请求器 提供2代动态密钥
+///
+internal abstract class DynamicSecretProvider2 : Md5Convert
+{
+ ///
+ /// 似乎已经与版本号无关,自2.11.1以来未曾改变salt
+ ///
+ public const string AppVersion = "2.16.1";
+
+ ///
+ /// 米游社的盐
+ /// 计算过程:https://gist.github.com/Lightczx/373c5940b36e24b25362728b52dec4fd
+ ///
+ private static readonly string APISalt = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs";
+ private static readonly Random Random = new();
+
+ ///
+ /// 创建动态密钥
+ ///
+ /// json格式化器
+ /// 查询url
+ /// 请求体
+ /// 密钥
+ public static string Create(Json json, string queryUrl, object? postBody = null)
+ {
+ // unix timestamp
+ long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
+
+ // random
+ string r = GetRandomString();
+
+ // body
+ string b = postBody is null ? string.Empty : json.Stringify(postBody);
+
+ // query
+ string q = string.Empty;
+ string? query = new UriBuilder(queryUrl).Query;
+ if (!string.IsNullOrEmpty(query))
+ {
+ q = string.Join("&", query.Split('&').OrderBy(x => x));
+ }
+
+ // check
+ string check = ToHexString($"salt={APISalt}&t={t}&r={r}&b={b}&q={q}");
+
+ return $"{t},{r},{check}";
+ }
+
+ private static string GetRandomString()
+ {
+ // 原汁原味
+ // https://github.com/Azure99/GenshinPlayerQuery/issues/20#issuecomment-913641512
+ // int rand = (int)((Random.Next() / (int.MaxValue + 3D) * 100000D) + 100000D) % 1000000;
+ // if (rand < 100001)
+ // {
+ // rand += 542367;
+ // }
+ int rand = Random.Next(100000, 200000);
+ if (rand == 100000)
+ {
+ rand = 642367;
+ }
+
+ return rand.ToString();
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/RequesterExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/RequesterExtensions.cs
new file mode 100644
index 00000000..649fcbe2
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/RequesterExtensions.cs
@@ -0,0 +1,55 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Web.Request;
+using Snap.Hutao.Web.Response;
+
+namespace Snap.Hutao.Web.Hoyolab.DynamicSecret;
+
+///
+/// 动态密钥扩展
+///
+internal static class RequesterExtensions
+{
+ ///
+ /// 使用二代动态密钥执行 GET 操作
+ ///
+ /// 返回的类类型
+ /// 请求器
+ /// 地址
+ /// 取消令牌
+ /// 响应
+ public static async Task?> GetUsingDS2Async(
+ this Requester requester,
+ string url,
+ CancellationToken cancellationToken = default)
+ {
+ requester.Headers["DS"] = DynamicSecretProvider2.Create(requester.Json, url);
+
+ return await requester
+ .GetAsync(url, cancellationToken)
+ .ConfigureAwait(false);
+ }
+
+ ///
+ /// 使用二代动态密钥执行 POST 操作
+ ///
+ /// 返回的类类型
+ /// 请求器
+ /// 地址
+ /// post数据
+ /// 取消令牌
+ /// 响应
+ public static async Task?> PostUsingDS2Async(
+ this Requester requester,
+ string url,
+ object data,
+ CancellationToken cancellationToken = default)
+ {
+ requester.Headers["DS"] = DynamicSecretProvider2.Create(requester.Json, url, data);
+
+ return await requester
+ .PostAsync(url, data, cancellationToken)
+ .ConfigureAwait(false);
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/Announcement.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/Announcement.cs
index 8fe42e99..a8cc5767 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/Announcement.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/Announcement.cs
@@ -100,19 +100,19 @@ public class Announcement : AnnouncementContent
/// 类型标签
///
[JsonPropertyName("type_label")]
- public string? TypeLabel { get; set; }
+ public string TypeLabel { get; set; } = default!;
///
/// 标签文本
///
[JsonPropertyName("tag_label")]
- public string? TagLabel { get; set; }
+ public string TagLabel { get; set; } = default!;
///
/// 标签图标
///
[JsonPropertyName("tag_icon")]
- public string? TagIcon { get; set; }
+ public string TagIcon { get; set; } = default!;
///
/// 登录提醒
@@ -156,13 +156,13 @@ public class Announcement : AnnouncementContent
/// 标签开始时间
///
[JsonPropertyName("tag_start_time")]
- public string? TagStartTime { get; set; }
+ public string TagStartTime { get; set; } = default!;
///
/// 标签结束时间
///
[JsonPropertyName("tag_end_time")]
- public string? TagEndTime { get; set; }
+ public string TagEndTime { get; set; } = default!;
///
/// 提醒版本
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementContent.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementContent.cs
index 3698463e..71159b01 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementContent.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementContent.cs
@@ -20,30 +20,30 @@ public class AnnouncementContent
/// 公告标题
///
[JsonPropertyName("title")]
- public string? Title { get; set; }
+ public string Title { get; set; } = default!;
///
/// 副标题
///
[JsonPropertyName("subtitle")]
- public string? Subtitle { get; set; }
+ public string Subtitle { get; set; } = default!;
///
/// 横幅Url
///
[JsonPropertyName("banner")]
- public string? Banner { get; set; }
+ public string Banner { get; set; } = default!;
///
/// 内容字符串
/// 可能包含了一些html格式
///
[JsonPropertyName("content")]
- public string? Content { get; set; }
+ public string Content { get; set; } = default!;
///
/// 语言
///
[JsonPropertyName("lang")]
- public string? Lang { get; set; }
+ public string Lang { get; set; } = default!;
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementListWrapper.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementListWrapper.cs
index bd9ab6a9..201d8b31 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementListWrapper.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementListWrapper.cs
@@ -21,5 +21,5 @@ public class AnnouncementListWrapper : ListWrapper
/// 类型标签
///
[JsonPropertyName("type_label")]
- public string? TypeLabel { get; set; }
+ public string TypeLabel { get; set; } = default!;
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementProvider.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementProvider.cs
index 603a31ff..8202ad9b 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementProvider.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementProvider.cs
@@ -20,7 +20,6 @@ internal class AnnouncementProvider
/// 构造一个新的公告提供器
///
/// 请求器
- /// GZip 请求器
public AnnouncementProvider(Requester requester)
{
this.requester = requester;
@@ -31,12 +30,13 @@ internal class AnnouncementProvider
///
/// 取消令牌
/// 公告列表
- public async Task GetAnnouncementWrapperAsync(CancellationToken cancellationToken = default)
+ public async Task GetAnnouncementsAsync(CancellationToken cancellationToken = default)
{
Response? resp = await requester
.Reset()
.GetAsync(ApiEndpoints.AnnList, cancellationToken)
.ConfigureAwait(false);
+
return resp?.Data;
}
@@ -51,9 +51,10 @@ internal class AnnouncementProvider
Response>? resp = await requester
.Reset()
- .AddHeader("Accept", RequestOptions.Json)
+ .AddHeader(RequestHeaders.Accept, RequestOptions.Json)
.GetAsync>(ApiEndpoints.AnnContent, cancellationToken)
.ConfigureAwait(false);
+
return resp?.Data?.List ?? new();
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementType.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementType.cs
index d37c1d73..510a959b 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementType.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementType.cs
@@ -20,11 +20,11 @@ public class AnnouncementType
/// 名称
///
[JsonPropertyName("name")]
- public string? Name { get; set; }
+ public string Name { get; set; } = default!;
///
/// 国际化名称
///
[JsonPropertyName("mi18n_name")]
- public string? MI18NName { get; set; }
+ public string MI18NName { get; set; } = default!;
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementWrapper.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementWrapper.cs
index bf374452..8eb65b3b 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementWrapper.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementWrapper.cs
@@ -22,7 +22,7 @@ public class AnnouncementWrapper : ListWrapper
/// 类型列表
///
[JsonPropertyName("type_list")]
- public List? TypeList { get; set; }
+ public List TypeList { get; set; } = default!;
///
/// 提醒
@@ -46,5 +46,5 @@ public class AnnouncementWrapper : ListWrapper
/// 时间戳
///
[JsonPropertyName("t")]
- public string? TimeStamp { get; set; }
+ public string TimeStamp { get; set; } = default!;
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRole.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRole.cs
new file mode 100644
index 00000000..e57bdb75
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRole.cs
@@ -0,0 +1,75 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.Binding;
+
+///
+/// 用户游戏角色
+///
+public record UserGameRole
+{
+ ///
+ /// hk4e_cn for Genshin Impact
+ ///
+ [JsonPropertyName("game_biz")]
+ public string GameBiz { get; set; } = default!;
+
+ ///
+ /// 服务器
+ ///
+ [JsonPropertyName("region")]
+ public string Region { get; set; } = default!;
+
+ ///
+ /// 游戏Uid
+ ///
+ [JsonPropertyName("game_uid")]
+ public string GameUid { get; set; } = default!;
+
+ ///
+ /// 昵称
+ ///
+ [JsonPropertyName("nickname")]
+ public string Nickname { get; set; } = default!;
+
+ ///
+ /// 等级
+ ///
+ [JsonPropertyName("level")]
+ public int Level { get; set; }
+
+ ///
+ /// 是否选中
+ ///
+ [JsonPropertyName("is_chosen")]
+ public bool IsChosen { get; set; }
+
+ ///
+ /// 地区名称
+ ///
+ [JsonPropertyName("region_name")]
+ public string RegionName { get; set; } = default!;
+
+ ///
+ /// 是否为官服
+ ///
+ [JsonPropertyName("is_official")]
+ public string IsOfficial { get; set; } = default!;
+
+ ///
+ /// 转化为
+ ///
+ /// 一个等价的 实例
+ public PlayerUid AsPlayerUid()
+ {
+ return new PlayerUid(GameUid, Region);
+ }
+
+ ///
+ public override string ToString()
+ {
+ return $"{Nickname} | {RegionName} | Lv.{Level}";
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRoleProvider.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRoleProvider.cs
new file mode 100644
index 00000000..4088d32d
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRoleProvider.cs
@@ -0,0 +1,48 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Service.Abstraction;
+using Snap.Hutao.Web.Request;
+using Snap.Hutao.Web.Response;
+using System.Collections.Generic;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.Binding;
+
+///
+/// 用户游戏角色提供器
+///
+[Injection(InjectAs.Transient)]
+internal class UserGameRoleProvider
+{
+ private readonly IUserService userService;
+ private readonly Requester requester;
+
+ ///
+ /// 构造一个新的用户游戏角色提供器
+ ///
+ /// 用户服务
+ /// 请求器
+ public UserGameRoleProvider(IUserService userService, Requester requester)
+ {
+ this.userService = userService;
+ this.requester = requester;
+ }
+
+ ///
+ /// 获取用户角色信息
+ ///
+ /// 取消令牌
+ /// 用户角色信息
+ public async Task> GetUserGameRolesAsync(CancellationToken cancellationToken = default)
+ {
+ Response>? resp = await requester
+ .Reset()
+ .SetAcceptJson()
+ .SetCommonUA()
+ .SetRequestWithHyperion()
+ .SetUser(userService.CurrentUser)
+ .GetAsync>(ApiEndpoints.UserGameRoles, cancellationToken)
+ .ConfigureAwait(false);
+ return resp?.Data?.List ?? new();
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Avatar.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Avatar.cs
new file mode 100644
index 00000000..6cc04c6d
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Avatar.cs
@@ -0,0 +1,60 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
+
+///
+/// 角色的基础信息
+///
+public class Avatar
+{
+ ///
+ /// Id
+ ///
+ [JsonPropertyName("id")]
+ public int Id { get; set; }
+
+ ///
+ /// 图片Url
+ ///
+ [JsonPropertyName("image")]
+ public string Image { get; set; } = default!;
+
+ ///
+ /// 名称
+ ///
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = default!;
+
+ ///
+ /// 元素英文名称
+ ///
+ [JsonPropertyName("element")]
+ public string Element { get; set; } = default!;
+
+ ///
+ /// 好感度
+ ///
+ [JsonPropertyName("fetter")]
+ public int Fetter { get; set; }
+
+ ///
+ /// 等级
+ ///
+ [JsonPropertyName("level")]
+ public int Level { get; set; }
+
+ ///
+ /// 稀有度
+ ///
+ [JsonPropertyName("rarity")]
+ public Rarity Rarity { get; set; }
+
+ ///
+ /// 激活的命座数
+ ///
+ [JsonPropertyName("actived_constellation_num")]
+ public int ActivedConstellationNum { get; set; }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Character.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Character.cs
new file mode 100644
index 00000000..44102361
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Character.cs
@@ -0,0 +1,50 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
+
+///
+/// 角色详细详细
+///
+public class Character : Avatar
+{
+ ///
+ /// 角色图片
+ ///
+ [Obsolete("we don't want to use this ugly pic here")]
+ [JsonPropertyName("image")]
+ public new string Image { get; set; } = default!;
+
+ ///
+ /// 图标
+ ///
+ [JsonPropertyName("icon")]
+ public string Icon { get; set; } = default!;
+
+ ///
+ /// 武器
+ ///
+ [JsonPropertyName("weapon")]
+ public Weapon Weapon { get; set; } = default!;
+
+ ///
+ /// 圣遗物
+ ///
+ [JsonPropertyName("reliquaries")]
+ public List Reliquaries { get; set; } = default!;
+
+ ///
+ /// 命座
+ ///
+ [JsonPropertyName("constellations")]
+ public List Constellations { get; set; } = default!;
+
+ ///
+ /// 时装
+ ///
+ [JsonPropertyName("costumes")]
+ public List Costumes { get; set; } = default!;
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/CharacterWrapper.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/CharacterWrapper.cs
new file mode 100644
index 00000000..8366e8db
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/CharacterWrapper.cs
@@ -0,0 +1,19 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
+
+///
+/// 包装详细角色信息列表
+///
+public class CharacterWrapper
+{
+ ///
+ /// 角色列表
+ ///
+ [JsonPropertyName("avatars")]
+ public List Avatars { get; set; } = default!;
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Constellation.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Constellation.cs
new file mode 100644
index 00000000..4332df1c
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Constellation.cs
@@ -0,0 +1,49 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
+
+///
+/// 命座
+///
+public class Constellation
+{
+ ///
+ /// Id
+ ///
+ [JsonPropertyName("id")]
+ public int Id { get; set; }
+
+ ///
+ /// 名称
+ ///
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = default!;
+
+ ///
+ /// 图标
+ ///
+ [JsonPropertyName("icon")]
+ public string Icon { get; set; } = default!;
+
+ ///
+ /// 效果描述
+ ///
+ [JsonPropertyName("effect")]
+ public string Effect { get; set; } = default!;
+
+ ///
+ /// 是否激活
+ ///
+ [JsonPropertyName("is_actived")]
+ public bool IsActived { get; set; }
+
+ ///
+ /// 位置
+ ///
+ [JsonPropertyName("pos")]
+ public int Position { get; set; }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Costume.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Costume.cs
new file mode 100644
index 00000000..55156d88
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Costume.cs
@@ -0,0 +1,32 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
+
+///
+/// 角色装扮
+///
+public class Costume
+{
+ ///
+ /// Id
+ ///
+ [JsonPropertyName("id")]
+ public int Id { get; set; }
+
+ ///
+ /// 名称
+ ///
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = default!;
+
+ ///
+ /// 图标
+ ///
+ [Obsolete("不应使用此处的图标")]
+ [JsonPropertyName("icon")]
+ public string Icon { get; set; } = default!;
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Rarity.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Rarity.cs
new file mode 100644
index 00000000..0e2c964f
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Rarity.cs
@@ -0,0 +1,35 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
+
+///
+/// 稀有度
+///
+public enum Rarity
+{
+ ///
+ /// 一星
+ ///
+ Gray = 1,
+
+ ///
+ /// 二星
+ ///
+ Green = 2,
+
+ ///
+ /// 三星
+ ///
+ Blue = 3,
+
+ ///
+ /// 四星
+ ///
+ Purple = 4,
+
+ ///
+ /// 五星
+ ///
+ Orange = 5,
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Reliquary.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Reliquary.cs
new file mode 100644
index 00000000..4e4e5fa8
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Reliquary.cs
@@ -0,0 +1,61 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
+
+///
+/// 圣遗物
+///
+public class Reliquary
+{
+ ///
+ /// Id
+ ///
+ [JsonPropertyName("id")]
+ public int Id { get; set; }
+
+ ///
+ /// 名称
+ ///
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = default!;
+
+ ///
+ /// 图标
+ ///
+ [JsonPropertyName("icon")]
+ public string Icon { get; set; } = default!;
+
+ ///
+ /// 部位
+ ///
+ [JsonPropertyName("pos")]
+ public ReliquaryPosition Position { get; set; }
+
+ ///
+ /// 稀有度
+ ///
+ [JsonPropertyName("rarity")]
+ public Rarity Rarity { get; set; }
+
+ ///
+ /// 等级
+ ///
+ [JsonPropertyName("level")]
+ public int Level { get; set; }
+
+ ///
+ /// 圣遗物套装
+ ///
+ [JsonPropertyName("set")]
+ public ReliquarySet ReliquarySet { get; set; } = default!;
+
+ ///
+ /// 部位名称
+ ///
+ [JsonPropertyName("pos_name")]
+ public string PositionName { get; set; } = default!;
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/ReliquaryAffix.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/ReliquaryAffix.cs
new file mode 100644
index 00000000..fe8a5ade
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/ReliquaryAffix.cs
@@ -0,0 +1,25 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
+
+///
+/// 圣遗物套装效果
+///
+public class ReliquaryAffix
+{
+ ///
+ /// 激活个数
+ ///
+ [JsonPropertyName("activation_number")]
+ public int ActivationNumber { get; set; }
+
+ ///
+ /// 效果描述
+ ///
+ [JsonPropertyName("effect")]
+ public string? Effect { get; set; }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/ReliquaryPosition.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/ReliquaryPosition.cs
new file mode 100644
index 00000000..a14a76d3
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/ReliquaryPosition.cs
@@ -0,0 +1,35 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
+
+///
+/// 圣遗物部位
+///
+public enum ReliquaryPosition
+{
+ ///
+ /// 生之花
+ ///
+ FlowerOfLife = 1,
+
+ ///
+ /// 死之羽
+ ///
+ PlumeOfDeath = 2,
+
+ ///
+ /// 时之沙
+ ///
+ SandOfEon = 3,
+
+ ///
+ /// 空之杯
+ ///
+ GobletOfEonothem = 4,
+
+ ///
+ /// 理之冠
+ ///
+ CircletOfLogos = 5,
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/ReliquarySet.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/ReliquarySet.cs
new file mode 100644
index 00000000..b68b34ce
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/ReliquarySet.cs
@@ -0,0 +1,31 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
+
+///
+/// 圣遗物套装信息
+///
+public class ReliquarySet
+{
+ ///
+ /// Id
+ ///
+ [JsonPropertyName("id")]
+ public int Id { get; set; }
+
+ ///
+ /// 套装名称
+ ///
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = default!;
+
+ ///
+ /// 套装效果
+ ///
+ [JsonPropertyName("affixes")]
+ public List Affixes { get; set; } = default!;
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Weapon.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Weapon.cs
new file mode 100644
index 00000000..9e32d5a4
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Weapon.cs
@@ -0,0 +1,63 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
+
+///
+/// 武器信息
+///
+public class Weapon
+{
+ ///
+ /// Id
+ ///
+ [JsonPropertyName("id")] public int Id { get; set; }
+
+ ///
+ /// 名称
+ ///
+ [JsonPropertyName("name")] public string Name { get; set; } = default!;
+
+ ///
+ /// 图标
+ ///
+ [JsonPropertyName("icon")] public string Icon { get; set; } = default!;
+
+ ///
+ /// 类型
+ ///
+ [JsonPropertyName("type")] public WeaponType Type { get; set; }
+
+ ///
+ /// 稀有度
+ ///
+ [JsonPropertyName("rarity")] public Rarity Rarity { get; set; }
+
+ ///
+ /// 等级
+ ///
+ [JsonPropertyName("level")] public int Level { get; set; }
+
+ ///
+ /// 突破等级
+ ///
+ [JsonPropertyName("promote_level")] public int PromoteLevel { get; set; }
+
+ ///
+ /// 类型名称
+ ///
+ [JsonPropertyName("type_name")] public string TypeName { get; set; } = default!;
+
+ ///
+ /// 武器介绍
+ ///
+ [JsonPropertyName("desc")] public string Description { get; set; } = default!;
+
+ ///
+ /// 精炼等级
+ ///
+ [JsonPropertyName("affix_level")] public int AffixLevel { get; set; }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/WeaponType.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/WeaponType.cs
new file mode 100644
index 00000000..154399d3
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/WeaponType.cs
@@ -0,0 +1,35 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
+
+///
+/// 武器类型
+///
+public enum WeaponType
+{
+ ///
+ /// 单手剑
+ ///
+ Sword = 1,
+
+ ///
+ /// 法器
+ ///
+ Catalyst = 10,
+
+ ///
+ /// 双手剑
+ ///
+ Claymore = 11,
+
+ ///
+ /// 弓
+ ///
+ Bow = 12,
+
+ ///
+ /// 长柄武器
+ ///
+ Polearm = 13,
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordProvider.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordProvider.cs
new file mode 100644
index 00000000..d9614c45
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordProvider.cs
@@ -0,0 +1,118 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Service.Abstraction;
+using Snap.Hutao.Web.Hoyolab.DynamicSecret;
+using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
+using Snap.Hutao.Web.Request;
+using Snap.Hutao.Web.Response;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
+
+///
+/// 游戏记录提供器
+///
+[Injection(InjectAs.Transient)]
+internal class GameRecordProvider
+{
+ private readonly IUserService userService;
+ private readonly Requester requester;
+
+ ///
+ /// 构造一个新的游戏记录提供器
+ ///
+ /// 用户服务
+ /// 请求器
+ public GameRecordProvider(IUserService userService, Requester requester)
+ {
+ this.userService = userService;
+ this.requester = requester;
+ }
+
+ ///
+ /// 获取玩家基础信息
+ ///
+ /// uid
+ /// 取消令牌
+ /// 玩家的基础信息
+ public async Task GetPlayerInfoAsync(PlayerUid uid, CancellationToken token)
+ {
+ string url = string.Format(ApiEndpoints.GameRecordIndex, uid.Value, uid.Region);
+
+ Response? resp = await PrepareRequester()
+ .GetUsingDS2Async(url, token)
+ .ConfigureAwait(false);
+
+ return resp?.Data;
+ }
+
+ ///
+ /// 获取玩家深渊信息
+ ///
+ /// uid
+ /// 1:当期,2:上期
+ /// 取消令牌
+ /// 深渊信息
+ public async Task GetSpiralAbyssAsync(PlayerUid uid, SpiralAbyssSchedule schedule, CancellationToken token = default)
+ {
+ string url = string.Format(ApiEndpoints.SpiralAbyss, (int)schedule, uid.Value, uid.Region);
+
+ Response? resp = await PrepareRequester()
+ .GetUsingDS2Async(url, token)
+ .ConfigureAwait(false);
+
+ return resp?.Data;
+ }
+
+ ///
+ /// 获取玩家角色详细信息
+ ///
+ /// uid
+ /// 玩家的基础信息
+ /// 取消令牌
+ /// 角色列表
+ public async Task> GetCharactersAsync(PlayerUid uid, PlayerInfo playerInfo, CancellationToken cancellationToken = default)
+ {
+ CharacterData data = new(uid, playerInfo.Avatars.Select(x => x.Id));
+
+ Response? resp = await PrepareRequester()
+ .PostUsingDS2Async(ApiEndpoints.Character, data, cancellationToken)
+ .ConfigureAwait(false);
+
+ return resp?.Data?.Avatars ?? new();
+ }
+
+ private Requester PrepareRequester()
+ {
+ return requester
+ .Reset()
+ .SetAcceptJson()
+ .AddHeader(RequestHeaders.AppVersion, DynamicSecretProvider2.AppVersion)
+ .SetCommonUA()
+ .AddHeader(RequestHeaders.ClientType, RequestOptions.DefaultClientType)
+ .SetRequestWithHyperion()
+ .SetUser(userService.CurrentUser);
+ }
+
+ private class CharacterData
+ {
+ public CharacterData(PlayerUid uid, IEnumerable characterIds)
+ {
+ CharacterIds = characterIds;
+ Uid = uid.Value;
+ Server = uid.Region;
+ }
+
+ [JsonPropertyName("character_ids")]
+ public IEnumerable CharacterIds { get; }
+
+ [JsonPropertyName("role_id")]
+ public string Uid { get; }
+
+ [JsonPropertyName("server")]
+ public string Server { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Home.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Home.cs
new file mode 100644
index 00000000..9d493ace
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Home.cs
@@ -0,0 +1,60 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
+
+///
+/// 家园信息
+///
+public class Home
+{
+ ///
+ /// 等级
+ ///
+ [JsonPropertyName("level")]
+ public int Level { get; set; }
+
+ ///
+ /// 历史访客数
+ ///
+ [JsonPropertyName("visit_num")]
+ public int VisitNum { get; set; }
+
+ ///
+ /// 最高洞天仙力
+ ///
+ [JsonPropertyName("comfort_num")]
+ public int ComfortNum { get; set; }
+
+ ///
+ /// 获得摆设数
+ ///
+ [JsonPropertyName("item_num")]
+ public int ItemNum { get; set; }
+
+ ///
+ /// 洞天名称
+ ///
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = default!;
+
+ ///
+ /// 图标
+ ///
+ [JsonPropertyName("icon")]
+ public string Icon { get; set; } = default!;
+
+ ///
+ /// 洞天等级名称
+ ///
+ [JsonPropertyName("comfort_level_name")]
+ public string ComfortLevelName { get; set; } = default!;
+
+ ///
+ /// 洞天等级图标
+ ///
+ [JsonPropertyName("comfort_level_icon")]
+ public string ComfortLevelIcon { get; set; } = default!;
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Offering.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Offering.cs
new file mode 100644
index 00000000..fe1b82a0
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Offering.cs
@@ -0,0 +1,30 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
+
+///
+/// 供奉信息
+///
+public class Offering
+{
+ ///
+ /// 名称
+ ///
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = default!;
+
+ ///
+ /// 等级
+ ///
+ [JsonPropertyName("level")]
+ public string Level { get; set; } = default!;
+
+ ///
+ /// 图标
+ ///
+ [JsonPropertyName("icon")]
+ public string Icon { get; set; } = default!;
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/PlayerInfo.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/PlayerInfo.cs
new file mode 100644
index 00000000..370267af
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/PlayerInfo.cs
@@ -0,0 +1,43 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Service.Abstraction;
+using Snap.Hutao.Web.Request;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
+
+///
+/// 玩家信息
+///
+public class PlayerInfo
+{
+ ///
+ /// 持有的角色的信息
+ ///
+ [JsonPropertyName("avatars")]
+ public List Avatars { get; set; } = default!;
+
+ ///
+ /// 玩家的基本信息
+ ///
+ [JsonPropertyName("stats")]
+ public PlayerStats PlayerStat { get; set; } = default!;
+
+ ///
+ /// 世界探索
+ ///
+ [JsonPropertyName("world_explorations")]
+ public List WorldExplorations { get; set; } = default!;
+
+ ///
+ /// 洞天
+ ///
+ [JsonPropertyName("homes")]
+ public List Homes { get; set; } = default!;
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/PlayerStats.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/PlayerStats.cs
new file mode 100644
index 00000000..98025a85
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/PlayerStats.cs
@@ -0,0 +1,96 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
+
+///
+/// 玩家统计数据
+///
+public class PlayerStats
+{
+ ///
+ /// 活跃天数
+ ///
+ [JsonPropertyName("active_day_number")]
+ public int ActiveDayNumber { get; set; }
+
+ ///
+ /// 成就完成数
+ ///
+ [JsonPropertyName("achievement_number")]
+ public int AchievementNumber { get; set; }
+
+ ///
+ /// 风神瞳个数
+ ///
+ [JsonPropertyName("anemoculus_number")]
+ public int AnemoculusNumber { get; set; }
+
+ ///
+ /// 岩神瞳个数
+ ///
+ [JsonPropertyName("geoculus_number")]
+ public int GeoculusNumber { get; set; }
+
+ ///
+ /// 雷神瞳个数
+ ///
+ [JsonPropertyName("electroculus_number")]
+ public int ElectroculusNumber { get; set; }
+
+ ///
+ /// 角色个数
+ ///
+ [JsonPropertyName("avatar_number")]
+ public int AvatarNumber { get; set; }
+
+ ///
+ /// 传送锚点个数
+ ///
+ [JsonPropertyName("way_point_number")]
+ public int WayPointNumber { get; set; }
+
+ ///
+ /// 秘境个数
+ ///
+ [JsonPropertyName("domain_number")]
+ public int DomainNumber { get; set; }
+
+ ///
+ /// 深渊层数
+ ///
+ [JsonPropertyName("spiral_abyss")]
+ public string SpiralAbyss { get; set; } = default!;
+
+ ///
+ /// 华丽宝箱个数
+ ///
+ [JsonPropertyName("luxurious_chest_number")]
+ public int LuxuriousChestNumber { get; set; }
+
+ ///
+ /// 珍贵宝箱个数
+ ///
+ [JsonPropertyName("precious_chest_number")]
+ public int PreciousChestNumber { get; set; }
+
+ ///
+ /// 精致宝箱个数
+ ///
+ [JsonPropertyName("exquisite_chest_number")]
+ public int ExquisiteChestNumber { get; set; }
+
+ ///
+ /// 普通宝箱个数
+ ///
+ [JsonPropertyName("common_chest_number")]
+ public int CommonChestNumber { get; set; }
+
+ ///
+ /// 奇馈宝箱
+ ///
+ [JsonPropertyName("magic_chest_number")]
+ public int MagicChestNumber { get; set; }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Avatar.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Avatar.cs
new file mode 100644
index 00000000..9b8992a2
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Avatar.cs
@@ -0,0 +1,37 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.SpiralAbyss;
+
+///
+/// 仅包含头像的角色信息
+///
+public class Avatar
+{
+ ///
+ /// Id
+ ///
+ [JsonPropertyName("id")]
+ public int Id { get; set; }
+
+ ///
+ /// 图标
+ ///
+ [JsonPropertyName("icon")]
+ public string Icon { get; set; } = default!;
+
+ ///
+ /// 等级
+ ///
+ [JsonPropertyName("level")]
+ public int Level { get; set; }
+
+ ///
+ /// 稀有度
+ ///
+ [JsonPropertyName("rarity")]
+ public int Rarity { get; set; }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Battle.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Battle.cs
new file mode 100644
index 00000000..6c35e6b1
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Battle.cs
@@ -0,0 +1,42 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.SpiralAbyss;
+
+///
+/// 表示一次战斗
+///
+public class Battle
+{
+ ///
+ /// 索引
+ ///
+ [JsonPropertyName("index")]
+ public int Index { get; set; }
+
+ ///
+ /// 时间戳
+ ///
+ [JsonPropertyName("timestamp")]
+ public string Timestamp { get; set; } = default!;
+
+ ///
+ /// 参战角色
+ ///
+ [JsonPropertyName("avatars")]
+ public List Avatars { get; set; } = default!;
+
+ ///
+ /// 时间
+ ///
+ public DateTime Time
+ {
+ get
+ {
+ return DateTimeOffset.FromUnixTimeSeconds(int.Parse(Timestamp)).LocalDateTime;
+ }
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Floor.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Floor.cs
new file mode 100644
index 00000000..82bc462f
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Floor.cs
@@ -0,0 +1,55 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.SpiralAbyss;
+
+///
+/// 层
+///
+public class Floor
+{
+ ///
+ /// 层号
+ ///
+ [JsonPropertyName("index")]
+ public int Index { get; set; }
+
+ ///
+ /// 图标
+ ///
+ [JsonPropertyName("icon")]
+ public string Icon { get; set; } = default!;
+
+ ///
+ /// 是否解锁
+ ///
+ [JsonPropertyName("is_unlock")]
+ public string IsUnlock { get; set; } = default!;
+
+ ///
+ /// 结束时间
+ ///
+ [JsonPropertyName("settle_time")]
+ public string SettleTime { get; set; } = default!;
+
+ ///
+ /// 星数
+ ///
+ [JsonPropertyName("star")]
+ public int Star { get; set; }
+
+ ///
+ /// 最大星数
+ ///
+ [JsonPropertyName("max_star")]
+ public int MaxStar { get; set; }
+
+ ///
+ /// 层信息
+ ///
+ [JsonPropertyName("levels")]
+ public List Levels { get; set; } = default!;
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Level.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Level.cs
new file mode 100644
index 00000000..f9590c08
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Level.cs
@@ -0,0 +1,37 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.SpiralAbyss;
+
+///
+/// 间
+///
+public class Level
+{
+ ///
+ /// 索引
+ ///
+ [JsonPropertyName("index")]
+ public int Index { get; set; }
+
+ ///
+ /// 星数
+ ///
+ [JsonPropertyName("star")]
+ public int Star { get; set; }
+
+ ///
+ /// 最大星数
+ ///
+ [JsonPropertyName("max_star")]
+ public int MaxStar { get; set; }
+
+ ///
+ /// 上下半
+ ///
+ [JsonPropertyName("battles")]
+ public List Battles { get; set; } = default!;
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Rank.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Rank.cs
new file mode 100644
index 00000000..877ab784
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Rank.cs
@@ -0,0 +1,36 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.SpiralAbyss;
+
+///
+/// 角色数值排行信息
+///
+public class Rank
+{
+ ///
+ /// 角色Id
+ ///
+ [JsonPropertyName("avatar_id")]
+ public int AvatarId { get; set; }
+
+ ///
+ /// 角色图标
+ ///
+ [JsonPropertyName("avatar_icon")]
+ public string AvatarIcon { get; set; } = default!;
+
+ ///
+ /// 值
+ ///
+ [JsonPropertyName("value")]
+ public int Value { get; set; }
+
+ ///
+ /// 稀有度
+ ///
+ [JsonPropertyName("rarity")]
+ public int Rarity { get; set; }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/SpiralAbyss.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/SpiralAbyss.cs
new file mode 100644
index 00000000..33ca5740
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/SpiralAbyss.cs
@@ -0,0 +1,103 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.SpiralAbyss;
+
+///
+/// 深境螺旋信息
+///
+public class SpiralAbyss
+{
+ ///
+ /// 计划Id
+ ///
+ [JsonPropertyName("schedule_id")]
+ public int ScheduleId { get; set; }
+
+ ///
+ /// 开始时间
+ ///
+ [JsonPropertyName("start_time")]
+ public string StartTime { get; set; } = default!;
+
+ ///
+ /// 结束时间
+ ///
+ [JsonPropertyName("end_time")]
+ public string EndTime { get; set; } = default!;
+
+ ///
+ /// 战斗次数
+ ///
+ [JsonPropertyName("total_battle_times")]
+ public int TotalBattleTimes { get; set; }
+
+ ///
+ /// 胜利次数
+ ///
+ [JsonPropertyName("total_win_times")]
+ public int TotalWinTimes { get; set; }
+
+ ///
+ /// 最深抵达
+ ///
+ [JsonPropertyName("max_floor")]
+ public string MaxFloor { get; set; } = default!;
+
+ ///
+ /// 出战次数
+ ///
+ [JsonPropertyName("reveal_rank")]
+ public List RevealRank { get; set; } = default!;
+
+ ///
+ /// 击破次数
+ ///
+ [JsonPropertyName("defeat_rank")]
+ public List DefeatRank { get; set; } = default!;
+
+ ///
+ /// 最强一击
+ ///
+ [JsonPropertyName("damage_rank")]
+ public List DamageRank { get; set; } = default!;
+
+ ///
+ /// 承受伤害
+ ///
+ [JsonPropertyName("take_damage_rank")]
+ public List TakeDamageRank { get; set; } = default!;
+
+ ///
+ /// 元素战技
+ ///
+ [JsonPropertyName("normal_skill_rank")]
+ public List NormalSkillRank { get; set; } = default!;
+
+ ///
+ /// 元素爆发
+ ///
+ [JsonPropertyName("energy_skill_rank")]
+ public List EnergySkillRank { get; set; } = default!;
+
+ ///
+ /// 层信息
+ ///
+ [JsonPropertyName("floors")]
+ public List Floors { get; set; } = default!;
+
+ ///
+ /// 共获得渊星
+ ///
+ [JsonPropertyName("total_star")]
+ public int TotalStar { get; set; }
+
+ ///
+ /// 是否解锁
+ ///
+ [JsonPropertyName("is_unlock")]
+ public bool IsUnlock { get; set; }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyssSchedule.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyssSchedule.cs
new file mode 100644
index 00000000..d60054ef
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyssSchedule.cs
@@ -0,0 +1,20 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
+
+///
+/// 深渊期数类型
+///
+public enum SpiralAbyssSchedule
+{
+ ///
+ /// 当期
+ ///
+ Current = 1,
+
+ ///
+ /// 上期
+ ///
+ Last = 2,
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/WorldExploration.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/WorldExploration.cs
new file mode 100644
index 00000000..7c233ad3
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/WorldExploration.cs
@@ -0,0 +1,118 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
+
+///
+/// 世界探索
+///
+public class WorldExploration
+{
+ ///
+ /// 等级
+ ///
+ [JsonPropertyName("level")]
+ public int Level { get; set; }
+
+ ///
+ /// 探索度
+ /// Maxmium is 1000
+ ///
+ [JsonPropertyName("exploration_percentage")]
+ public int ExplorationPercentage { get; set; }
+
+ ///
+ /// 图标
+ ///
+ [JsonPropertyName("icon")]
+ public string Icon { get; set; } = default!;
+
+ ///
+ /// 名称
+ ///
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = default!;
+
+ ///
+ /// 类型
+ /// Offering
+ /// Reputation
+ ///
+ [JsonPropertyName("type")]
+ public string Type { get; set; } = default!;
+
+ ///
+ /// 供奉进度
+ ///
+ [JsonPropertyName("offerings")]
+ public List Offerings { get; set; } = default!;
+
+ ///
+ /// ID
+ ///
+ [JsonPropertyName("id")]
+ public int Id { get; set; }
+
+ ///
+ /// 父ID 当无链接的父对象时为0
+ ///
+ [JsonPropertyName("parent_id")]
+ public int ParentId { get; set; }
+
+ ///
+ /// 地图链接
+ ///
+ [JsonPropertyName("map_url")]
+ public string MapUrl { get; set; } = default!;
+
+ ///
+ /// 攻略链接 无攻略时为
+ ///
+ [JsonPropertyName("strategy_url")]
+ public string StrategyUrl { get; set; } = default!;
+
+ ///
+ /// 背景图片
+ ///
+ [JsonPropertyName("background_image")]
+ public string BackgroundImage { get; set; } = default!;
+
+ ///
+ /// 反色图标
+ ///
+ [JsonPropertyName("inner_icon")]
+ public string InnerIcon { get; set; } = default!;
+
+ ///
+ /// 背景图片
+ ///
+ [JsonPropertyName("cover")]
+ public string Cover { get; set; } = default!;
+
+ ///
+ /// 百分比*100进度
+ ///
+ public double ExplorationPercentageBy10
+ {
+ get => ExplorationPercentage / 10.0;
+ }
+
+ ///
+ /// 类型名称转换器
+ ///
+ public string TypeFormatted
+ {
+ get => IsReputation ? "声望等级" : "供奉等级";
+ }
+
+ ///
+ /// 指示当前是否为声望
+ ///
+ public bool IsReputation
+ {
+ get => Type == "Reputation";
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/PlayerUid.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/PlayerUid.cs
new file mode 100644
index 00000000..f214d0a0
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/PlayerUid.cs
@@ -0,0 +1,59 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi;
+
+///
+/// 玩家 Uid
+///
+public struct PlayerUid
+{
+ private string? region = null;
+
+ ///
+ /// 构造一个新的玩家 Uid 结构
+ ///
+ /// uid
+ /// 服务器,当提供该参数时会无条件信任
+ public PlayerUid(string value, string? region = default)
+ {
+ Requires.Argument(value.Length == 9, nameof(value), "uid应为9位数字");
+ Value = value;
+
+ if (region != null)
+ {
+ this.region = region;
+ }
+ }
+
+ ///
+ /// UID 的实际值
+ ///
+ public string Value { get; }
+
+ ///
+ /// 地区代码
+ ///
+ public string Region
+ {
+ get
+ {
+ region ??= EvaluateRegion(Value[0]);
+ return region;
+ }
+ }
+
+ private static string EvaluateRegion(char first)
+ {
+ return first switch
+ {
+ >= '1' and <= '4' => "cn_gf01", // 国服
+ '5' => "cn_qd01", // 渠道
+ '6' => "os_usa", // 美服
+ '7' => "os_euro", // 欧服
+ '8' => "os_asia", // 亚服
+ '9' => "os_cht", // 台服
+ _ => Must.NeverHappen(),
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HutaoAPIProvider.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HutaoAPIProvider.cs
new file mode 100644
index 00000000..62b7b702
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HutaoAPIProvider.cs
@@ -0,0 +1,341 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Core.Abstraction;
+using Snap.Hutao.Extension;
+using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
+using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
+using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
+using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.SpiralAbyss;
+using Snap.Hutao.Web.Hutao.Model;
+using Snap.Hutao.Web.Hutao.Model.Post;
+using Snap.Hutao.Web.Request;
+using Snap.Hutao.Web.Response;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Snap.Hutao.Web.Hutao;
+
+///
+/// 胡桃API提供器
+///
+[Injection(InjectAs.Transient)]
+internal class HutaoAPIProvider : IAsyncInitializable
+{
+ private const string AuthAPIHost = "https://auth.snapgenshin.com";
+ private const string HutaoAPI = "https://hutao-api.snapgenshin.com";
+ private const string PostContentType = "text/json";
+
+ private readonly Requester requester;
+ private readonly AuthRequester authRequester;
+ private readonly GameRecordProvider gameRecordProvider;
+ private readonly UserGameRoleProvider userGameRoleProvider;
+
+ private bool isInitialized = false;
+
+ ///
+ /// 构造一个新的胡桃API提供器
+ ///
+ /// 请求器
+ /// 支持验证的请求器
+ /// 游戏记录提供器
+ /// 用户游戏角色提供器
+ public HutaoAPIProvider(
+ Requester requester,
+ AuthRequester authRequester,
+ GameRecordProvider gameRecordProvider,
+ UserGameRoleProvider userGameRoleProvider)
+ {
+ this.requester = requester;
+ this.authRequester = authRequester;
+ this.gameRecordProvider = gameRecordProvider;
+ this.userGameRoleProvider = userGameRoleProvider;
+ }
+
+ ///
+ public bool IsInitialized { get => isInitialized; }
+
+ ///
+ public async Task InitializeAsync(CancellationToken token = default)
+ {
+ Auth auth = new(
+ "08d9e212-0cb3-4d71-8ed7-003606da7b20",
+ "7ueWgZGn53dDhrm8L5ZRw+YWfOeSWtgQmJWquRgaygw=");
+
+ Response? resp = await requester
+ .Reset()
+ .PostAsync($"{AuthAPIHost}/Auth/Login", auth, PostContentType, token)
+ .ConfigureAwait(false);
+
+ authRequester.AuthToken = Must.NotNull(resp?.Data?.AccessToken!);
+ isInitialized = true;
+
+ return isInitialized;
+ }
+
+ ///
+ /// 检查对应的uid当前是否上传了数据
+ ///
+ /// uid
+ /// 取消令牌
+ /// 当前是否上传了数据
+ public async Task CheckPeriodRecordUploadedAsync(string uid, CancellationToken token = default)
+ {
+ Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
+
+ Response? resp = await authRequester
+ .GetAsync($"{HutaoAPI}/Record/CheckRecord/{uid}", token)
+ .ConfigureAwait(false);
+ return resp?.Data is not null && resp.Data.PeriodUploaded;
+ }
+
+ ///
+ /// 异步获取总览数据
+ ///
+ /// 取消令牌
+ /// 总览信息
+ public async Task GetOverviewAsync(CancellationToken token = default)
+ {
+ Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
+
+ Response? resp = await authRequester
+ .GetAsync($"{HutaoAPI}/Statistics/Overview", token)
+ .ConfigureAwait(false);
+
+ return resp?.Data;
+ }
+
+ ///
+ /// 异步获取角色出场率
+ ///
+ /// 取消令牌
+ /// 角色出场率
+ public async Task> GetAvatarParticipationsAsync(CancellationToken token = default)
+ {
+ Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
+
+ Response>? resp = await authRequester
+ .GetAsync>($"{HutaoAPI}/Statistics/AvatarParticipation", token)
+ .ConfigureAwait(false);
+
+ return resp?.Data ?? Enumerable.Empty();
+ }
+
+ ///
+ /// 异步获取角色圣遗物搭配
+ ///
+ /// 取消令牌
+ /// 角色圣遗物搭配
+ public async Task> GetAvatarReliquaryUsagesAsync(CancellationToken token = default)
+ {
+ Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
+
+ Response>? resp = await authRequester
+ .GetAsync>($"{HutaoAPI}/Statistics/AvatarReliquaryUsage", token)
+ .ConfigureAwait(false);
+
+ return resp?.Data ?? Enumerable.Empty();
+ }
+
+ ///
+ /// 异步获取角色搭配数据
+ ///
+ /// 取消令牌
+ /// 角色搭配数据
+ public async Task> GetTeamCollocationsAsync(CancellationToken token = default)
+ {
+ Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
+
+ Response>? resp = await authRequester
+ .GetAsync>($"{HutaoAPI}/Statistics/TeamCollocation", token)
+ .ConfigureAwait(false);
+
+ return resp?.Data ?? Enumerable.Empty();
+ }
+
+ ///
+ /// 异步获取角色武器搭配数据
+ ///
+ /// 取消令牌
+ /// 角色武器搭配数据
+ public async Task> GetAvatarWeaponUsagesAsync(CancellationToken token = default)
+ {
+ Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
+
+ Response>? resp = await authRequester
+ .GetAsync>($"{HutaoAPI}/Statistics/AvatarWeaponUsage", token)
+ .ConfigureAwait(false);
+
+ return resp?.Data ?? Enumerable.Empty();
+ }
+
+ ///
+ /// 异步获取角色命座信息
+ ///
+ /// 取消令牌
+ /// 角色图片列表
+ public async Task> GetAvatarConstellationsAsync(CancellationToken token = default)
+ {
+ Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
+
+ Response>? resp = await authRequester
+ .GetAsync>($"{HutaoAPI}/Statistics/Constellation", token)
+ .ConfigureAwait(false);
+
+ return resp?.Data ?? Enumerable.Empty();
+ }
+
+ ///
+ /// 异步获取队伍出场次数
+ ///
+ /// 取消令牌
+ /// 队伍出场列表
+ public async Task> GetTeamCombinationsAsync(CancellationToken token = default)
+ {
+ Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
+
+ Response>? resp = await authRequester
+ .GetAsync>($"{HutaoAPI}/Statistics/TeamCombination", token)
+ .ConfigureAwait(false);
+ return resp?.Data ?? Enumerable.Empty();
+ }
+
+ ///
+ /// 异步获取角色图片列表
+ ///
+ /// 取消令牌
+ /// 角色图片列表
+ public async Task> GetAvatarMapAsync(CancellationToken token = default)
+ {
+ Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
+
+ Response>? resp = await authRequester
+ .GetAsync>($"{HutaoAPI}/GenshinItem/Avatars", token)
+ .ConfigureAwait(false);
+
+ return resp?.Data ?? Enumerable.Empty- ();
+ }
+
+ ///
+ /// 异步获取武器图片列表
+ ///
+ /// 取消令牌
+ /// 武器图片列表
+ public async Task> GetWeaponMapAsync(CancellationToken token = default)
+ {
+ Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
+
+ Response>? resp = await authRequester
+ .GetAsync>($"{HutaoAPI}/GenshinItem/Weapons", token)
+ .ConfigureAwait(false);
+
+ return resp?.Data ?? Enumerable.Empty
- ();
+ }
+
+ ///
+ /// 异步获取圣遗物图片列表
+ ///
+ /// 取消令牌
+ /// 圣遗物图片列表
+ public async Task> GetReliquaryMapAsync(CancellationToken token = default)
+ {
+ Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
+
+ Response>? resp = await authRequester
+ .GetAsync>($"{HutaoAPI}/GenshinItem/Reliquaries", token)
+ .ConfigureAwait(false);
+
+ return resp?.Data ?? Enumerable.Empty
- ();
+ }
+
+ ///
+ /// 异步获取所有记录并上传到数据库
+ ///
+ /// 异步确认委托
+ /// 结果确认委托
+ /// 取消令牌
+ /// 任务
+ public async Task GetAllRecordsAndUploadAsync(Func> confirmAsyncFunc, Func resultAsyncFunc, CancellationToken token = default)
+ {
+ List userGameRoles = await userGameRoleProvider
+ .GetUserGameRolesAsync(token);
+
+ foreach (UserGameRole role in userGameRoles)
+ {
+ PlayerInfo? playerInfo = await gameRecordProvider
+ .GetPlayerInfoAsync(role.AsPlayerUid(), token);
+ Must.NotNull(playerInfo!);
+
+ List characters = await gameRecordProvider
+ .GetCharactersAsync(role.AsPlayerUid(), playerInfo, token);
+
+ SpiralAbyss? spiralAbyssInfo = await gameRecordProvider
+ .GetSpiralAbyssAsync(role.AsPlayerUid(), SpiralAbyssSchedule.Current, token);
+ Must.NotNull(spiralAbyssInfo!);
+
+ PlayerRecord playerRecord = PlayerRecord.Create(role.GameUid, characters, spiralAbyssInfo);
+ if (await confirmAsyncFunc(playerRecord))
+ {
+ Response? resp = null;
+ if (Response.Response.IsOk(await UploadItemsAsync(characters, token)))
+ {
+ resp = await authRequester
+ .PostAsync($"{HutaoAPI}/Record/Upload", playerRecord, PostContentType, token);
+ }
+
+ await resultAsyncFunc(resp ?? Response.Response.CreateForException($"{role.GameUid}-记录提交失败。"));
+ }
+ }
+ }
+
+ ///
+ /// 异步上传物品所有物品
+ ///
+ /// 角色详细信息
+ /// 取消令牌
+ /// 响应
+ private async Task?> UploadItemsAsync(List characters, CancellationToken token = default)
+ {
+ Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
+
+ IEnumerable
- avatars = characters
+ .Select(avatar => new Item(avatar.Id, avatar.Name, avatar.Icon))
+ .DistinctBy(item => item.Id);
+
+ IEnumerable
- weapons = characters
+ .Select(avatar => avatar.Weapon)
+ .Select(weapon => new Item(weapon.Id, weapon.Name, weapon.Icon))
+ .DistinctBy(item => item.Id);
+
+ IEnumerable
- reliquaries = characters
+ .Select(avatars => avatars.Reliquaries)
+ .Flatten()
+ .Where(relic => relic.Position == ReliquaryPosition.FlowerOfLife)
+ .DistinctBy(relic => relic.Id)
+ .Select(relic => new Item(relic.ReliquarySet.Id, relic.ReliquarySet.Name, relic.Icon));
+
+ GenshinItemWrapper? data = new(avatars, weapons, reliquaries);
+
+ return await authRequester
+ .PostAsync($"{HutaoAPI}/GenshinItem/Upload", data, PostContentType, token)
+ .ConfigureAwait(false);
+ }
+
+ private class Auth
+ {
+ public Auth(string appid, string secret)
+ {
+ Appid = appid;
+ Secret = secret;
+ }
+
+ public string Appid { get; }
+
+ public string Secret { get; }
+ }
+
+ private class Token
+ {
+ public string AccessToken { get; } = default!;
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/AvatarConstellationNum.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/AvatarConstellationNum.cs
new file mode 100644
index 00000000..aa136cb2
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/AvatarConstellationNum.cs
@@ -0,0 +1,27 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+
+namespace Snap.Hutao.Web.Hutao.Model;
+
+///
+/// 命座比例
+///
+public class AvatarConstellationNum
+{
+ ///
+ /// 角色ID
+ ///
+ public int Avatar { get; set; }
+
+ ///
+ /// 持有率
+ ///
+ public decimal HoldingRate { get; set; }
+
+ ///
+ /// 各命座比率
+ ///
+ public IEnumerable> Rate { get; set; } = default!;
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/AvatarParticipation.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/AvatarParticipation.cs
new file mode 100644
index 00000000..9096b3a3
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/AvatarParticipation.cs
@@ -0,0 +1,22 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+
+namespace Snap.Hutao.Web.Hutao.Model;
+
+///
+/// 出场数据
+///
+public class AvatarParticipation
+{
+ ///
+ /// 层
+ ///
+ public int Floor { get; set; }
+
+ ///
+ /// 角色比率
+ ///
+ public IEnumerable> AvatarUsage { get; set; } = default!;
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/AvatarReliquaryUsage.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/AvatarReliquaryUsage.cs
new file mode 100644
index 00000000..2dd4774c
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/AvatarReliquaryUsage.cs
@@ -0,0 +1,22 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+
+namespace Snap.Hutao.Web.Hutao.Model;
+
+///
+/// 圣遗物配置数据
+///
+public class AvatarReliquaryUsage
+{
+ ///
+ /// 角色Id
+ ///
+ public int Avatar { get; set; }
+
+ ///
+ /// 圣遗物比率
+ ///
+ public IEnumerable> ReliquaryUsage { get; set; } = default!;
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/AvatarWeaponUsage.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/AvatarWeaponUsage.cs
new file mode 100644
index 00000000..5f0b63a8
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/AvatarWeaponUsage.cs
@@ -0,0 +1,22 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+
+namespace Snap.Hutao.Web.Hutao.Model;
+
+///
+/// 武器使用数据
+///
+public class AvatarWeaponUsage
+{
+ ///
+ /// 角色Id
+ ///
+ public int Avatar { get; set; }
+
+ ///
+ /// 武器比率
+ ///
+ public IEnumerable> Weapons { get; set; } = default!;
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Converter/ReliquarySetsConverter.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Converter/ReliquarySetsConverter.cs
new file mode 100644
index 00000000..96b43696
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Converter/ReliquarySetsConverter.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+
+namespace Snap.Hutao.Web.Hutao.Model.Converter;
+
+///
+/// 圣遗物套装转换器
+///
+internal class ReliquarySetsConverter : JsonConverter
+{
+ ///
+ public override ReliquarySets? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ string? source = reader.GetString();
+
+ if (source != null)
+ {
+ string[] sets = source.Split(';');
+ return new(sets.Select(set => new ReliquarySet(set)));
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, ReliquarySets value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue(string.Join(';', value));
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Item.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Item.cs
new file mode 100644
index 00000000..e93737b7
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Item.cs
@@ -0,0 +1,41 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hutao.Model;
+
+///
+/// 胡桃数据库物品
+///
+public class Item
+{
+ ///
+ /// 构造一个新的胡桃数据库物品
+ ///
+ /// 物品Id
+ /// 名称
+ /// 链接
+ [JsonConstructor]
+ public Item(int id, string name, string url)
+ {
+ Id = id;
+ Name = name;
+ Url = url;
+ }
+
+ ///
+ /// 物品Id
+ ///
+ public int Id { get; }
+
+ ///
+ /// 名称
+ ///
+ public string Name { get; }
+
+ ///
+ /// 链接
+ ///
+ public string Url { get; }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/LevelInfo.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/LevelInfo.cs
new file mode 100644
index 00000000..b6b9e62a
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/LevelInfo.cs
@@ -0,0 +1,20 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Web.Hutao.Model;
+
+///
+/// 层
+///
+public class LevelInfo
+{
+ ///
+ /// 间
+ ///
+ public int Floor { get; set; }
+
+ ///
+ /// 上下半 0,1
+ ///
+ public int Index { get; set; }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Overview.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Overview.cs
new file mode 100644
index 00000000..2cc73cc0
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Overview.cs
@@ -0,0 +1,25 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Web.Hutao.Model;
+
+///
+/// 统计数据
+///
+public class Overview
+{
+ ///
+ /// 所有用户数量
+ ///
+ public int TotalPlayerCount { get; set; }
+
+ ///
+ /// 当期提交深渊数据用户数量
+ ///
+ public int CollectedPlayerCount { get; set; }
+
+ ///
+ /// 当期满星用户数量
+ ///
+ public int FullStarPlayerCount { get; set; }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/AvatarReliquarySet.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/AvatarReliquarySet.cs
new file mode 100644
index 00000000..279cf8be
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/AvatarReliquarySet.cs
@@ -0,0 +1,42 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+
+namespace Snap.Hutao.Web.Hutao.Model.Post;
+
+///
+/// 角色圣遗物套装
+///
+public class AvatarReliquarySet
+{
+ ///
+ /// 构造一个新的角色圣遗物套装
+ ///
+ /// 套装Id
+ /// 个数
+ public AvatarReliquarySet(int id, int count)
+ {
+ Id = id;
+ Count = count;
+ }
+
+ ///
+ /// 构造一个新的角色圣遗物套装
+ ///
+ /// 键值对
+ public AvatarReliquarySet(KeyValuePair kvp)
+ : this(kvp.Key, kvp.Value)
+ {
+ }
+
+ ///
+ /// 套装Id
+ ///
+ public int Id { get; }
+
+ ///
+ /// 个数
+ ///
+ public int Count { get; }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/AvatarWeapon.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/AvatarWeapon.cs
new file mode 100644
index 00000000..9231c7ce
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/AvatarWeapon.cs
@@ -0,0 +1,40 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+
+namespace Snap.Hutao.Web.Hutao.Model.Post;
+
+///
+/// 角色武器
+///
+public class AvatarWeapon
+{
+ ///
+ /// 构造一个新的角色武器
+ ///
+ /// 武器Id
+ /// 武器等级
+ /// 精炼等级
+ public AvatarWeapon(int id, int level, int affixLevel)
+ {
+ Id = id;
+ Level = level;
+ AffixLevel = affixLevel;
+ }
+
+ ///
+ /// 武器等级
+ ///
+ public int Id { get; }
+
+ ///
+ /// 武器等级
+ ///
+ public int Level { get; }
+
+ ///
+ /// 精炼
+ ///
+ public int AffixLevel { get; }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/GenshinItemWrapper.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/GenshinItemWrapper.cs
new file mode 100644
index 00000000..2ce4d475
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/GenshinItemWrapper.cs
@@ -0,0 +1,40 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+
+namespace Snap.Hutao.Web.Hutao.Model.Post;
+
+///
+/// 原神物品包装器
+///
+public class GenshinItemWrapper
+{
+ ///
+ /// 构造一个新的原神物品包装器
+ ///
+ /// 角色
+ /// 武器
+ /// 圣遗物
+ public GenshinItemWrapper(IEnumerable
- avatars, IEnumerable
- weapons, IEnumerable
- reliquaries)
+ {
+ Avatars = avatars;
+ Weapons = weapons;
+ Reliquaries = reliquaries;
+ }
+
+ ///
+ /// 角色列表
+ ///
+ public IEnumerable
- Avatars { get; }
+
+ ///
+ /// 武器列表
+ ///
+ public IEnumerable
- Weapons { get; }
+
+ ///
+ /// 圣遗物列表
+ ///
+ public IEnumerable
- Reliquaries { get; }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/IndexedLevel.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/IndexedLevel.cs
new file mode 100644
index 00000000..fe7b8699
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/IndexedLevel.cs
@@ -0,0 +1,33 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.SpiralAbyss;
+
+namespace Snap.Hutao.Web.Hutao.Model.Post;
+
+///
+/// 带有层信息的间
+///
+internal class IndexedLevel
+{
+ ///
+ /// 构造一个新的带有层信息的间
+ ///
+ /// 层号
+ /// 间信息
+ public IndexedLevel(int floorIndex, Level level)
+ {
+ FloorIndex = floorIndex;
+ Level = level;
+ }
+
+ ///
+ /// 层号
+ ///
+ public int FloorIndex { get; }
+
+ ///
+ /// 层信息
+ ///
+ public Level Level { get; }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/PlayerAvatar.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/PlayerAvatar.cs
new file mode 100644
index 00000000..68302756
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/PlayerAvatar.cs
@@ -0,0 +1,56 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Extension;
+using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Snap.Hutao.Web.Hutao.Model.Post;
+
+///
+/// 玩家角色
+///
+public class PlayerAvatar
+{
+ ///
+ /// 构造一个新的玩家角色
+ ///
+ /// 角色
+ internal PlayerAvatar(Character avatar)
+ {
+ Id = avatar.Id;
+ Level = avatar.Level;
+ ActivedConstellationNum = avatar.ActivedConstellationNum;
+ Weapon = new(avatar.Weapon.Id, avatar.Weapon.Level, avatar.Weapon.AffixLevel);
+ ReliquarySets = avatar.Reliquaries
+ .CountBy(relic => relic.ReliquarySet.Id)
+ .Select(kvp => new AvatarReliquarySet(kvp))
+ .ToList();
+ }
+
+ ///
+ /// 角色Id
+ ///
+ public int Id { get; }
+
+ ///
+ /// 角色等级
+ ///
+ public int Level { get; }
+
+ ///
+ /// 命座
+ ///
+ public int ActivedConstellationNum { get; }
+
+ ///
+ /// 武器
+ ///
+ public AvatarWeapon Weapon { get; }
+
+ ///
+ /// 圣遗物套装
+ ///
+ public List ReliquarySets { get; }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/PlayerRecord.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/PlayerRecord.cs
new file mode 100644
index 00000000..6a29ad9a
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/PlayerRecord.cs
@@ -0,0 +1,64 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
+using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.SpiralAbyss;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Snap.Hutao.Web.Hutao.Model.Post;
+
+///
+/// 玩家记录
+/// 使用 来构建一个实例
+///
+public class PlayerRecord
+{
+ ///
+ /// 构造一个新的玩家记录
+ ///
+ /// uid
+ /// 玩家角色
+ /// 玩家深渊信息
+ private PlayerRecord(string uid, IEnumerable playerAvatars, IEnumerable playerSpiralAbyssesLevels)
+ {
+ Uid = uid;
+ PlayerAvatars = playerAvatars;
+ PlayerSpiralAbyssesLevels = playerSpiralAbyssesLevels;
+ }
+
+ ///
+ /// uid
+ ///
+ public string Uid { get; }
+
+ ///
+ /// 玩家角色
+ ///
+ public IEnumerable PlayerAvatars { get; }
+
+ ///
+ /// 玩家深渊信息
+ ///
+ public IEnumerable PlayerSpiralAbyssesLevels { get; }
+
+ ///
+ /// 建造玩家记录
+ ///
+ /// 玩家的uid
+ /// 角色详情信息
+ /// 深渊信息
+ /// 玩家记录
+ internal static PlayerRecord Create(string uid, List detailAvatars, SpiralAbyss spiralAbyss)
+ {
+ IEnumerable playerAvatars = detailAvatars
+ .Select(avatar => new PlayerAvatar(avatar));
+
+ IEnumerable playerSpiralAbyssLevels = spiralAbyss.Floors
+ .SelectMany(f => f.Levels, (f, level) => new IndexedLevel(f.Index, level))
+ .Select(indexedLevel => new PlayerSpiralAbyssLevel(indexedLevel));
+
+ PlayerRecord playerRecord = new(uid, playerAvatars, playerSpiralAbyssLevels);
+ return playerRecord;
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/PlayerSpiralAbyssBattle.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/PlayerSpiralAbyssBattle.cs
new file mode 100644
index 00000000..aa23b909
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/PlayerSpiralAbyssBattle.cs
@@ -0,0 +1,34 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.SpiralAbyss;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Snap.Hutao.Web.Hutao.Model.Post;
+
+///
+/// 玩家深渊某间的战斗信息
+///
+public class PlayerSpiralAbyssBattle
+{
+ ///
+ /// 构造一个新的战斗信息
+ ///
+ /// 战斗
+ internal PlayerSpiralAbyssBattle(Battle battle)
+ {
+ BattleIndex = battle.Index;
+ AvatarIds = battle.Avatars.Select(a => a.Id);
+ }
+
+ ///
+ /// 战斗上下半间 0,1
+ ///
+ public int BattleIndex { get; }
+
+ ///
+ /// 角色Id列表
+ ///
+ public IEnumerable AvatarIds { get; }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/PlayerSpiralAbyssLevel.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/PlayerSpiralAbyssLevel.cs
new file mode 100644
index 00000000..9e5fa808
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Post/PlayerSpiralAbyssLevel.cs
@@ -0,0 +1,46 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Snap.Hutao.Web.Hutao.Model.Post;
+
+///
+/// 玩家深渊战斗间信息
+///
+public class PlayerSpiralAbyssLevel
+{
+ ///
+ /// 构造一个新的玩家深渊战斗间信息
+ ///
+ /// 楼层
+ internal PlayerSpiralAbyssLevel(IndexedLevel indexedLevel)
+ {
+ FloorIndex = indexedLevel.FloorIndex;
+ LevelIndex = indexedLevel.Level.Index;
+ Star = indexedLevel.Level.Star;
+ Battles = indexedLevel.Level.Battles
+ .Select(battle => new PlayerSpiralAbyssBattle(battle));
+ }
+
+ ///
+ /// 层号
+ ///
+ public int FloorIndex { get; }
+
+ ///
+ /// 间号
+ ///
+ public int LevelIndex { get; }
+
+ ///
+ /// 星数
+ ///
+ public int Star { get; }
+
+ ///
+ /// 战斗列表 分上下半间
+ ///
+ public IEnumerable Battles { get; }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Rate{T}.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Rate{T}.cs
new file mode 100644
index 00000000..7a02b675
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Rate{T}.cs
@@ -0,0 +1,21 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Web.Hutao.Model;
+
+///
+/// 比率
+///
+/// 的类型
+public class Rate
+{
+ ///
+ /// 表示唯一标识符的实例
+ ///
+ public T Id { get; set; } = default!;
+
+ ///
+ /// 比率
+ ///
+ public decimal Value { get; set; }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/ReliquarySet.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/ReliquarySet.cs
new file mode 100644
index 00000000..7bc85c53
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/ReliquarySet.cs
@@ -0,0 +1,38 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Web.Hutao.Model;
+
+///
+/// 圣遗物套装
+///
+public class ReliquarySet
+{
+ ///
+ /// 构造一个新的圣遗物套装
+ ///
+ /// 简单套装字符串
+ public ReliquarySet(string set)
+ {
+ string[]? deconstructed = set.Split('-');
+
+ Id = int.Parse(deconstructed[0]);
+ Count = int.Parse(deconstructed[1]);
+ }
+
+ ///
+ /// Id
+ ///
+ public int Id { get; }
+
+ ///
+ /// 个数
+ ///
+ public int Count { get; }
+
+ ///
+ public override string ToString()
+ {
+ return $"{Id}-{Count}";
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/ReliquarySets.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/ReliquarySets.cs
new file mode 100644
index 00000000..be8c5708
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/ReliquarySets.cs
@@ -0,0 +1,24 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Web.Hutao.Model.Converter;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hutao.Model;
+
+///
+/// 包装圣遗物套装
+///
+[JsonConverter(typeof(ReliquarySetsConverter))]
+public class ReliquarySets : List
+{
+ ///
+ /// 构造一个新的圣遗物包装器
+ ///
+ /// 圣遗物套装
+ public ReliquarySets(IEnumerable sets)
+ : base(sets)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Team.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Team.cs
new file mode 100644
index 00000000..95be596b
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Team.cs
@@ -0,0 +1,26 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Core.Json.Converter;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hutao.Model;
+
+///
+/// 队伍
+///
+public class Team
+{
+ ///
+ /// 上半
+ ///
+ [JsonConverter(typeof(SeparatorCommaInt32EnumerableConverter))]
+ public IEnumerable UpHalf { get; set; } = null!;
+
+ ///
+ /// 下半
+ ///
+ [JsonConverter(typeof(SeparatorCommaInt32EnumerableConverter))]
+ public IEnumerable DownHalf { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/TeamCollocation.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/TeamCollocation.cs
new file mode 100644
index 00000000..ec70a88c
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/TeamCollocation.cs
@@ -0,0 +1,22 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+
+namespace Snap.Hutao.Web.Hutao.Model;
+
+///
+/// 组队数据
+///
+public class TeamCollocation
+{
+ ///
+ /// 角色Id
+ ///
+ public int Avatar { get; set; }
+
+ ///
+ /// 角色搭配比率
+ ///
+ public IEnumerable> Collocations { get; set; } = default!;
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/TeamCombination.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/TeamCombination.cs
new file mode 100644
index 00000000..411649d6
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/TeamCombination.cs
@@ -0,0 +1,22 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+
+namespace Snap.Hutao.Web.Hutao.Model;
+
+///
+/// 队伍上场率
+///
+public record TeamCombination
+{
+ ///
+ /// 层
+ ///
+ public LevelInfo Level { get; set; } = null!;
+
+ ///
+ /// 队伍
+ ///
+ public IEnumerable> Teams { get; set; } = null!;
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/UploadStatus.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/UploadStatus.cs
new file mode 100644
index 00000000..9856a27f
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/UploadStatus.cs
@@ -0,0 +1,15 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Web.Hutao.Model;
+
+///
+/// 上传信息
+///
+public class UploadStatus
+{
+ ///
+ /// 本期是否已经上传
+ ///
+ public bool PeriodUploaded { get; set; }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Request/RequestHeaders.cs b/src/Snap.Hutao/Snap.Hutao/Web/Request/RequestHeaders.cs
new file mode 100644
index 00000000..a75e3d03
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Request/RequestHeaders.cs
@@ -0,0 +1,41 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Web.Request
+{
+ ///
+ /// 请求头的键
+ ///
+ public class RequestHeaders
+ {
+ ///
+ /// Accept
+ ///
+ public const string Accept = "Accept";
+
+ ///
+ /// Cookie
+ ///
+ public const string Cookie = "Cookie";
+
+ ///
+ /// x-rpc-app_version
+ ///
+ public const string AppVersion = "x-rpc-app_version";
+
+ ///
+ /// User-Agent
+ ///
+ public const string UserAgent = "User-Agent";
+
+ ///
+ /// x-rpc-client_type
+ ///
+ public const string ClientType = "x-rpc-client_type";
+
+ ///
+ /// X-Requested-With
+ ///
+ public const string RequestWith = "X-Requested-With";
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Request/RequestOptions.cs b/src/Snap.Hutao/Snap.Hutao/Web/Request/RequestOptions.cs
index c4a43a32..7ea48f6e 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Request/RequestOptions.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Request/RequestOptions.cs
@@ -8,22 +8,10 @@ namespace Snap.Hutao.Web.Request
///
public class RequestOptions : Dictionary
{
- ///
- /// 不再使用
- ///
- [Obsolete("不再使用")]
- public const string CommonUA2101 = @"Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBS/2.10.1";
-
- ///
- /// 不再使用
- ///
- [Obsolete("不再使用")]
- public const string CommonUA2111 = @"Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBS/2.11.1";
-
///
/// 支持更新的DS2算法
///
- public const string CommonUA2161 = @"Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBS/2.16.1";
+ public const string CommonUA = @"Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBS/2.16.1";
///
/// 应用程序/Json
@@ -35,6 +23,11 @@ namespace Snap.Hutao.Web.Request
///
public const string Hyperion = "com.mihoyo.hyperion";
+ ///
+ /// 默认的客户端类型
+ ///
+ public const string DefaultClientType = "5";
+
///
/// 设备Id
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Request/Requester.cs b/src/Snap.Hutao/Snap.Hutao/Web/Request/Requester.cs
index e902de79..444f305d 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Request/Requester.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Request/Requester.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Core.Json;
+using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Web.Response;
using System.Net.Http;
@@ -20,6 +21,8 @@ public class Requester
private readonly IInfoBarService infoBarService;
private readonly ILogger logger;
+ private bool isRequesting = false;
+
///
/// 构造一个新的 对象
///
@@ -40,6 +43,11 @@ public class Requester
///
public RequestOptions Headers { get; set; } = new RequestOptions();
+ ///
+ /// 此请求器使用的Json解析器
+ ///
+ public Json Json { get => json; }
+
///
/// 内部使用的
///
@@ -52,7 +60,7 @@ public class Requester
/// 地址
/// 取消令牌
/// 响应
- public async Task?> GetAsync(string? url, CancellationToken cancellationToken = default)
+ public async Task?> GetAsync(string url, CancellationToken cancellationToken = default)
{
if (url is null)
{
@@ -73,14 +81,14 @@ public class Requester
/// 要发送的.NET(匿名)对象
/// 取消令牌
/// 响应
- public async Task?> PostAsync(string? url, object data, CancellationToken cancellationToken = default)
+ public async Task?> PostAsync(string url, object data, CancellationToken cancellationToken = default)
{
if (url is null)
{
return Response.CreateForEmptyUrl();
}
- string dataString = json.Stringify(data);
+ string dataString = Json.Stringify(data);
HttpContent content = new StringContent(dataString);
Task PostMethod(HttpClient client, CancellationToken token) => client.PostAsync(url, content, token);
@@ -98,14 +106,14 @@ public class Requester
/// 内容类型
/// 取消令牌
/// 响应
- public async Task?> PostAsync(string? url, object data, string contentType, CancellationToken cancellationToken = default)
+ public async Task?> PostAsync(string url, object data, string contentType, CancellationToken cancellationToken = default)
{
if (url is null)
{
return Response.CreateForEmptyUrl();
}
- string dataString = json.Stringify(data);
+ string dataString = Json.Stringify(data);
HttpContent content = new StringContent(dataString, Encoding.UTF8, contentType);
Task PostMethod(HttpClient client, CancellationToken token) => client.PostAsync(url, content, token);
@@ -121,6 +129,7 @@ public class Requester
/// 链式调用需要的实例
public Requester Reset()
{
+ Verify.Operation(!isRequesting, "无法在请求发生时重置请求器");
Headers.Clear();
return this;
}
@@ -133,10 +142,48 @@ public class Requester
/// 链式调用需要的实例
public Requester AddHeader(string key, string value)
{
+ Verify.Operation(!isRequesting, "无法在请求发生时修改请求头");
Headers.Add(key, value);
return this;
}
+ ///
+ /// 接受Json
+ ///
+ /// 链式调用需要的实例
+ public Requester SetAcceptJson()
+ {
+ return AddHeader(RequestHeaders.Accept, RequestOptions.Json);
+ }
+
+ ///
+ /// 设置常规UA
+ ///
+ /// 链式调用需要的实例
+ public Requester SetCommonUA()
+ {
+ return AddHeader(RequestHeaders.UserAgent, RequestOptions.CommonUA);
+ }
+
+ ///
+ /// 设置为由米游社请求
+ ///
+ /// 链式调用需要的实例
+ public Requester SetRequestWithHyperion()
+ {
+ return AddHeader(RequestHeaders.RequestWith, RequestOptions.Hyperion);
+ }
+
+ ///
+ /// 添加Cookie请求头
+ ///
+ /// 用户
+ /// 链式调用需要的实例
+ public Requester SetUser(User user)
+ {
+ return AddHeader(RequestHeaders.Cookie, user.Cookie ?? string.Empty);
+ }
+
///
/// 在请求前准备
///
@@ -151,14 +198,15 @@ public class Requester
}
private async Task?> RequestAsync(
- Func> requestFunc,
+ Func> requestAsyncFunc,
CancellationToken cancellationToken = default)
{
+ isRequesting = true;
PrepareHttpClient();
try
{
- HttpResponseMessage response = await requestFunc
+ HttpResponseMessage response = await requestAsyncFunc
.Invoke(HttpClient, cancellationToken)
.ConfigureAwait(false);
@@ -166,12 +214,15 @@ public class Requester
.ReadAsStringAsync(cancellationToken)
.ConfigureAwait(false);
- Response? resp = json.ToObject>(contentString);
- if (resp?.ToString() is string representable)
+ Response? resp = Json.ToObject>(contentString);
+
+ if (resp is null)
{
- infoBarService.Information(representable);
+ return Response.CreateForJsonException(contentString);
}
+ ValidateResponse(resp);
+
return resp;
}
catch (Exception ex)
@@ -181,7 +232,17 @@ public class Requester
}
finally
{
+ isRequesting = false;
logger.LogInformation("Request Completed");
}
}
+
+ private void ValidateResponse(Response resp)
+ {
+ // 未知的返回代码
+ if (!Enum.IsDefined(typeof(KnownReturnCode), resp.ReturnCode))
+ {
+ infoBarService.Information(resp.ToString());
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs b/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs
index 47cf998a..3aceff4b 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs
@@ -8,6 +8,11 @@ namespace Snap.Hutao.Web.Response;
///
public enum KnownReturnCode
{
+ ///
+ /// Url为 空
+ ///
+ JsonParseIssue = -2000000002,
+
///
/// Url为 空
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Response/ListWrapper.cs b/src/Snap.Hutao/Snap.Hutao/Web/Response/ListWrapper.cs
index 3c977a3c..8f1c7e34 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Response/ListWrapper.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Response/ListWrapper.cs
@@ -16,5 +16,5 @@ public class ListWrapper
/// 列表
///
[JsonPropertyName("list")]
- public List? List { get; set; }
+ public List List { get; set; } = default!;
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs b/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs
index fbb8fae5..60c57cbe 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs
@@ -20,7 +20,7 @@ public class Response
/// 消息
///
[JsonPropertyName("message")]
- public string? Message { get; set; }
+ public string Message { get; set; } = default!;
///
/// 响应是否正常
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Response/Response{TData}.cs b/src/Snap.Hutao/Snap.Hutao/Web/Response/Response{TData}.cs
index 6effbe96..68166309 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Response/Response{TData}.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Response/Response{TData}.cs
@@ -31,6 +31,20 @@ public class Response : Response
};
}
+ ///
+ /// 构造一个失败的响应
+ ///
+ /// 消息
+ /// 响应
+ public static Response CreateForJsonException(string message)
+ {
+ return new Response()
+ {
+ ReturnCode = (int)KnownReturnCode.InternalFailure,
+ Message = message,
+ };
+ }
+
///
/// 构造一个空Url的响应
///