diff --git a/src/Snap.Hutao/.editorconfig b/src/Snap.Hutao/.editorconfig index da1c9479..7fb1d9f5 100644 --- a/src/Snap.Hutao/.editorconfig +++ b/src/Snap.Hutao/.editorconfig @@ -47,8 +47,8 @@ dotnet_style_prefer_auto_properties = false:silent dotnet_style_object_initializer = true:suggestion dotnet_style_collection_initializer = true:suggestion dotnet_style_prefer_simplified_boolean_expressions = true:suggestion -dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion -dotnet_style_prefer_conditional_expression_over_return = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent dotnet_style_explicit_tuple_names = true:suggestion dotnet_style_prefer_inferred_tuple_names = true:suggestion dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/GlobalSuppressions.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/GlobalSuppressions.cs deleted file mode 100644 index 4dfe2d1e..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/GlobalSuppressions.cs +++ /dev/null @@ -1,8 +0,0 @@ -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. - -using System.Diagnostics.CodeAnalysis; - -[assembly: SuppressMessage("MicrosoftCodeAnalysisReleaseTracking", "RS2008:启用分析器发布跟踪", Justification = "<挂起>", Scope = "member", Target = "~F:Snap.Hutao.SourceGeneration.TodoAnalyzer.Descriptor")] diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocConfiguration.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocConfiguration.cs index e911e636..8ca741d4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocConfiguration.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocConfiguration.cs @@ -43,6 +43,13 @@ internal static class IocConfiguration } } - return services.AddDbContext(builder => builder.UseSqlite(sqlConnectionString)); + return services.AddDbContext(builder => + { + builder +#if DEBUG + .EnableSensitiveDataLogging() +#endif + .UseSqlite(sqlConnectionString); + }); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ExceptionRecorder.cs b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ExceptionRecorder.cs index 2a4ad9c0..59a73b83 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ExceptionRecorder.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ExceptionRecorder.cs @@ -27,10 +27,13 @@ internal class ExceptionRecorder application.DebugSettings.BindingFailed += OnXamlBindingFailed; } - [SuppressMessage("", "VSTHRD002")] private void OnAppUnhandledException(object? sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) { - Ioc.Default.GetRequiredService().UploadLogAsync(e.Exception).GetAwaiter().GetResult(); +#if RELEASE + #pragma warning disable VSTHRD002 + Ioc.Default.GetRequiredService().UploadLogAsync(e.Exception).GetAwaiter().GetResult(); + #pragma warning restore VSTHRD002 +#endif logger.LogError(EventIds.UnhandledException, e.Exception, "未经处理的异常"); foreach (ILoggerProvider provider in Ioc.Default.GetRequiredService>()) diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/DispatcherQueueExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/DispatcherQueueExtension.cs new file mode 100644 index 00000000..92d34011 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/DispatcherQueueExtension.cs @@ -0,0 +1,31 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.UI.Dispatching; + +namespace Snap.Hutao.Core.Threading; + +/// +/// 调度器队列拓展 +/// +public static class DispatcherQueueExtension +{ + /// + /// 在调度器队列同步调用,直到执行结束,会持续阻塞当前线程 + /// + /// 调度器队列 + /// 执行的回调 + public static void Invoke(this DispatcherQueue dispatcherQueue, Action action) + { + using (ManualResetEventSlim blockEvent = new()) + { + dispatcherQueue.TryEnqueue(() => + { + action(); + blockEvent.Set(); + }); + + blockEvent.Wait(); + } + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/TaskExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/TaskExtensions.cs index 46062560..317f8a94 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Threading/TaskExtensions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/TaskExtensions.cs @@ -95,4 +95,4 @@ public static class TaskExtensions onException?.Invoke(e); } } -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Factory/Abstraction/IContentDialogFactory.cs b/src/Snap.Hutao/Snap.Hutao/Factory/Abstraction/IContentDialogFactory.cs index 0e005821..82688747 100644 --- a/src/Snap.Hutao/Snap.Hutao/Factory/Abstraction/IContentDialogFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Factory/Abstraction/IContentDialogFactory.cs @@ -27,14 +27,6 @@ internal interface IContentDialogFactory /// 结果 ValueTask ConfirmCancelAsync(string title, string content, ContentDialogButton defaultButton = ContentDialogButton.Close); - /// - /// 在主线程异步创建一个新的内容对话框 - /// - /// 对话框类型 - /// 一个新的内容对话框 - ValueTask CreateAsync() - where TContentDialog : ContentDialog, new(); - /// /// 异步创建一个新的内容对话框,用于提示未知的进度 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Factory/AsyncRelayCommandFactory.cs b/src/Snap.Hutao/Snap.Hutao/Factory/AsyncRelayCommandFactory.cs index 9ecedc68..84006d72 100644 --- a/src/Snap.Hutao/Snap.Hutao/Factory/AsyncRelayCommandFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Factory/AsyncRelayCommandFactory.cs @@ -83,7 +83,6 @@ internal class AsyncRelayCommandFactory : IAsyncRelayCommandFactory return command; } - [SuppressMessage("", "VSTHRD002")] private void ReportException(IAsyncRelayCommand command) { command.PropertyChanged += (sender, args) => @@ -96,7 +95,6 @@ internal class AsyncRelayCommandFactory : IAsyncRelayCommandFactory { Exception baseException = exception.GetBaseException(); logger.LogError(EventIds.AsyncCommandException, baseException, "{name} Exception", nameof(AsyncRelayCommand)); - Ioc.Default.GetRequiredService().UploadLogAsync(baseException).GetAwaiter().GetResult(); } } } diff --git a/src/Snap.Hutao/Snap.Hutao/Factory/ContentDialogFactory.cs b/src/Snap.Hutao/Snap.Hutao/Factory/ContentDialogFactory.cs index dc11dd41..b82941f4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Factory/ContentDialogFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Factory/ContentDialogFactory.cs @@ -21,25 +21,19 @@ internal class ContentDialogFactory : IContentDialogFactory this.mainWindow = mainWindow; } - /// - public async ValueTask CreateAsync() - where TContentDialog : ContentDialog, new() - { - await ThreadHelper.SwitchToMainThreadAsync(); - return new(); - } - /// public async ValueTask ConfirmAsync(string title, string content) { - ContentDialog dialog = await CreateForConfirmAsync(title, content).ConfigureAwait(true); + ContentDialog dialog = await CreateForConfirmAsync(title, content).ConfigureAwait(false); + await ThreadHelper.SwitchToMainThreadAsync(); return await dialog.ShowAsync(); } /// public async ValueTask ConfirmCancelAsync(string title, string content, ContentDialogButton defaultButton = ContentDialogButton.Close) { - ContentDialog dialog = await CreateForConfirmCancelAsync(title, content, defaultButton).ConfigureAwait(true); + ContentDialog dialog = await CreateForConfirmCancelAsync(title, content, defaultButton).ConfigureAwait(false); + await ThreadHelper.SwitchToMainThreadAsync(); return await dialog.ShowAsync(); } diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/SpiralAbyss/RankAvatar.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/SpiralAbyss/RankAvatar.cs index 18e3d5b4..49d5959c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Binding/SpiralAbyss/RankAvatar.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/SpiralAbyss/RankAvatar.cs @@ -22,5 +22,8 @@ public class RankAvatar : Avatar Value = value; } + /// + /// 排行 + /// public int Value { get; set; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/User/User.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/User/User.cs index 9c558afe..fe041d44 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Binding/User/User.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/User/User.cs @@ -10,6 +10,7 @@ using Snap.Hutao.Web.Hoyolab.Bbs.User; using Snap.Hutao.Web.Hoyolab.Passport; using Snap.Hutao.Web.Hoyolab.Takumi.Auth; using Snap.Hutao.Web.Hoyolab.Takumi.Binding; +using Snap.Hutao.Web.Response; using EntityUser = Snap.Hutao.Model.Entity.User; namespace Snap.Hutao.Model.Binding.User; @@ -98,8 +99,8 @@ public class User : ObservableObject /// /// 数据库实体 /// 取消令牌 - /// 用户是否初始化完成,若Cookie失效会返回 - internal static async Task ResumeAsync(EntityUser inner, CancellationToken token = default) + /// 用户 + internal static async Task ResumeAsync(EntityUser inner, CancellationToken token = default) { User user = new(inner); bool isOk = await user.InitializeCoreAsync(token).ConfigureAwait(false); @@ -107,6 +108,7 @@ public class User : ObservableObject if (!isOk) { user.UserInfo = new UserInfo() { Nickname = "网络异常" }; + user.UserGameRoles = new(); } return user; @@ -117,7 +119,7 @@ public class User : ObservableObject /// /// cookie /// 取消令牌 - /// 用户是否初始化完成,若Cookie失效会返回 + /// 用户 internal static async Task CreateAsync(Cookie cookie, CancellationToken token = default) { // 这里只负责创建实体用户,稍后在用户服务中保存到数据库 @@ -153,7 +155,7 @@ public class User : ObservableObject using (IServiceScope scope = Ioc.Default.CreateScope()) { - Web.Response.Response response = await scope.ServiceProvider + Response response = await scope.ServiceProvider .GetRequiredService() .GetUserFullInfoAsync(Entity, token) .ConfigureAwait(false); @@ -162,39 +164,49 @@ public class User : ObservableObject // 自动填充 Ltoken if (Ltoken == null) { - string? ltoken = await scope.ServiceProvider + Response ltokenResponse = await scope.ServiceProvider .GetRequiredService() .GetLtokenBySTokenAsync(Entity, token) .ConfigureAwait(false); - if (ltoken != null) + if (ltokenResponse.IsOk()) { - Cookie ltokenCookie = Cookie.Parse($"ltuid={Entity.Aid};ltoken={ltoken}"); + Cookie ltokenCookie = Cookie.Parse($"ltuid={Entity.Aid};ltoken={ltokenResponse.Data.Ltoken}"); Entity.Ltoken = ltokenCookie; } } - string? actionTicket = await scope.ServiceProvider + Response actionTicketResponse = await scope.ServiceProvider .GetRequiredService() .GetActionTicketByStokenAsync("game_role", Entity) .ConfigureAwait(false); - UserGameRoles = await scope.ServiceProvider - .GetRequiredService() - .GetUserGameRolesByActionTicketAsync(actionTicket!, Entity, token) - .ConfigureAwait(false); + if (actionTicketResponse.IsOk()) + { + string actionTicket = actionTicketResponse.Data.Ticket; + + Response> userGameRolesResponse = await scope.ServiceProvider + .GetRequiredService() + .GetUserGameRolesByActionTicketAsync(actionTicket, Entity, token) + .ConfigureAwait(false); + + if (userGameRolesResponse.IsOk()) + { + UserGameRoles = userGameRolesResponse.Data.List; + } + } // 自动填充 CookieToken if (CookieToken == null) { - string? cookieToken = await scope.ServiceProvider + Response cookieTokenResponse = await scope.ServiceProvider .GetRequiredService() .GetCookieAccountInfoBySTokenAsync(Entity, token) .ConfigureAwait(false); - if (cookieToken != null) + if (cookieTokenResponse.IsOk()) { - Cookie cookieTokenCookie = Cookie.Parse($"account_id={Entity.Aid};cookie_token={cookieToken}"); + Cookie cookieTokenCookie = Cookie.Parse($"account_id={Entity.Aid};cookie_token={cookieTokenResponse.Data.CookieToken}"); Entity.CookieToken = cookieTokenCookie; } } diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/User/UserAndRole.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/User/UserAndUid.cs similarity index 66% rename from src/Snap.Hutao/Snap.Hutao/Model/Binding/User/UserAndRole.cs rename to src/Snap.Hutao/Snap.Hutao/Model/Binding/User/UserAndUid.cs index 1c5cb472..c11d4072 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Binding/User/UserAndRole.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/User/UserAndUid.cs @@ -1,25 +1,27 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Snap.Hutao.Web.Hoyolab.Takumi.Binding; +using Snap.Hutao.Web.Hoyolab; using EntityUser = Snap.Hutao.Model.Entity.User; namespace Snap.Hutao.Model.Binding.User; /// -/// 角色与实体用户 +/// 实体用户与角色 +/// 由于许多操作需要同时用到ck与uid +/// 抽象此类用于简化这类调用 /// -public class UserAndRole +public class UserAndUid { /// /// 构造一个新的实体用户与角色 /// /// 实体用户 /// 角色 - public UserAndRole(EntityUser user, UserGameRole role) + public UserAndUid(EntityUser user, PlayerUid role) { User = user; - Role = role; + Uid = role; } /// @@ -30,33 +32,33 @@ public class UserAndRole /// /// 角色 /// - public UserGameRole Role { get; private set; } + public PlayerUid Uid { get; private set; } /// /// 从用户与选中的角色转换 /// /// 角色 /// 用户与角色 - public static UserAndRole FromUser(User user) + public static UserAndUid FromUser(User user) { - return new UserAndRole(user.Entity, user.SelectedUserGameRole!); + return new UserAndUid(user.Entity, user.SelectedUserGameRole!); } /// /// 尝试转换到用户与角色 /// /// 用户 - /// 用户与角色 + /// 用户与角色 /// 是否转换成功 - public static bool TryFromUser(User? user, [NotNullWhen(true)] out UserAndRole? userAndRole) + public static bool TryFromUser(User? user, [NotNullWhen(true)] out UserAndUid? userAndUid) { if (user != null && user.SelectedUserGameRole != null) { - userAndRole = FromUser(user); + userAndUid = FromUser(user); return true; } - userAndRole = null; + userAndUid = null; return false; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/DailyNoteEntry.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/DailyNoteEntry.cs index bd720786..f6018c37 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/DailyNoteEntry.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/DailyNoteEntry.cs @@ -108,14 +108,14 @@ public class DailyNoteEntry : ObservableObject /// /// 构造一个新的实时便笺 /// - /// 用户与角色 + /// 用户与角色 /// 新的实时便笺 - public static DailyNoteEntry Create(UserAndRole userAndRole) + public static DailyNoteEntry Create(UserAndUid userAndUid) { return new() { - UserId = userAndRole.User.InnerId, - Uid = userAndRole.Role.GameUid, + UserId = userAndUid.User.InnerId, + Uid = userAndUid.Uid.Value, ResinNotifyThreshold = 160, HomeCoinNotifyThreshold = 2400, }; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs index 1b0e845e..8b1919f0 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs @@ -67,7 +67,10 @@ internal class AchievementService : IAchievementService // Sync database await ThreadHelper.SwitchToBackgroundAsync(); - await appDbContext.AchievementArchives.RemoveAndSaveAsync(archive).ConfigureAwait(false); + await appDbContext.AchievementArchives + .Where(a => a.InnerId == archive.InnerId) + .ExecuteDeleteAsync() + .ConfigureAwait(false); } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoService.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoService.cs index 9f2d64d7..ac9c3324 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoService.cs @@ -16,6 +16,7 @@ using Snap.Hutao.Web.Enka.Model; using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord; +using Snap.Hutao.Web.Response; using CalculateAvatar = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Avatar; using EnkaAvatarInfo = Snap.Hutao.Web.Enka.Model.AvatarInfo; using EnkaPlayerInfo = Snap.Hutao.Web.Enka.Model.PlayerInfo; @@ -56,7 +57,7 @@ internal class AvatarInfoService : IAvatarInfoService } /// - public async Task> GetSummaryAsync(UserAndRole userAndRole, RefreshOption refreshOption, CancellationToken token = default) + public async Task> GetSummaryAsync(UserAndUid userAndUid, RefreshOption refreshOption, CancellationToken token = default) { if (await metadataService.InitializeAsync().ConfigureAwait(false)) { @@ -66,7 +67,7 @@ internal class AvatarInfoService : IAvatarInfoService { case RefreshOption.RequestFromEnkaAPI: { - EnkaResponse? resp = await GetEnkaResponseAsync(userAndRole.Role, token).ConfigureAwait(false); + EnkaResponse? resp = await GetEnkaResponseAsync(userAndUid.Uid, token).ConfigureAwait(false); token.ThrowIfCancellationRequested(); if (resp == null) { @@ -75,7 +76,7 @@ internal class AvatarInfoService : IAvatarInfoService if (resp.IsValid) { - IList list = UpdateDbAvatarInfos(userAndRole.Role.GameUid, resp.AvatarInfoList); + IList list = UpdateDbAvatarInfos(userAndUid.Uid.Value, resp.AvatarInfoList); Summary summary = await GetSummaryCoreAsync(resp.PlayerInfo, list, token).ConfigureAwait(false); token.ThrowIfCancellationRequested(); return new(RefreshResult.Ok, summary); @@ -88,24 +89,24 @@ internal class AvatarInfoService : IAvatarInfoService case RefreshOption.RequestFromHoyolabGameRecord: { - EnkaPlayerInfo info = EnkaPlayerInfo.CreateEmpty(userAndRole.Role.GameUid); - IList list = await UpdateDbAvatarInfosByGameRecordCharacterAsync(userAndRole).ConfigureAwait(false); + EnkaPlayerInfo info = EnkaPlayerInfo.CreateEmpty(userAndUid.Uid.Value); + IList list = await UpdateDbAvatarInfosByGameRecordCharacterAsync(userAndUid).ConfigureAwait(false); Summary summary = await GetSummaryCoreAsync(info, list, token).ConfigureAwait(false); return new(RefreshResult.Ok, summary); } case RefreshOption.RequestFromHoyolabCalculate: { - EnkaPlayerInfo info = EnkaPlayerInfo.CreateEmpty(userAndRole.Role.GameUid); - IList list = await UpdateDbAvatarInfosByCalculateAvatarDetailAsync(userAndRole).ConfigureAwait(false); + EnkaPlayerInfo info = EnkaPlayerInfo.CreateEmpty(userAndUid.Uid.Value); + IList list = await UpdateDbAvatarInfosByCalculateAvatarDetailAsync(userAndUid).ConfigureAwait(false); Summary summary = await GetSummaryCoreAsync(info, list, token).ConfigureAwait(false); return new(RefreshResult.Ok, summary); } default: { - EnkaPlayerInfo info = EnkaPlayerInfo.CreateEmpty(userAndRole.Role.GameUid); - Summary summary = await GetSummaryCoreAsync(info, GetDbAvatarInfos(userAndRole.Role.GameUid), token).ConfigureAwait(false); + EnkaPlayerInfo info = EnkaPlayerInfo.CreateEmpty(userAndUid.Uid.Value); + Summary summary = await GetSummaryCoreAsync(info, GetDbAvatarInfos(userAndUid.Uid.Value), token).ConfigureAwait(false); token.ThrowIfCancellationRequested(); return new(RefreshResult.Ok, summary.Avatars.Count == 0 ? null : summary); } @@ -164,58 +165,68 @@ internal class AvatarInfoService : IAvatarInfoService return GetDbAvatarInfos(uid); } - private async Task> UpdateDbAvatarInfosByGameRecordCharacterAsync(UserAndRole userAndRole) + private async Task> UpdateDbAvatarInfosByGameRecordCharacterAsync(UserAndUid userAndUid) { - string uid = userAndRole.Role.GameUid; + string uid = userAndUid.Uid.Value; List dbInfos = appDbContext.AvatarInfos .Where(i => i.Uid == uid) .ToList(); GameRecordClient gameRecordClient = Ioc.Default.GetRequiredService(); - RecordPlayerInfo? playerInfo = await gameRecordClient - .GetPlayerInfoAsync(userAndRole) - .ConfigureAwait(false); - List characters = await gameRecordClient - .GetCharactersAsync(userAndRole, playerInfo!) + Response playerInfoResponse = await gameRecordClient + .GetPlayerInfoAsync(userAndUid) .ConfigureAwait(false); - GameRecordCharacterAvatarInfoComposer composer = Ioc.Default.GetRequiredService(); - - foreach (RecordCharacter character in characters) + // TODO: We should not refresh if response is not correct here. + if (playerInfoResponse.IsOk()) { - if (AvatarIds.IsPlayer(character.Id)) - { - continue; - } + Response charactersResponse = await gameRecordClient + .GetCharactersAsync(userAndUid, playerInfoResponse.Data) + .ConfigureAwait(false); - ModelAvatarInfo? entity = dbInfos.SingleOrDefault(i => i.Info.AvatarId == character.Id); + if (charactersResponse.IsOk()) + { + List characters = charactersResponse.Data.Avatars; - if (entity == null) - { - EnkaAvatarInfo avatarInfo = new() { AvatarId = character.Id }; - avatarInfo = await composer.ComposeAsync(avatarInfo, character).ConfigureAwait(false); - entity = ModelAvatarInfo.Create(uid, avatarInfo); - appDbContext.AvatarInfos.AddAndSave(entity); - } - else - { - entity.Info = await composer.ComposeAsync(entity.Info, character).ConfigureAwait(false); - appDbContext.AvatarInfos.UpdateAndSave(entity); + GameRecordCharacterAvatarInfoComposer composer = Ioc.Default.GetRequiredService(); + + foreach (RecordCharacter character in characters) + { + if (AvatarIds.IsPlayer(character.Id)) + { + continue; + } + + ModelAvatarInfo? entity = dbInfos.SingleOrDefault(i => i.Info.AvatarId == character.Id); + + if (entity == null) + { + EnkaAvatarInfo avatarInfo = new() { AvatarId = character.Id }; + avatarInfo = await composer.ComposeAsync(avatarInfo, character).ConfigureAwait(false); + entity = ModelAvatarInfo.Create(uid, avatarInfo); + appDbContext.AvatarInfos.AddAndSave(entity); + } + else + { + entity.Info = await composer.ComposeAsync(entity.Info, character).ConfigureAwait(false); + appDbContext.AvatarInfos.UpdateAndSave(entity); + } + } } } return GetDbAvatarInfos(uid); } - private async Task> UpdateDbAvatarInfosByCalculateAvatarDetailAsync(UserAndRole userAndRole) + private async Task> UpdateDbAvatarInfosByCalculateAvatarDetailAsync(UserAndUid userAndUid) { - string uid = userAndRole.Role.GameUid; + string uid = userAndUid.Uid.Value; List dbInfos = appDbContext.AvatarInfos .Where(i => i.Uid == uid) .ToList(); CalculateClient calculateClient = Ioc.Default.GetRequiredService(); - List avatars = await calculateClient.GetAvatarsAsync(userAndRole.User, userAndRole.Role).ConfigureAwait(false); + List avatars = await calculateClient.GetAvatarsAsync(userAndUid).ConfigureAwait(false); CalculateAvatarDetailAvatarInfoComposer composer = Ioc.Default.GetRequiredService(); @@ -226,14 +237,15 @@ internal class AvatarInfoService : IAvatarInfoService continue; } - AvatarDetail? detailAvatar = await calculateClient.GetAvatarDetailAsync(userAndRole.User, userAndRole.Role, avatar).ConfigureAwait(false); + Response detailAvatarResponse = await calculateClient.GetAvatarDetailAsync(userAndUid, avatar).ConfigureAwait(false); - if (detailAvatar == null) + if (!detailAvatarResponse.IsOk()) { continue; } ModelAvatarInfo? entity = dbInfos.SingleOrDefault(i => i.Info.AvatarId == avatar.Id); + AvatarDetail detailAvatar = detailAvatarResponse.Data; if (entity == null) { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/IAvatarInfoService.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/IAvatarInfoService.cs index 11625686..f812b44e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/IAvatarInfoService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/IAvatarInfoService.cs @@ -14,9 +14,9 @@ internal interface IAvatarInfoService /// /// 异步获取总览数据 /// - /// uid + /// uid /// 刷新选项 /// 取消令牌 /// 总览数据 - Task> GetSummaryAsync(UserAndRole userAndRole, RefreshOption refreshOption, CancellationToken token = default); + Task> GetSummaryAsync(UserAndUid userAndUid, RefreshOption refreshOption, CancellationToken token = default); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteNotifier.cs b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteNotifier.cs index a401a470..ce2c4919 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteNotifier.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteNotifier.cs @@ -10,6 +10,7 @@ using Snap.Hutao.Model.Metadata.Converter; using Snap.Hutao.Web.Hoyolab.Takumi.Auth; using Snap.Hutao.Web.Hoyolab.Takumi.Binding; using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote; +using Snap.Hutao.Web.Response; using Windows.Foundation.Metadata; namespace Snap.Hutao.Service.DailyNote; @@ -145,16 +146,24 @@ internal class DailyNoteNotifier BindingClient bindingClient = scope.ServiceProvider.GetRequiredService(); AuthClient authClient = scope.ServiceProvider.GetRequiredService(); - string? actionTicket = await authClient + Response actionTicketResponse = await authClient .GetActionTicketByStokenAsync("game_role", entry.User) .ConfigureAwait(false); - List roles = await scope.ServiceProvider - .GetRequiredService() - .GetUserGameRolesByActionTicketAsync(actionTicket!, entry.User) - .ConfigureAwait(false); + string? attribution = "请求异常"; + if (actionTicketResponse.IsOk()) + { + Response> rolesResponse = await scope.ServiceProvider + .GetRequiredService() + .GetUserGameRolesByActionTicketAsync(actionTicketResponse.Data.Ticket, entry.User) + .ConfigureAwait(false); - string attribution = roles.SingleOrDefault(r => r.GameUid == entry.Uid)?.ToString() ?? "未知角色"; + if (rolesResponse.IsOk()) + { + List roles = rolesResponse.Data.List; + attribution = roles.SingleOrDefault(r => r.GameUid == entry.Uid)?.ToString() ?? "未知角色"; + } + } ToastContentBuilder builder = new ToastContentBuilder() .AddHeader("DAILYNOTE", "实时便笺提醒", "DAILYNOTE") @@ -181,11 +190,11 @@ internal class DailyNoteNotifier { HintWeight = 1, Children = - { - new AdaptiveImage() { Source = info.AdaptiveIcon, HintRemoveMargin = true, }, - new AdaptiveText() { Text = info.AdaptiveHint, HintAlign = AdaptiveTextAlign.Center, }, - new AdaptiveText() { Text = info.Title, HintAlign = AdaptiveTextAlign.Center, HintStyle = AdaptiveTextStyle.CaptionSubtle, }, - }, + { + new AdaptiveImage() { Source = info.AdaptiveIcon, HintRemoveMargin = true, }, + new AdaptiveText() { Text = info.AdaptiveHint, HintAlign = AdaptiveTextAlign.Center, }, + new AdaptiveText() { Text = info.Title, HintAlign = AdaptiveTextAlign.Center, HintStyle = AdaptiveTextStyle.CaptionSubtle, }, + }, }; group.Children.Add(subgroup); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs index e678ce0b..186a6990 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs @@ -49,9 +49,9 @@ internal class DailyNoteService : IDailyNoteService, IRecipient - public async Task AddDailyNoteAsync(UserAndRole role) + public async Task AddDailyNoteAsync(UserAndUid role) { - string roleUid = role.Role.GameUid; + string roleUid = role.Uid.Value; using (IServiceScope scope = scopeFactory.CreateScope()) { AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); @@ -60,7 +60,16 @@ internal class DailyNoteService : IDailyNoteService, IRecipient n.Uid == roleUid)) { DailyNoteEntry newEntry = DailyNoteEntry.Create(role); - newEntry.DailyNote = await gameRecordClient.GetDailyNoteAsync(role.User, newEntry.Uid).ConfigureAwait(false); + + Web.Response.Response dailyNoteResponse = await gameRecordClient + .GetDailyNoteAsync(role) + .ConfigureAwait(false); + + if (dailyNoteResponse.IsOk()) + { + newEntry.DailyNote = dailyNoteResponse.Data; + } + newEntry.UserGameRole = userService.GetUserGameRoleByUid(roleUid); await appDbContext.DailyNotes.AddAndSaveAsync(newEntry).ConfigureAwait(false); @@ -106,18 +115,25 @@ internal class DailyNoteService : IDailyNoteService, IRecipient n.User)) { - WebDailyNote? dailyNote = await gameRecordClient.GetDailyNoteAsync(entry.User, entry.Uid).ConfigureAwait(false); + Web.Response.Response dailyNoteResponse = await gameRecordClient + .GetDailyNoteAsync(new(entry.User, entry.Uid)) + .ConfigureAwait(false); - // database - entry.DailyNote = dailyNote; - - // cache - await ThreadHelper.SwitchToMainThreadAsync(); - entries?.SingleOrDefault(e => e.UserId == entry.UserId && e.Uid == entry.Uid)?.UpdateDailyNote(dailyNote); - - if (notify) + if (dailyNoteResponse.IsOk()) { - await new DailyNoteNotifier(scopeFactory, entry).NotifyAsync().ConfigureAwait(false); + WebDailyNote dailyNote = dailyNoteResponse.Data; + + // database + entry.DailyNote = dailyNote; + + // cache + await ThreadHelper.SwitchToMainThreadAsync(); + entries?.SingleOrDefault(e => e.UserId == entry.UserId && e.Uid == entry.Uid)?.UpdateDailyNote(dailyNote); + + if (notify) + { + await new DailyNoteNotifier(scopeFactory, entry).NotifyAsync().ConfigureAwait(false); + } } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/IDailyNoteService.cs b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/IDailyNoteService.cs index d0f36adf..0a9bea3a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/IDailyNoteService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/IDailyNoteService.cs @@ -17,7 +17,7 @@ public interface IDailyNoteService /// /// 角色 /// 任务 - Task AddDailyNoteAsync(UserAndRole role); + Task AddDailyNoteAsync(UserAndUid role); /// /// 异步获取实时便笺列表 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs index e9fe376b..4194dc6c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs @@ -119,7 +119,7 @@ internal class GachaLogService : IGachaLogService, ISupportAsyncInitialization /// public ObservableCollection GetArchiveCollection() { - return archiveCollection ??= new(appDbContext.GachaArchives.ToList()); + return archiveCollection ??= new(appDbContext.GachaArchives.AsNoTracking().ToList()); } /// @@ -224,11 +224,10 @@ internal class GachaLogService : IGachaLogService, ISupportAsyncInitialization // Sync database await ThreadHelper.SwitchToBackgroundAsync(); - await appDbContext.GachaItems - .Where(item => item.ArchiveId == archive.InnerId) + await appDbContext.GachaArchives + .Where(a => a.InnerId == archive.InnerId) .ExecuteDeleteAsync() .ConfigureAwait(false); - await appDbContext.GachaArchives.RemoveAndSaveAsync(archive).ConfigureAwait(false); } private static Task RandomDelayAsync(CancellationToken token) @@ -250,13 +249,15 @@ internal class GachaLogService : IGachaLogService, ISupportAsyncInitialization do { - Response? response = await gachaInfoClient.GetGachaLogPageAsync(configration, token).ConfigureAwait(false); + Response response = await gachaInfoClient.GetGachaLogPageAsync(configration, token).ConfigureAwait(false); - if (response?.Data is GachaLogPage page) + if (response.IsOk()) { + GachaLogPage page = response.Data; + state.Items.Clear(); List items = page.List; - bool completedCurrentTypeAdding = false; + bool currentTypeAddingCompleted = false; foreach (GachaLogItem item in items) { @@ -271,14 +272,14 @@ internal class GachaLogService : IGachaLogService, ISupportAsyncInitialization } else { - completedCurrentTypeAdding = true; + currentTypeAddingCompleted = true; break; } } progress.Report(state); - if (completedCurrentTypeAdding || items.Count < GachaLogConfigration.Size) + if (currentTypeAddingCompleted || items.Count < GachaLogConfigration.Size) { // exit current type fetch loop break; @@ -320,7 +321,7 @@ internal class GachaLogService : IGachaLogService, ISupportAsyncInitialization archive = appDbContext.GachaArchives.Single(a => a.Uid == uid); GachaArchive temp = archive; - Program.DispatcherQueue!.TryEnqueue(() => archiveCollection!.Add(temp)); + Program.DispatcherQueue!.Invoke(() => archiveCollection!.Add(temp)); } } } 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 f70df515..68f481c1 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlStokenProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlStokenProvider.cs @@ -42,10 +42,10 @@ internal class GachaLogUrlStokenProvider : IGachaLogUrlProvider PlayerUid uid = (PlayerUid)user.SelectedUserGameRole; GenAuthKeyData data = GenAuthKeyData.CreateForWebViewGacha(uid); - GameAuthKey? authkey = await bindingClient2.GenerateAuthenticationKeyAsync(user.Entity, data).ConfigureAwait(false); - if (authkey != null) + Web.Response.Response authkeyResponse = await bindingClient2.GenerateAuthenticationKeyAsync(user.Entity, data).ConfigureAwait(false); + if (authkeyResponse.IsOk()) { - return new(true, GachaLogConfigration.AsQuery(data, authkey)); + return new(true, GachaLogConfigration.AsQuery(data, authkeyResponse.Data)); } else { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoService.cs index 55ae8517..2b7650da 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoService.cs @@ -7,6 +7,7 @@ using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity.Database; using Snap.Hutao.Web.Hutao; using Snap.Hutao.Web.Hutao.Model; +using Snap.Hutao.Web.Response; namespace Snap.Hutao.Service.Hutao; @@ -37,7 +38,7 @@ internal class HutaoService : IHutaoService } /// - public ValueTask GetOverviewAsync() + public ValueTask GetOverviewAsync() { return FromCacheOrWebAsync(nameof(Overview), homaClient.GetOverviewAsync); } @@ -78,7 +79,8 @@ internal class HutaoService : IHutaoService return FromCacheOrWebAsync(nameof(TeamAppearance), homaClient.GetTeamCombinationsAsync); } - private async ValueTask FromCacheOrWebAsync(string typeName, Func> taskFunc) + private async ValueTask FromCacheOrWebAsync(string typeName, Func>> taskFunc) + where T : new() { string key = $"{nameof(HutaoService)}.Cache.{typeName}"; if (memoryCache.TryGetValue(key, out object? cache)) @@ -99,7 +101,9 @@ internal class HutaoService : IHutaoService } } - T web = await taskFunc(default).ConfigureAwait(false); + Response webResponse = await taskFunc(default).ConfigureAwait(false); + T web = webResponse.IsOk() ? webResponse.Data : new(); + appDbContext.ObjectCache.AddAndSave(new() { Key = key, diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoService.cs index e0670a69..8fcd68a8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoService.cs @@ -38,7 +38,7 @@ internal interface IHutaoService /// 异步获取统计数据 /// /// 统计数据 - ValueTask GetOverviewAsync(); + ValueTask GetOverviewAsync(); /// /// 异步获取队伍上场 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/SpiralAbyss/ISpiralAbyssRecordService.cs b/src/Snap.Hutao/Snap.Hutao/Service/SpiralAbyss/ISpiralAbyssRecordService.cs index 802c5658..4cf1a479 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/SpiralAbyss/ISpiralAbyssRecordService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/SpiralAbyss/ISpiralAbyssRecordService.cs @@ -15,14 +15,14 @@ internal interface ISpiralAbyssRecordService /// /// 异步获取深渊记录集合 /// - /// 当前角色 + /// 当前角色 /// 深渊记录集合 - Task> GetSpiralAbyssCollectionAsync(UserAndRole userAndRole); + Task> GetSpiralAbyssCollectionAsync(UserAndUid userAndUid); /// /// 异步刷新深渊记录 /// - /// 当前角色 + /// 当前角色 /// 任务 - Task RefreshSpiralAbyssAsync(UserAndRole userAndRole); + Task RefreshSpiralAbyssAsync(UserAndUid userAndUid); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/SpiralAbyss/SpiralAbyssRecordService.cs b/src/Snap.Hutao/Snap.Hutao/Service/SpiralAbyss/SpiralAbyssRecordService.cs index defed1e6..d7106c7a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/SpiralAbyss/SpiralAbyssRecordService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/SpiralAbyss/SpiralAbyssRecordService.cs @@ -7,6 +7,7 @@ using Snap.Hutao.Model.Binding.User; using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity.Database; using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord; +using Snap.Hutao.Web.Response; using System.Collections.ObjectModel; namespace Snap.Hutao.Service.SpiralAbyss; @@ -35,19 +36,19 @@ internal class SpiralAbyssRecordService : ISpiralAbyssRecordService } /// - public async Task> GetSpiralAbyssCollectionAsync(UserAndRole userAndRole) + public async Task> GetSpiralAbyssCollectionAsync(UserAndUid userAndUid) { - if (uid != userAndRole.Role.GameUid) + if (uid != userAndUid.Uid.Value) { spiralAbysses = null; } - uid = userAndRole.Role.GameUid; + uid = userAndUid.Uid.Value; if (spiralAbysses == null) { List entries = await appDbContext.SpiralAbysses .AsNoTracking() - .Where(s => s.Uid == userAndRole.Role.GameUid) + .Where(s => s.Uid == userAndUid.Uid.Value) .OrderByDescending(s => s.ScheduleId) .ToListAsync() .ConfigureAwait(false); @@ -60,14 +61,16 @@ internal class SpiralAbyssRecordService : ISpiralAbyssRecordService } /// - public async Task RefreshSpiralAbyssAsync(UserAndRole userAndRole) + public async Task RefreshSpiralAbyssAsync(UserAndUid userAndUid) { - Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss? last = await gameRecordClient - .GetSpiralAbyssAsync(userAndRole, SpiralAbyssSchedule.Last) + Response lastResponse = await gameRecordClient + .GetSpiralAbyssAsync(userAndUid, SpiralAbyssSchedule.Last) .ConfigureAwait(false); - if (last != null) + if (lastResponse.IsOk()) { + Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss last = lastResponse.Data; + SpiralAbyssEntry? lastEntry = spiralAbysses!.SingleOrDefault(s => s.ScheduleId == last.ScheduleId); if (lastEntry != null) { @@ -79,7 +82,7 @@ internal class SpiralAbyssRecordService : ISpiralAbyssRecordService } else { - SpiralAbyssEntry entry = SpiralAbyssEntry.Create(userAndRole.Role.GameUid, last); + SpiralAbyssEntry entry = SpiralAbyssEntry.Create(userAndUid.Uid.Value, last); await ThreadHelper.SwitchToMainThreadAsync(); spiralAbysses!.Insert(0, entry); @@ -89,12 +92,14 @@ internal class SpiralAbyssRecordService : ISpiralAbyssRecordService } } - Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss? current = await gameRecordClient - .GetSpiralAbyssAsync(userAndRole, SpiralAbyssSchedule.Current) + Response currentResponse = await gameRecordClient + .GetSpiralAbyssAsync(userAndUid, SpiralAbyssSchedule.Current) .ConfigureAwait(false); - if (current != null) + if (currentResponse.IsOk()) { + Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss current = currentResponse.Data; + SpiralAbyssEntry? currentEntry = spiralAbysses!.SingleOrDefault(s => s.ScheduleId == current.ScheduleId); if (currentEntry != null) { @@ -106,7 +111,7 @@ internal class SpiralAbyssRecordService : ISpiralAbyssRecordService } else { - SpiralAbyssEntry entry = SpiralAbyssEntry.Create(userAndRole.Role.GameUid, current); + SpiralAbyssEntry entry = SpiralAbyssEntry.Create(userAndUid.Uid.Value, current); await ThreadHelper.SwitchToMainThreadAsync(); spiralAbysses!.Insert(0, entry); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs index 99c5aaa5..bd25d83d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs @@ -22,7 +22,7 @@ public interface IUserService /// 异步获取角色与用户集合 /// /// 角色与用户集合 - Task> GetRoleCollectionAsync(); + Task> GetRoleCollectionAsync(); /// /// 初始化用户服务及所有用户 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs index b6a30cd5..58ed9f05 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using CommunityToolkit.Mvvm.Messaging; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Snap.Hutao.Core.Database; using Snap.Hutao.Extension; @@ -27,7 +28,7 @@ internal class UserService : IUserService private BindingUser? currentUser; private ObservableCollection? userCollection; - private ObservableCollection? roleCollection; + private ObservableCollection? roleCollection; /// /// 构造一个新的用户服务 @@ -115,23 +116,14 @@ internal class UserService : IUserService { AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - foreach (Model.Entity.User entity in appDbContext.Users) + foreach (Model.Entity.User entity in appDbContext.Users.AsNoTracking()) { - BindingUser? initialized = await BindingUser.ResumeAsync(entity).ConfigureAwait(false); - - if (initialized != null) - { - users.Add(initialized); - } - else - { - // User is unable to be initialized, remove it. - await appDbContext.Users.RemoveAndSaveAsync(entity).ConfigureAwait(false); - } + BindingUser initialized = await BindingUser.ResumeAsync(entity).ConfigureAwait(false); + users.Add(initialized); } } - userCollection = new(users); + userCollection = users.ToObservableCollection(); Current = users.SingleOrDefault(user => user.IsSelected); } @@ -139,22 +131,22 @@ internal class UserService : IUserService } /// - public async Task> GetRoleCollectionAsync() + public async Task> GetRoleCollectionAsync() { await ThreadHelper.SwitchToBackgroundAsync(); if (roleCollection == null) { - List userAndRoles = new(); + List userAndUids = new(); ObservableCollection observableUsers = await GetUserCollectionAsync().ConfigureAwait(false); foreach (BindingUser user in observableUsers.ToList()) { foreach (UserGameRole role in user.UserGameRoles) { - userAndRoles.Add(new(user.Entity, role)); + userAndUids.Add(new(user.Entity, role)); } } - roleCollection = new(userAndRoles); + roleCollection = new(userAndUids); } return roleCollection; @@ -163,11 +155,10 @@ internal class UserService : IUserService /// public UserGameRole? GetUserGameRoleByUid(string uid) { - if (roleCollection != null) + if (userCollection != null) { - // System.InvalidOperationException: Sequence contains no matching element - // Not quiet sure why this happen when its Single() - return roleCollection.SingleOrDefault(r => r.Role.GameUid == uid)?.Role; + // TODO: optimize match speed. + return userCollection.SelectMany(u => u.UserGameRoles).SingleOrDefault(r => r.GameUid == uid); } return null; diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/DailyNoteVerificationDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/DailyNoteVerificationDialog.xaml.cs index ab89d2bb..f87d66a1 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/DailyNoteVerificationDialog.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/DailyNoteVerificationDialog.xaml.cs @@ -11,25 +11,25 @@ using Snap.Hutao.Web.Bridge; namespace Snap.Hutao.View.Dialog; /// -/// ʵʱ֤Ի +/// ʵʱ�����֤�Ի��� /// public sealed partial class DailyNoteVerificationDialog : ContentDialog { private readonly IServiceScope scope; - private readonly UserAndRole userAndRole; + private readonly UserAndUid userAndUid; [SuppressMessage("", "IDE0052")] private DailyNoteJsInterface? dailyNoteJsInterface; /// - /// һµʵʱ֤Ի + /// ����һ���µ�ʵʱ�����֤�Ի��� /// - /// ûɫ - public DailyNoteVerificationDialog(UserAndRole userAndRole) + /// �û����ɫ + public DailyNoteVerificationDialog(UserAndUid userAndUid) { InitializeComponent(); XamlRoot = Ioc.Default.GetRequiredService().Content.XamlRoot; - this.userAndRole = userAndRole; + this.userAndUid = userAndUid; scope = Ioc.Default.CreateScope(); } @@ -43,11 +43,11 @@ public sealed partial class DailyNoteVerificationDialog : ContentDialog await WebView.EnsureCoreWebView2Async(); CoreWebView2 coreWebView2 = WebView.CoreWebView2; - Model.Entity.User user = userAndRole.User; + Model.Entity.User user = userAndUid.User; coreWebView2.SetCookie(user.CookieToken, user.Ltoken, null).SetMobileUserAgent(); dailyNoteJsInterface = new(coreWebView2, scope.ServiceProvider); - string query = $"?role_id={userAndRole.Role.GameUid}&server={userAndRole.Role.Region}"; + string query = $"?role_id={userAndUid.Uid.Value}&server={userAndUid.Uid.Region}"; coreWebView2.Navigate($"https://webstatic.mihoyo.com/app/community-game-records/index.html?bbs_presentation_style=fullscreen#/ys/daily/{query}"); } diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoUserPage.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoUserPage.xaml.cs index 278c24e0..35222199 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoUserPage.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoUserPage.xaml.cs @@ -9,6 +9,7 @@ using Snap.Hutao.Service.User; using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab.Passport; using Snap.Hutao.Web.Hoyolab.Takumi.Auth; +using Snap.Hutao.Web.Response; namespace Snap.Hutao.View.Page; @@ -46,11 +47,34 @@ public sealed partial class LoginMihoyoUserPage : Microsoft.UI.Xaml.Controls.Pag IReadOnlyList cookies = await manager.GetCookiesAsync("https://user.mihoyo.com"); Cookie loginTicketCookie = Cookie.FromCoreWebView2Cookies(cookies); - Dictionary multiToken = await Ioc.Default.GetRequiredService().GetMultiTokenByLoginTicketAsync(loginTicketCookie, token).ConfigureAwait(false); - Cookie stokenV1 = Cookie.Parse($"stuid={loginTicketCookie["login_uid"]};stoken={multiToken["stoken"]}"); - LoginResult? loginResult = await Ioc.Default.GetRequiredService().LoginByStokenAsync(stokenV1, token).ConfigureAwait(false); - Cookie stokenV2 = Cookie.FromLoginResult(loginResult); - (UserOptionResult result, string nickname) = await Ioc.Default.GetRequiredService().ProcessInputCookieAsync(stokenV2).ConfigureAwait(false); + Response> multiTokenResponse = await Ioc.Default + .GetRequiredService() + .GetMultiTokenByLoginTicketAsync(loginTicketCookie, token) + .ConfigureAwait(false); + + if (!multiTokenResponse.IsOk()) + { + return; + } + + Dictionary multiTokenMap = multiTokenResponse.Data.List.ToDictionary(n => n.Name, n => n.Token); + + Cookie stokenV1 = Cookie.Parse($"stuid={loginTicketCookie["login_uid"]};stoken={multiTokenMap["stoken"]}"); + Response loginResultResponse = await Ioc.Default + .GetRequiredService() + .LoginByStokenAsync(stokenV1, token) + .ConfigureAwait(false); + + if (!loginResultResponse.IsOk()) + { + return; + } + + Cookie stokenV2 = Cookie.FromLoginResult(loginResultResponse.Data); + (UserOptionResult result, string nickname) = await Ioc.Default + .GetRequiredService() + .ProcessInputCookieAsync(stokenV2) + .ConfigureAwait(false); Ioc.Default.GetRequiredService().GoBack(); IInfoBarService infoBarService = Ioc.Default.GetRequiredService(); diff --git a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml index bab287f3..4477c3c9 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml @@ -62,7 +62,7 @@ - + + Visibility="{Binding Users.Count, Converter={StaticResource Int32ToVisibilityRevertConverter}}"/> logger; private AnnouncementWrapper? announcement; @@ -32,13 +31,9 @@ internal class AnnouncementViewModel : ObservableObject, ISupportCancellation /// 异步命令工厂 /// 信息条服务 /// 日志器 - public AnnouncementViewModel( - IAnnouncementService announcementService, - IAsyncRelayCommandFactory asyncRelayCommandFactory, - ILogger logger) + public AnnouncementViewModel(IAnnouncementService announcementService, IAsyncRelayCommandFactory asyncRelayCommandFactory) { this.announcementService = announcementService; - this.logger = logger; OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync); OpenAnnouncementUICommand = new RelayCommand(OpenAnnouncementUI); diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarPropertyViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarPropertyViewModel.cs index 93789dd9..0edba602 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarPropertyViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarPropertyViewModel.cs @@ -18,6 +18,7 @@ using Snap.Hutao.Service.Cultivation; using Snap.Hutao.Service.User; using Snap.Hutao.View.Dialog; using Snap.Hutao.Web.Hoyolab.Takumi.Binding; +using Snap.Hutao.Web.Response; using Windows.Graphics.Imaging; using Windows.Storage.Streams; using Windows.UI; @@ -115,8 +116,8 @@ internal class AvatarPropertyViewModel : ObservableObject, ISupportCancellation { if (user.SelectedUserGameRole is UserGameRole role) { - UserAndRole userAndRole = new(user.Entity, role); - return RefreshCoreAsync(userAndRole, RefreshOption.None, CancellationToken); + UserAndUid userAndUid = new(user.Entity, role); + return RefreshCoreAsync(userAndUid, RefreshOption.None, CancellationToken); } } @@ -129,8 +130,8 @@ internal class AvatarPropertyViewModel : ObservableObject, ISupportCancellation { if (user.SelectedUserGameRole is UserGameRole role) { - UserAndRole userAndRole = new(user.Entity, role); - return RefreshCoreAsync(userAndRole, RefreshOption.RequestFromEnkaAPI, CancellationToken); + UserAndUid userAndUid = new(user.Entity, role); + return RefreshCoreAsync(userAndUid, RefreshOption.RequestFromEnkaAPI, CancellationToken); } } @@ -143,8 +144,8 @@ internal class AvatarPropertyViewModel : ObservableObject, ISupportCancellation { if (user.SelectedUserGameRole is UserGameRole role) { - UserAndRole userAndRole = new(user.Entity, role); - return RefreshCoreAsync(userAndRole, RefreshOption.RequestFromHoyolabGameRecord, CancellationToken); + UserAndUid userAndUid = new(user.Entity, role); + return RefreshCoreAsync(userAndUid, RefreshOption.RequestFromHoyolabGameRecord, CancellationToken); } } @@ -157,19 +158,19 @@ internal class AvatarPropertyViewModel : ObservableObject, ISupportCancellation { if (user.SelectedUserGameRole is UserGameRole role) { - UserAndRole userAndRole = new(user.Entity, role); - return RefreshCoreAsync(userAndRole, RefreshOption.RequestFromHoyolabCalculate, CancellationToken); + UserAndUid userAndUid = new(user.Entity, role); + return RefreshCoreAsync(userAndUid, RefreshOption.RequestFromHoyolabCalculate, CancellationToken); } } return Task.CompletedTask; } - private async Task RefreshCoreAsync(UserAndRole userAndRole, RefreshOption option, CancellationToken token) + private async Task RefreshCoreAsync(UserAndUid userAndUid, RefreshOption option, CancellationToken token) { try { - (RefreshResult result, Summary? summary) = await avatarInfoService.GetSummaryAsync(userAndRole, option, token).ConfigureAwait(false); + (RefreshResult result, Summary? summary) = await avatarInfoService.GetSummaryAsync(userAndUid, option, token).ConfigureAwait(false); if (result == RefreshResult.Ok) { @@ -212,25 +213,27 @@ internal class AvatarPropertyViewModel : ObservableObject, ISupportCancellation if (isOk) { - CalcConsumption? consumption = await Ioc.Default + Response consumptionResponse = await Ioc.Default .GetRequiredService() .ComputeAsync(userService.Current.Entity, delta) .ConfigureAwait(false); - if (consumption != null) + if (consumptionResponse.IsOk()) { + ICultivationService cultivationService = Ioc.Default.GetRequiredService(); + CalcConsumption consumption = consumptionResponse.Data; + List items = CalcItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume); - bool avatarSaved = await Ioc.Default - .GetRequiredService() + bool avatarSaved = await cultivationService .SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items) .ConfigureAwait(false); - bool weaponSaved = await Ioc.Default - .GetRequiredService() + // take a short path if avatar is not saved. + bool avatarAndWeaponSaved = avatarSaved && await cultivationService .SaveConsumptionAsync(CultivateType.Weapon, avatar.Weapon.Id, consumption.WeaponConsume.EmptyIfNull()) .ConfigureAwait(false); - if (avatarSaved && weaponSaved) + if (avatarAndWeaponSaved) { infoBarService.Success("已成功添加至当前养成计划"); } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNoteViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNoteViewModel.cs index a55aca9b..0d006ac8 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNoteViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNoteViewModel.cs @@ -40,7 +40,7 @@ internal class DailyNoteViewModel : ObservableObject, ISupportCancellation private bool isReminderNotification; private NamedValue? selectedRefreshTime; - private ObservableCollection? userAndRoles; + private ObservableCollection? userAndUids; private SettingEntry? refreshSecondsEntry; private SettingEntry? reminderNotifyEntry; @@ -66,7 +66,7 @@ internal class DailyNoteViewModel : ObservableObject, ISupportCancellation this.appDbContext = appDbContext; OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync); - TrackRoleCommand = asyncRelayCommandFactory.Create(TrackRoleAsync); + TrackRoleCommand = asyncRelayCommandFactory.Create(TrackRoleAsync); RefreshCommand = asyncRelayCommandFactory.Create(RefreshAsync); RemoveDailyNoteCommand = new RelayCommand(RemoveDailyNote); ModifyNotificationCommand = asyncRelayCommandFactory.Create(ModifyDailyNoteNotificationAsync); @@ -136,7 +136,7 @@ internal class DailyNoteViewModel : ObservableObject, ISupportCancellation /// /// 用户与角色集合 /// - public ObservableCollection? UserAndRoles { get => userAndRoles; set => userAndRoles = value; } + public ObservableCollection? UserAndRoles { get => userAndUids; set => userAndUids = value; } /// /// 实时便笺集合 @@ -195,7 +195,7 @@ internal class DailyNoteViewModel : ObservableObject, ISupportCancellation DailyNoteEntries = temp; } - private async Task TrackRoleAsync(UserAndRole? role) + private async Task TrackRoleAsync(UserAndUid? role) { if (role != null) { @@ -229,11 +229,11 @@ internal class DailyNoteViewModel : ObservableObject, ISupportCancellation private async Task VerifyDailyNoteVerificationAsync() { - if (UserAndRole.TryFromUser(userService.Current, out UserAndRole? userAndRole)) + if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid)) { // ContentDialog must be created by main thread. await ThreadHelper.SwitchToMainThreadAsync(); - await new DailyNoteVerificationDialog(userAndRole).ShowAsync(); + await new DailyNoteVerificationDialog(userAndUid).ShowAsync(); } else { diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLogViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLogViewModel.cs index 583f0d4d..870a31a4 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLogViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLogViewModel.cs @@ -259,10 +259,9 @@ internal class GachaLogViewModel : ObservableObject, ISupportCancellation UIGF uigf = await gachaLogService.ExportToUIGFAsync(SelectedArchive).ConfigureAwait(false); bool isOk = await file.SerializeToJsonAsync(uigf, options).ConfigureAwait(false); - ValueTask dialogTask = isOk - ? contentDialogFactory.ConfirmAsync("导出成功", "成功保存到指定位置") - : contentDialogFactory.ConfirmAsync("导出失败", "写入文件时遇到问题"); - await dialogTask.ConfigureAwait(false); + _ = isOk + ? await contentDialogFactory.ConfirmAsync("导出成功", "成功保存到指定位置").ConfigureAwait(false) + : await contentDialogFactory.ConfirmAsync("导出失败", "写入文件时遇到问题").ConfigureAwait(false); } } @@ -279,6 +278,7 @@ internal class GachaLogViewModel : ObservableObject, ISupportCancellation await gachaLogService.RemoveArchiveAsync(SelectedArchive).ConfigureAwait(false); // reselect first archive + await ThreadHelper.SwitchToMainThreadAsync(); SelectedArchive = Archives.FirstOrDefault(); } } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs index 4192ec2d..7e638c9f 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs @@ -10,6 +10,7 @@ using Snap.Hutao.Factory.Abstraction; using Snap.Hutao.Model; using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity.Database; +using Snap.Hutao.Service.Abstraction; using Snap.Hutao.Service.GachaLog; using Snap.Hutao.Service.Game; using Snap.Hutao.Service.Game.Locator; @@ -203,7 +204,17 @@ internal class SettingViewModel : ObservableObject { string cacheFilePath = GachaLogUrlWebCacheProvider.GetCacheFile(gamePath); string cacheFolder = Path.GetDirectoryName(cacheFilePath)!; - Directory.Delete(cacheFolder, true); + + IInfoBarService infoBarService = Ioc.Default.GetRequiredService(); + if (Directory.Exists(cacheFolder)) + { + Directory.Delete(cacheFolder, true); + infoBarService.Success("清除完成"); + } + else + { + infoBarService.Warning($"清除失败,找不到目录:{cacheFolder}"); + } } } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyssRecordViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyssRecordViewModel.cs index 2bdc6977..04c863a1 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyssRecordViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyssRecordViewModel.cs @@ -110,17 +110,14 @@ internal class SpiralAbyssRecordViewModel : ObservableObject, ISupportCancellati /// public void Receive(UserChangedMessage message) { - if (message.NewValue != null) + if (UserAndUid.TryFromUser(message.NewValue, out UserAndUid? userAndUid)) { - UserAndRole userAndRole = UserAndRole.FromUser(message.NewValue); - if (userAndRole.Role != null) - { - UpdateSpiralAbyssCollectionAsync(UserAndRole.FromUser(message.NewValue)).SafeForget(); - return; - } + UpdateSpiralAbyssCollectionAsync(userAndUid).SafeForget(); + } + else + { + SpiralAbyssView = null; } - - SpiralAbyssView = null; } private async Task OpenUIAsync() @@ -131,15 +128,15 @@ internal class SpiralAbyssRecordViewModel : ObservableObject, ISupportCancellati idAvatarMap = AvatarIds.ExtendAvatars(idAvatarMap); if (userService.Current?.SelectedUserGameRole != null) { - await UpdateSpiralAbyssCollectionAsync(UserAndRole.FromUser(userService.Current)).ConfigureAwait(false); + await UpdateSpiralAbyssCollectionAsync(UserAndUid.FromUser(userService.Current)).ConfigureAwait(false); } } } - private async Task UpdateSpiralAbyssCollectionAsync(UserAndRole userAndRole) + private async Task UpdateSpiralAbyssCollectionAsync(UserAndUid userAndUid) { ObservableCollection temp = await spiralAbyssRecordService - .GetSpiralAbyssCollectionAsync(userAndRole) + .GetSpiralAbyssCollectionAsync(userAndUid) .ConfigureAwait(false); await ThreadHelper.SwitchToMainThreadAsync(); @@ -154,7 +151,7 @@ internal class SpiralAbyssRecordViewModel : ObservableObject, ISupportCancellati if (userService.Current?.SelectedUserGameRole != null) { await spiralAbyssRecordService - .RefreshSpiralAbyssAsync(UserAndRole.FromUser(userService.Current)) + .RefreshSpiralAbyssAsync(UserAndUid.FromUser(userService.Current)) .ConfigureAwait(false); await ThreadHelper.SwitchToMainThreadAsync(); @@ -168,27 +165,22 @@ internal class SpiralAbyssRecordViewModel : ObservableObject, ISupportCancellati HomaClient homaClient = Ioc.Default.GetRequiredService(); IInfoBarService infoBarService = Ioc.Default.GetRequiredService(); - if (userService.Current is Model.Binding.User.User user) + if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid)) { - if (user.SelectedUserGameRole == null) + SimpleRecord? record = await homaClient.GetPlayerRecordAsync(userAndUid).ConfigureAwait(false); + if (record != null) { - infoBarService.Warning("尚未选择角色"); - } + Web.Response.Response response = await homaClient.UploadRecordAsync(record).ConfigureAwait(false); - SimpleRecord record = await homaClient.GetPlayerRecordAsync(user).ConfigureAwait(false); - Web.Response.Response? response = await homaClient.UploadRecordAsync(record).ConfigureAwait(false); - - if (response != null) - { if (response.IsOk()) { infoBarService.Success(response.Message); } - else - { - infoBarService.Information(response.Message); - } } } + else + { + infoBarService.Warning("请先选择账号与角色"); + } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/WelcomeViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/WelcomeViewModel.cs index dfac990a..54c9d47d 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/WelcomeViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/WelcomeViewModel.cs @@ -15,6 +15,7 @@ using Snap.Hutao.Factory.Abstraction; using System.Collections.ObjectModel; using System.IO; using System.IO.Compression; +using System.Runtime.InteropServices; namespace Snap.Hutao.ViewModel; @@ -84,10 +85,17 @@ internal class WelcomeViewModel : ObservableObject LocalSetting.Set(SettingKeys.StaticResourceV1Contract, true); LocalSetting.Set(SettingKeys.StaticResourceV2Contract, true); - new ToastContentBuilder() - .AddText("下载完成") - .AddText("现在可以开始使用胡桃了") - .Show(); + try + { + new ToastContentBuilder() + .AddText("下载完成") + .AddText("现在可以开始使用胡桃了") + .Show(); + } + catch (COMException) + { + // 0x803E0105 + } } /// diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiAvatarViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiAvatarViewModel.cs index 0ce7734e..faf0c324 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiAvatarViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiAvatarViewModel.cs @@ -19,6 +19,7 @@ using Snap.Hutao.Service.Hutao; using Snap.Hutao.Service.Metadata; using Snap.Hutao.Service.User; using Snap.Hutao.View.Dialog; +using Snap.Hutao.Web.Response; using System.Collections.Immutable; using CalcAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta; using CalcClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient; @@ -137,13 +138,15 @@ internal class WikiAvatarViewModel : ObservableObject if (isOk) { - CalcConsumption? consumption = await Ioc.Default + Response consumptionResponse = await Ioc.Default .GetRequiredService() .ComputeAsync(userService.Current.Entity, delta) .ConfigureAwait(false); - if (consumption != null) + if (consumptionResponse.IsOk()) { + CalcConsumption consumption = consumptionResponse.Data; + List items = CalcItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume); bool saved = await Ioc.Default .GetRequiredService() diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiWeaponViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiWeaponViewModel.cs index fcac3e39..b413542d 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiWeaponViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiWeaponViewModel.cs @@ -19,6 +19,7 @@ using Snap.Hutao.Service.Hutao; using Snap.Hutao.Service.Metadata; using Snap.Hutao.Service.User; using Snap.Hutao.View.Dialog; +using Snap.Hutao.Web.Response; using System.Collections.Immutable; using CalcAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta; using CalcClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient; @@ -140,13 +141,15 @@ internal class WikiWeaponViewModel : ObservableObject, ISupportCancellation if (isOk) { - CalcConsumption? consumption = await Ioc.Default + Response consumptionResponse = await Ioc.Default .GetRequiredService() .ComputeAsync(userService.Current.Entity, delta) .ConfigureAwait(false); - if (consumption != null) + if (consumptionResponse.IsOk()) { + CalcConsumption consumption = consumptionResponse.Data; + bool saved = await Ioc.Default .GetRequiredService() .SaveConsumptionAsync(CultivateType.Weapon, weapon.Id, consumption.WeaponConsume.EmptyIfNull()) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs index c9836285..085e7acf 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs @@ -41,7 +41,6 @@ public class MiHoYoJSInterface private readonly CoreWebView2 webView; private readonly IServiceProvider serviceProvider; private readonly ILogger logger; - private readonly JsonSerializerOptions options; /// /// 构造一个新的调用桥 @@ -54,7 +53,6 @@ public class MiHoYoJSInterface this.serviceProvider = serviceProvider; logger = serviceProvider.GetRequiredService>(); - options = serviceProvider.GetRequiredService(); webView.WebMessageReceived += OnWebMessageReceived; webView.DOMContentLoaded += OnDOMContentLoaded; @@ -72,7 +70,7 @@ public class MiHoYoJSInterface User user = serviceProvider.GetRequiredService().Current!; return await serviceProvider .GetRequiredService() - .GetActionTicketWrapperByStokenAsync(jsParam.Payload!.ActionType, user.Entity) + .GetActionTicketByStokenAsync(jsParam.Payload!.ActionType, user.Entity) .ConfigureAwait(false); } @@ -204,14 +202,19 @@ public class MiHoYoJSInterface public virtual async Task>> GetCookieTokenAsync(JsParam param) { User user = serviceProvider.GetRequiredService().Current!; - string? cookieToken; + string cookieToken = string.Empty; if (param.Payload!.ForceRefresh) { - cookieToken = await Ioc.Default + Response.Response cookieTokenResponse = await Ioc.Default .GetRequiredService() .GetCookieAccountInfoBySTokenAsync(user.Entity, default) .ConfigureAwait(false); + if (cookieTokenResponse.IsOk()) + { + cookieToken = cookieTokenResponse.Data.CookieToken; + } + // sync ui and database user.CookieToken![Cookie.COOKIE_TOKEN] = cookieToken!; Ioc.Default.GetRequiredService().Users.UpdateAndSave(user.Entity); } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/CookieType.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/CookieType.cs index 590af7c6..c2fcbfcc 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/CookieType.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/CookieType.cs @@ -33,9 +33,4 @@ public enum CookieType /// 需要 Stoken /// Stoken = 0B0100, - - /// - /// 需要 Mid - /// - Mid = 0B1000, } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaInfoClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaInfoClient.cs index 4aa6956a..cdfe3d06 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaInfoClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaInfoClient.cs @@ -4,7 +4,6 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.Web.Response; using System.Net.Http; -using System.Net.Http.Json; namespace Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo; @@ -16,16 +15,19 @@ internal class GachaInfoClient { private readonly HttpClient httpClient; private readonly JsonSerializerOptions options; + private readonly ILogger logger; /// /// 构造一个新的祈愿记录客户端 /// /// http客户端 /// Json序列化选项 - public GachaInfoClient(HttpClient httpClient, JsonSerializerOptions options) + /// 日志器 + public GachaInfoClient(HttpClient httpClient, JsonSerializerOptions options, ILogger logger) { this.httpClient = httpClient; this.options = options; + this.logger = logger; } /// @@ -34,13 +36,16 @@ internal class GachaInfoClient /// 查询 /// 取消令牌 /// 单个祈愿记录页面 - public Task?> GetGachaLogPageAsync(GachaLogConfigration config, CancellationToken token = default) + public async Task> GetGachaLogPageAsync(GachaLogConfigration config, CancellationToken token = default) { string query = config.AsQuery(); string url = query.Contains("hoyoverse.com") ? ApiOsEndpoints.GachaInfoGetGachaLog(query) : ApiEndpoints.GachaInfoGetGachaLog(query); - return httpClient.GetFromJsonAsync>(url, options, token); + Response? response = await httpClient + .TryCatchGetFromJsonAsync>(url, options, logger, token) + .ConfigureAwait(false); + return Response.Response.DefaultIfNull(response); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabHttpClientExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabHttpClientExtensions.cs index df0bc994..1157eb88 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabHttpClientExtensions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabHttpClientExtensions.cs @@ -40,11 +40,6 @@ internal static class HoyolabHttpClientExtensions stringBuilder.Append(user.Stoken).AppendIf(user.Stoken != null, ';'); } - if ((cookie & CookieType.Mid) == CookieType.Mid) - { - stringBuilder.Append("mid=").Append(user.Mid).Append(';'); - } - httpClient.DefaultRequestHeaders.Set("Cookie", stringBuilder.ToString()); return httpClient; } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.cs index 2ceec602..836de228 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.cs @@ -39,19 +39,19 @@ internal class PassportClient /// 取消令牌 /// 验证信息 [ApiInformation(Cookie = CookieType.Ltoken)] - public async Task VerifyLtokenAsync(User user, CancellationToken token) + public async Task> VerifyLtokenAsync(User user, CancellationToken token) { Response? response = await httpClient .SetUser(user, CookieType.Ltoken) .TryCatchPostAsJsonAsync>(ApiEndpoints.AccountVerifyLtoken, new(), options, logger, token) .ConfigureAwait(false); - return response?.Data?.UserInfo; + return Response.Response.DefaultIfNull(response); } private class Timestamp { [JsonPropertyName("t")] - public long T { get; set; } = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + public long Time { get; set; } = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs index 71a6b1fe..81d2ad9f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs @@ -64,7 +64,7 @@ internal class PassportClient2 /// 取消令牌 /// 登录数据 [ApiInformation(Salt = SaltType.PROD)] - public async Task LoginByStokenAsync(Cookie stokenV1, CancellationToken token) + public async Task> LoginByStokenAsync(Cookie stokenV1, CancellationToken token) { HttpResponseMessage message = await httpClient .SetHeader("Cookie", stokenV1.ToString()) @@ -76,7 +76,7 @@ internal class PassportClient2 .ReadFromJsonAsync>(options, token) .ConfigureAwait(false); - return resp?.Data; + return Response.Response.DefaultIfNull(resp); } /// @@ -86,7 +86,7 @@ internal class PassportClient2 /// 取消令牌 /// cookie token [ApiInformation(Cookie = CookieType.Stoken, Salt = SaltType.PROD)] - public async Task GetCookieAccountInfoBySTokenAsync(User user, CancellationToken token) + public async Task> GetCookieAccountInfoBySTokenAsync(User user, CancellationToken token) { Response? resp = await httpClient .SetUser(user, CookieType.Stoken) @@ -94,7 +94,7 @@ internal class PassportClient2 .TryCatchGetFromJsonAsync>(ApiEndpoints.AccountGetCookieTokenBySToken, options, logger, token) .ConfigureAwait(false); - return resp?.Data?.CookieToken; + return Response.Response.DefaultIfNull(resp); } /// @@ -104,7 +104,7 @@ internal class PassportClient2 /// 取消令牌 /// uid 与 cookie token [ApiInformation(Cookie = CookieType.Stoken, Salt = SaltType.PROD)] - public async Task GetLtokenBySTokenAsync(User user, CancellationToken token) + public async Task> GetLtokenBySTokenAsync(User user, CancellationToken token) { Response? resp = await httpClient .SetUser(user, CookieType.Stoken) @@ -112,6 +112,6 @@ internal class PassportClient2 .TryCatchGetFromJsonAsync>(ApiEndpoints.AccountGetLtokenByStoken, options, logger, token) .ConfigureAwait(false); - return resp?.Data?.Ltoken; + return Response.Response.DefaultIfNull(resp); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/SdkStatic/Hk4e/Launcher/ResourceClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/SdkStatic/Hk4e/Launcher/ResourceClient.cs index 5c9c3e4d..4de96a04 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/SdkStatic/Hk4e/Launcher/ResourceClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/SdkStatic/Hk4e/Launcher/ResourceClient.cs @@ -37,13 +37,13 @@ internal class ResourceClient /// 方案 /// 取消令牌 /// 游戏资源 - public async Task GetResourceAsync(LaunchScheme scheme, CancellationToken token = default) + public async Task> GetResourceAsync(LaunchScheme scheme, CancellationToken token = default) { string url = ApiEndpoints.SdkStaticLauncherResource(scheme.LauncherId, scheme.Channel, scheme.SubChannel); Response? response = await httpClient .TryCatchGetFromJsonAsync>(url, options, logger, token) .ConfigureAwait(false); - return response?.Data; + return Response.Response.DefaultIfNull(response); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Auth/AuthClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Auth/AuthClient.cs index 9bf9c57b..ff6422e2 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Auth/AuthClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Auth/AuthClient.cs @@ -42,38 +42,15 @@ internal class AuthClient /// 用户 /// 操作凭证 [ApiInformation(Cookie = CookieType.Stoken, Salt = SaltType.K2)] - public async Task GetActionTicketByStokenAsync(string action, User user) - { - if (user.Stoken != null) - { - Response? resp = await httpClient - .SetUser(user, CookieType.Stoken) - .UseDynamicSecret(DynamicSecretVersion.Gen1, SaltType.K2, true) - .TryCatchGetFromJsonAsync>(ApiEndpoints.AuthActionTicket(action, user.Stoken[Cookie.STOKEN], user.Aid!), options, logger) - .ConfigureAwait(false); - - return resp?.Data?.Ticket; - } - - return null; - } - - /// - /// 异步获取操作凭证 - /// - /// 操作 - /// 用户 - /// 操作凭证 - [ApiInformation(Cookie = CookieType.Stoken, Salt = SaltType.K2)] - public async Task?> GetActionTicketWrapperByStokenAsync(string action, User user) + public async Task> GetActionTicketByStokenAsync(string action, User user) { Response? resp = await httpClient - .SetUser(user, CookieType.Stoken) - .UseDynamicSecret(DynamicSecretVersion.Gen1, SaltType.K2, true) - .TryCatchGetFromJsonAsync>(ApiEndpoints.AuthActionTicket(action, user.Stoken![Cookie.STOKEN], user.Aid!), options, logger) - .ConfigureAwait(false); + .SetUser(user, CookieType.Stoken) + .UseDynamicSecret(DynamicSecretVersion.Gen1, SaltType.K2, true) + .TryCatchGetFromJsonAsync>(ApiEndpoints.AuthActionTicket(action, user.Stoken?[Cookie.STOKEN] ?? string.Empty, user.Aid!), options, logger) + .ConfigureAwait(false); - return resp; + return Response.Response.DefaultIfNull(resp); } /// @@ -82,7 +59,7 @@ internal class AuthClient /// login cookie /// 取消令牌 /// 包含token的字典 - public async Task> GetMultiTokenByLoginTicketAsync(Cookie cookie, CancellationToken token) + public async Task>> GetMultiTokenByLoginTicketAsync(Cookie cookie, CancellationToken token) { string loginTicket = cookie["login_ticket"]; string loginUid = cookie["login_uid"]; @@ -91,14 +68,6 @@ internal class AuthClient .TryCatchGetFromJsonAsync>>(ApiEndpoints.AuthMultiToken(loginTicket, loginUid), options, logger, token) .ConfigureAwait(false); - if (resp?.Data != null) - { - Dictionary dict = resp.Data.List.ToDictionary(n => n.Name, n => n.Token); - Must.Argument(dict.ContainsKey(Cookie.LTOKEN), "MultiToken 应该包含 ltoken"); - Must.Argument(dict.ContainsKey(Cookie.STOKEN), "MultiToken 应该包含 stoken"); - return dict; - } - - return new(); + return Response.Response.DefaultIfNull(resp); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient.cs index 9313031c..9d6d0270 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient.cs @@ -41,7 +41,7 @@ internal class BindingClient /// 取消令牌 /// 用户角色信息 [ApiInformation(Cookie = CookieType.Ltoken)] - public async Task> GetUserGameRolesByActionTicketAsync(string actionTicket, User user, CancellationToken token = default) + public async Task>> GetUserGameRolesByActionTicketAsync(string actionTicket, User user, CancellationToken token = default) { string url = ApiEndpoints.UserGameRolesByActionTicket(actionTicket); @@ -50,24 +50,6 @@ internal class BindingClient .TryCatchGetFromJsonAsync>>(url, options, logger, token) .ConfigureAwait(false); - return EnumerableExtension.EmptyIfNull(resp?.Data?.List); - } - - /// - /// 获取用户角色信息 - /// - /// 用户 - /// 取消令牌 - /// 用户角色信息 - [Obsolete("Set-Cookie 冲突")] - [ApiInformation(Cookie = CookieType.Ltoken)] - public async Task> GetUserGameRolesByCookieAsync(User user, CancellationToken token = default) - { - Response>? resp = await httpClient - .SetUser(user, CookieType.Ltoken) - .TryCatchGetFromJsonAsync>>(ApiEndpoints.UserGameRolesByCookie, options, logger, token) - .ConfigureAwait(false); - - return EnumerableExtension.EmptyIfNull(resp?.Data?.List); + return Response.Response.DefaultIfNull(resp); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient2.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient2.cs index 8f08b9e7..b2dbe543 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient2.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient2.cs @@ -62,7 +62,7 @@ internal class BindingClient2 /// 取消令牌 /// 用户角色信息 [ApiInformation(Cookie = CookieType.Stoken, Salt = SaltType.LK2)] - public async Task GenerateAuthenticationKeyAsync(User user, GenAuthKeyData data, CancellationToken token = default) + public async Task> GenerateAuthenticationKeyAsync(User user, GenAuthKeyData data, CancellationToken token = default) { Response? resp = await httpClient .SetUser(user, CookieType.Stoken) @@ -71,6 +71,6 @@ internal class BindingClient2 .TryCatchPostAsJsonAsync>(ApiEndpoints.BindingGenAuthKey, data, options, logger, token) .ConfigureAwait(false); - return resp?.Data; + return Response.Response.DefaultIfNull(resp); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/CalculateClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/CalculateClient.cs index a4dc536b..8fab3ab3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/CalculateClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/CalculateClient.cs @@ -2,8 +2,7 @@ // Licensed under the MIT license. using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; -using Snap.Hutao.Extension; -using Snap.Hutao.Model.Entity; +using Snap.Hutao.Model.Binding.User; using Snap.Hutao.Web.Response; using System.Net.Http; @@ -39,30 +38,30 @@ internal class CalculateClient /// 差异 /// 取消令牌 /// 消耗结果 - public async Task ComputeAsync(User user, AvatarPromotionDelta delta, CancellationToken token = default) + public async Task> ComputeAsync(Model.Entity.User user, AvatarPromotionDelta delta, CancellationToken token = default) { Response? resp = await httpClient .SetUser(user, CookieType.CookieToken) .TryCatchPostAsJsonAsync>(ApiEndpoints.CalculateCompute, delta, options, logger, token) .ConfigureAwait(false); - return resp?.Data; + + return Response.Response.DefaultIfNull(resp); } /// /// 异步获取角色列表 /// - /// 用户 - /// Uid + /// 用户与角色 /// 取消令牌 /// 角色列表 - public async Task> GetAvatarsAsync(User user, PlayerUid uid, CancellationToken token = default) + public async Task> GetAvatarsAsync(UserAndUid userAndUid, CancellationToken token = default) { int currentPage = 1; - SyncAvatarFilter filter = new() { Uid = uid.Value, Region = uid.Region }; + SyncAvatarFilter filter = new() { Uid = userAndUid.Uid.Value, Region = userAndUid.Uid.Region }; List avatars = new(); Response>? resp; - httpClient.SetUser(user, CookieType.CookieToken); + httpClient.SetUser(userAndUid.User, CookieType.CookieToken); do { @@ -71,43 +70,14 @@ internal class CalculateClient .TryCatchPostAsJsonAsync>>(ApiEndpoints.CalculateSyncAvatarList, filter, options, logger, token) .ConfigureAwait(false); - if (resp?.Data?.List is not null) + if (resp != null && resp.IsOk()) { avatars.AddRange(resp.Data.List); } - - await Task.Delay(Random.Shared.Next(0, 1000), token).ConfigureAwait(false); - } - while (resp?.Data?.List?.Count == 20); - - return avatars; - } - - /// - /// 异步获取角色列表 - /// - /// 用户 - /// 取消令牌 - /// 角色列表 - public async Task> GetAvatarsAsync(User user, CancellationToken token = default) - { - int currentPage = 1; - AvatarFilter filter = new(); - - List avatars = new(); - Response>? resp; - httpClient.SetUser(user, CookieType.CookieToken); - - do - { - filter.Page = currentPage++; - resp = await httpClient - .TryCatchPostAsJsonAsync>>(ApiEndpoints.CalculateAvatarList, filter, options, logger, token) - .ConfigureAwait(false); - - if (resp?.Data?.List is not null) + else { - avatars.AddRange(resp.Data.List); + // Hot path to exit loop + break; } await Task.Delay(Random.Shared.Next(0, 1000), token).ConfigureAwait(false); @@ -120,70 +90,18 @@ internal class CalculateClient /// /// 异步获取角色详情 /// - /// 用户 - /// uid + /// 用户与角色 /// 角色 /// 取消令牌 /// 角色详情 - public async Task GetAvatarDetailAsync(User user, PlayerUid uid, Avatar avatar, CancellationToken token = default) + public async Task> GetAvatarDetailAsync(UserAndUid userAndUid, Avatar avatar, CancellationToken token = default) { Response? resp = await httpClient - .SetUser(user, CookieType.CookieToken) - .TryCatchGetFromJsonAsync>(ApiEndpoints.CalculateSyncAvatarDetail(avatar.Id, uid), options, logger, token) + .SetUser(userAndUid.User, CookieType.CookieToken) + .TryCatchGetFromJsonAsync>(ApiEndpoints.CalculateSyncAvatarDetail(avatar.Id, userAndUid.Uid.Value), options, logger, token) .ConfigureAwait(false); - return resp?.Data; - } - - /// - /// 异步获取角色技能列表 - /// - /// 用户 - /// 角色 - /// 取消令牌 - /// 角色技能列表 - public async Task> GetAvatarSkillsAsync(User user, Avatar avatar, CancellationToken token) - { - Response>? resp = await httpClient - .SetUser(user, CookieType.CookieToken) - .TryCatchGetFromJsonAsync>>(ApiEndpoints.CalculateAvatarSkillList(avatar), options, logger, token) - .ConfigureAwait(false); - - return EnumerableExtension.EmptyIfNull(resp?.Data?.List); - } - - /// - /// 异步获取角色列表 - /// - /// 用户 - /// 取消令牌 - /// 角色列表 - public async Task> GetWeaponsAsync(User user, CancellationToken token) - { - int currentPage = 1; - WeaponFilter filter = new(); - - List weapons = new(); - Response>? resp; - httpClient.SetUser(user, CookieType.CookieToken); - - do - { - filter.Page = currentPage++; - resp = await httpClient - .TryCatchPostAsJsonAsync>>(ApiEndpoints.CalculateWeaponList, filter, options, logger, token) - .ConfigureAwait(false); - - if (resp?.Data?.List is not null) - { - weapons.AddRange(resp.Data.List); - } - - await Task.Delay(Random.Shared.Next(0, 1000), token).ConfigureAwait(false); - } - while (resp?.Data?.List?.Count == 20); - - return weapons; + return Response.Response.DefaultIfNull(resp); } /// @@ -193,14 +111,14 @@ internal class CalculateClient /// 摹本码 /// 取消令牌 /// 家具列表 - public async Task FurnitureBlueprintAsync(User user, string shareCode, CancellationToken token) + public async Task> FurnitureBlueprintAsync(Model.Entity.User user, string shareCode, CancellationToken token) { Response? resp = await httpClient .SetUser(user, CookieType.CookieToken) .TryCatchGetFromJsonAsync>(ApiEndpoints.CalculateFurnitureBlueprint(shareCode), options, logger, token) .ConfigureAwait(false); - return resp?.Data; + return Response.Response.DefaultIfNull(resp); } /// @@ -210,7 +128,7 @@ internal class CalculateClient /// 物品 /// 取消令牌 /// 消耗 - public async Task> FurnitureComputeAsync(User user, List items, CancellationToken token) + public async Task>> FurnitureComputeAsync(Model.Entity.User user, List items, CancellationToken token) { ListWrapper data = new() { List = items.Select(i => new IdCount { Id = i.Id, Count = i.Num }).ToList() }; @@ -219,25 +137,7 @@ internal class CalculateClient .TryCatchPostAsJsonAsync, Response>>(ApiEndpoints.CalculateFurnitureCompute, data, options, logger, token) .ConfigureAwait(false); - return EnumerableExtension.EmptyIfNull(resp?.Data?.List); - } - - private class AvatarFilter - { - [JsonPropertyName("element_attr_ids")] - public List? ElementAttrIds { get; set; } = new(); - - [JsonPropertyName("weapon_cat_ids")] - public List? WeaponCatIds { get; set; } = new(); - - [JsonPropertyName("page")] - public int Page { get; set; } - - [JsonPropertyName("size")] - public int Size { get; set; } = 20; - - [JsonPropertyName("is_all")] - public bool IsAll { get; set; } = true; + return Response.Response.DefaultIfNull(resp); } private class SyncAvatarFilter @@ -261,21 +161,6 @@ internal class CalculateClient public string Region { get; set; } = default!; } - private class WeaponFilter - { - [JsonPropertyName("weapon_levels")] - public List WeaponLevels { get; set; } = new(); - - [JsonPropertyName("weapon_cat_ids")] - public List WeaponCatIds { get; set; } = new(); - - [JsonPropertyName("page")] - public int Page { get; set; } - - [JsonPropertyName("size")] - public int Size { get; set; } = 20; - } - private class IdCount { [JsonPropertyName("cnt")] diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/CardClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/CardClient.cs index a9a6d572..8764a45e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/CardClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/CardClient.cs @@ -41,7 +41,7 @@ public class CardClient /// 用户 /// 取消令牌 /// 注册结果 - public async Task CreateVerificationAsync(User user, CancellationToken token) + public async Task> CreateVerificationAsync(User user, CancellationToken token) { Response? resp = await httpClient .SetUser(user, CookieType.Ltoken) @@ -49,7 +49,7 @@ public class CardClient .TryCatchGetFromJsonAsync>(ApiEndpoints.CardCreateVerification, options, logger, token) .ConfigureAwait(false); - return resp?.Data; + return Response.Response.DefaultIfNull(resp); } /// @@ -59,7 +59,7 @@ public class CardClient /// 验证 /// 取消令牌 /// 验证结果 - public async Task VerifyVerificationAsync(string challenge, string validate, CancellationToken token) + public async Task> VerifyVerificationAsync(string challenge, string validate, CancellationToken token) { VerificationData data = new(challenge, validate); @@ -67,7 +67,7 @@ public class CardClient .TryCatchPostAsJsonAsync>(ApiEndpoints.CardVerifyVerification, data, options, logger, token) .ConfigureAwait(false); - return resp?.Data; + return Response.Response.DefaultIfNull(resp); } /// @@ -77,7 +77,7 @@ public class CardClient /// 取消令牌 /// 桌面小组件数据 [ApiInformation(Cookie = CookieType.Stoken, Salt = SaltType.X6)] - public async Task GetWidgetDataAsync(User user, CancellationToken token) + public async Task>> GetWidgetDataAsync(User user, CancellationToken token) { Response>? resp = await httpClient .SetUser(user, CookieType.Stoken) @@ -85,7 +85,7 @@ public class CardClient .TryCatchGetFromJsonAsync>>(ApiEndpoints.CardWidgetData, options, logger, token) .ConfigureAwait(false); - return resp?.Data?.Data; + return Response.Response.DefaultIfNull(resp); } private class VerificationData diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/CardVerifier.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/CardVerifier.cs index 40216125..b4ee5bbf 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/CardVerifier.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/CardVerifier.cs @@ -34,18 +34,25 @@ internal class CardVerifier /// 流水号 public async Task TryGetXrpcChallengeAsync(User user, CancellationToken token) { - if (await cardClient.CreateVerificationAsync(user, token).ConfigureAwait(false) is VerificationRegistration registration) + Response.Response registrationResponse = await cardClient.CreateVerificationAsync(user, token).ConfigureAwait(false); + if (registrationResponse.IsOk()) { - _ = await geetestClient.GetTypeAsync(registration.Gt).ConfigureAwait(false); + VerificationRegistration registration = registrationResponse.Data; + + await geetestClient.GetTypeAsync(registration.Gt).ConfigureAwait(false); GeetestResult? ajax = await geetestClient.GetAjaxAsync(registration).ConfigureAwait(false); if (ajax?.Data.Validate is string validate) { - VerificationResult? result = await cardClient.VerifyVerificationAsync(registration.Challenge, validate, token).ConfigureAwait(false); - - if (result?.Challenge != null) + Response.Response verifyResponse = await cardClient.VerifyVerificationAsync(registration.Challenge, validate, token).ConfigureAwait(false); + if (verifyResponse.IsOk()) { - return result.Challenge; + VerificationResult result = verifyResponse.Data; + + if (result.Challenge != null) + { + return result.Challenge; + } } } } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs index 75ad60b4..5596927f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs @@ -41,17 +41,16 @@ internal class GameRecordClient /// /// 异步获取实时便笺 /// - /// 用户 - /// 查询uid + /// 用户与角色 /// 取消令牌 /// 实时便笺 - [ApiInformation(Cookie = CookieType.CookieToken | CookieType.Ltoken | CookieType.Mid, Salt = SaltType.X4)] - public async Task GetDailyNoteAsync(Model.Entity.User user, PlayerUid uid, CancellationToken token = default) + [ApiInformation(Cookie = CookieType.Cookie, Salt = SaltType.X4)] + public async Task> GetDailyNoteAsync(UserAndUid userAndUid, CancellationToken token = default) { Response? resp = await httpClient - .SetUser(user, CookieType.CookieToken | CookieType.Ltoken) + .SetUser(userAndUid.User, CookieType.Cookie) .UseDynamicSecret(DynamicSecretVersion.Gen2, SaltType.X4, false) - .TryCatchGetFromJsonAsync>(ApiEndpoints.GameRecordDailyNote(uid), options, logger, token) + .TryCatchGetFromJsonAsync>(ApiEndpoints.GameRecordDailyNote(userAndUid.Uid.Value), options, logger, token) .ConfigureAwait(false); // We hava a verification procedure to handle @@ -59,139 +58,96 @@ internal class GameRecordClient { CardVerifier cardVerifier = Ioc.Default.GetRequiredService(); - if (await cardVerifier.TryGetXrpcChallengeAsync(user, token).ConfigureAwait(false) is string challenge) + if (await cardVerifier.TryGetXrpcChallengeAsync(userAndUid.User, token).ConfigureAwait(false) is string challenge) { Ioc.Default.GetRequiredService().Success("无感验证成功"); resp = await httpClient - .SetUser(user, CookieType.CookieToken | CookieType.Ltoken) + .SetUser(userAndUid.User, CookieType.Cookie) .SetXrpcChallenge(challenge) .UseDynamicSecret(DynamicSecretVersion.Gen2, SaltType.X4, false) - .TryCatchGetFromJsonAsync>(ApiEndpoints.GameRecordDailyNote(uid), options, logger, token) + .TryCatchGetFromJsonAsync>(ApiEndpoints.GameRecordDailyNote(userAndUid.Uid.Value), options, logger, token) .ConfigureAwait(false); } - else - { - Ioc.Default.GetRequiredService().Warning("无感验证失败,请前往「米游社-我的角色-实时便笺」页面查看"); - } } - return resp?.Data; + return Response.Response.DefaultIfNull(resp, "请求失败,请前往「米游社-我的角色-实时便笺」页面查看"); } /// /// 获取玩家基础信息 /// - /// 用户 - /// 取消令牌 - /// 玩家的基础信息 - public Task GetPlayerInfoAsync(UserAndRole userAndRole, CancellationToken token = default) - { - return GetPlayerInfoAsync(userAndRole.User, userAndRole.Role, token); - } - - /// - /// 获取玩家基础信息 - /// - /// 用户 - /// uid + /// 用户与角色 /// 取消令牌 /// 玩家的基础信息 [ApiInformation(Cookie = CookieType.Ltoken, Salt = SaltType.X4)] - public async Task GetPlayerInfoAsync(Model.Entity.User user, PlayerUid uid, CancellationToken token = default) + public async Task> GetPlayerInfoAsync(UserAndUid userAndUid, CancellationToken token = default) { Response? resp = await httpClient - .SetUser(user, CookieType.Ltoken) + .SetUser(userAndUid.User, CookieType.Ltoken) .UseDynamicSecret(DynamicSecretVersion.Gen2, SaltType.X4, false) - .TryCatchGetFromJsonAsync>(ApiEndpoints.GameRecordIndex(uid), options, logger, token) + .TryCatchGetFromJsonAsync>(ApiEndpoints.GameRecordIndex(userAndUid.Uid), options, logger, token) .ConfigureAwait(false); - return resp?.Data; + return Response.Response.DefaultIfNull(resp); } /// /// 获取玩家深渊信息 /// - /// 用户 - /// 1:当期,2:上期 - /// 取消令牌 - /// 深渊信息 - public Task GetSpiralAbyssAsync(UserAndRole userAndRole, SpiralAbyssSchedule schedule, CancellationToken token = default) - { - return GetSpiralAbyssAsync(userAndRole.User, userAndRole.Role, schedule, token); - } - - /// - /// 获取玩家深渊信息 - /// - /// 用户 - /// uid + /// 用户 /// 1:当期,2:上期 /// 取消令牌 /// 深渊信息 [ApiInformation(Cookie = CookieType.Ltoken, Salt = SaltType.X4)] - public async Task GetSpiralAbyssAsync(Model.Entity.User user, PlayerUid uid, SpiralAbyssSchedule schedule, CancellationToken token = default) + public async Task> GetSpiralAbyssAsync(UserAndUid userAndUid, SpiralAbyssSchedule schedule, CancellationToken token = default) { Response? resp = await httpClient - .SetUser(user, CookieType.Ltoken) + .SetUser(userAndUid.User, CookieType.Ltoken) .UseDynamicSecret(DynamicSecretVersion.Gen2, SaltType.X4, false) - .TryCatchGetFromJsonAsync>(ApiEndpoints.GameRecordSpiralAbyss(schedule, uid), options, logger, token) + .TryCatchGetFromJsonAsync>(ApiEndpoints.GameRecordSpiralAbyss(schedule, userAndUid.Uid), options, logger, token) .ConfigureAwait(false); - return resp?.Data; + return Response.Response.DefaultIfNull(resp); } /// /// 异步获取角色基本信息 /// - /// 用户 - /// uid + /// 用户与角色 /// 取消令牌 /// 角色基本信息 [ApiInformation(Cookie = CookieType.Ltoken, Salt = SaltType.X4)] - public async Task GetRoleBasicInfoAsync(Model.Entity.User user, PlayerUid uid, CancellationToken token = default) + public async Task> GetRoleBasicInfoAsync(UserAndUid userAndUid, CancellationToken token = default) { Response? resp = await httpClient - .SetUser(user, CookieType.Ltoken) + .SetUser(userAndUid.User, CookieType.Ltoken) .UseDynamicSecret(DynamicSecretVersion.Gen2, SaltType.X4, false) - .TryCatchGetFromJsonAsync>(ApiEndpoints.GameRecordRoleBasicInfo(uid), options, logger, token) + .TryCatchGetFromJsonAsync>(ApiEndpoints.GameRecordRoleBasicInfo(userAndUid.Uid), options, logger, token) .ConfigureAwait(false); - return resp?.Data; + return Response.Response.DefaultIfNull(resp); } /// /// 获取玩家角色详细信息 /// - /// 用户与角色 - /// 玩家的基础信息 - /// 取消令牌 - /// 角色列表 - public Task> GetCharactersAsync(UserAndRole userAndRole, PlayerInfo playerInfo, CancellationToken token = default) - { - return GetCharactersAsync(userAndRole.User, userAndRole.Role, playerInfo, token); - } - - /// - /// 获取玩家角色详细信息 - /// - /// 用户 - /// uid + /// 用户与角色 /// 玩家的基础信息 /// 取消令牌 /// 角色列表 [ApiInformation(Cookie = CookieType.Ltoken, Salt = SaltType.X4)] - public async Task> GetCharactersAsync(Model.Entity.User user, PlayerUid uid, PlayerInfo playerInfo, CancellationToken token = default) + public async Task> GetCharactersAsync(UserAndUid userAndUid, PlayerInfo playerInfo, CancellationToken token = default) { - CharacterData data = new(uid, playerInfo.Avatars.Select(x => x.Id)); + CharacterData data = new(userAndUid.Uid, playerInfo.Avatars.Select(x => x.Id)); Response? resp = await httpClient - .SetUser(user, CookieType.Ltoken) + .SetUser(userAndUid.User, CookieType.Ltoken) .UseDynamicSecret(DynamicSecretVersion.Gen2, SaltType.X4, false) .TryCatchPostAsJsonAsync>(ApiEndpoints.GameRecordCharacter, data, options, logger, token) .ConfigureAwait(false); - return EnumerableExtension.EmptyIfNull(resp?.Data?.Avatars); + return Response.Response.DefaultIfNull(resp); } private class CharacterData diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs index 891874b1..c5c83dda 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs @@ -49,13 +49,13 @@ internal class HomaClient /// uid /// 取消令牌 /// 当前是否上传了数据 - public async Task CheckRecordUploadedAsync(PlayerUid uid, CancellationToken token = default) + public async Task> CheckRecordUploadedAsync(PlayerUid uid, CancellationToken token = default) { Response? resp = await httpClient .GetFromJsonAsync>(HutaoEndpoints.RecordCheck(uid.Value), token) .ConfigureAwait(false); - return resp?.Data == true; + return Response.Response.DefaultIfNull(resp); } /// @@ -65,13 +65,13 @@ internal class HomaClient /// uid /// 取消令牌 /// 排行信息 - public async Task GetRankAsync(PlayerUid uid, CancellationToken token = default) + public async Task> GetRankAsync(PlayerUid uid, CancellationToken token = default) { Response? resp = await httpClient .GetFromJsonAsync>(HutaoEndpoints.RecordRank(uid.Value), token) .ConfigureAwait(false); - return resp?.Data; + return Response.Response.DefaultIfNull(resp); } /// @@ -80,13 +80,13 @@ internal class HomaClient /// /// 取消令牌 /// 总览信息 - public async Task GetOverviewAsync(CancellationToken token = default) + public async Task> GetOverviewAsync(CancellationToken token = default) { Response? resp = await httpClient .GetFromJsonAsync>(HutaoEndpoints.StatisticsOverview, token) .ConfigureAwait(false); - return resp?.Data; + return Response.Response.DefaultIfNull(resp); } /// @@ -95,13 +95,13 @@ internal class HomaClient /// /// 取消令牌 /// 角色出场率 - public async Task> GetAvatarAttendanceRatesAsync(CancellationToken token = default) + public async Task>> GetAvatarAttendanceRatesAsync(CancellationToken token = default) { Response>? resp = await httpClient .TryCatchGetFromJsonAsync>>(HutaoEndpoints.StatisticsAvatarAttendanceRate, options, logger, token) .ConfigureAwait(false); - return EnumerableExtension.EmptyIfNull(resp?.Data); + return Response.Response.DefaultIfNull(resp); } /// @@ -110,13 +110,13 @@ internal class HomaClient /// /// 取消令牌 /// 角色出场率 - public async Task> GetAvatarUtilizationRatesAsync(CancellationToken token = default) + public async Task>> GetAvatarUtilizationRatesAsync(CancellationToken token = default) { Response>? resp = await httpClient .TryCatchGetFromJsonAsync>>(HutaoEndpoints.StatisticsAvatarUtilizationRate, options, logger, token) .ConfigureAwait(false); - return EnumerableExtension.EmptyIfNull(resp?.Data); + return Response.Response.DefaultIfNull(resp); } /// @@ -125,13 +125,13 @@ internal class HomaClient /// /// 取消令牌 /// 角色/武器/圣遗物搭配 - public async Task> GetAvatarCollocationsAsync(CancellationToken token = default) + public async Task>> GetAvatarCollocationsAsync(CancellationToken token = default) { Response>? resp = await httpClient .TryCatchGetFromJsonAsync>>(HutaoEndpoints.StatisticsAvatarAvatarCollocation, options, logger, token) .ConfigureAwait(false); - return EnumerableExtension.EmptyIfNull(resp?.Data); + return Response.Response.DefaultIfNull(resp); } /// @@ -140,13 +140,13 @@ internal class HomaClient /// /// 取消令牌 /// 角色/武器/圣遗物搭配 - public async Task> GetWeaponCollocationsAsync(CancellationToken token = default) + public async Task>> GetWeaponCollocationsAsync(CancellationToken token = default) { Response>? resp = await httpClient .TryCatchGetFromJsonAsync>>(HutaoEndpoints.StatisticsWeaponWeaponCollocation, options, logger, token) .ConfigureAwait(false); - return EnumerableExtension.EmptyIfNull(resp?.Data); + return Response.Response.DefaultIfNull(resp); } /// @@ -155,13 +155,13 @@ internal class HomaClient /// /// 取消令牌 /// 角色图片列表 - public async Task> GetAvatarHoldingRatesAsync(CancellationToken token = default) + public async Task>> GetAvatarHoldingRatesAsync(CancellationToken token = default) { Response>? resp = await httpClient .TryCatchGetFromJsonAsync>>(HutaoEndpoints.StatisticsAvatarHoldingRate, options, logger, token) .ConfigureAwait(false); - return EnumerableExtension.EmptyIfNull(resp?.Data); + return Response.Response.DefaultIfNull(resp); } /// @@ -170,40 +170,51 @@ internal class HomaClient /// /// 取消令牌 /// 队伍出场列表 - public async Task> GetTeamCombinationsAsync(CancellationToken token = default) + public async Task>> GetTeamCombinationsAsync(CancellationToken token = default) { Response>? resp = await httpClient .TryCatchGetFromJsonAsync>>(HutaoEndpoints.StatisticsTeamCombination, options, logger, token) .ConfigureAwait(false); - return EnumerableExtension.EmptyIfNull(resp?.Data); + return Response.Response.DefaultIfNull(resp); } /// /// 异步获取角色的深渊记录 /// - /// 用户 + /// 用户与角色 /// 取消令牌 /// 玩家记录 - public async Task GetPlayerRecordAsync(User user, CancellationToken token = default) + public async Task GetPlayerRecordAsync(UserAndUid userAndUid, CancellationToken token = default) { - Must.NotNull(user.SelectedUserGameRole!); - - PlayerInfo? playerInfo = await gameRecordClient - .GetPlayerInfoAsync(user.Entity, user.SelectedUserGameRole, token) - .ConfigureAwait(false); - Must.NotNull(playerInfo!); - - List characters = await gameRecordClient - .GetCharactersAsync(user.Entity, user.SelectedUserGameRole, playerInfo, token) + Response playerInfoResponse = await gameRecordClient + .GetPlayerInfoAsync(userAndUid, token) .ConfigureAwait(false); - SpiralAbyss? spiralAbyssInfo = await gameRecordClient - .GetSpiralAbyssAsync(user.Entity, user.SelectedUserGameRole, SpiralAbyssSchedule.Current, token) - .ConfigureAwait(false); - Must.NotNull(spiralAbyssInfo!); + if (!playerInfoResponse.IsOk()) + { + return null; + } - return new(user.SelectedUserGameRole.GameUid, characters, spiralAbyssInfo); + Response charactersResponse = await gameRecordClient + .GetCharactersAsync(userAndUid, playerInfoResponse.Data, token) + .ConfigureAwait(false); + + if (!charactersResponse.IsOk()) + { + return null; + } + + Response spiralAbyssResponse = await gameRecordClient + .GetSpiralAbyssAsync(userAndUid, SpiralAbyssSchedule.Current, token) + .ConfigureAwait(false); + + if (!spiralAbyssResponse.IsOk()) + { + return null; + } + + return new(userAndUid.Uid.Value, charactersResponse.Data.Avatars, spiralAbyssResponse.Data); } /// @@ -213,8 +224,12 @@ internal class HomaClient /// 玩家记录 /// 取消令牌 /// 响应 - public Task?> UploadRecordAsync(SimpleRecord playerRecord, CancellationToken token = default) + public async Task> UploadRecordAsync(SimpleRecord playerRecord, CancellationToken token = default) { - return httpClient.TryCatchPostAsJsonAsync>(HutaoEndpoints.RecordUpload, playerRecord, options, logger, token); + Response? resp = await httpClient + .TryCatchPostAsJsonAsync>(HutaoEndpoints.RecordUpload, playerRecord, options, logger, token) + .ConfigureAwait(false); + + return Response.Response.DefaultIfNull(resp); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Log/HutaoLog.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Log/HutaoLog.cs index b3b4dede..ace04322 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Log/HutaoLog.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Log/HutaoLog.cs @@ -22,4 +22,4 @@ public class HutaoLog /// 错误信息 /// public string Info { get; set; } = default!; -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs b/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs index 3c527260..c554ebc3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs @@ -48,7 +48,20 @@ public class Response : ISupportValidation public static Response DefaultIfNull(Response? response) { // 0x26F19335 is a magic number that hashed from "Snap.Hutao" - return response ?? new(0x26F19335, "请求异常", default); + return response ?? new(0x26F19335, $"[{typeof(TData).Name}] 请求异常", default); + } + + /// + /// 返回本体或带有消息提示的默认值 + /// + /// 类型 + /// 本体 + /// 消息 + /// 本体或默认值,当本体为 null 时 返回默认值 + public static Response DefaultIfNull(Response? response, string message) + { + // 0x26F19335 is a magic number that hashed from "Snap.Hutao" + return response ?? new(0x26F19335, message, default); } ///