mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
improve web request experience
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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")]
|
||||
@@ -43,6 +43,13 @@ internal static class IocConfiguration
|
||||
}
|
||||
}
|
||||
|
||||
return services.AddDbContext<AppDbContext>(builder => builder.UseSqlite(sqlConnectionString));
|
||||
return services.AddDbContext<AppDbContext>(builder =>
|
||||
{
|
||||
builder
|
||||
#if DEBUG
|
||||
.EnableSensitiveDataLogging()
|
||||
#endif
|
||||
.UseSqlite(sqlConnectionString);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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<HomaClient2>().UploadLogAsync(e.Exception).GetAwaiter().GetResult();
|
||||
#if RELEASE
|
||||
#pragma warning disable VSTHRD002
|
||||
Ioc.Default.GetRequiredService<Web.Hutao.HomaClient2>().UploadLogAsync(e.Exception).GetAwaiter().GetResult();
|
||||
#pragma warning restore VSTHRD002
|
||||
#endif
|
||||
logger.LogError(EventIds.UnhandledException, e.Exception, "未经处理的异常");
|
||||
|
||||
foreach (ILoggerProvider provider in Ioc.Default.GetRequiredService<IEnumerable<ILoggerProvider>>())
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 调度器队列拓展
|
||||
/// </summary>
|
||||
public static class DispatcherQueueExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 在调度器队列同步调用,直到执行结束,会持续阻塞当前线程
|
||||
/// </summary>
|
||||
/// <param name="dispatcherQueue">调度器队列</param>
|
||||
/// <param name="action">执行的回调</param>
|
||||
public static void Invoke(this DispatcherQueue dispatcherQueue, Action action)
|
||||
{
|
||||
using (ManualResetEventSlim blockEvent = new())
|
||||
{
|
||||
dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
action();
|
||||
blockEvent.Set();
|
||||
});
|
||||
|
||||
blockEvent.Wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,4 +95,4 @@ public static class TaskExtensions
|
||||
onException?.Invoke(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,14 +27,6 @@ internal interface IContentDialogFactory
|
||||
/// <returns>结果</returns>
|
||||
ValueTask<ContentDialogResult> ConfirmCancelAsync(string title, string content, ContentDialogButton defaultButton = ContentDialogButton.Close);
|
||||
|
||||
/// <summary>
|
||||
/// 在主线程异步创建一个新的内容对话框
|
||||
/// </summary>
|
||||
/// <typeparam name="TContentDialog">对话框类型</typeparam>
|
||||
/// <returns>一个新的内容对话框</returns>
|
||||
ValueTask<TContentDialog> CreateAsync<TContentDialog>()
|
||||
where TContentDialog : ContentDialog, new();
|
||||
|
||||
/// <summary>
|
||||
/// 异步创建一个新的内容对话框,用于提示未知的进度
|
||||
/// </summary>
|
||||
|
||||
@@ -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<HomaClient2>().UploadLogAsync(baseException).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,25 +21,19 @@ internal class ContentDialogFactory : IContentDialogFactory
|
||||
this.mainWindow = mainWindow;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<TContentDialog> CreateAsync<TContentDialog>()
|
||||
where TContentDialog : ContentDialog, new()
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
return new();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<ContentDialogResult> 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();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<ContentDialogResult> 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();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,5 +22,8 @@ public class RankAvatar : Avatar
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 排行
|
||||
/// </summary>
|
||||
public int Value { get; set; }
|
||||
}
|
||||
@@ -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
|
||||
/// </summary>
|
||||
/// <param name="inner">数据库实体</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>用户是否初始化完成,若Cookie失效会返回 <see langword="false"/> </returns>
|
||||
internal static async Task<User?> ResumeAsync(EntityUser inner, CancellationToken token = default)
|
||||
/// <returns>用户</returns>
|
||||
internal static async Task<User> 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
|
||||
/// </summary>
|
||||
/// <param name="cookie">cookie</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>用户是否初始化完成,若Cookie失效会返回 <see langword="null"/> </returns>
|
||||
/// <returns>用户</returns>
|
||||
internal static async Task<User?> CreateAsync(Cookie cookie, CancellationToken token = default)
|
||||
{
|
||||
// 这里只负责创建实体用户,稍后在用户服务中保存到数据库
|
||||
@@ -153,7 +155,7 @@ public class User : ObservableObject
|
||||
|
||||
using (IServiceScope scope = Ioc.Default.CreateScope())
|
||||
{
|
||||
Web.Response.Response<UserFullInfoWrapper> response = await scope.ServiceProvider
|
||||
Response<UserFullInfoWrapper> response = await scope.ServiceProvider
|
||||
.GetRequiredService<UserClient2>()
|
||||
.GetUserFullInfoAsync(Entity, token)
|
||||
.ConfigureAwait(false);
|
||||
@@ -162,39 +164,49 @@ public class User : ObservableObject
|
||||
// 自动填充 Ltoken
|
||||
if (Ltoken == null)
|
||||
{
|
||||
string? ltoken = await scope.ServiceProvider
|
||||
Response<LtokenWrapper> ltokenResponse = await scope.ServiceProvider
|
||||
.GetRequiredService<PassportClient2>()
|
||||
.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<ActionTicketWrapper> actionTicketResponse = await scope.ServiceProvider
|
||||
.GetRequiredService<AuthClient>()
|
||||
.GetActionTicketByStokenAsync("game_role", Entity)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
UserGameRoles = await scope.ServiceProvider
|
||||
.GetRequiredService<BindingClient>()
|
||||
.GetUserGameRolesByActionTicketAsync(actionTicket!, Entity, token)
|
||||
.ConfigureAwait(false);
|
||||
if (actionTicketResponse.IsOk())
|
||||
{
|
||||
string actionTicket = actionTicketResponse.Data.Ticket;
|
||||
|
||||
Response<ListWrapper<UserGameRole>> userGameRolesResponse = await scope.ServiceProvider
|
||||
.GetRequiredService<BindingClient>()
|
||||
.GetUserGameRolesByActionTicketAsync(actionTicket, Entity, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (userGameRolesResponse.IsOk())
|
||||
{
|
||||
UserGameRoles = userGameRolesResponse.Data.List;
|
||||
}
|
||||
}
|
||||
|
||||
// 自动填充 CookieToken
|
||||
if (CookieToken == null)
|
||||
{
|
||||
string? cookieToken = await scope.ServiceProvider
|
||||
Response<UidCookieToken> cookieTokenResponse = await scope.ServiceProvider
|
||||
.GetRequiredService<PassportClient2>()
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 角色与实体用户
|
||||
/// 实体用户与角色
|
||||
/// 由于许多操作需要同时用到ck与uid
|
||||
/// 抽象此类用于简化这类调用
|
||||
/// </summary>
|
||||
public class UserAndRole
|
||||
public class UserAndUid
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的实体用户与角色
|
||||
/// </summary>
|
||||
/// <param name="user">实体用户</param>
|
||||
/// <param name="role">角色</param>
|
||||
public UserAndRole(EntityUser user, UserGameRole role)
|
||||
public UserAndUid(EntityUser user, PlayerUid role)
|
||||
{
|
||||
User = user;
|
||||
Role = role;
|
||||
Uid = role;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -30,33 +32,33 @@ public class UserAndRole
|
||||
/// <summary>
|
||||
/// 角色
|
||||
/// </summary>
|
||||
public UserGameRole Role { get; private set; }
|
||||
public PlayerUid Uid { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 从用户与选中的角色转换
|
||||
/// </summary>
|
||||
/// <param name="user">角色</param>
|
||||
/// <returns>用户与角色</returns>
|
||||
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!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试转换到用户与角色
|
||||
/// </summary>
|
||||
/// <param name="user">用户</param>
|
||||
/// <param name="userAndRole">用户与角色</param>
|
||||
/// <param name="userAndUid">用户与角色</param>
|
||||
/// <returns>是否转换成功</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -108,14 +108,14 @@ public class DailyNoteEntry : ObservableObject
|
||||
/// <summary>
|
||||
/// 构造一个新的实时便笺
|
||||
/// </summary>
|
||||
/// <param name="userAndRole">用户与角色</param>
|
||||
/// <param name="userAndUid">用户与角色</param>
|
||||
/// <returns>新的实时便笺</returns>
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ValueResult<RefreshResult, Summary?>> GetSummaryAsync(UserAndRole userAndRole, RefreshOption refreshOption, CancellationToken token = default)
|
||||
public async Task<ValueResult<RefreshResult, Summary?>> 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<EnkaAvatarInfo> list = UpdateDbAvatarInfos(userAndRole.Role.GameUid, resp.AvatarInfoList);
|
||||
IList<EnkaAvatarInfo> 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<EnkaAvatarInfo> list = await UpdateDbAvatarInfosByGameRecordCharacterAsync(userAndRole).ConfigureAwait(false);
|
||||
EnkaPlayerInfo info = EnkaPlayerInfo.CreateEmpty(userAndUid.Uid.Value);
|
||||
IList<EnkaAvatarInfo> 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<EnkaAvatarInfo> list = await UpdateDbAvatarInfosByCalculateAvatarDetailAsync(userAndRole).ConfigureAwait(false);
|
||||
EnkaPlayerInfo info = EnkaPlayerInfo.CreateEmpty(userAndUid.Uid.Value);
|
||||
IList<EnkaAvatarInfo> 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<List<EnkaAvatarInfo>> UpdateDbAvatarInfosByGameRecordCharacterAsync(UserAndRole userAndRole)
|
||||
private async Task<List<EnkaAvatarInfo>> UpdateDbAvatarInfosByGameRecordCharacterAsync(UserAndUid userAndUid)
|
||||
{
|
||||
string uid = userAndRole.Role.GameUid;
|
||||
string uid = userAndUid.Uid.Value;
|
||||
List<ModelAvatarInfo> dbInfos = appDbContext.AvatarInfos
|
||||
.Where(i => i.Uid == uid)
|
||||
.ToList();
|
||||
|
||||
GameRecordClient gameRecordClient = Ioc.Default.GetRequiredService<GameRecordClient>();
|
||||
RecordPlayerInfo? playerInfo = await gameRecordClient
|
||||
.GetPlayerInfoAsync(userAndRole)
|
||||
.ConfigureAwait(false);
|
||||
List<RecordCharacter> characters = await gameRecordClient
|
||||
.GetCharactersAsync(userAndRole, playerInfo!)
|
||||
Response<RecordPlayerInfo> playerInfoResponse = await gameRecordClient
|
||||
.GetPlayerInfoAsync(userAndUid)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
GameRecordCharacterAvatarInfoComposer composer = Ioc.Default.GetRequiredService<GameRecordCharacterAvatarInfoComposer>();
|
||||
|
||||
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<Web.Hoyolab.Takumi.GameRecord.Avatar.CharacterWrapper> charactersResponse = await gameRecordClient
|
||||
.GetCharactersAsync(userAndUid, playerInfoResponse.Data)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
ModelAvatarInfo? entity = dbInfos.SingleOrDefault(i => i.Info.AvatarId == character.Id);
|
||||
if (charactersResponse.IsOk())
|
||||
{
|
||||
List<RecordCharacter> 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<GameRecordCharacterAvatarInfoComposer>();
|
||||
|
||||
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<List<EnkaAvatarInfo>> UpdateDbAvatarInfosByCalculateAvatarDetailAsync(UserAndRole userAndRole)
|
||||
private async Task<List<EnkaAvatarInfo>> UpdateDbAvatarInfosByCalculateAvatarDetailAsync(UserAndUid userAndUid)
|
||||
{
|
||||
string uid = userAndRole.Role.GameUid;
|
||||
string uid = userAndUid.Uid.Value;
|
||||
List<ModelAvatarInfo> dbInfos = appDbContext.AvatarInfos
|
||||
.Where(i => i.Uid == uid)
|
||||
.ToList();
|
||||
|
||||
CalculateClient calculateClient = Ioc.Default.GetRequiredService<CalculateClient>();
|
||||
List<CalculateAvatar> avatars = await calculateClient.GetAvatarsAsync(userAndRole.User, userAndRole.Role).ConfigureAwait(false);
|
||||
List<CalculateAvatar> avatars = await calculateClient.GetAvatarsAsync(userAndUid).ConfigureAwait(false);
|
||||
|
||||
CalculateAvatarDetailAvatarInfoComposer composer = Ioc.Default.GetRequiredService<CalculateAvatarDetailAvatarInfoComposer>();
|
||||
|
||||
@@ -226,14 +237,15 @@ internal class AvatarInfoService : IAvatarInfoService
|
||||
continue;
|
||||
}
|
||||
|
||||
AvatarDetail? detailAvatar = await calculateClient.GetAvatarDetailAsync(userAndRole.User, userAndRole.Role, avatar).ConfigureAwait(false);
|
||||
Response<AvatarDetail> 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)
|
||||
{
|
||||
|
||||
@@ -14,9 +14,9 @@ internal interface IAvatarInfoService
|
||||
/// <summary>
|
||||
/// 异步获取总览数据
|
||||
/// </summary>
|
||||
/// <param name="userAndRole">uid</param>
|
||||
/// <param name="userAndUid">uid</param>
|
||||
/// <param name="refreshOption">刷新选项</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>总览数据</returns>
|
||||
Task<ValueResult<RefreshResult, Summary?>> GetSummaryAsync(UserAndRole userAndRole, RefreshOption refreshOption, CancellationToken token = default);
|
||||
Task<ValueResult<RefreshResult, Summary?>> GetSummaryAsync(UserAndUid userAndUid, RefreshOption refreshOption, CancellationToken token = default);
|
||||
}
|
||||
@@ -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<BindingClient>();
|
||||
AuthClient authClient = scope.ServiceProvider.GetRequiredService<AuthClient>();
|
||||
|
||||
string? actionTicket = await authClient
|
||||
Response<ActionTicketWrapper> actionTicketResponse = await authClient
|
||||
.GetActionTicketByStokenAsync("game_role", entry.User)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
List<UserGameRole> roles = await scope.ServiceProvider
|
||||
.GetRequiredService<BindingClient>()
|
||||
.GetUserGameRolesByActionTicketAsync(actionTicket!, entry.User)
|
||||
.ConfigureAwait(false);
|
||||
string? attribution = "请求异常";
|
||||
if (actionTicketResponse.IsOk())
|
||||
{
|
||||
Response<ListWrapper<UserGameRole>> rolesResponse = await scope.ServiceProvider
|
||||
.GetRequiredService<BindingClient>()
|
||||
.GetUserGameRolesByActionTicketAsync(actionTicketResponse.Data.Ticket, entry.User)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
string attribution = roles.SingleOrDefault(r => r.GameUid == entry.Uid)?.ToString() ?? "未知角色";
|
||||
if (rolesResponse.IsOk())
|
||||
{
|
||||
List<UserGameRole> 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);
|
||||
|
||||
@@ -49,9 +49,9 @@ internal class DailyNoteService : IDailyNoteService, IRecipient<UserRemovedMessa
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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<AppDbContext>();
|
||||
@@ -60,7 +60,16 @@ internal class DailyNoteService : IDailyNoteService, IRecipient<UserRemovedMessa
|
||||
if (!appDbContext.DailyNotes.Any(n => n.Uid == roleUid))
|
||||
{
|
||||
DailyNoteEntry newEntry = DailyNoteEntry.Create(role);
|
||||
newEntry.DailyNote = await gameRecordClient.GetDailyNoteAsync(role.User, newEntry.Uid).ConfigureAwait(false);
|
||||
|
||||
Web.Response.Response<WebDailyNote> 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<UserRemovedMessa
|
||||
|
||||
foreach (DailyNoteEntry entry in appDbContext.DailyNotes.Include(n => n.User))
|
||||
{
|
||||
WebDailyNote? dailyNote = await gameRecordClient.GetDailyNoteAsync(entry.User, entry.Uid).ConfigureAwait(false);
|
||||
Web.Response.Response<WebDailyNote> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ public interface IDailyNoteService
|
||||
/// </summary>
|
||||
/// <param name="role">角色</param>
|
||||
/// <returns>任务</returns>
|
||||
Task AddDailyNoteAsync(UserAndRole role);
|
||||
Task AddDailyNoteAsync(UserAndUid role);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取实时便笺列表
|
||||
|
||||
@@ -119,7 +119,7 @@ internal class GachaLogService : IGachaLogService, ISupportAsyncInitialization
|
||||
/// <inheritdoc/>
|
||||
public ObservableCollection<GachaArchive> GetArchiveCollection()
|
||||
{
|
||||
return archiveCollection ??= new(appDbContext.GachaArchives.ToList());
|
||||
return archiveCollection ??= new(appDbContext.GachaArchives.AsNoTracking().ToList());
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -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<GachaLogPage>? response = await gachaInfoClient.GetGachaLogPageAsync(configration, token).ConfigureAwait(false);
|
||||
Response<GachaLogPage> 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<GachaLogItem> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<GameAuthKey> 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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Overview?> GetOverviewAsync()
|
||||
public ValueTask<Overview> GetOverviewAsync()
|
||||
{
|
||||
return FromCacheOrWebAsync(nameof(Overview), homaClient.GetOverviewAsync);
|
||||
}
|
||||
@@ -78,7 +79,8 @@ internal class HutaoService : IHutaoService
|
||||
return FromCacheOrWebAsync(nameof(TeamAppearance), homaClient.GetTeamCombinationsAsync);
|
||||
}
|
||||
|
||||
private async ValueTask<T> FromCacheOrWebAsync<T>(string typeName, Func<CancellationToken, Task<T>> taskFunc)
|
||||
private async ValueTask<T> FromCacheOrWebAsync<T>(string typeName, Func<CancellationToken, Task<Response<T>>> 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<T> webResponse = await taskFunc(default).ConfigureAwait(false);
|
||||
T web = webResponse.IsOk() ? webResponse.Data : new();
|
||||
|
||||
appDbContext.ObjectCache.AddAndSave(new()
|
||||
{
|
||||
Key = key,
|
||||
|
||||
@@ -38,7 +38,7 @@ internal interface IHutaoService
|
||||
/// 异步获取统计数据
|
||||
/// </summary>
|
||||
/// <returns>统计数据</returns>
|
||||
ValueTask<Overview?> GetOverviewAsync();
|
||||
ValueTask<Overview> GetOverviewAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取队伍上场
|
||||
|
||||
@@ -15,14 +15,14 @@ internal interface ISpiralAbyssRecordService
|
||||
/// <summary>
|
||||
/// 异步获取深渊记录集合
|
||||
/// </summary>
|
||||
/// <param name="userAndRole">当前角色</param>
|
||||
/// <param name="userAndUid">当前角色</param>
|
||||
/// <returns>深渊记录集合</returns>
|
||||
Task<ObservableCollection<SpiralAbyssEntry>> GetSpiralAbyssCollectionAsync(UserAndRole userAndRole);
|
||||
Task<ObservableCollection<SpiralAbyssEntry>> GetSpiralAbyssCollectionAsync(UserAndUid userAndUid);
|
||||
|
||||
/// <summary>
|
||||
/// 异步刷新深渊记录
|
||||
/// </summary>
|
||||
/// <param name="userAndRole">当前角色</param>
|
||||
/// <param name="userAndUid">当前角色</param>
|
||||
/// <returns>任务</returns>
|
||||
Task RefreshSpiralAbyssAsync(UserAndRole userAndRole);
|
||||
Task RefreshSpiralAbyssAsync(UserAndUid userAndUid);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ObservableCollection<SpiralAbyssEntry>> GetSpiralAbyssCollectionAsync(UserAndRole userAndRole)
|
||||
public async Task<ObservableCollection<SpiralAbyssEntry>> 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<SpiralAbyssEntry> 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
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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<Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss> 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<Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss> 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);
|
||||
|
||||
@@ -22,7 +22,7 @@ public interface IUserService
|
||||
/// 异步获取角色与用户集合
|
||||
/// </summary>
|
||||
/// <returns>角色与用户集合</returns>
|
||||
Task<ObservableCollection<Model.Binding.User.UserAndRole>> GetRoleCollectionAsync();
|
||||
Task<ObservableCollection<Model.Binding.User.UserAndUid>> GetRoleCollectionAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化用户服务及所有用户
|
||||
|
||||
@@ -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<BindingUser>? userCollection;
|
||||
private ObservableCollection<Model.Binding.User.UserAndRole>? roleCollection;
|
||||
private ObservableCollection<Model.Binding.User.UserAndUid>? roleCollection;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的用户服务
|
||||
@@ -115,23 +116,14 @@ internal class UserService : IUserService
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ObservableCollection<Model.Binding.User.UserAndRole>> GetRoleCollectionAsync()
|
||||
public async Task<ObservableCollection<Model.Binding.User.UserAndUid>> GetRoleCollectionAsync()
|
||||
{
|
||||
await ThreadHelper.SwitchToBackgroundAsync();
|
||||
if (roleCollection == null)
|
||||
{
|
||||
List<Model.Binding.User.UserAndRole> userAndRoles = new();
|
||||
List<Model.Binding.User.UserAndUid> userAndUids = new();
|
||||
ObservableCollection<BindingUser> 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
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
|
||||
@@ -11,25 +11,25 @@ using Snap.Hutao.Web.Bridge;
|
||||
namespace Snap.Hutao.View.Dialog;
|
||||
|
||||
/// <summary>
|
||||
/// ʵʱ<CAB5><CAB1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD>Ի<EFBFBD><EFBFBD><EFBFBD>
|
||||
/// ʵʱ<CAB5><CAB1><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD>Ի<EFBFBD><D4BB><EFBFBD>
|
||||
/// </summary>
|
||||
public sealed partial class DailyNoteVerificationDialog : ContentDialog
|
||||
{
|
||||
private readonly IServiceScope scope;
|
||||
private readonly UserAndRole userAndRole;
|
||||
private readonly UserAndUid userAndUid;
|
||||
|
||||
[SuppressMessage("", "IDE0052")]
|
||||
private DailyNoteJsInterface? dailyNoteJsInterface;
|
||||
|
||||
/// <summary>
|
||||
/// <20><><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>µ<EFBFBD>ʵʱ<CAB5><CAB1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD>Ի<EFBFBD><EFBFBD><EFBFBD>
|
||||
/// <20><><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>µ<EFBFBD>ʵʱ<CAB5><CAB1><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD>Ի<EFBFBD><D4BB><EFBFBD>
|
||||
/// </summary>
|
||||
/// <param name="userAndRole"><3E>û<EFBFBD><C3BB><EFBFBD><EFBFBD><EFBFBD>ɫ</param>
|
||||
public DailyNoteVerificationDialog(UserAndRole userAndRole)
|
||||
/// <param name="userAndUid"><3E>û<EFBFBD><C3BB><EFBFBD><EFBFBD>ɫ</param>
|
||||
public DailyNoteVerificationDialog(UserAndUid userAndUid)
|
||||
{
|
||||
InitializeComponent();
|
||||
XamlRoot = Ioc.Default.GetRequiredService<MainWindow>().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}");
|
||||
}
|
||||
|
||||
|
||||
@@ -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<CoreWebView2Cookie> cookies = await manager.GetCookiesAsync("https://user.mihoyo.com");
|
||||
|
||||
Cookie loginTicketCookie = Cookie.FromCoreWebView2Cookies(cookies);
|
||||
Dictionary<string, string> multiToken = await Ioc.Default.GetRequiredService<AuthClient>().GetMultiTokenByLoginTicketAsync(loginTicketCookie, token).ConfigureAwait(false);
|
||||
Cookie stokenV1 = Cookie.Parse($"stuid={loginTicketCookie["login_uid"]};stoken={multiToken["stoken"]}");
|
||||
LoginResult? loginResult = await Ioc.Default.GetRequiredService<PassportClient2>().LoginByStokenAsync(stokenV1, token).ConfigureAwait(false);
|
||||
Cookie stokenV2 = Cookie.FromLoginResult(loginResult);
|
||||
(UserOptionResult result, string nickname) = await Ioc.Default.GetRequiredService<IUserService>().ProcessInputCookieAsync(stokenV2).ConfigureAwait(false);
|
||||
Response<ListWrapper<NameToken>> multiTokenResponse = await Ioc.Default
|
||||
.GetRequiredService<AuthClient>()
|
||||
.GetMultiTokenByLoginTicketAsync(loginTicketCookie, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!multiTokenResponse.IsOk())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<string, string> multiTokenMap = multiTokenResponse.Data.List.ToDictionary(n => n.Name, n => n.Token);
|
||||
|
||||
Cookie stokenV1 = Cookie.Parse($"stuid={loginTicketCookie["login_uid"]};stoken={multiTokenMap["stoken"]}");
|
||||
Response<LoginResult> loginResultResponse = await Ioc.Default
|
||||
.GetRequiredService<PassportClient2>()
|
||||
.LoginByStokenAsync(stokenV1, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!loginResultResponse.IsOk())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Cookie stokenV2 = Cookie.FromLoginResult(loginResultResponse.Data);
|
||||
(UserOptionResult result, string nickname) = await Ioc.Default
|
||||
.GetRequiredService<IUserService>()
|
||||
.ProcessInputCookieAsync(stokenV2)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
Ioc.Default.GetRequiredService<INavigationService>().GoBack();
|
||||
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
</Style>
|
||||
</Flyout.FlyoutPresenterStyle>
|
||||
<StackPanel>
|
||||
<StackPanel Visibility="{Binding Users, Converter={StaticResource EmptyCollectionToVisibilityConverter}}">
|
||||
<StackPanel Visibility="{Binding Users.Count, Converter={StaticResource Int32ToVisibilityConverter}}">
|
||||
<TextBlock
|
||||
Margin="10,6,0,6"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
@@ -190,7 +190,7 @@
|
||||
Margin="10,6,0,6"
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
Text="请先登录"
|
||||
Visibility="{Binding Users, Converter={StaticResource EmptyCollectionToVisibilityRevertConverter}}"/>
|
||||
Visibility="{Binding Users.Count, Converter={StaticResource Int32ToVisibilityRevertConverter}}"/>
|
||||
<TextBlock
|
||||
Margin="10,6,0,6"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
|
||||
@@ -20,7 +20,6 @@ namespace Snap.Hutao.ViewModel;
|
||||
internal class AnnouncementViewModel : ObservableObject, ISupportCancellation
|
||||
{
|
||||
private readonly IAnnouncementService announcementService;
|
||||
private readonly ILogger<AnnouncementViewModel> logger;
|
||||
|
||||
private AnnouncementWrapper? announcement;
|
||||
|
||||
@@ -32,13 +31,9 @@ internal class AnnouncementViewModel : ObservableObject, ISupportCancellation
|
||||
/// <param name="asyncRelayCommandFactory">异步命令工厂</param>
|
||||
/// <param name="infoBarService">信息条服务</param>
|
||||
/// <param name="logger">日志器</param>
|
||||
public AnnouncementViewModel(
|
||||
IAnnouncementService announcementService,
|
||||
IAsyncRelayCommandFactory asyncRelayCommandFactory,
|
||||
ILogger<AnnouncementViewModel> logger)
|
||||
public AnnouncementViewModel(IAnnouncementService announcementService, IAsyncRelayCommandFactory asyncRelayCommandFactory)
|
||||
{
|
||||
this.announcementService = announcementService;
|
||||
this.logger = logger;
|
||||
|
||||
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
|
||||
OpenAnnouncementUICommand = new RelayCommand<string>(OpenAnnouncementUI);
|
||||
|
||||
@@ -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<CalcConsumption> consumptionResponse = await Ioc.Default
|
||||
.GetRequiredService<CalcClient>()
|
||||
.ComputeAsync(userService.Current.Entity, delta)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (consumption != null)
|
||||
if (consumptionResponse.IsOk())
|
||||
{
|
||||
ICultivationService cultivationService = Ioc.Default.GetRequiredService<ICultivationService>();
|
||||
CalcConsumption consumption = consumptionResponse.Data;
|
||||
|
||||
List<CalcItem> items = CalcItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume);
|
||||
bool avatarSaved = await Ioc.Default
|
||||
.GetRequiredService<ICultivationService>()
|
||||
bool avatarSaved = await cultivationService
|
||||
.SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
bool weaponSaved = await Ioc.Default
|
||||
.GetRequiredService<ICultivationService>()
|
||||
// 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("已成功添加至当前养成计划");
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ internal class DailyNoteViewModel : ObservableObject, ISupportCancellation
|
||||
|
||||
private bool isReminderNotification;
|
||||
private NamedValue<int>? selectedRefreshTime;
|
||||
private ObservableCollection<UserAndRole>? userAndRoles;
|
||||
private ObservableCollection<UserAndUid>? 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<UserAndRole>(TrackRoleAsync);
|
||||
TrackRoleCommand = asyncRelayCommandFactory.Create<UserAndUid>(TrackRoleAsync);
|
||||
RefreshCommand = asyncRelayCommandFactory.Create(RefreshAsync);
|
||||
RemoveDailyNoteCommand = new RelayCommand<DailyNoteEntry>(RemoveDailyNote);
|
||||
ModifyNotificationCommand = asyncRelayCommandFactory.Create<DailyNoteEntry>(ModifyDailyNoteNotificationAsync);
|
||||
@@ -136,7 +136,7 @@ internal class DailyNoteViewModel : ObservableObject, ISupportCancellation
|
||||
/// <summary>
|
||||
/// 用户与角色集合
|
||||
/// </summary>
|
||||
public ObservableCollection<UserAndRole>? UserAndRoles { get => userAndRoles; set => userAndRoles = value; }
|
||||
public ObservableCollection<UserAndUid>? UserAndRoles { get => userAndUids; set => userAndUids = value; }
|
||||
|
||||
/// <summary>
|
||||
/// 实时便笺集合
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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<ContentDialogResult> 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<IInfoBarService>();
|
||||
if (Directory.Exists(cacheFolder))
|
||||
{
|
||||
Directory.Delete(cacheFolder, true);
|
||||
infoBarService.Success("清除完成");
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning($"清除失败,找不到目录:{cacheFolder}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,17 +110,14 @@ internal class SpiralAbyssRecordViewModel : ObservableObject, ISupportCancellati
|
||||
/// <inheritdoc/>
|
||||
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<SpiralAbyssEntry> 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<HomaClient>();
|
||||
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
||||
|
||||
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<string> response = await homaClient.UploadRecordAsync(record).ConfigureAwait(false);
|
||||
|
||||
SimpleRecord record = await homaClient.GetPlayerRecordAsync(user).ConfigureAwait(false);
|
||||
Web.Response.Response<string>? 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("请先选择账号与角色");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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<CalcConsumption> consumptionResponse = await Ioc.Default
|
||||
.GetRequiredService<CalcClient>()
|
||||
.ComputeAsync(userService.Current.Entity, delta)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (consumption != null)
|
||||
if (consumptionResponse.IsOk())
|
||||
{
|
||||
CalcConsumption consumption = consumptionResponse.Data;
|
||||
|
||||
List<CalcItem> items = CalcItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume);
|
||||
bool saved = await Ioc.Default
|
||||
.GetRequiredService<ICultivationService>()
|
||||
|
||||
@@ -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<CalcConsumption> consumptionResponse = await Ioc.Default
|
||||
.GetRequiredService<CalcClient>()
|
||||
.ComputeAsync(userService.Current.Entity, delta)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (consumption != null)
|
||||
if (consumptionResponse.IsOk())
|
||||
{
|
||||
CalcConsumption consumption = consumptionResponse.Data;
|
||||
|
||||
bool saved = await Ioc.Default
|
||||
.GetRequiredService<ICultivationService>()
|
||||
.SaveConsumptionAsync(CultivateType.Weapon, weapon.Id, consumption.WeaponConsume.EmptyIfNull())
|
||||
|
||||
@@ -41,7 +41,6 @@ public class MiHoYoJSInterface
|
||||
private readonly CoreWebView2 webView;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly ILogger<MiHoYoJSInterface> logger;
|
||||
private readonly JsonSerializerOptions options;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的调用桥
|
||||
@@ -54,7 +53,6 @@ public class MiHoYoJSInterface
|
||||
this.serviceProvider = serviceProvider;
|
||||
|
||||
logger = serviceProvider.GetRequiredService<ILogger<MiHoYoJSInterface>>();
|
||||
options = serviceProvider.GetRequiredService<JsonSerializerOptions>();
|
||||
|
||||
webView.WebMessageReceived += OnWebMessageReceived;
|
||||
webView.DOMContentLoaded += OnDOMContentLoaded;
|
||||
@@ -72,7 +70,7 @@ public class MiHoYoJSInterface
|
||||
User user = serviceProvider.GetRequiredService<IUserService>().Current!;
|
||||
return await serviceProvider
|
||||
.GetRequiredService<AuthClient>()
|
||||
.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<JsResult<Dictionary<string, string>>> GetCookieTokenAsync(JsParam<CookieTokenPayload> param)
|
||||
{
|
||||
User user = serviceProvider.GetRequiredService<IUserService>().Current!;
|
||||
string? cookieToken;
|
||||
string cookieToken = string.Empty;
|
||||
if (param.Payload!.ForceRefresh)
|
||||
{
|
||||
cookieToken = await Ioc.Default
|
||||
Response.Response<UidCookieToken> cookieTokenResponse = await Ioc.Default
|
||||
.GetRequiredService<PassportClient2>()
|
||||
.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<AppDbContext>().Users.UpdateAndSave(user.Entity);
|
||||
}
|
||||
|
||||
@@ -33,9 +33,4 @@ public enum CookieType
|
||||
/// 需要 Stoken
|
||||
/// </summary>
|
||||
Stoken = 0B0100,
|
||||
|
||||
/// <summary>
|
||||
/// 需要 Mid
|
||||
/// </summary>
|
||||
Mid = 0B1000,
|
||||
}
|
||||
@@ -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<GachaInfoClient> logger;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的祈愿记录客户端
|
||||
/// </summary>
|
||||
/// <param name="httpClient">http客户端</param>
|
||||
/// <param name="options">Json序列化选项</param>
|
||||
public GachaInfoClient(HttpClient httpClient, JsonSerializerOptions options)
|
||||
/// <param name="logger">日志器</param>
|
||||
public GachaInfoClient(HttpClient httpClient, JsonSerializerOptions options, ILogger<GachaInfoClient> logger)
|
||||
{
|
||||
this.httpClient = httpClient;
|
||||
this.options = options;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -34,13 +36,16 @@ internal class GachaInfoClient
|
||||
/// <param name="config">查询</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>单个祈愿记录页面</returns>
|
||||
public Task<Response<GachaLogPage>?> GetGachaLogPageAsync(GachaLogConfigration config, CancellationToken token = default)
|
||||
public async Task<Response<GachaLogPage>> 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<Response<GachaLogPage>>(url, options, token);
|
||||
Response<GachaLogPage>? response = await httpClient
|
||||
.TryCatchGetFromJsonAsync<Response<GachaLogPage>>(url, options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
return Response.Response.DefaultIfNull(response);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -39,19 +39,19 @@ internal class PassportClient
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>验证信息</returns>
|
||||
[ApiInformation(Cookie = CookieType.Ltoken)]
|
||||
public async Task<UserInformation?> VerifyLtokenAsync(User user, CancellationToken token)
|
||||
public async Task<Response<UserInfoWrapper>> VerifyLtokenAsync(User user, CancellationToken token)
|
||||
{
|
||||
Response<UserInfoWrapper>? response = await httpClient
|
||||
.SetUser(user, CookieType.Ltoken)
|
||||
.TryCatchPostAsJsonAsync<Timestamp, Response<UserInfoWrapper>>(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();
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ internal class PassportClient2
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>登录数据</returns>
|
||||
[ApiInformation(Salt = SaltType.PROD)]
|
||||
public async Task<LoginResult?> LoginByStokenAsync(Cookie stokenV1, CancellationToken token)
|
||||
public async Task<Response<LoginResult>> LoginByStokenAsync(Cookie stokenV1, CancellationToken token)
|
||||
{
|
||||
HttpResponseMessage message = await httpClient
|
||||
.SetHeader("Cookie", stokenV1.ToString())
|
||||
@@ -76,7 +76,7 @@ internal class PassportClient2
|
||||
.ReadFromJsonAsync<Response<LoginResult>>(options, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data;
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -86,7 +86,7 @@ internal class PassportClient2
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>cookie token</returns>
|
||||
[ApiInformation(Cookie = CookieType.Stoken, Salt = SaltType.PROD)]
|
||||
public async Task<string?> GetCookieAccountInfoBySTokenAsync(User user, CancellationToken token)
|
||||
public async Task<Response<UidCookieToken>> GetCookieAccountInfoBySTokenAsync(User user, CancellationToken token)
|
||||
{
|
||||
Response<UidCookieToken>? resp = await httpClient
|
||||
.SetUser(user, CookieType.Stoken)
|
||||
@@ -94,7 +94,7 @@ internal class PassportClient2
|
||||
.TryCatchGetFromJsonAsync<Response<UidCookieToken>>(ApiEndpoints.AccountGetCookieTokenBySToken, options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data?.CookieToken;
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -104,7 +104,7 @@ internal class PassportClient2
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>uid 与 cookie token</returns>
|
||||
[ApiInformation(Cookie = CookieType.Stoken, Salt = SaltType.PROD)]
|
||||
public async Task<string?> GetLtokenBySTokenAsync(User user, CancellationToken token)
|
||||
public async Task<Response<LtokenWrapper>> GetLtokenBySTokenAsync(User user, CancellationToken token)
|
||||
{
|
||||
Response<LtokenWrapper>? resp = await httpClient
|
||||
.SetUser(user, CookieType.Stoken)
|
||||
@@ -112,6 +112,6 @@ internal class PassportClient2
|
||||
.TryCatchGetFromJsonAsync<Response<LtokenWrapper>>(ApiEndpoints.AccountGetLtokenByStoken, options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data?.Ltoken;
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
}
|
||||
@@ -37,13 +37,13 @@ internal class ResourceClient
|
||||
/// <param name="scheme">方案</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>游戏资源</returns>
|
||||
public async Task<GameResource?> GetResourceAsync(LaunchScheme scheme, CancellationToken token = default)
|
||||
public async Task<Response<GameResource>> GetResourceAsync(LaunchScheme scheme, CancellationToken token = default)
|
||||
{
|
||||
string url = ApiEndpoints.SdkStaticLauncherResource(scheme.LauncherId, scheme.Channel, scheme.SubChannel);
|
||||
Response<GameResource>? response = await httpClient
|
||||
.TryCatchGetFromJsonAsync<Response<GameResource>>(url, options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return response?.Data;
|
||||
return Response.Response.DefaultIfNull(response);
|
||||
}
|
||||
}
|
||||
@@ -42,38 +42,15 @@ internal class AuthClient
|
||||
/// <param name="user">用户</param>
|
||||
/// <returns>操作凭证</returns>
|
||||
[ApiInformation(Cookie = CookieType.Stoken, Salt = SaltType.K2)]
|
||||
public async Task<string?> GetActionTicketByStokenAsync(string action, User user)
|
||||
{
|
||||
if (user.Stoken != null)
|
||||
{
|
||||
Response<ActionTicketWrapper>? resp = await httpClient
|
||||
.SetUser(user, CookieType.Stoken)
|
||||
.UseDynamicSecret(DynamicSecretVersion.Gen1, SaltType.K2, true)
|
||||
.TryCatchGetFromJsonAsync<Response<ActionTicketWrapper>>(ApiEndpoints.AuthActionTicket(action, user.Stoken[Cookie.STOKEN], user.Aid!), options, logger)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data?.Ticket;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取操作凭证
|
||||
/// </summary>
|
||||
/// <param name="action">操作</param>
|
||||
/// <param name="user">用户</param>
|
||||
/// <returns>操作凭证</returns>
|
||||
[ApiInformation(Cookie = CookieType.Stoken, Salt = SaltType.K2)]
|
||||
public async Task<Response<ActionTicketWrapper>?> GetActionTicketWrapperByStokenAsync(string action, User user)
|
||||
public async Task<Response<ActionTicketWrapper>> GetActionTicketByStokenAsync(string action, User user)
|
||||
{
|
||||
Response<ActionTicketWrapper>? resp = await httpClient
|
||||
.SetUser(user, CookieType.Stoken)
|
||||
.UseDynamicSecret(DynamicSecretVersion.Gen1, SaltType.K2, true)
|
||||
.TryCatchGetFromJsonAsync<Response<ActionTicketWrapper>>(ApiEndpoints.AuthActionTicket(action, user.Stoken![Cookie.STOKEN], user.Aid!), options, logger)
|
||||
.ConfigureAwait(false);
|
||||
.SetUser(user, CookieType.Stoken)
|
||||
.UseDynamicSecret(DynamicSecretVersion.Gen1, SaltType.K2, true)
|
||||
.TryCatchGetFromJsonAsync<Response<ActionTicketWrapper>>(ApiEndpoints.AuthActionTicket(action, user.Stoken?[Cookie.STOKEN] ?? string.Empty, user.Aid!), options, logger)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp;
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -82,7 +59,7 @@ internal class AuthClient
|
||||
/// <param name="cookie">login cookie</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>包含token的字典</returns>
|
||||
public async Task<Dictionary<string, string>> GetMultiTokenByLoginTicketAsync(Cookie cookie, CancellationToken token)
|
||||
public async Task<Response<ListWrapper<NameToken>>> GetMultiTokenByLoginTicketAsync(Cookie cookie, CancellationToken token)
|
||||
{
|
||||
string loginTicket = cookie["login_ticket"];
|
||||
string loginUid = cookie["login_uid"];
|
||||
@@ -91,14 +68,6 @@ internal class AuthClient
|
||||
.TryCatchGetFromJsonAsync<Response<ListWrapper<NameToken>>>(ApiEndpoints.AuthMultiToken(loginTicket, loginUid), options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (resp?.Data != null)
|
||||
{
|
||||
Dictionary<string, string> 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);
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ internal class BindingClient
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>用户角色信息</returns>
|
||||
[ApiInformation(Cookie = CookieType.Ltoken)]
|
||||
public async Task<List<UserGameRole>> GetUserGameRolesByActionTicketAsync(string actionTicket, User user, CancellationToken token = default)
|
||||
public async Task<Response<ListWrapper<UserGameRole>>> GetUserGameRolesByActionTicketAsync(string actionTicket, User user, CancellationToken token = default)
|
||||
{
|
||||
string url = ApiEndpoints.UserGameRolesByActionTicket(actionTicket);
|
||||
|
||||
@@ -50,24 +50,6 @@ internal class BindingClient
|
||||
.TryCatchGetFromJsonAsync<Response<ListWrapper<UserGameRole>>>(url, options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return EnumerableExtension.EmptyIfNull(resp?.Data?.List);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户角色信息
|
||||
/// </summary>
|
||||
/// <param name="user">用户</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>用户角色信息</returns>
|
||||
[Obsolete("Set-Cookie 冲突")]
|
||||
[ApiInformation(Cookie = CookieType.Ltoken)]
|
||||
public async Task<List<UserGameRole>> GetUserGameRolesByCookieAsync(User user, CancellationToken token = default)
|
||||
{
|
||||
Response<ListWrapper<UserGameRole>>? resp = await httpClient
|
||||
.SetUser(user, CookieType.Ltoken)
|
||||
.TryCatchGetFromJsonAsync<Response<ListWrapper<UserGameRole>>>(ApiEndpoints.UserGameRolesByCookie, options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return EnumerableExtension.EmptyIfNull(resp?.Data?.List);
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ internal class BindingClient2
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>用户角色信息</returns>
|
||||
[ApiInformation(Cookie = CookieType.Stoken, Salt = SaltType.LK2)]
|
||||
public async Task<GameAuthKey?> GenerateAuthenticationKeyAsync(User user, GenAuthKeyData data, CancellationToken token = default)
|
||||
public async Task<Response<GameAuthKey>> GenerateAuthenticationKeyAsync(User user, GenAuthKeyData data, CancellationToken token = default)
|
||||
{
|
||||
Response<GameAuthKey>? resp = await httpClient
|
||||
.SetUser(user, CookieType.Stoken)
|
||||
@@ -71,6 +71,6 @@ internal class BindingClient2
|
||||
.TryCatchPostAsJsonAsync<GenAuthKeyData, Response<GameAuthKey>>(ApiEndpoints.BindingGenAuthKey, data, options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data;
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
/// <param name="delta">差异</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>消耗结果</returns>
|
||||
public async Task<Consumption?> ComputeAsync(User user, AvatarPromotionDelta delta, CancellationToken token = default)
|
||||
public async Task<Response<Consumption>> ComputeAsync(Model.Entity.User user, AvatarPromotionDelta delta, CancellationToken token = default)
|
||||
{
|
||||
Response<Consumption>? resp = await httpClient
|
||||
.SetUser(user, CookieType.CookieToken)
|
||||
.TryCatchPostAsJsonAsync<AvatarPromotionDelta, Response<Consumption>>(ApiEndpoints.CalculateCompute, delta, options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
return resp?.Data;
|
||||
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取角色列表
|
||||
/// </summary>
|
||||
/// <param name="user">用户</param>
|
||||
/// <param name="uid">Uid</param>
|
||||
/// <param name="userAndUid">用户与角色</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色列表</returns>
|
||||
public async Task<List<Avatar>> GetAvatarsAsync(User user, PlayerUid uid, CancellationToken token = default)
|
||||
public async Task<List<Avatar>> 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<Avatar> avatars = new();
|
||||
Response<ListWrapper<Avatar>>? resp;
|
||||
httpClient.SetUser(user, CookieType.CookieToken);
|
||||
httpClient.SetUser(userAndUid.User, CookieType.CookieToken);
|
||||
|
||||
do
|
||||
{
|
||||
@@ -71,43 +70,14 @@ internal class CalculateClient
|
||||
.TryCatchPostAsJsonAsync<SyncAvatarFilter, Response<ListWrapper<Avatar>>>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取角色列表
|
||||
/// </summary>
|
||||
/// <param name="user">用户</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色列表</returns>
|
||||
public async Task<List<Avatar>> GetAvatarsAsync(User user, CancellationToken token = default)
|
||||
{
|
||||
int currentPage = 1;
|
||||
AvatarFilter filter = new();
|
||||
|
||||
List<Avatar> avatars = new();
|
||||
Response<ListWrapper<Avatar>>? resp;
|
||||
httpClient.SetUser(user, CookieType.CookieToken);
|
||||
|
||||
do
|
||||
{
|
||||
filter.Page = currentPage++;
|
||||
resp = await httpClient
|
||||
.TryCatchPostAsJsonAsync<AvatarFilter, Response<ListWrapper<Avatar>>>(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
|
||||
/// <summary>
|
||||
/// 异步获取角色详情
|
||||
/// </summary>
|
||||
/// <param name="user">用户</param>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="userAndUid">用户与角色</param>
|
||||
/// <param name="avatar">角色</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色详情</returns>
|
||||
public async Task<AvatarDetail?> GetAvatarDetailAsync(User user, PlayerUid uid, Avatar avatar, CancellationToken token = default)
|
||||
public async Task<Response<AvatarDetail>> GetAvatarDetailAsync(UserAndUid userAndUid, Avatar avatar, CancellationToken token = default)
|
||||
{
|
||||
Response<AvatarDetail>? resp = await httpClient
|
||||
.SetUser(user, CookieType.CookieToken)
|
||||
.TryCatchGetFromJsonAsync<Response<AvatarDetail>>(ApiEndpoints.CalculateSyncAvatarDetail(avatar.Id, uid), options, logger, token)
|
||||
.SetUser(userAndUid.User, CookieType.CookieToken)
|
||||
.TryCatchGetFromJsonAsync<Response<AvatarDetail>>(ApiEndpoints.CalculateSyncAvatarDetail(avatar.Id, userAndUid.Uid.Value), options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取角色技能列表
|
||||
/// </summary>
|
||||
/// <param name="user">用户</param>
|
||||
/// <param name="avatar">角色</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色技能列表</returns>
|
||||
public async Task<List<Skill>> GetAvatarSkillsAsync(User user, Avatar avatar, CancellationToken token)
|
||||
{
|
||||
Response<ListWrapper<Skill>>? resp = await httpClient
|
||||
.SetUser(user, CookieType.CookieToken)
|
||||
.TryCatchGetFromJsonAsync<Response<ListWrapper<Skill>>>(ApiEndpoints.CalculateAvatarSkillList(avatar), options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return EnumerableExtension.EmptyIfNull(resp?.Data?.List);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取角色列表
|
||||
/// </summary>
|
||||
/// <param name="user">用户</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色列表</returns>
|
||||
public async Task<List<Weapon>> GetWeaponsAsync(User user, CancellationToken token)
|
||||
{
|
||||
int currentPage = 1;
|
||||
WeaponFilter filter = new();
|
||||
|
||||
List<Weapon> weapons = new();
|
||||
Response<ListWrapper<Weapon>>? resp;
|
||||
httpClient.SetUser(user, CookieType.CookieToken);
|
||||
|
||||
do
|
||||
{
|
||||
filter.Page = currentPage++;
|
||||
resp = await httpClient
|
||||
.TryCatchPostAsJsonAsync<WeaponFilter, Response<ListWrapper<Weapon>>>(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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -193,14 +111,14 @@ internal class CalculateClient
|
||||
/// <param name="shareCode">摹本码</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>家具列表</returns>
|
||||
public async Task<FurnitureListWrapper?> FurnitureBlueprintAsync(User user, string shareCode, CancellationToken token)
|
||||
public async Task<Response<FurnitureListWrapper>> FurnitureBlueprintAsync(Model.Entity.User user, string shareCode, CancellationToken token)
|
||||
{
|
||||
Response<FurnitureListWrapper>? resp = await httpClient
|
||||
.SetUser(user, CookieType.CookieToken)
|
||||
.TryCatchGetFromJsonAsync<Response<FurnitureListWrapper>>(ApiEndpoints.CalculateFurnitureBlueprint(shareCode), options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data;
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -210,7 +128,7 @@ internal class CalculateClient
|
||||
/// <param name="items">物品</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>消耗</returns>
|
||||
public async Task<List<Item>> FurnitureComputeAsync(User user, List<Item> items, CancellationToken token)
|
||||
public async Task<Response<ListWrapper<Item>>> FurnitureComputeAsync(Model.Entity.User user, List<Item> items, CancellationToken token)
|
||||
{
|
||||
ListWrapper<IdCount> data = new() { List = items.Select(i => new IdCount { Id = i.Id, Count = i.Num }).ToList() };
|
||||
|
||||
@@ -219,25 +137,7 @@ internal class CalculateClient
|
||||
.TryCatchPostAsJsonAsync<ListWrapper<IdCount>, Response<ListWrapper<Item>>>(ApiEndpoints.CalculateFurnitureCompute, data, options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return EnumerableExtension.EmptyIfNull(resp?.Data?.List);
|
||||
}
|
||||
|
||||
private class AvatarFilter
|
||||
{
|
||||
[JsonPropertyName("element_attr_ids")]
|
||||
public List<int>? ElementAttrIds { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("weapon_cat_ids")]
|
||||
public List<int>? 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<int> WeaponLevels { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("weapon_cat_ids")]
|
||||
public List<int> WeaponCatIds { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("page")]
|
||||
public int Page { get; set; }
|
||||
|
||||
[JsonPropertyName("size")]
|
||||
public int Size { get; set; } = 20;
|
||||
}
|
||||
|
||||
private class IdCount
|
||||
{
|
||||
[JsonPropertyName("cnt")]
|
||||
|
||||
@@ -41,7 +41,7 @@ public class CardClient
|
||||
/// <param name="user">用户</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>注册结果</returns>
|
||||
public async Task<VerificationRegistration?> CreateVerificationAsync(User user, CancellationToken token)
|
||||
public async Task<Response<VerificationRegistration>> CreateVerificationAsync(User user, CancellationToken token)
|
||||
{
|
||||
Response<VerificationRegistration>? resp = await httpClient
|
||||
.SetUser(user, CookieType.Ltoken)
|
||||
@@ -49,7 +49,7 @@ public class CardClient
|
||||
.TryCatchGetFromJsonAsync<Response<VerificationRegistration>>(ApiEndpoints.CardCreateVerification, options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data;
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -59,7 +59,7 @@ public class CardClient
|
||||
/// <param name="validate">验证</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>验证结果</returns>
|
||||
public async Task<VerificationResult?> VerifyVerificationAsync(string challenge, string validate, CancellationToken token)
|
||||
public async Task<Response<VerificationResult>> VerifyVerificationAsync(string challenge, string validate, CancellationToken token)
|
||||
{
|
||||
VerificationData data = new(challenge, validate);
|
||||
|
||||
@@ -67,7 +67,7 @@ public class CardClient
|
||||
.TryCatchPostAsJsonAsync<VerificationData, Response<VerificationResult>>(ApiEndpoints.CardVerifyVerification, data, options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data;
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -77,7 +77,7 @@ public class CardClient
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>桌面小组件数据</returns>
|
||||
[ApiInformation(Cookie = CookieType.Stoken, Salt = SaltType.X6)]
|
||||
public async Task<WidgetData?> GetWidgetDataAsync(User user, CancellationToken token)
|
||||
public async Task<Response<DataWrapper<WidgetData>>> GetWidgetDataAsync(User user, CancellationToken token)
|
||||
{
|
||||
Response<DataWrapper<WidgetData>>? resp = await httpClient
|
||||
.SetUser(user, CookieType.Stoken)
|
||||
@@ -85,7 +85,7 @@ public class CardClient
|
||||
.TryCatchGetFromJsonAsync<Response<DataWrapper<WidgetData>>>(ApiEndpoints.CardWidgetData, options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data?.Data;
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
private class VerificationData
|
||||
|
||||
@@ -34,18 +34,25 @@ internal class CardVerifier
|
||||
/// <returns>流水号</returns>
|
||||
public async Task<string?> TryGetXrpcChallengeAsync(User user, CancellationToken token)
|
||||
{
|
||||
if (await cardClient.CreateVerificationAsync(user, token).ConfigureAwait(false) is VerificationRegistration registration)
|
||||
Response.Response<VerificationRegistration> 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<GeetestData>? 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<VerificationResult> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,17 +41,16 @@ internal class GameRecordClient
|
||||
/// <summary>
|
||||
/// 异步获取实时便笺
|
||||
/// </summary>
|
||||
/// <param name="user">用户</param>
|
||||
/// <param name="uid">查询uid</param>
|
||||
/// <param name="userAndUid">用户与角色</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>实时便笺</returns>
|
||||
[ApiInformation(Cookie = CookieType.CookieToken | CookieType.Ltoken | CookieType.Mid, Salt = SaltType.X4)]
|
||||
public async Task<DailyNote.DailyNote?> GetDailyNoteAsync(Model.Entity.User user, PlayerUid uid, CancellationToken token = default)
|
||||
[ApiInformation(Cookie = CookieType.Cookie, Salt = SaltType.X4)]
|
||||
public async Task<Response<DailyNote.DailyNote>> GetDailyNoteAsync(UserAndUid userAndUid, CancellationToken token = default)
|
||||
{
|
||||
Response<DailyNote.DailyNote>? resp = await httpClient
|
||||
.SetUser(user, CookieType.CookieToken | CookieType.Ltoken)
|
||||
.SetUser(userAndUid.User, CookieType.Cookie)
|
||||
.UseDynamicSecret(DynamicSecretVersion.Gen2, SaltType.X4, false)
|
||||
.TryCatchGetFromJsonAsync<Response<DailyNote.DailyNote>>(ApiEndpoints.GameRecordDailyNote(uid), options, logger, token)
|
||||
.TryCatchGetFromJsonAsync<Response<DailyNote.DailyNote>>(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<CardVerifier>();
|
||||
|
||||
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<IInfoBarService>().Success("无感验证成功");
|
||||
|
||||
resp = await httpClient
|
||||
.SetUser(user, CookieType.CookieToken | CookieType.Ltoken)
|
||||
.SetUser(userAndUid.User, CookieType.Cookie)
|
||||
.SetXrpcChallenge(challenge)
|
||||
.UseDynamicSecret(DynamicSecretVersion.Gen2, SaltType.X4, false)
|
||||
.TryCatchGetFromJsonAsync<Response<DailyNote.DailyNote>>(ApiEndpoints.GameRecordDailyNote(uid), options, logger, token)
|
||||
.TryCatchGetFromJsonAsync<Response<DailyNote.DailyNote>>(ApiEndpoints.GameRecordDailyNote(userAndUid.Uid.Value), options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Ioc.Default.GetRequiredService<IInfoBarService>().Warning("无感验证失败,请前往「米游社-我的角色-实时便笺」页面查看");
|
||||
}
|
||||
}
|
||||
|
||||
return resp?.Data;
|
||||
return Response.Response.DefaultIfNull(resp, "请求失败,请前往「米游社-我的角色-实时便笺」页面查看");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取玩家基础信息
|
||||
/// </summary>
|
||||
/// <param name="userAndRole">用户</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>玩家的基础信息</returns>
|
||||
public Task<PlayerInfo?> GetPlayerInfoAsync(UserAndRole userAndRole, CancellationToken token = default)
|
||||
{
|
||||
return GetPlayerInfoAsync(userAndRole.User, userAndRole.Role, token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取玩家基础信息
|
||||
/// </summary>
|
||||
/// <param name="user">用户</param>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="userAndUid">用户与角色</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>玩家的基础信息</returns>
|
||||
[ApiInformation(Cookie = CookieType.Ltoken, Salt = SaltType.X4)]
|
||||
public async Task<PlayerInfo?> GetPlayerInfoAsync(Model.Entity.User user, PlayerUid uid, CancellationToken token = default)
|
||||
public async Task<Response<PlayerInfo>> GetPlayerInfoAsync(UserAndUid userAndUid, CancellationToken token = default)
|
||||
{
|
||||
Response<PlayerInfo>? resp = await httpClient
|
||||
.SetUser(user, CookieType.Ltoken)
|
||||
.SetUser(userAndUid.User, CookieType.Ltoken)
|
||||
.UseDynamicSecret(DynamicSecretVersion.Gen2, SaltType.X4, false)
|
||||
.TryCatchGetFromJsonAsync<Response<PlayerInfo>>(ApiEndpoints.GameRecordIndex(uid), options, logger, token)
|
||||
.TryCatchGetFromJsonAsync<Response<PlayerInfo>>(ApiEndpoints.GameRecordIndex(userAndUid.Uid), options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data;
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取玩家深渊信息
|
||||
/// </summary>
|
||||
/// <param name="userAndRole">用户</param>
|
||||
/// <param name="schedule">1:当期,2:上期</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>深渊信息</returns>
|
||||
public Task<SpiralAbyss.SpiralAbyss?> GetSpiralAbyssAsync(UserAndRole userAndRole, SpiralAbyssSchedule schedule, CancellationToken token = default)
|
||||
{
|
||||
return GetSpiralAbyssAsync(userAndRole.User, userAndRole.Role, schedule, token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取玩家深渊信息
|
||||
/// </summary>
|
||||
/// <param name="user">用户</param>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="userAndUid">用户</param>
|
||||
/// <param name="schedule">1:当期,2:上期</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>深渊信息</returns>
|
||||
[ApiInformation(Cookie = CookieType.Ltoken, Salt = SaltType.X4)]
|
||||
public async Task<SpiralAbyss.SpiralAbyss?> GetSpiralAbyssAsync(Model.Entity.User user, PlayerUid uid, SpiralAbyssSchedule schedule, CancellationToken token = default)
|
||||
public async Task<Response<SpiralAbyss.SpiralAbyss>> GetSpiralAbyssAsync(UserAndUid userAndUid, SpiralAbyssSchedule schedule, CancellationToken token = default)
|
||||
{
|
||||
Response<SpiralAbyss.SpiralAbyss>? resp = await httpClient
|
||||
.SetUser(user, CookieType.Ltoken)
|
||||
.SetUser(userAndUid.User, CookieType.Ltoken)
|
||||
.UseDynamicSecret(DynamicSecretVersion.Gen2, SaltType.X4, false)
|
||||
.TryCatchGetFromJsonAsync<Response<SpiralAbyss.SpiralAbyss>>(ApiEndpoints.GameRecordSpiralAbyss(schedule, uid), options, logger, token)
|
||||
.TryCatchGetFromJsonAsync<Response<SpiralAbyss.SpiralAbyss>>(ApiEndpoints.GameRecordSpiralAbyss(schedule, userAndUid.Uid), options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data;
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取角色基本信息
|
||||
/// </summary>
|
||||
/// <param name="user">用户</param>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="userAndUid">用户与角色</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色基本信息</returns>
|
||||
[ApiInformation(Cookie = CookieType.Ltoken, Salt = SaltType.X4)]
|
||||
public async Task<BasicRoleInfo?> GetRoleBasicInfoAsync(Model.Entity.User user, PlayerUid uid, CancellationToken token = default)
|
||||
public async Task<Response<BasicRoleInfo>> GetRoleBasicInfoAsync(UserAndUid userAndUid, CancellationToken token = default)
|
||||
{
|
||||
Response<BasicRoleInfo>? resp = await httpClient
|
||||
.SetUser(user, CookieType.Ltoken)
|
||||
.SetUser(userAndUid.User, CookieType.Ltoken)
|
||||
.UseDynamicSecret(DynamicSecretVersion.Gen2, SaltType.X4, false)
|
||||
.TryCatchGetFromJsonAsync<Response<BasicRoleInfo>>(ApiEndpoints.GameRecordRoleBasicInfo(uid), options, logger, token)
|
||||
.TryCatchGetFromJsonAsync<Response<BasicRoleInfo>>(ApiEndpoints.GameRecordRoleBasicInfo(userAndUid.Uid), options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data;
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取玩家角色详细信息
|
||||
/// </summary>
|
||||
/// <param name="userAndRole">用户与角色</param>
|
||||
/// <param name="playerInfo">玩家的基础信息</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色列表</returns>
|
||||
public Task<List<Character>> GetCharactersAsync(UserAndRole userAndRole, PlayerInfo playerInfo, CancellationToken token = default)
|
||||
{
|
||||
return GetCharactersAsync(userAndRole.User, userAndRole.Role, playerInfo, token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取玩家角色详细信息
|
||||
/// </summary>
|
||||
/// <param name="user">用户</param>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="userAndUid">用户与角色</param>
|
||||
/// <param name="playerInfo">玩家的基础信息</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色列表</returns>
|
||||
[ApiInformation(Cookie = CookieType.Ltoken, Salt = SaltType.X4)]
|
||||
public async Task<List<Character>> GetCharactersAsync(Model.Entity.User user, PlayerUid uid, PlayerInfo playerInfo, CancellationToken token = default)
|
||||
public async Task<Response<CharacterWrapper>> 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<CharacterWrapper>? resp = await httpClient
|
||||
.SetUser(user, CookieType.Ltoken)
|
||||
.SetUser(userAndUid.User, CookieType.Ltoken)
|
||||
.UseDynamicSecret(DynamicSecretVersion.Gen2, SaltType.X4, false)
|
||||
.TryCatchPostAsJsonAsync<CharacterData, Response<CharacterWrapper>>(ApiEndpoints.GameRecordCharacter, data, options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return EnumerableExtension.EmptyIfNull(resp?.Data?.Avatars);
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
private class CharacterData
|
||||
|
||||
@@ -49,13 +49,13 @@ internal class HomaClient
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>当前是否上传了数据</returns>
|
||||
public async Task<bool> CheckRecordUploadedAsync(PlayerUid uid, CancellationToken token = default)
|
||||
public async Task<Response<bool>> CheckRecordUploadedAsync(PlayerUid uid, CancellationToken token = default)
|
||||
{
|
||||
Response<bool>? resp = await httpClient
|
||||
.GetFromJsonAsync<Response<bool>>(HutaoEndpoints.RecordCheck(uid.Value), token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data == true;
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -65,13 +65,13 @@ internal class HomaClient
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>排行信息</returns>
|
||||
public async Task<RankInfo?> GetRankAsync(PlayerUid uid, CancellationToken token = default)
|
||||
public async Task<Response<RankInfo>> GetRankAsync(PlayerUid uid, CancellationToken token = default)
|
||||
{
|
||||
Response<RankInfo>? resp = await httpClient
|
||||
.GetFromJsonAsync<Response<RankInfo>>(HutaoEndpoints.RecordRank(uid.Value), token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data;
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -80,13 +80,13 @@ internal class HomaClient
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>总览信息</returns>
|
||||
public async Task<Overview?> GetOverviewAsync(CancellationToken token = default)
|
||||
public async Task<Response<Overview>> GetOverviewAsync(CancellationToken token = default)
|
||||
{
|
||||
Response<Overview>? resp = await httpClient
|
||||
.GetFromJsonAsync<Response<Overview>>(HutaoEndpoints.StatisticsOverview, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data;
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -95,13 +95,13 @@ internal class HomaClient
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色出场率</returns>
|
||||
public async Task<List<AvatarAppearanceRank>> GetAvatarAttendanceRatesAsync(CancellationToken token = default)
|
||||
public async Task<Response<List<AvatarAppearanceRank>>> GetAvatarAttendanceRatesAsync(CancellationToken token = default)
|
||||
{
|
||||
Response<List<AvatarAppearanceRank>>? resp = await httpClient
|
||||
.TryCatchGetFromJsonAsync<Response<List<AvatarAppearanceRank>>>(HutaoEndpoints.StatisticsAvatarAttendanceRate, options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return EnumerableExtension.EmptyIfNull(resp?.Data);
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -110,13 +110,13 @@ internal class HomaClient
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色出场率</returns>
|
||||
public async Task<List<AvatarUsageRank>> GetAvatarUtilizationRatesAsync(CancellationToken token = default)
|
||||
public async Task<Response<List<AvatarUsageRank>>> GetAvatarUtilizationRatesAsync(CancellationToken token = default)
|
||||
{
|
||||
Response<List<AvatarUsageRank>>? resp = await httpClient
|
||||
.TryCatchGetFromJsonAsync<Response<List<AvatarUsageRank>>>(HutaoEndpoints.StatisticsAvatarUtilizationRate, options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return EnumerableExtension.EmptyIfNull(resp?.Data);
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -125,13 +125,13 @@ internal class HomaClient
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色/武器/圣遗物搭配</returns>
|
||||
public async Task<List<AvatarCollocation>> GetAvatarCollocationsAsync(CancellationToken token = default)
|
||||
public async Task<Response<List<AvatarCollocation>>> GetAvatarCollocationsAsync(CancellationToken token = default)
|
||||
{
|
||||
Response<List<AvatarCollocation>>? resp = await httpClient
|
||||
.TryCatchGetFromJsonAsync<Response<List<AvatarCollocation>>>(HutaoEndpoints.StatisticsAvatarAvatarCollocation, options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return EnumerableExtension.EmptyIfNull(resp?.Data);
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -140,13 +140,13 @@ internal class HomaClient
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色/武器/圣遗物搭配</returns>
|
||||
public async Task<List<WeaponCollocation>> GetWeaponCollocationsAsync(CancellationToken token = default)
|
||||
public async Task<Response<List<WeaponCollocation>>> GetWeaponCollocationsAsync(CancellationToken token = default)
|
||||
{
|
||||
Response<List<WeaponCollocation>>? resp = await httpClient
|
||||
.TryCatchGetFromJsonAsync<Response<List<WeaponCollocation>>>(HutaoEndpoints.StatisticsWeaponWeaponCollocation, options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return EnumerableExtension.EmptyIfNull(resp?.Data);
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -155,13 +155,13 @@ internal class HomaClient
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色图片列表</returns>
|
||||
public async Task<List<AvatarConstellationInfo>> GetAvatarHoldingRatesAsync(CancellationToken token = default)
|
||||
public async Task<Response<List<AvatarConstellationInfo>>> GetAvatarHoldingRatesAsync(CancellationToken token = default)
|
||||
{
|
||||
Response<List<AvatarConstellationInfo>>? resp = await httpClient
|
||||
.TryCatchGetFromJsonAsync<Response<List<AvatarConstellationInfo>>>(HutaoEndpoints.StatisticsAvatarHoldingRate, options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return EnumerableExtension.EmptyIfNull(resp?.Data);
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -170,40 +170,51 @@ internal class HomaClient
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>队伍出场列表</returns>
|
||||
public async Task<List<TeamAppearance>> GetTeamCombinationsAsync(CancellationToken token = default)
|
||||
public async Task<Response<List<TeamAppearance>>> GetTeamCombinationsAsync(CancellationToken token = default)
|
||||
{
|
||||
Response<List<TeamAppearance>>? resp = await httpClient
|
||||
.TryCatchGetFromJsonAsync<Response<List<TeamAppearance>>>(HutaoEndpoints.StatisticsTeamCombination, options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return EnumerableExtension.EmptyIfNull(resp?.Data);
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取角色的深渊记录
|
||||
/// </summary>
|
||||
/// <param name="user">用户</param>
|
||||
/// <param name="userAndUid">用户与角色</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>玩家记录</returns>
|
||||
public async Task<SimpleRecord> GetPlayerRecordAsync(User user, CancellationToken token = default)
|
||||
public async Task<SimpleRecord?> 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<Character> characters = await gameRecordClient
|
||||
.GetCharactersAsync(user.Entity, user.SelectedUserGameRole, playerInfo, token)
|
||||
Response<PlayerInfo> 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<CharacterWrapper> charactersResponse = await gameRecordClient
|
||||
.GetCharactersAsync(userAndUid, playerInfoResponse.Data, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!charactersResponse.IsOk())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Response<SpiralAbyss> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -213,8 +224,12 @@ internal class HomaClient
|
||||
/// <param name="playerRecord">玩家记录</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>响应</returns>
|
||||
public Task<Response<string>?> UploadRecordAsync(SimpleRecord playerRecord, CancellationToken token = default)
|
||||
public async Task<Response<string>> UploadRecordAsync(SimpleRecord playerRecord, CancellationToken token = default)
|
||||
{
|
||||
return httpClient.TryCatchPostAsJsonAsync<SimpleRecord, Response<string>>(HutaoEndpoints.RecordUpload, playerRecord, options, logger, token);
|
||||
Response<string>? resp = await httpClient
|
||||
.TryCatchPostAsJsonAsync<SimpleRecord, Response<string>>(HutaoEndpoints.RecordUpload, playerRecord, options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
}
|
||||
@@ -22,4 +22,4 @@ public class HutaoLog
|
||||
/// 错误信息
|
||||
/// </summary>
|
||||
public string Info { get; set; } = default!;
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,20 @@ public class Response : ISupportValidation
|
||||
public static Response<TData> DefaultIfNull<TData>(Response<TData>? response)
|
||||
{
|
||||
// 0x26F19335 is a magic number that hashed from "Snap.Hutao"
|
||||
return response ?? new(0x26F19335, "请求异常", default);
|
||||
return response ?? new(0x26F19335, $"[{typeof(TData).Name}] 请求异常", default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回本体或带有消息提示的默认值
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">类型</typeparam>
|
||||
/// <param name="response">本体</param>
|
||||
/// <param name="message">消息</param>
|
||||
/// <returns>本体或默认值,当本体为 null 时 返回默认值</returns>
|
||||
public static Response<TData> DefaultIfNull<TData>(Response<TData>? response, string message)
|
||||
{
|
||||
// 0x26F19335 is a magic number that hashed from "Snap.Hutao"
|
||||
return response ?? new(0x26F19335, message, default);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
Reference in New Issue
Block a user