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的响应 ///