From fda642b72fb6a8b157e539a7a84b9a91af641f16 Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Fri, 21 Oct 2022 16:40:10 +0800 Subject: [PATCH] hutao api v2 page --- src/Snap.Hutao/Snap.Hutao/App.xaml.cs | 8 +- .../Snap.Hutao/Control/ScopedPage.cs | 3 + .../Snap.Hutao/Core/CoreEnvironment.cs | 21 ++ .../Core/Exception/ExceptionRecorder.cs | 10 +- .../Factory/AsyncRelayCommandFactory.cs | 9 +- .../Model/Binding/Hutao/ComplexAvatar.cs | 47 +++ .../Binding/Hutao/ComplexAvatarCollocation.cs | 39 +++ .../Hutao/ComplexAvatarConstellationInfo.cs | 29 ++ .../Model/Binding/Hutao/ComplexAvatarRank.cs | 20 ++ .../Binding/Hutao/ComplexReliquarySet.cs | 66 ++++ .../Model/Binding/Hutao/ComplexTeamRank.cs | 40 +++ .../Model/Binding/Hutao/ComplexWeapon.cs | 47 +++ .../Snap.Hutao/Model/Binding/Hutao/Team.cs | 36 ++ .../Model/InterChange/GachaLog/UIGFInfo.cs | 1 + .../Model/Metadata/Avatar/AvatarIds.cs | 2 + .../Model/Metadata/Reliquary/ReliquarySet.cs | 21 +- .../Snap.Hutao/Package.appxmanifest | 2 +- src/Snap.Hutao/Snap.Hutao/Program.cs | 1 + .../Resource/Icon/UI_ChapterIcon_Hutao.png | Bin 0 -> 13015 bytes .../Service/Abstraction/IHutaoService.cs | 36 ++ .../Snap.Hutao/Service/AppCenter/AppCenter.cs | 95 ++++++ .../Service/AppCenter/DeviceHelper.cs | 64 ++++ .../AppCenter/Model/AppCenterException.cs | 20 ++ .../Service/AppCenter/Model/Device.cs | 53 +++ .../Service/AppCenter/Model/Log/EventLog.cs | 22 ++ .../AppCenter/Model/Log/HandledErrorLog.cs | 23 ++ .../Service/AppCenter/Model/Log/Log.cs | 23 ++ .../AppCenter/Model/Log/LogContainer.cs | 16 + .../AppCenter/Model/Log/LogConverter.cs | 22 ++ .../Service/AppCenter/Model/Log/LogHelper.cs | 46 +++ .../Service/AppCenter/Model/Log/LogStatus.cs | 13 + .../AppCenter/Model/Log/ManagedErrorLog.cs | 51 +++ .../Service/AppCenter/Model/Log/PageLog.cs | 19 ++ .../AppCenter/Model/Log/PropertiesLog.cs | 11 + .../AppCenter/Model/Log/StartServiceLog.cs | 19 ++ .../AppCenter/Model/Log/StartSessionLog.cs | 11 + .../AppCenter/Model/LogUploadResult.cs | 20 ++ .../GachaLogUrlManualInputProvider.cs | 23 +- .../UrlProvider/GachaLogUrlStokenProvider.cs | 18 +- .../GachaLogUrlWebCacheProvider.cs | 14 +- .../Game/Locator/RegistryLauncherLocator.cs | 2 +- .../Snap.Hutao/Service/HutaoService.cs | 53 ++- .../Service/Metadata/IMetadataService.cs | 14 + .../MetadataService.Implementation.cs | 43 +-- .../Metadata/MetadataService.Indexing.cs | 57 ++++ .../Snap.Hutao/Service/User/UserService.cs | 2 +- src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj | 25 +- .../View/Dialog/GachaLogUrlDialog.xaml | 19 ++ .../View/Dialog/GachaLogUrlDialog.xaml.cs | 36 ++ src/Snap.Hutao/Snap.Hutao/View/MainView.xaml | 7 +- .../View/Page/HutaoDatabasePage.xaml | 309 ++++++++++++++++++ .../View/Page/HutaoDatabasePage.xaml.cs | 22 ++ .../Snap.Hutao/ViewModel/GachaLogViewModel.cs | 7 + .../ViewModel/HutaoDatabaseViewModel.cs | 151 +++++++++ .../Snap.Hutao/Web/Hoyolab/Cookie.cs | 2 +- .../Web/Hoyolab/HttpClientExtensions.cs | 15 + .../Snap.Hutao/Web/Hutao/HomaClient.cs | 30 +- .../Model/Converter/ReliquarySetsConverter.cs | 2 +- .../Web/Hutao/Model/ReliquarySet.cs | 6 +- .../Web/Hutao/Model/TeamAppearance.cs | 6 + 60 files changed, 1745 insertions(+), 84 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexAvatar.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexAvatarCollocation.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexAvatarConstellationInfo.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexAvatarRank.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexReliquarySet.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexTeamRank.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexWeapon.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/Team.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Resource/Icon/UI_ChapterIcon_Hutao.png create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/AppCenter/AppCenter.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/AppCenter/DeviceHelper.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/AppCenterException.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Device.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/EventLog.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/HandledErrorLog.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/Log.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogContainer.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogConverter.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogHelper.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogStatus.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/ManagedErrorLog.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/PageLog.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/PropertiesLog.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/StartServiceLog.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/StartSessionLog.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/LogUploadResult.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Indexing.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogUrlDialog.xaml create mode 100644 src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogUrlDialog.xaml.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml create mode 100644 src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/ViewModel/HutaoDatabaseViewModel.cs diff --git a/src/Snap.Hutao/Snap.Hutao/App.xaml.cs b/src/Snap.Hutao/Snap.Hutao/App.xaml.cs index 6974a34c57..fa75ecd0e3 100644 --- a/src/Snap.Hutao/Snap.Hutao/App.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/App.xaml.cs @@ -9,6 +9,7 @@ using Snap.Hutao.Core.Logging; using Snap.Hutao.Core.Threading; using Snap.Hutao.Extension; +using Snap.Hutao.Service.AppCenter; using Snap.Hutao.Service.Metadata; using System.Diagnostics; using Windows.Storage; @@ -27,13 +28,14 @@ public partial class App : Application /// Initializes the singleton application object. /// /// 日志器 - public App(ILogger logger) + /// App Center + public App(ILogger logger, AppCenter appCenter) { // load app resource InitializeComponent(); this.logger = logger; - _ = new ExceptionRecorder(this, logger); + _ = new ExceptionRecorder(this, logger, appCenter); } /// @@ -57,6 +59,8 @@ protected override async void OnLaunched(LaunchActivatedEventArgs args) .ImplictAs()? .InitializeInternalAsync() .SafeForget(logger); + + Ioc.Default.GetRequiredService().Initialize(); } else { diff --git a/src/Snap.Hutao/Snap.Hutao/Control/ScopedPage.cs b/src/Snap.Hutao/Snap.Hutao/Control/ScopedPage.cs index f1d37b92ec..88b6d70b93 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/ScopedPage.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/ScopedPage.cs @@ -11,6 +11,9 @@ namespace Snap.Hutao.Control; /// /// 表示支持取消加载的异步页面 /// 在被导航到其他页面前触发取消异步通知 +/// +/// InitializeWith{T}(); +/// InitializeComponent(); /// public class ScopedPage : Page { diff --git a/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs index 54e38e48d4..5378799a40 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs @@ -1,7 +1,10 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Microsoft.Win32; using Snap.Hutao.Extension; +using System.Security.Cryptography; +using System.Text; using System.Text.Encodings.Web; using Windows.ApplicationModel; @@ -12,6 +15,9 @@ namespace Snap.Hutao.Core; /// internal static class CoreEnvironment { + private const string CryptographyKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\"; + private const string MachineGuidValue = "MachineGuid"; + // 计算过程:https://gist.github.com/Lightczx/373c5940b36e24b25362728b52dec4fd /// @@ -49,6 +55,11 @@ internal static class CoreEnvironment /// public static readonly string HoyolabDeviceId; + /// + /// AppCenter 设备Id + /// + public static readonly string AppCenterDeviceId; + /// /// 默认的Json序列化选项 /// @@ -67,5 +78,15 @@ static CoreEnvironment() // simply assign a random guid HoyolabDeviceId = Guid.NewGuid().ToString(); + AppCenterDeviceId = GetUniqueUserID(); + } + + private static string GetUniqueUserID() + { + string userName = Environment.UserName; + object? machineGuid = Registry.GetValue(CryptographyKey, MachineGuidValue, userName); + byte[] bytes = Encoding.UTF8.GetBytes($"{userName}{machineGuid}"); + byte[] hash = MD5.Create().ComputeHash(bytes); + return System.Convert.ToHexString(hash); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Exception/ExceptionRecorder.cs b/src/Snap.Hutao/Snap.Hutao/Core/Exception/ExceptionRecorder.cs index 6bd7d8ecbb..f9fe94b7c0 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Exception/ExceptionRecorder.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Exception/ExceptionRecorder.cs @@ -3,6 +3,7 @@ using Microsoft.UI.Xaml; using Snap.Hutao.Core.Logging; +using Snap.Hutao.Service.AppCenter; namespace Snap.Hutao.Core.Exception; @@ -12,15 +13,18 @@ namespace Snap.Hutao.Core.Exception; internal class ExceptionRecorder { private readonly ILogger logger; + private readonly AppCenter appCenter; /// /// 构造一个新的异常记录器 /// /// 应用程序 /// 日志器 - public ExceptionRecorder(Application application, ILogger logger) + /// App Center + public ExceptionRecorder(Application application, ILogger logger, AppCenter appCenter) { this.logger = logger; + this.appCenter = appCenter; application.UnhandledException += OnAppUnhandledException; application.DebugSettings.BindingFailed += OnXamlBindingFailed; @@ -28,9 +32,7 @@ public ExceptionRecorder(Application application, ILogger logger) private void OnAppUnhandledException(object? sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) { - // string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); - // string fileName = $"ex-{DateTimeOffset.Now:yyyyMMddHHmmssffff}.txt"; - // File.WriteAllText(Path.Combine(path, fileName), $"{e.Exception}\r\n{e.Exception.StackTrace}"); + appCenter.TrackCrash(e.Exception); logger.LogError(EventIds.UnhandledException, e.Exception, "未经处理的异常"); foreach (ILoggerProvider provider in Ioc.Default.GetRequiredService>()) diff --git a/src/Snap.Hutao/Snap.Hutao/Factory/AsyncRelayCommandFactory.cs b/src/Snap.Hutao/Snap.Hutao/Factory/AsyncRelayCommandFactory.cs index 9c1c159d4d..2ffc73b63b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Factory/AsyncRelayCommandFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Factory/AsyncRelayCommandFactory.cs @@ -4,6 +4,7 @@ using CommunityToolkit.Mvvm.Input; using Snap.Hutao.Core.Logging; using Snap.Hutao.Factory.Abstraction; +using Snap.Hutao.Service.AppCenter; namespace Snap.Hutao.Factory; @@ -11,15 +12,18 @@ namespace Snap.Hutao.Factory; [Injection(InjectAs.Transient, typeof(IAsyncRelayCommandFactory))] internal class AsyncRelayCommandFactory : IAsyncRelayCommandFactory { - private readonly ILogger logger; + private readonly ILogger logger; + private readonly AppCenter appCenter; /// /// 构造一个新的异步命令工厂 /// /// 日志器 - public AsyncRelayCommandFactory(ILogger logger) + /// App Center + public AsyncRelayCommandFactory(ILogger logger, AppCenter appCenter) { this.logger = logger; + this.appCenter = appCenter; } /// @@ -94,6 +98,7 @@ private void ReportException(IAsyncRelayCommand command) { Exception baseException = exception.GetBaseException(); logger.LogError(EventIds.AsyncCommandException, baseException, "{name} Exception", nameof(AsyncRelayCommand)); + appCenter.TrackError(exception); } } } diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexAvatar.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexAvatar.cs new file mode 100644 index 0000000000..8b2867297d --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexAvatar.cs @@ -0,0 +1,47 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Intrinsic; +using Snap.Hutao.Model.Metadata.Avatar; +using Snap.Hutao.Model.Metadata.Converter; + +namespace Snap.Hutao.Model.Binding.Hutao; + +/// +/// 角色 +/// +internal class ComplexAvatar +{ + /// + /// 构造一个胡桃数据库角色 + /// + /// 元数据角色 + /// 率 + public ComplexAvatar(Avatar avatar, double rate) + { + Name = avatar.Name; + Icon = AvatarIconConverter.IconNameToUri(avatar.Icon); + Quality = avatar.Quality; + Rate = $"{rate:P3}"; + } + + /// + /// 名称 + /// + public string Name { get; set; } = default!; + + /// + /// 图标 + /// + public Uri Icon { get; set; } = default!; + + /// + /// 星级 + /// + public ItemQuality Quality { get; set; } + + /// + /// 比率 + /// + public string Rate { get; set; } = default!; +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexAvatarCollocation.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexAvatarCollocation.cs new file mode 100644 index 0000000000..f0557f7abe --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexAvatarCollocation.cs @@ -0,0 +1,39 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Intrinsic; +using Snap.Hutao.Model.Metadata.Avatar; +using Snap.Hutao.Model.Metadata.Converter; + +namespace Snap.Hutao.Model.Binding.Hutao; + +/// +/// 角色搭配 +/// +internal class ComplexAvatarCollocation : ComplexAvatar +{ + /// + /// 构造一个新的角色搭配 + /// + /// 角色 + /// 比率 + public ComplexAvatarCollocation(Avatar avatar) + : base(avatar, 0) + { + } + + /// + /// 角色 + /// + public List Avatars { get; set; } = default!; + + /// + /// 武器 + /// + public List Weapons { get; set; } = default!; + + /// + /// 圣遗物套装 + /// + public List ReliquarySets { get; set; } = default!; +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexAvatarConstellationInfo.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexAvatarConstellationInfo.cs new file mode 100644 index 0000000000..758d4b9e35 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexAvatarConstellationInfo.cs @@ -0,0 +1,29 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Metadata.Avatar; + +namespace Snap.Hutao.Model.Binding.Hutao; + +/// +/// 角色命座信息 +/// +internal class ComplexAvatarConstellationInfo : ComplexAvatar +{ + /// + /// 构造一个新的角色命座信息 + /// + /// 角色 + /// 持有率 + /// 命座比率 + public ComplexAvatarConstellationInfo(Avatar avatar, double rate, IEnumerable rates) + : base(avatar, rate) + { + Rates = rates.Select(r => $"{r:P3}").ToList(); + } + + /// + /// 命座比率 + /// + public List Rates { get; set; } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexAvatarRank.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexAvatarRank.cs new file mode 100644 index 0000000000..0973c0bc2b --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexAvatarRank.cs @@ -0,0 +1,20 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Model.Binding.Hutao; + +/// +/// 角色榜 +/// +internal class ComplexAvatarRank +{ + /// + /// 层数 + /// + public string Floor { get; set; } = default!; + + /// + /// 排行信息 + /// + public List Avatars { get; set; } = default!; +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexReliquarySet.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexReliquarySet.cs new file mode 100644 index 0000000000..b6c7d08925 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexReliquarySet.cs @@ -0,0 +1,66 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Metadata.Converter; +using Snap.Hutao.Web.Hutao.Model; +using System.Text; + +namespace Snap.Hutao.Model.Binding.Hutao; + +/// +/// 圣遗物套装 +/// +internal class ComplexReliquarySet +{ + /// + /// 构造一个新的胡桃数据库圣遗物套装 + /// + /// 圣遗物套装率 + /// 圣遗物套装映射 + public ComplexReliquarySet(ItemRate reliquarySetRate, Dictionary idReliquarySetMap) + { + ReliquarySets sets = reliquarySetRate.Item; + + if (sets.Count >= 1) + { + StringBuilder setStringBuilder = new(); + List icons = new(); + foreach (ReliquarySet set in sets) + { + Metadata.Reliquary.ReliquarySet metaSet = idReliquarySetMap[set.EquipAffixId / 10]; + + if (setStringBuilder.Length != 0) + { + setStringBuilder.Append(Environment.NewLine); + } + + setStringBuilder.Append(set.Count).Append('×').Append(metaSet.Name); + icons.Add(RelicIconConverter.IconNameToUri(metaSet.Icon)); + } + + Name = setStringBuilder.ToString(); + Icons = icons; + } + else + { + Name = "无圣遗物"; + } + + Rate = $"{reliquarySetRate.Rate:P3}"; + } + + /// + /// 名称 + /// + public string Name { get; set; } = default!; + + /// + /// 图标 + /// + public List Icons { get; set; } = default!; + + /// + /// 比率 + /// + public string Rate { get; set; } = default!; +} diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexTeamRank.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexTeamRank.cs new file mode 100644 index 0000000000..8d2ce83c0a --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexTeamRank.cs @@ -0,0 +1,40 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Metadata.Avatar; +using Snap.Hutao.Web.Hutao.Model; + +namespace Snap.Hutao.Model.Binding.Hutao; + +/// +/// 队伍排行 +/// +internal class ComplexTeamRank +{ + /// + /// 构造一个新的队伍排行 + /// + /// 队伍排行 + /// 映射 + public ComplexTeamRank(TeamAppearance teamRank, Dictionary idAvatarMap) + { + Floor = $"第 {teamRank.Floor} 层"; + Up = teamRank.Up.Select(teamRate => new Team(teamRate, idAvatarMap)).ToList(); + Down = teamRank.Down.Select(teamRate => new Team(teamRate, idAvatarMap)).ToList(); + } + + /// + /// 层数 + /// + public string Floor { get; set; } = default!; + + /// + /// 上半阵容 + /// + public List Up { get; set; } = default!; + + /// + /// 下半阵容 + /// + public List Down { get; set; } = default!; +} diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexWeapon.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexWeapon.cs new file mode 100644 index 0000000000..1a8b6ae0f9 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexWeapon.cs @@ -0,0 +1,47 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Intrinsic; +using Snap.Hutao.Model.Metadata.Converter; +using Snap.Hutao.Model.Metadata.Weapon; + +namespace Snap.Hutao.Model.Binding.Hutao; + +/// +/// 胡桃数据库武器 +/// +internal class ComplexWeapon +{ + /// + /// 构造一个胡桃数据库武器 + /// + /// 元数据武器 + /// 率 + public ComplexWeapon(Weapon weapon, double rate) + { + Name = weapon.Name; + Icon = EquipIconConverter.IconNameToUri(weapon.Icon); + Quality = weapon.Quality; + Rate = $"{rate:P3}"; + } + + /// + /// 名称 + /// + public string Name { get; set; } = default!; + + /// + /// 图标 + /// + public Uri Icon { get; set; } = default!; + + /// + /// 星级 + /// + public ItemQuality Quality { get; set; } + + /// + /// 比率 + /// + public string Rate { get; set; } = default!; +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/Team.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/Team.cs new file mode 100644 index 0000000000..66f8ef5287 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/Team.cs @@ -0,0 +1,36 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Metadata.Avatar; +using Snap.Hutao.Web.Hutao.Model; + +namespace Snap.Hutao.Model.Binding.Hutao; + +/// +/// 队伍 +/// +internal class Team : List +{ + /// + /// 构造一个新的队伍 + /// + /// 队伍 + /// 映射 + public Team(ItemRate team, Dictionary idAvatarMap) + : base(4) + { + IEnumerable ids = team.Item.Split(',').Select(i => int.Parse(i)); + + foreach (int id in ids) + { + Add(new(idAvatarMap[id], 0)); + } + + Rate = $"上场 {team.Rate} 次"; + } + + /// + /// 上场次数 + /// + public string Rate { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGFInfo.cs b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGFInfo.cs index ec7f6f278c..144c78c6d0 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGFInfo.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGFInfo.cs @@ -27,6 +27,7 @@ public class UIGFInfo /// 导出的时间戳 /// [JsonPropertyName("export_timestamp")] + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] public long? ExportTimestamp { get; set; } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/AvatarIds.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/AvatarIds.cs index 42b2899e4c..d77013532d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/AvatarIds.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/AvatarIds.cs @@ -71,4 +71,6 @@ public static class AvatarIds public const int Nilou = 10000070; public const int Cyno = 10000071; public const int Candace = 10000072; + public const int Nahida = 10000073; + public const int Layla = 10000074; } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Reliquary/ReliquarySet.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Reliquary/ReliquarySet.cs index 3f711fe5b8..4cc90bffd4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Reliquary/ReliquarySet.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Reliquary/ReliquarySet.cs @@ -11,15 +11,30 @@ public class ReliquarySet /// /// 套装Id /// - public int SetId { get; set; } = default!; + public int SetId { get; set; } + + /// + /// 装备被动Id + /// + public int EquipAffixId { get; set; } + + /// + /// 套装名称 + /// + public string Name { get; set; } = default!; + + /// + /// 套装图标 + /// + public string Icon { get; set; } = default!; /// /// 需要的数量 /// - public IEnumerable NeedNumber { get; set; } = default!; + public List NeedNumber { get; set; } = default!; /// /// 描述 /// - public IEnumerable Descriptions { get; set; } = default!; + public List Descriptions { get; set; } = default!; } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest index 5f25f93ca7..1a04cd7100 100644 --- a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest +++ b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest @@ -9,7 +9,7 @@ + Version="1.1.13.0" /> 胡桃 diff --git a/src/Snap.Hutao/Snap.Hutao/Program.cs b/src/Snap.Hutao/Snap.Hutao/Program.cs index 486050da25..aa64a0a54c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Program.cs +++ b/src/Snap.Hutao/Snap.Hutao/Program.cs @@ -20,6 +20,7 @@ public static partial class Program /// /// 主线程队列 /// + [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [SuppressMessage("", "SA1401")] internal static volatile DispatcherQueue? DispatcherQueue; diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Icon/UI_ChapterIcon_Hutao.png b/src/Snap.Hutao/Snap.Hutao/Resource/Icon/UI_ChapterIcon_Hutao.png new file mode 100644 index 0000000000000000000000000000000000000000..bb1ec9f1222c087d28566997c07912169dbe61bb GIT binary patch literal 13015 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 @@ -8,5 +10,39 @@ namespace Snap.Hutao.Service.Abstraction; ///
internal interface IHutaoService { + /// + /// 异步获取角色上场率 + /// + /// 角色上场率 + ValueTask> GetAvatarAppearanceRanksAsync(); + + /// + /// 异步获取角色搭配 + /// + /// 角色搭配 + ValueTask> GetAvatarCollocationsAsync(); + + /// + /// 异步获取角色持有率信息 + /// + /// 角色持有率信息 + ValueTask> GetAvatarConstellationInfosAsync(); + + /// + /// 异步获取角色使用率 + /// + /// 角色使用率 + ValueTask> GetAvatarUsageRanksAsync(); + + /// + /// 异步获取统计数据 + /// + /// 统计数据 + ValueTask GetOverviewAsync(); + /// + /// 异步获取队伍上场 + /// + /// 队伍上场 + ValueTask> GetTeamAppearancesAsync(); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/AppCenter.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/AppCenter.cs new file mode 100644 index 0000000000..042cebd6ce --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/AppCenter.cs @@ -0,0 +1,95 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core; +using Snap.Hutao.Core.Threading; +using Snap.Hutao.Service.AppCenter.Model; +using Snap.Hutao.Service.AppCenter.Model.Log; +using Snap.Hutao.Web.Hoyolab; +using System.Net.Http; + +namespace Snap.Hutao.Service.AppCenter; + +[SuppressMessage("", "SA1600")] +[Injection(InjectAs.Singleton)] +public sealed class AppCenter : IDisposable +{ + private const string AppSecret = "de5bfc48-17fc-47ee-8e7e-dee7dc59d554"; + private const string API = "https://in.appcenter.ms/logs?api-version=1.0.0"; + + private readonly TaskCompletionSource uploadTaskCompletionSource = new(); + private readonly CancellationTokenSource uploadTaskCancllationTokenSource = new(); + private readonly HttpClient httpClient; + private readonly List queue; + private readonly Device deviceInfo; + private readonly JsonSerializerOptions options; + + private Guid sessionID; + + public AppCenter() + { + options = new(CoreEnvironment.JsonOptions); + options.Converters.Add(new LogConverter()); + + httpClient = new() { DefaultRequestHeaders = { { "Install-ID", CoreEnvironment.AppCenterDeviceId }, { "App-Secret", AppSecret } } }; + queue = new List(); + deviceInfo = new Device(); + Task.Run(async () => + { + while (!uploadTaskCancllationTokenSource.Token.IsCancellationRequested) + { + await UploadAsync().ConfigureAwait(false); + await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false); + } + + uploadTaskCompletionSource.TrySetResult(); + }).SafeForget(); + } + + public async Task UploadAsync() + { + if (queue.Count == 0) + { + return; + } + + string? uploadStatus = null; + do + { + queue.ForEach(log => log.Status = LogStatus.Uploading); + LogContainer container = new(queue); + + LogUploadResult? response = await httpClient + .TryCatchPostAsJsonAsync(API, container, options) + .ConfigureAwait(false); + uploadStatus = response?.Status; + } + while (uploadStatus != "Success"); + + queue.RemoveAll(log => log.Status == LogStatus.Uploading); + } + + public void Initialize() + { + sessionID = Guid.NewGuid(); + queue.Add(new StartServiceLog("Analytics", "Crashes").Initialize(sessionID, deviceInfo)); + queue.Add(new StartSessionLog().Initialize(sessionID, deviceInfo).Initialize(sessionID, deviceInfo)); + } + + public void TrackCrash(Exception exception, bool isFatal = true) + { + queue.Add(new ManagedErrorLog(exception, isFatal).Initialize(sessionID, deviceInfo)); + } + + public void TrackError(Exception exception) + { + queue.Add(new HandledErrorLog(exception).Initialize(sessionID, deviceInfo)); + } + + [SuppressMessage("", "VSTHRD002")] + public void Dispose() + { + uploadTaskCancllationTokenSource.Cancel(); + uploadTaskCompletionSource.Task.GetAwaiter().GetResult(); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/DeviceHelper.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/DeviceHelper.cs new file mode 100644 index 0000000000..a45b48c08b --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/DeviceHelper.cs @@ -0,0 +1,64 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.UI.Windowing; +using Microsoft.Win32; +using Windows.Graphics; + +namespace Snap.Hutao.Service.AppCenter; + +/// +/// 设备帮助类 +/// +[SuppressMessage("", "SA1600")] +public static class DeviceHelper +{ + private static readonly RegistryKey? BiosKey = Registry.LocalMachine.OpenSubKey("HARDWARE\\DESCRIPTION\\System\\BIOS"); + private static readonly RegistryKey? GeoKey = Registry.CurrentUser.OpenSubKey("Control Panel\\International\\Geo"); + private static readonly RegistryKey? CurrentVersionKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"); + + public static string? GetOem() + { + string? oem = BiosKey?.GetValue("SystemManufacturer") as string; + return oem == "System manufacturer" ? null : oem; + } + + public static string? GetModel() + { + string? model = BiosKey?.GetValue("SystemProductName") as string; + return model == "System Product Name" ? null : model; + } + + public static string GetScreenSize() + { + RectInt32 screen = DisplayArea.Primary.OuterBounds; + return $"{screen.Width}x{screen.Height}"; + } + + public static string? GetCountry() + { + return GeoKey?.GetValue("Name") as string; + } + + public static string GetSystemVersion() + { + object? majorVersion = CurrentVersionKey?.GetValue("CurrentMajorVersionNumber"); + if (majorVersion != null) + { + object? minorVersion = CurrentVersionKey?.GetValue("CurrentMinorVersionNumber", "0"); + object? buildNumber = CurrentVersionKey?.GetValue("CurrentBuildNumber", "0"); + return $"{majorVersion}.{minorVersion}.{buildNumber}"; + } + else + { + object? version = CurrentVersionKey?.GetValue("CurrentVersion", "0.0"); + object? buildNumber = CurrentVersionKey?.GetValue("CurrentBuild", "0"); + return $"{version}.{buildNumber}"; + } + } + + public static int GetSystemBuild() + { + return (int)(CurrentVersionKey?.GetValue("UBR") ?? 0); + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/AppCenterException.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/AppCenterException.cs new file mode 100644 index 0000000000..97f30077a6 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/AppCenterException.cs @@ -0,0 +1,20 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.AppCenter.Model; + +[SuppressMessage("", "SA1600")] +public class AppCenterException +{ + [JsonPropertyName("type")] + public string Type { get; set; } = "UnknownType"; + + [JsonPropertyName("message")] + public string? Message { get; set; } + + [JsonPropertyName("stackTrace")] + public string? StackTrace { get; set; } + + [JsonPropertyName("innerExceptions")] + public List? InnerExceptions { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Device.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Device.cs new file mode 100644 index 0000000000..e92c28808e --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Device.cs @@ -0,0 +1,53 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core; +using System.Globalization; + +namespace Snap.Hutao.Service.AppCenter.Model; + +[SuppressMessage("", "SA1600")] +public class Device +{ + [JsonPropertyName("sdkName")] + public string SdkName { get; set; } = "appcenter.winui"; + + [JsonPropertyName("sdkVersion")] + public string SdkVersion { get; set; } = "4.5.0"; + + [JsonPropertyName("osName")] + public string OsName { get; set; } = "WINDOWS"; + + [JsonPropertyName("osVersion")] + public string OsVersion { get; set; } = DeviceHelper.GetSystemVersion(); + + [JsonPropertyName("osBuild")] + public string OsBuild { get; set; } = $"{DeviceHelper.GetSystemVersion()}.{DeviceHelper.GetSystemBuild()}"; + + [JsonPropertyName("model")] + public string? Model { get; set; } = DeviceHelper.GetModel(); + + [JsonPropertyName("oemName")] + public string? OemName { get; set; } = DeviceHelper.GetOem(); + + [JsonPropertyName("screenSize")] + public string ScreenSize { get; set; } = DeviceHelper.GetScreenSize(); + + [JsonPropertyName("carrierCountry")] + public string Country { get; set; } = DeviceHelper.GetCountry() ?? "CN"; + + [JsonPropertyName("locale")] + public string Locale { get; set; } = CultureInfo.CurrentCulture.Name; + + [JsonPropertyName("timeZoneOffset")] + public int TimeZoneOffset { get; set; } = (int)TimeZoneInfo.Local.BaseUtcOffset.TotalMinutes; + + [JsonPropertyName("appVersion")] + public string AppVersion { get; set; } = CoreEnvironment.Version.ToString(); + + [JsonPropertyName("appBuild")] + public string AppBuild { get; set; } = CoreEnvironment.Version.ToString(); + + [JsonPropertyName("appNamespace")] + public string AppNamespace { get; set; } = typeof(App).Namespace ?? string.Empty; +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/EventLog.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/EventLog.cs new file mode 100644 index 0000000000..dd5354edce --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/EventLog.cs @@ -0,0 +1,22 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.AppCenter.Model.Log; + +[SuppressMessage("", "SA1600")] +public class EventLog : PropertiesLog +{ + public EventLog(string name) + { + Name = name; + } + + [JsonPropertyName("type")] + public override string Type { get => "event"; } + + [JsonPropertyName("id")] + public Guid Id { get; set; } = Guid.NewGuid(); + + [JsonPropertyName("name")] + public string Name { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/HandledErrorLog.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/HandledErrorLog.cs new file mode 100644 index 0000000000..9ab5a763e6 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/HandledErrorLog.cs @@ -0,0 +1,23 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.AppCenter.Model.Log; + +[SuppressMessage("", "SA1600")] +public class HandledErrorLog : PropertiesLog +{ + public HandledErrorLog(Exception exception) + { + Id = Guid.NewGuid(); + Exception = LogHelper.Create(exception); + } + + [JsonPropertyName("id")] + public Guid? Id { get; set; } + + [JsonPropertyName("exception")] + public AppCenterException Exception { get; set; } + + [JsonPropertyName("type")] + public override string Type { get => "handledError"; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/Log.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/Log.cs new file mode 100644 index 0000000000..5375dc75e7 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/Log.cs @@ -0,0 +1,23 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.AppCenter.Model.Log; + +[SuppressMessage("", "SA1600")] +public abstract class Log +{ + [JsonIgnore] + public LogStatus Status { get; set; } = LogStatus.Pending; + + [JsonPropertyName("type")] + public abstract string Type { get; } + + [JsonPropertyName("sid")] + public Guid Session { get; set; } + + [JsonPropertyName("timestamp")] + public string Timestamp { get; set; } = DateTime.UtcNow.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"); + + [JsonPropertyName("device")] + public Device Device { get; set; } = default!; +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogContainer.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogContainer.cs new file mode 100644 index 0000000000..dd4d8c020f --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogContainer.cs @@ -0,0 +1,16 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.AppCenter.Model.Log; + +[SuppressMessage("", "SA1600")] +public class LogContainer +{ + public LogContainer(IEnumerable logs) + { + Logs = logs; + } + + [JsonPropertyName("logs")] + public IEnumerable Logs { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogConverter.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogConverter.cs new file mode 100644 index 0000000000..ada58c5991 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogConverter.cs @@ -0,0 +1,22 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.AppCenter.Model.Log; + +/// +/// 日志转换器 +/// +public class LogConverter : JsonConverter +{ + /// + public override Log? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw Must.NeverHappen(); + } + + /// + public override void Write(Utf8JsonWriter writer, Log value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value, value.GetType(), options); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogHelper.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogHelper.cs new file mode 100644 index 0000000000..c91412a43e --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogHelper.cs @@ -0,0 +1,46 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.AppCenter.Model.Log; + +[SuppressMessage("", "SA1600")] +public static class LogHelper +{ + public static T Initialize(this T log, Guid sid, Device device) + where T : Log + { + log.Session = sid; + log.Device = device; + + return log; + } + + public static AppCenterException Create(Exception exception) + { + AppCenterException current = new() + { + Type = exception.GetType().ToString(), + Message = exception.Message, + StackTrace = exception.ToString(), + }; + + if (exception is AggregateException aggregateException) + { + if (aggregateException.InnerExceptions.Count != 0) + { + current.InnerExceptions = new(); + foreach (var innerException in aggregateException.InnerExceptions) + { + current.InnerExceptions.Add(Create(innerException)); + } + } + } + else if (exception.InnerException != null) + { + current.InnerExceptions ??= new(); + current.InnerExceptions.Add(Create(exception.InnerException)); + } + + return current; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogStatus.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogStatus.cs new file mode 100644 index 0000000000..5f570a90fd --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogStatus.cs @@ -0,0 +1,13 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.AppCenter.Model.Log; + +[SuppressMessage("", "SA1600")] +[SuppressMessage("", "SA1602")] +public enum LogStatus +{ + Pending, + Uploading, + Uploaded, +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/ManagedErrorLog.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/ManagedErrorLog.cs new file mode 100644 index 0000000000..f071b88dff --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/ManagedErrorLog.cs @@ -0,0 +1,51 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core; +using System.Diagnostics; + +namespace Snap.Hutao.Service.AppCenter.Model.Log; + +[SuppressMessage("", "SA1600")] +public class ManagedErrorLog : Log +{ + public ManagedErrorLog(Exception exception, bool fatal = true) + { + var p = Process.GetCurrentProcess(); + Id = Guid.NewGuid(); + Fatal = fatal; + UserId = CoreEnvironment.AppCenterDeviceId; + ProcessId = p.Id; + Exception = LogHelper.Create(exception); + ProcessName = p.ProcessName; + Architecture = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE"); + AppLaunchTimestamp = p.StartTime.ToUniversalTime(); + } + + [JsonPropertyName("id")] + public Guid Id { get; set; } + + [JsonPropertyName("userId")] + public string? UserId { get; set; } + + [JsonPropertyName("processId")] + public int ProcessId { get; set; } + + [JsonPropertyName("processName")] + public string ProcessName { get; set; } + + [JsonPropertyName("fatal")] + public bool Fatal { get; set; } + + [JsonPropertyName("appLaunchTimestamp")] + public DateTime? AppLaunchTimestamp { get; set; } + + [JsonPropertyName("architecture")] + public string? Architecture { get; set; } + + [JsonPropertyName("exception")] + public AppCenterException Exception { get; set; } + + [JsonPropertyName("type")] + public override string Type { get => "managedError"; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/PageLog.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/PageLog.cs new file mode 100644 index 0000000000..a340965243 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/PageLog.cs @@ -0,0 +1,19 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.AppCenter.Model.Log; + +[SuppressMessage("", "SA1600")] +public class PageLog : PropertiesLog +{ + public PageLog(string name) + { + Name = name; + } + + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("type")] + public override string Type { get => "page"; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/PropertiesLog.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/PropertiesLog.cs new file mode 100644 index 0000000000..cc4eb32b3c --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/PropertiesLog.cs @@ -0,0 +1,11 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.AppCenter.Model.Log; + +[SuppressMessage("", "SA1600")] +public abstract class PropertiesLog : Log +{ + [JsonPropertyName("properties")] + public IDictionary Properties { get; set; } = new Dictionary(); +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/StartServiceLog.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/StartServiceLog.cs new file mode 100644 index 0000000000..06370d890d --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/StartServiceLog.cs @@ -0,0 +1,19 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.AppCenter.Model.Log; + +[SuppressMessage("", "SA1600")] +public class StartServiceLog : Log +{ + public StartServiceLog(params string[] services) + { + Services = services; + } + + [JsonPropertyName("services")] + public string[] Services { get; set; } + + [JsonPropertyName("type")] + public override string Type { get => "startService"; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/StartSessionLog.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/StartSessionLog.cs new file mode 100644 index 0000000000..c690f38a8e --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/StartSessionLog.cs @@ -0,0 +1,11 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.AppCenter.Model.Log; + +[SuppressMessage("", "SA1600")] +public class StartSessionLog : Log +{ + [JsonPropertyName("type")] + public override string Type { get => "startSession"; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/LogUploadResult.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/LogUploadResult.cs new file mode 100644 index 0000000000..95d6aaac23 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/LogUploadResult.cs @@ -0,0 +1,20 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.AppCenter.Model; + +[SuppressMessage("", "SA1600")] +public class LogUploadResult +{ + [JsonPropertyName("status")] + public string Status { get; set; } = null!; + + [JsonPropertyName("validDiagnosticsIds")] + public List ValidDiagnosticsIds { get; set; } = null!; + + [JsonPropertyName("throttledDiagnosticsIds")] + public List ThrottledDiagnosticsIds { get; set; } = null!; + + [JsonPropertyName("correlationId")] + public Guid CorrelationId { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlManualInputProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlManualInputProvider.cs index e5ee75f67b..62b55c7675 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlManualInputProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlManualInputProvider.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Snap.Hutao.Core.Threading; +using Snap.Hutao.View.Dialog; namespace Snap.Hutao.Service.GachaLog; @@ -15,8 +16,26 @@ internal class GachaLogUrlManualInputProvider : IGachaLogUrlProvider public string Name { get => nameof(GachaLogUrlManualInputProvider); } /// - public Task> GetQueryAsync() + public async Task> GetQueryAsync() { - throw new NotImplementedException(); + MainWindow mainWindow = Ioc.Default.GetRequiredService(); + await ThreadHelper.SwitchToMainThreadAsync(); + ValueResult result = await new GachaLogUrlDialog(mainWindow).GetInputUrlAsync().ConfigureAwait(false); + + if (result.IsOk) + { + if (result.Value.Contains("&auth_appid=webview_gacha")) + { + return result; + } + else + { + return new(false, "提供的Url无效"); + } + } + else + { + return new(false, null!); + } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlStokenProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlStokenProvider.cs index 9994545f30..eeb689769c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlStokenProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlStokenProvider.cs @@ -36,9 +36,9 @@ public GachaLogUrlStokenProvider(IUserService userService, BindingClient2 bindin public async Task> GetQueryAsync() { Model.Binding.User? user = userService.Current; - if (user != null) + if (user != null && user.SelectedUserGameRole != null) { - if (user.Cookie!.ContainsSToken() && user.SelectedUserGameRole != null) + if (user.Cookie!.ContainsSToken()) { PlayerUid uid = (PlayerUid)user.SelectedUserGameRole; GenAuthKeyData data = GenAuthKeyData.CreateForWebViewGacha(uid); @@ -48,9 +48,19 @@ public async Task> GetQueryAsync() { return new(true, GachaLogConfigration.AsQuery(data, authkey)); } + else + { + return new(false, "请求验证密钥失败"); + } + } + else + { + return new(false, "当前用户的Cookie不包含 Stoken"); } } - - return new(false, "当前用户的Cookie不包含 Stoken"); + else + { + return new(false, "尚未选择要刷新的用户以及角色"); + } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlWebCacheProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlWebCacheProvider.cs index a621c9803f..58c5dafcf5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlWebCacheProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlWebCacheProvider.cs @@ -40,7 +40,17 @@ public async Task> GetQueryAsync() string folder = Path.GetDirectoryName(path) ?? string.Empty; string cacheFile = Path.Combine(folder, @"YuanShen_Data\webCaches\Cache\Cache_Data\data_2"); - using (TemporaryFile tempFile = TemporaryFile.CreateFromFileCopy(cacheFile)) + TemporaryFile tempFile; + try + { + tempFile = TemporaryFile.CreateFromFileCopy(cacheFile); + } + catch (DirectoryNotFoundException) + { + return new(false, $"找不到原神内置浏览器缓存路径:\n{cacheFile}"); + } + + using (tempFile) { using (FileStream fileStream = new(tempFile.Path, FileMode.Open, FileAccess.Read, FileShare.Read)) { @@ -74,7 +84,7 @@ public async Task> GetQueryAsync() } else { - return new(false, null!); + return new(false, $"未正确提供原神路径,或当前设置的路径不正确"); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/RegistryLauncherLocator.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/RegistryLauncherLocator.cs index 4035256d48..616b462fc4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/RegistryLauncherLocator.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/RegistryLauncherLocator.cs @@ -21,7 +21,7 @@ internal class RegistryLauncherLocator : IGameLocator /// public Task> LocateGamePathAsync() { - ValueResult result = LocateInternal("InstallPath"); + ValueResult result = LocateInternal("DisplayIcon"); if (result.IsOk == false) { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/HutaoService.cs b/src/Snap.Hutao/Snap.Hutao/Service/HutaoService.cs index 2a5b83d201..120c265419 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/HutaoService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/HutaoService.cs @@ -4,16 +4,18 @@ using Microsoft.Extensions.Caching.Memory; using Snap.Hutao.Service.Abstraction; using Snap.Hutao.Web.Hutao; +using Snap.Hutao.Web.Hutao.Model; namespace Snap.Hutao.Service; /// /// 胡桃 API 服务 /// -[Injection(InjectAs.Transient)] +[Injection(InjectAs.Transient, typeof(IHutaoService))] internal class HutaoService : IHutaoService { private readonly HomaClient homaClient; + private readonly IMemoryCache memoryCache; /// /// 构造一个新的胡桃 API 服务 @@ -23,5 +25,54 @@ internal class HutaoService : IHutaoService public HutaoService(HomaClient homaClient, IMemoryCache memoryCache) { this.homaClient = homaClient; + this.memoryCache = memoryCache; + } + + /// + public ValueTask GetOverviewAsync() + { + return FromCacheOrWebAsync(nameof(Overview), homaClient.GetOverviewAsync); + } + + /// + public ValueTask> GetAvatarAppearanceRanksAsync() + { + return FromCacheOrWebAsync(nameof(AvatarAppearanceRank), homaClient.GetAvatarAttendanceRatesAsync); + } + + /// + public ValueTask> GetAvatarUsageRanksAsync() + { + return FromCacheOrWebAsync(nameof(AvatarUsageRank), homaClient.GetAvatarUtilizationRatesAsync); + } + + /// + public ValueTask> GetAvatarConstellationInfosAsync() + { + return FromCacheOrWebAsync(nameof(AvatarConstellationInfo), homaClient.GetAvatarHoldingRatesAsync); + } + + /// + public ValueTask> GetAvatarCollocationsAsync() + { + return FromCacheOrWebAsync(nameof(AvatarCollocation), homaClient.GetAvatarCollocationsAsync); + } + + /// + public ValueTask> GetTeamAppearancesAsync() + { + return FromCacheOrWebAsync(nameof(TeamAppearance), homaClient.GetTeamCombinationsAsync); + } + + private async ValueTask FromCacheOrWebAsync(string typeName, Func> taskFunc) + { + string key = $"{nameof(HutaoService)}.Cache.{typeName}"; + if (memoryCache.TryGetValue(key, out object? cache)) + { + return (T)cache; + } + + T web = await taskFunc(default).ConfigureAwait(false); + return memoryCache.Set(key, web, TimeSpan.FromMinutes(30)); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataService.cs index 9109577b2c..6e5fb6191c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataService.cs @@ -43,6 +43,13 @@ internal interface IMetadataService /// 角色列表 ValueTask> GetAvatarsAsync(CancellationToken token = default); + /// + /// 异步获取装备被动Id到圣遗物套装的映射 + /// + /// 取消令牌 + /// 装备被动Id到圣遗物套装的映射 + ValueTask> GetEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default); + /// /// 异步获取卡池配置列表 /// @@ -126,4 +133,11 @@ internal interface IMetadataService /// 取消令牌 /// 武器列表 ValueTask> GetWeaponsAsync(CancellationToken token = default); + + /// + /// 异步获取圣遗物套装 + /// + /// 取消令牌 + /// 圣遗物套装列表 + ValueTask> GetReliquarySetsAsync(CancellationToken token = default); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Implementation.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Implementation.cs index 2e55651fae..c6c6c20307 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Implementation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Implementation.cs @@ -1,7 +1,6 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Metadata; using Snap.Hutao.Model.Metadata.Achievement; using Snap.Hutao.Model.Metadata.Avatar; @@ -39,42 +38,6 @@ public ValueTask> GetGachaEventsAsync(CancellationToken token = return FromCacheOrFileAsync>("GachaEvent", token); } - /// - public ValueTask> GetIdToAvatarMapAsync(CancellationToken token = default) - { - return FromCacheAsDictionaryAsync("Avatar", a => a.Id, token); - } - - /// - public ValueTask> GetIdReliquaryAffixMapAsync(CancellationToken token = default) - { - return FromCacheAsDictionaryAsync("ReliquaryAffix", a => a.Id, token); - } - - /// - public ValueTask> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default) - { - return FromCacheAsDictionaryAsync("ReliquaryMainAffix", r => r.Id, r => r.Type, token); - } - - /// - public ValueTask> GetIdToWeaponMapAsync(CancellationToken token = default) - { - return FromCacheAsDictionaryAsync("Weapon", w => w.Id, token); - } - - /// - public ValueTask> GetNameToAvatarMapAsync(CancellationToken token = default) - { - return FromCacheAsDictionaryAsync("Avatar", a => a.Name, token); - } - - /// - public ValueTask> GetNameToWeaponMapAsync(CancellationToken token = default) - { - return FromCacheAsDictionaryAsync("Weapon", w => w.Name, token); - } - /// public ValueTask> GetReliquariesAsync(CancellationToken token = default) { @@ -99,6 +62,12 @@ public ValueTask> GetReliquaryMainAffixesAsync(Cancella return FromCacheOrFileAsync>("ReliquaryMainAffix", token); } + /// + public ValueTask> GetReliquarySetsAsync(CancellationToken token = default) + { + return FromCacheOrFileAsync>("ReliquarySet", token); + } + /// public ValueTask> GetWeaponsAsync(CancellationToken token = default) { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Indexing.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Indexing.cs new file mode 100644 index 0000000000..e7496a772d --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Indexing.cs @@ -0,0 +1,57 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Intrinsic; +using Snap.Hutao.Model.Metadata.Avatar; +using Snap.Hutao.Model.Metadata.Reliquary; +using Snap.Hutao.Model.Metadata.Weapon; + +namespace Snap.Hutao.Service.Metadata; + +/// +/// 索引部分 +/// +internal partial class MetadataService +{ + /// + public ValueTask> GetEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default) + { + return FromCacheAsDictionaryAsync("ReliquarySet", r => r.EquipAffixId, token); + } + + /// + public ValueTask> GetIdToAvatarMapAsync(CancellationToken token = default) + { + return FromCacheAsDictionaryAsync("Avatar", a => a.Id, token); + } + + /// + public ValueTask> GetIdReliquaryAffixMapAsync(CancellationToken token = default) + { + return FromCacheAsDictionaryAsync("ReliquaryAffix", a => a.Id, token); + } + + /// + public ValueTask> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default) + { + return FromCacheAsDictionaryAsync("ReliquaryMainAffix", r => r.Id, r => r.Type, token); + } + + /// + public ValueTask> GetIdToWeaponMapAsync(CancellationToken token = default) + { + return FromCacheAsDictionaryAsync("Weapon", w => w.Id, token); + } + + /// + public ValueTask> GetNameToAvatarMapAsync(CancellationToken token = default) + { + return FromCacheAsDictionaryAsync("Avatar", a => a.Name, token); + } + + /// + public ValueTask> GetNameToWeaponMapAsync(CancellationToken token = default) + { + return FromCacheAsDictionaryAsync("Weapon", w => w.Name, token); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs index 314a0a8448..eed4d38eae 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs @@ -137,6 +137,7 @@ public async Task> GetUserCollectionAsync() /// public async Task> ProcessInputCookieAsync(Cookie cookie) { + cookie.Trim(); Must.NotNull(userCollection!); // 检查 uid 是否存在 @@ -197,7 +198,6 @@ private async Task TryAddMultiTokenAsync(Cookie cookie, string uid) private async Task> TryCreateUserAndAddAsync(ObservableCollection users, Cookie cookie) { - cookie.Trim(); BindingUser? newUser = await BindingUser.CreateAsync(cookie, userClient, bindingClient).ConfigureAwait(false); if (newUser != null) { diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj index 96dbb563bb..575d5367ae 100644 --- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj +++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj @@ -29,6 +29,12 @@ $(DefineConstants);DISABLE_XAML_GENERATED_MAIN;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;DISABLE_XAML_GENERATED_BINDING_DEBUG_OUTPUT True + + embedded + + + embedded + @@ -38,6 +44,7 @@ + @@ -54,6 +61,7 @@ + @@ -62,6 +70,7 @@ + @@ -87,6 +96,7 @@ + @@ -99,10 +109,9 @@ - - + - + @@ -139,6 +148,16 @@ + + + MSBuild:Compile + + + + + MSBuild:Compile + + MSBuild:Compile diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogUrlDialog.xaml b/src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogUrlDialog.xaml new file mode 100644 index 0000000000..6654d1e391 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogUrlDialog.xaml @@ -0,0 +1,19 @@ + + + + + + diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogUrlDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogUrlDialog.xaml.cs new file mode 100644 index 0000000000..febbb96e85 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogUrlDialog.xaml.cs @@ -0,0 +1,36 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Snap.Hutao.Core.Threading; + +namespace Snap.Hutao.View.Dialog; + +/// +/// 祈愿记录Url对话框 +/// +public sealed partial class GachaLogUrlDialog : ContentDialog +{ + /// + /// 初始化一个新的祈愿记录Url对话框 + /// + /// 窗体 + public GachaLogUrlDialog(Window window) + { + InitializeComponent(); + XamlRoot = window.Content.XamlRoot; + } + + /// + /// 获取输入的Url + /// + /// 输入的结果 + public async Task> GetInputUrlAsync() + { + ContentDialogResult result = await ShowAsync(); + string url = InputText.Text; + + return new(result == ContentDialogResult.Primary, url); + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml index 42f837ca42..302cefe57d 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml @@ -42,7 +42,12 @@ Content="属性统计" shvh:NavHelper.NavigateTo="shvp:AvatarPropertyPage" Icon="{shcm:BitmapIcon Source=ms-appx:///Resource/Icon/UI_Icon_BoostUp.png}"/> - + + + + + + + + + + 8,0,8,0 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml.cs new file mode 100644 index 0000000000..ef29fdb602 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml.cs @@ -0,0 +1,22 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Control; +using Snap.Hutao.ViewModel; + +namespace Snap.Hutao.View.Page; + +/// +/// 胡桃数据库页面 +/// +public sealed partial class HutaoDatabasePage : ScopedPage +{ + /// + /// 构造一个新的胡桃数据库页面 + /// + public HutaoDatabasePage() + { + InitializeWith(); + InitializeComponent(); + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLogViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLogViewModel.cs index de9c0b2f89..52b66a5217 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLogViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLogViewModel.cs @@ -233,6 +233,13 @@ private async Task RefreshInternalAsync(RefreshOption option) dialog.DefaultButton = ContentDialogButton.Primary; } } + else + { + if (query is string message) + { + infoBarService.Warning(message); + } + } } } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/HutaoDatabaseViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/HutaoDatabaseViewModel.cs new file mode 100644 index 0000000000..b2ed0cbe23 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/HutaoDatabaseViewModel.cs @@ -0,0 +1,151 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using CommunityToolkit.Mvvm.ComponentModel; +using Snap.Hutao.Control; +using Snap.Hutao.Core.Threading; +using Snap.Hutao.Factory.Abstraction; +using Snap.Hutao.Model.Binding.Hutao; +using Snap.Hutao.Model.Metadata.Avatar; +using Snap.Hutao.Model.Metadata.Weapon; +using Snap.Hutao.Service.Abstraction; +using Snap.Hutao.Service.Metadata; +using Snap.Hutao.Web.Hutao.Model; + +namespace Snap.Hutao.ViewModel; + +/// +/// 胡桃数据库视图模型 +/// +[Injection(InjectAs.Transient)] +internal class HutaoDatabaseViewModel : ObservableObject, ISupportCancellation +{ + private readonly IHutaoService hutaoService; + private readonly IMetadataService metadataService; + + private List? avatarUsageRanks; + private List? avatarAppearanceRanks; + private List? avatarConstellationInfos; + private List? teamAppearances; + + /// + /// 构造一个新的胡桃数据库视图模型 + /// + /// 胡桃服务 + /// 元数据服务 + /// 异步命令工厂 + public HutaoDatabaseViewModel(IHutaoService hutaoService, IMetadataService metadataService, IAsyncRelayCommandFactory asyncRelayCommandFactory) + { + this.hutaoService = hutaoService; + this.metadataService = metadataService; + + OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync); + } + + /// + public CancellationToken CancellationToken { get; set; } + + /// + /// 角色使用率 + /// + public List? AvatarUsageRanks { get => avatarUsageRanks; set => SetProperty(ref avatarUsageRanks, value); } + + /// + /// 角色上场率 + /// + public List? AvatarAppearanceRanks { get => avatarAppearanceRanks; set => SetProperty(ref avatarAppearanceRanks, value); } + + /// + /// 角色命座信息 + /// + public List? AvatarConstellationInfos { get => avatarConstellationInfos; set => avatarConstellationInfos = value; } + + /// + /// 队伍出场 + /// + public List? TeamAppearances { get => teamAppearances; set => SetProperty(ref teamAppearances, value); } + + /// + /// 打开界面命令 + /// + public ICommand OpenUICommand { get; } + + private async Task OpenUIAsync() + { + if (await metadataService.InitializeAsync().ConfigureAwait(false)) + { + Dictionary idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false); + idAvatarMap = new(idAvatarMap) + { + [10000005] = new() { Name = "旅行者", Icon = "UI_AvatarIcon_PlayerBoy", Quality = Model.Intrinsic.ItemQuality.QUALITY_ORANGE }, + [10000007] = new() { Name = "旅行者", Icon = "UI_AvatarIcon_PlayerGirl", Quality = Model.Intrinsic.ItemQuality.QUALITY_ORANGE }, + }; + + Dictionary idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false); + Dictionary idReliquarySetMap = await metadataService.GetEquipAffixIdToReliquarySetMapAsync().ConfigureAwait(false); + + List avatarAppearanceRanksLocal = default!; + List avatarUsageRanksLocal = default!; + List avatarConstellationInfosLocal = default!; + List teamAppearancesLocal = default!; + + Task avatarAppearanceRankTask = Task.Run(async () => + { + // AvatarAppearanceRank + List avatarAppearanceRanksRaw = await hutaoService.GetAvatarAppearanceRanksAsync().ConfigureAwait(false); + avatarAppearanceRanksLocal = avatarAppearanceRanksRaw.OrderByDescending(r => r.Floor).Select(rank => new ComplexAvatarRank + { + Floor = $"第 {rank.Floor} 层", + Avatars = rank.Ranks.OrderByDescending(r => r.Rate).Select(rank => new ComplexAvatar(idAvatarMap[rank.Item], rank.Rate)).ToList(), + }).ToList(); + }); + + Task avatarUsageRank = Task.Run(async () => + { + // AvatarUsageRank + List avatarUsageRanksRaw = await hutaoService.GetAvatarUsageRanksAsync().ConfigureAwait(false); + avatarUsageRanksLocal = avatarUsageRanksRaw.OrderByDescending(r => r.Floor).Select(rank => new ComplexAvatarRank + { + Floor = $"第 {rank.Floor} 层", + Avatars = rank.Ranks.OrderByDescending(r => r.Rate).Select(rank => new ComplexAvatar(idAvatarMap[rank.Item], rank.Rate)).ToList(), + }).ToList(); + }); + + Task avatarConstellationInfoTask = Task.Run(async () => + { + // AvatarConstellationInfo + List avatarConstellationInfosRaw = await hutaoService.GetAvatarConstellationInfosAsync().ConfigureAwait(false); + avatarConstellationInfosLocal = avatarConstellationInfosRaw.OrderBy(i => i.HoldingRate).Select(info => + { + return new ComplexAvatarConstellationInfo(idAvatarMap[info.AvatarId], info.HoldingRate, info.Constellations.Select(x => x.Rate)); + }).ToList(); + }); + + Task teamAppearanceTask = Task.Run(async () => + { + List teamAppearancesRaw = await hutaoService.GetTeamAppearancesAsync().ConfigureAwait(false); + teamAppearancesLocal = teamAppearancesRaw.OrderByDescending(t => t.Floor).Select(team => new ComplexTeamRank(team, idAvatarMap)).ToList(); + }); + + await Task.WhenAll(avatarAppearanceRankTask, avatarUsageRank, avatarConstellationInfoTask, teamAppearanceTask).ConfigureAwait(false); + + await ThreadHelper.SwitchToMainThreadAsync(); + AvatarAppearanceRanks = avatarAppearanceRanksLocal; + AvatarUsageRanks = avatarUsageRanksLocal; + AvatarConstellationInfos = avatarConstellationInfosLocal; + TeamAppearances = teamAppearancesLocal; + + //// AvatarCollocation + //List avatarCollocationsRaw = await hutaoService.GetAvatarCollocationsAsync().ConfigureAwait(false); + //List avatarCollocationsLocal = avatarCollocationsRaw.Select(co => + //{ + // return new ComplexAvatarCollocation(idAvatarMap[co.AvatarId]) + // { + // Avatars = co.Avatars.Select(a => new ComplexAvatar(idAvatarMap[a.Item], a.Rate)).ToList(), + // Weapons = co.Weapons.Select(w => new ComplexWeapon(idWeaponMap[w.Item], w.Rate)).ToList(), + // ReliquarySets = co.Reliquaries.Select(r => new ComplexReliquarySet(r, idReliquarySetMap)).ToList(), + // }; + //}).ToList(); + } + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs index f1549fc220..b98f3f92e5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs @@ -38,7 +38,7 @@ private Cookie(SortedDictionary dict) public static Cookie Parse(string cookieString) { SortedDictionary cookieMap = new(); - + cookieString = cookieString.Replace(" ", string.Empty); string[] values = cookieString.TrimEnd(';').Split(';'); foreach (string[] parts in values.Select(c => c.Split('=', 2))) { diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs index a412c05556..7e558fe3f0 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs @@ -45,6 +45,21 @@ internal static class HttpClientExtensions } } + /// + internal static async Task TryCatchPostAsJsonAsync(this HttpClient httpClient, string requestUri, TValue value, JsonSerializerOptions options, CancellationToken token = default) + where TResult : class + { + try + { + HttpResponseMessage message = await httpClient.PostAsJsonAsync(requestUri, value, options, token).ConfigureAwait(false); + return await message.Content.ReadFromJsonAsync(options, token).ConfigureAwait(false); + } + catch (HttpRequestException) + { + return null; + } + } + /// /// 设置用户的Cookie /// diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs index 16459662a5..bd3b5c1e0d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs @@ -97,10 +97,10 @@ public async Task CheckRecordUploadedAsync(PlayerUid uid, CancellationToke /// /// 取消令牌 /// 角色出场率 - public async Task> GetAvatarAttendanceRatesAsync(CancellationToken token = default) + public async Task> GetAvatarAttendanceRatesAsync(CancellationToken token = default) { - Response>? resp = await httpClient - .GetFromJsonAsync>>($"{HutaoAPI}/Statistics/Avatar/AttendanceRate", token) + Response>? resp = await httpClient + .GetFromJsonAsync>>($"{HutaoAPI}/Statistics/Avatar/AttendanceRate", token) .ConfigureAwait(false); return EnumerableExtension.EmptyIfNull(resp?.Data); @@ -112,10 +112,10 @@ public async Task> GetAvatarAttendanceRatesAsy /// /// 取消令牌 /// 角色出场率 - public async Task> GetAvatarUtilizationRatesAsync(CancellationToken token = default) + public async Task> GetAvatarUtilizationRatesAsync(CancellationToken token = default) { - Response>? resp = await httpClient - .GetFromJsonAsync>>($"{HutaoAPI}/Statistics/Avatar/UtilizationRate", token) + Response>? resp = await httpClient + .GetFromJsonAsync>>($"{HutaoAPI}/Statistics/Avatar/UtilizationRate", token) .ConfigureAwait(false); return EnumerableExtension.EmptyIfNull(resp?.Data); @@ -127,10 +127,10 @@ public async Task> GetAvatarUtilizationRatesAsync(C /// /// 取消令牌 /// 角色/武器/圣遗物搭配 - public async Task> GetAvatarCollocationsAsync(CancellationToken token = default) + public async Task> GetAvatarCollocationsAsync(CancellationToken token = default) { - Response>? resp = await httpClient - .GetFromJsonAsync>>($"{HutaoAPI}/Statistics/Avatar/AvatarCollocation", token) + Response>? resp = await httpClient + .GetFromJsonAsync>>($"{HutaoAPI}/Statistics/Avatar/AvatarCollocation", token) .ConfigureAwait(false); return EnumerableExtension.EmptyIfNull(resp?.Data); @@ -142,10 +142,10 @@ public async Task> GetAvatarCollocationsAsync(Can /// /// 取消令牌 /// 角色图片列表 - public async Task> GetAvatarHoldingRatesAsync(CancellationToken token = default) + public async Task> GetAvatarHoldingRatesAsync(CancellationToken token = default) { - Response>? resp = await httpClient - .GetFromJsonAsync>>($"{HutaoAPI}/Statistics/Avatar/HoldingRate", token) + Response>? resp = await httpClient + .GetFromJsonAsync>>($"{HutaoAPI}/Statistics/Avatar/HoldingRate", token) .ConfigureAwait(false); return EnumerableExtension.EmptyIfNull(resp?.Data); @@ -157,10 +157,10 @@ public async Task> GetAvatarHoldingRatesAsy /// /// 取消令牌 /// 队伍出场列表 - public async Task> GetTeamCombinationsAsync(CancellationToken token = default) + public async Task> GetTeamCombinationsAsync(CancellationToken token = default) { - Response>? resp = await httpClient - .GetFromJsonAsync>>($"{HutaoAPI}/Team/Combination", token) + Response>? resp = await httpClient + .GetFromJsonAsync>>($"{HutaoAPI}/Statistics/Team/Combination", token) .ConfigureAwait(false); return EnumerableExtension.EmptyIfNull(resp?.Data); 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 index b2e9fd9746..9fee87592a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Converter/ReliquarySetsConverter.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Converter/ReliquarySetsConverter.cs @@ -15,7 +15,7 @@ internal class ReliquarySetsConverter : JsonConverter { if (reader.GetString() is string source) { - string[] sets = source.Split(Separator); + string[] sets = source.Split(Separator, StringSplitOptions.RemoveEmptyEntries); return new(sets.Select(set => new ReliquarySet(set))); } else diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/ReliquarySet.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/ReliquarySet.cs index 7bc85c5363..05ad81345d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/ReliquarySet.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/ReliquarySet.cs @@ -16,14 +16,14 @@ public ReliquarySet(string set) { string[]? deconstructed = set.Split('-'); - Id = int.Parse(deconstructed[0]); + EquipAffixId = int.Parse(deconstructed[0]); Count = int.Parse(deconstructed[1]); } /// /// Id /// - public int Id { get; } + public int EquipAffixId { get; } /// /// 个数 @@ -33,6 +33,6 @@ public ReliquarySet(string set) /// public override string ToString() { - return $"{Id}-{Count}"; + return $"{EquipAffixId}-{Count}"; } } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/TeamAppearance.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/TeamAppearance.cs index 2526f284c9..576fe87032 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/TeamAppearance.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/TeamAppearance.cs @@ -2,11 +2,17 @@ // Licensed under the MIT license. namespace Snap.Hutao.Web.Hutao.Model; + /// /// 队伍出场次数 /// public class TeamAppearance { + /// + /// 层 + /// + public int Floor { get; set; } + /// /// 上半 ///