fix game records for #207

This commit is contained in:
DismissedLight
2022-11-13 20:36:12 +08:00
parent 971f319b76
commit 283df388bb
23 changed files with 104 additions and 101 deletions

View File

@@ -105,6 +105,8 @@ dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_diagnostic.SA1629.severity = silent
dotnet_diagnostic.SA1642.severity = silent
dotnet_diagnostic.IDE0060.severity = none
# SA1208: System using directives should be placed before other using directives
dotnet_diagnostic.SA1208.severity = none
@@ -160,6 +162,7 @@ dotnet_diagnostic.CA1805.severity = suggestion
# VSTHRD111: Use ConfigureAwait(bool)
dotnet_diagnostic.VSTHRD111.severity = suggestion
csharp_style_prefer_top_level_statements = true:silent
csharp_style_prefer_readonly_struct = true:suggestion
[*.vb]
#### 命名样式 ####

View File

@@ -26,7 +26,7 @@ public sealed class CancellationTokenTaskCompletionSource : IDisposable
return;
}
TaskCompletionSource tcs = new TaskCompletionSource();
TaskCompletionSource tcs = new();
registration = cancellationToken.Register(() => tcs.TrySetResult(), useSynchronizationContext: false);
Task = tcs.Task;
}

View File

@@ -14,7 +14,6 @@ internal class ThreadAccessAttribute : Attribute
/// 指示方法的进入线程访问状态
/// </summary>
/// <param name="enter">进入状态</param>
[SuppressMessage("", "IDE0060")]
public ThreadAccessAttribute(ThreadAccessState enter)
{
}

View File

@@ -133,7 +133,7 @@ public class User : ObservableObject
}
UserInfo = await userClient
.GetUserFullInfoAsync(this, token)
.GetUserFullInfoAsync(Entity, token)
.ConfigureAwait(false);
UserGameRoles = await userGameRoleClient

View File

@@ -62,7 +62,7 @@ internal class DailyNoteService : IDailyNoteService, IRecipient<UserRemovedMessa
if (!appDbContext.DailyNotes.Any(n => n.Uid == roleUid))
{
DailyNoteEntry newEntry = DailyNoteEntry.Create(role);
newEntry.DailyNote = await gameRecordClient.GetDialyNoteAsync(role.User, newEntry.Uid).ConfigureAwait(false);
newEntry.DailyNote = await gameRecordClient.GetDailyNoteAsync(role.User, newEntry.Uid).ConfigureAwait(false);
appDbContext.DailyNotes.AddAndSave(newEntry);
newEntry.UserGameRole = userService.GetUserGameRoleByUid(roleUid);
@@ -102,7 +102,7 @@ internal class DailyNoteService : IDailyNoteService, IRecipient<UserRemovedMessa
foreach (DailyNoteEntry entry in appDbContext.DailyNotes.Include(n => n.User))
{
WebDailyNote? dailyNote = await gameRecordClient.GetDialyNoteAsync(entry.User, entry.Uid).ConfigureAwait(false);
WebDailyNote? dailyNote = await gameRecordClient.GetDailyNoteAsync(entry.User, entry.Uid).ConfigureAwait(false);
// database
entry.DailyNote = dailyNote;

View File

@@ -42,7 +42,7 @@ internal class GachaLogUrlStokenProvider : IGachaLogUrlProvider
PlayerUid uid = (PlayerUid)user.SelectedUserGameRole;
GenAuthKeyData data = GenAuthKeyData.CreateForWebViewGacha(uid);
GameAuthKey? authkey = await bindingClient2.GenerateAuthenticationKeyAsync(user, data).ConfigureAwait(false);
GameAuthKey? authkey = await bindingClient2.GenerateAuthenticationKeyAsync(user.Entity, data).ConfigureAwait(false);
if (authkey != null)
{
return new(true, GachaLogConfigration.AsQuery(data, authkey));

View File

@@ -7,6 +7,7 @@ namespace Snap.Hutao.Web.Hoyolab.Annotation;
/// <summary>
/// API 信息
/// 指示此API 已经经过验证,且明确其调用
/// </summary>
/// <typeparam name="TReturnType">API 的返回类型</typeparam>
[AttributeUsage(AttributeTargets.Method)]

View File

@@ -40,6 +40,16 @@ internal static class ApiEndpoints
#region GameRecord
/// <summary>
/// 角色基本信息
/// </summary>
/// <param name="uid">uid</param>
/// <returns>角色基本信息字符串</returns>
public static string GameRecordRoleBasicInfo(PlayerUid uid)
{
return $"{ApiTakumiRecordApi}/roleBasicInfo?role_id={uid.Value}&server={uid.Region}";
}
/// <summary>
/// 角色信息
/// </summary>

View File

@@ -2,6 +2,8 @@
// Licensed under the MIT license.
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Web.Hoyolab.Annotation;
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
using Snap.Hutao.Web.Response;
using System.Net.Http;
@@ -33,13 +35,34 @@ internal class UserClient
/// <summary>
/// 获取当前用户详细信息
/// </summary>
/// <param name="uid">用户id</param>
/// <param name="user">用户</param>
/// <param name="token">取消令牌</param>
/// <returns>详细信息</returns>
public async Task<UserInfo?> GetUserFullInfoAsync(Model.Binding.User.User user, CancellationToken token = default)
[ApiInformation(Cookie = CookieType.Stoken, Salt = SaltType.K2)]
public async Task<UserInfo?> GetUserFullInfoAsync(string uid, Model.Entity.User user, CancellationToken token = default)
{
Response<UserFullInfoWrapper>? resp = await httpClient
.SetUser(user)
.SetUser(user, CookieType.Stoken)
.SetReferer(ApiEndpoints.BbsReferer)
.UsingDynamicSecret1(SaltType.K2)
.TryCatchGetFromJsonAsync<Response<UserFullInfoWrapper>>(ApiEndpoints.UserFullInfoQuery(uid), options, logger, token)
.ConfigureAwait(false);
return resp?.Data?.UserInfo;
}
/// <summary>
/// 获取当前用户详细信息
/// </summary>
/// <param name="user">用户</param>
/// <param name="token">取消令牌</param>
/// <returns>详细信息</returns>
[ApiInformation(Cookie = CookieType.Ltoken)]
public async Task<UserInfo?> GetUserFullInfoAsync(Model.Entity.User user, CancellationToken token = default)
{
Response<UserFullInfoWrapper>? resp = await httpClient
.SetUser(user, CookieType.Ltoken)
.SetReferer(ApiEndpoints.BbsReferer)
.TryCatchGetFromJsonAsync<Response<UserFullInfoWrapper>>(ApiEndpoints.UserFullInfo, options, logger, token)
.ConfigureAwait(false);

View File

@@ -204,6 +204,7 @@ public partial class Cookie
results = type switch
{
CookieType.None => Enumerable.Empty<KeyValuePair<string, string>>(),
CookieType.Ltoken => inner.Where(kvp => kvp.Key is E_HK4E_TOKEN or LTUID or LTOKEN or ACCOUNT_ID or COOKIE_TOKEN),
CookieType.Stoken => inner.Where(kvp => kvp.Key is STUID or STOKEN or MID),
CookieType.All => inner,
_ => throw Must.NeverHappen(type.ToString()),

View File

@@ -14,7 +14,7 @@ public enum CookieType
None,
/// <summary>
/// 需要 Ltoken e_hk4e_token
/// 需要 Ltoken &amp; CookieToken &amp; e_hk4e_token
/// </summary>
Ltoken,

View File

@@ -15,7 +15,6 @@ namespace Snap.Hutao.Web.Hoyolab.DynamicSecret.Http;
internal class DynamicSecretHttpClient : IDynamicSecretHttpClient
{
private readonly HttpClient httpClient;
private readonly SaltType saltType;
private readonly JsonSerializerOptions options;
private readonly string url;
@@ -29,11 +28,10 @@ internal class DynamicSecretHttpClient : IDynamicSecretHttpClient
public DynamicSecretHttpClient(HttpClient httpClient, SaltType saltType, JsonSerializerOptions options, string url)
{
this.httpClient = httpClient;
this.saltType = saltType;
this.options = options;
this.url = url;
httpClient.DefaultRequestHeaders.Set("DS", DynamicSecretProvider2.Create(options, url, null));
httpClient.DefaultRequestHeaders.Set("DS", DynamicSecretProvider2.Create(saltType, options, url, null));
}
/// <inheritdoc/>
@@ -81,7 +79,6 @@ internal class DynamicSecretHttpClient<TValue> : IDynamicSecretHttpClient<TValue
where TValue : class
{
private readonly HttpClient httpClient;
private readonly SaltType saltType;
private readonly JsonSerializerOptions options;
private readonly string url;
private readonly TValue data;
@@ -97,12 +94,11 @@ internal class DynamicSecretHttpClient<TValue> : IDynamicSecretHttpClient<TValue
public DynamicSecretHttpClient(HttpClient httpClient, SaltType saltType, JsonSerializerOptions options, string url, TValue data)
{
this.httpClient = httpClient;
this.saltType = saltType;
this.options = options;
this.url = url;
this.data = data;
httpClient.DefaultRequestHeaders.Set("DS", DynamicSecretProvider2.Create(options, url, data));
httpClient.DefaultRequestHeaders.Set("DS", DynamicSecretProvider2.Create(saltType, options, url, data));
}
/// <inheritdoc/>

View File

@@ -81,7 +81,7 @@ internal static class HttpClientExtensions
/// <param name="user">实体用户</param>
/// <param name="cookie">Cookie类型</param>
/// <returns>客户端</returns>
internal static HttpClient SetUser(this HttpClient httpClient, Model.Entity.User user, CookieType cookie = CookieType.All)
internal static HttpClient SetUser(this HttpClient httpClient, Model.Entity.User user, CookieType cookie)
{
httpClient.DefaultRequestHeaders.Set("Cookie", user.Cookie!.ToString(cookie));
return httpClient;

View File

@@ -38,7 +38,7 @@ internal class PassportClient
/// <param name="user">用户</param>
/// <param name="token">取消令牌</param>
/// <returns>验证信息</returns>
[ApiInformation(Cookie = CookieType.All)]
[ApiInformation(Cookie = CookieType.Ltoken)]
public async Task<VerifyInformation?> VerifyLtokenAsync(User user, CancellationToken token)
{
Response<VerifyInformation>? response = await httpClient

View File

@@ -14,7 +14,6 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.Auth;
[SuppressMessage("", "SA1600")]
public class AccountInfo
{
[JsonPropertyName("is_realname")]
public bool IsRealname { get; set; }

View File

@@ -17,7 +17,7 @@ public class ActionTicketWrapper
/// 凭证
/// </summary>
[JsonPropertyName("ticket")]
public string Ticket { get; set; }
public string Ticket { get; set; } = default!;
/// <summary>
/// 是否验证
@@ -29,5 +29,5 @@ public class ActionTicketWrapper
/// 账户信息
/// </summary>
[JsonPropertyName("account_info")]
public AccountInfo AccountInfo { get; set; }
public AccountInfo AccountInfo { get; set; } = default!;
}

View File

@@ -50,7 +50,7 @@ internal class AuthClient
.TryCatchGetFromJsonAsync<Response<ActionTicketWrapper>>(ApiEndpoints.AuthActionTicket(action, stoken, uid), options, logger)
.ConfigureAwait(false);
return resp.Data?.Ticket;
return resp?.Data?.Ticket;
}
}

View File

@@ -3,7 +3,7 @@
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Extension;
using Snap.Hutao.Model.Binding.User;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Web.Hoyolab.Annotation;
using Snap.Hutao.Web.Response;
using System.Net.Http;
@@ -41,12 +41,12 @@ internal class BindingClient
/// <param name="token">取消令牌</param>
/// <returns>用户角色信息</returns>
[ApiInformation(Cookie = CookieType.Ltoken)]
public async Task<List<UserGameRole>> GetUserGameRolesAsync(string actionTicket, Model.Entity.User user, CancellationToken token = default)
public async Task<List<UserGameRole>> GetUserGameRolesAsync(string actionTicket, User user, CancellationToken token = default)
{
string url = ApiEndpoints.UserGameRolesByActionTicket(actionTicket);
Response<ListWrapper<UserGameRole>>? resp = await httpClient
.SetUser(user)
.SetUser(user, CookieType.Ltoken)
.TryCatchGetFromJsonAsync<Response<ListWrapper<UserGameRole>>>(url, options, logger, token)
.ConfigureAwait(false);
@@ -59,10 +59,11 @@ internal class BindingClient
/// <param name="user">用户</param>
/// <param name="token">取消令牌</param>
/// <returns>用户角色信息</returns>
public async Task<List<UserGameRole>> GetUserGameRolesAsync(Model.Entity.User user, CancellationToken token = default)
[ApiInformation(Cookie = CookieType.Ltoken)]
public async Task<List<UserGameRole>> GetUserGameRolesAsync(User user, CancellationToken token = default)
{
Response<ListWrapper<UserGameRole>>? resp = await httpClient
.SetUser(user)
.SetUser(user, CookieType.Ltoken)
.TryCatchGetFromJsonAsync<Response<ListWrapper<UserGameRole>>>(ApiEndpoints.UserGameRolesByCookie, options, logger, token)
.ConfigureAwait(false);

View File

@@ -2,7 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Model.Binding.User;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
using Snap.Hutao.Web.Response;
using System.Net.Http;
@@ -43,9 +43,9 @@ internal class BindingClient2
public async Task<GameAuthKey?> GenerateAuthenticationKeyAsync(User user, GenAuthKeyData data, CancellationToken token = default)
{
Response<GameAuthKey>? resp = await httpClient
.SetUser(user)
.SetUser(user, CookieType.Stoken)
.SetReferer("https://app.mihoyo.com")
.UsingDynamicSecret1()
.UsingDynamicSecret1(SaltType.LK2)
.TryCatchPostAsJsonAsync<GenAuthKeyData, Response<GameAuthKey>>(ApiEndpoints.GenAuthKey, data, options, logger, token)
.ConfigureAwait(false);

View File

@@ -6,7 +6,7 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
/// <summary>
/// 玩家的主角信息
/// </summary>
public class PlayerRole
public class BasicRoleInfo
{
/// <summary>
/// 角色图标Url

View File

@@ -3,7 +3,8 @@
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Extension;
using Snap.Hutao.Model.Binding.User;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Web.Hoyolab.Annotation;
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
using Snap.Hutao.Web.Response;
@@ -41,47 +42,18 @@ internal class GameRecordClient
/// <param name="uid">查询uid</param>
/// <param name="token">取消令牌</param>
/// <returns>实时便笺</returns>
public async Task<DailyNote.DailyNote?> GetDialyNoteAsync(User user, PlayerUid uid, CancellationToken token = default)
[ApiInformation(Cookie = CookieType.Ltoken, Salt = SaltType.X4)]
public async Task<DailyNote.DailyNote?> GetDailyNoteAsync(User user, PlayerUid uid, CancellationToken token = default)
{
Response<DailyNote.DailyNote>? resp = await httpClient
.SetUser(user)
.UsingDynamicSecret2(options, ApiEndpoints.GameRecordDailyNote(uid.Value, uid.Region))
.SetUser(user, CookieType.Ltoken)
.UsingDynamicSecret2(SaltType.X4, options, ApiEndpoints.GameRecordDailyNote(uid.Value, uid.Region))
.GetFromJsonAsync<Response<DailyNote.DailyNote>>(token)
.ConfigureAwait(false);
return resp?.Data;
}
/// <summary>
/// 异步获取实时便笺
/// </summary>
/// <param name="user">用户</param>
/// <param name="uid">查询uid</param>
/// <param name="token">取消令牌</param>
/// <returns>实时便笺</returns>
public async Task<DailyNote.DailyNote?> GetDialyNoteAsync(Model.Entity.User user, PlayerUid uid, CancellationToken token = default)
{
Response<DailyNote.DailyNote>? resp = await httpClient
.SetUser(user)
.UsingDynamicSecret2(options, ApiEndpoints.GameRecordDailyNote(uid.Value, uid.Region))
.GetFromJsonAsync<Response<DailyNote.DailyNote>>(token)
.ConfigureAwait(false);
return resp?.Data;
}
/// <summary>
/// 获取玩家基础信息
/// </summary>
/// <param name="user">用户</param>
/// <param name="token">取消令牌</param>
/// <returns>玩家的基础信息</returns>
public Task<PlayerInfo?> GetPlayerInfoAsync(User user, CancellationToken token = default)
{
PlayerUid uid = (PlayerUid)Must.NotNull(user.SelectedUserGameRole!);
return GetPlayerInfoAsync(user, uid, token);
}
/// <summary>
/// 获取玩家基础信息
/// </summary>
@@ -89,30 +61,18 @@ internal class GameRecordClient
/// <param name="uid">uid</param>
/// <param name="token">取消令牌</param>
/// <returns>玩家的基础信息</returns>
[ApiInformation(Cookie = CookieType.Ltoken, Salt = SaltType.X4)]
public async Task<PlayerInfo?> GetPlayerInfoAsync(User user, PlayerUid uid, CancellationToken token = default)
{
Response<PlayerInfo>? resp = await httpClient
.SetUser(user)
.UsingDynamicSecret2(options, ApiEndpoints.GameRecordIndex(uid.Value, uid.Region))
.SetUser(user, CookieType.Ltoken)
.UsingDynamicSecret2(SaltType.X4, options, ApiEndpoints.GameRecordIndex(uid.Value, uid.Region))
.GetFromJsonAsync<Response<PlayerInfo>>(token)
.ConfigureAwait(false);
return resp?.Data;
}
/// <summary>
/// 获取玩家深渊信息
/// </summary>
/// <param name="user">用户</param>
/// <param name="schedule">期</param>
/// <param name="token">取消令牌</param>
/// <returns>深渊信息</returns>
public Task<SpiralAbyss.SpiralAbyss?> GetSpiralAbyssAsync(User user, SpiralAbyssSchedule schedule, CancellationToken token = default)
{
PlayerUid uid = (PlayerUid)Must.NotNull(user.SelectedUserGameRole!);
return GetSpiralAbyssAsync(user, uid, schedule, token);
}
/// <summary>
/// 获取玩家深渊信息
/// </summary>
@@ -121,30 +81,37 @@ internal class GameRecordClient
/// <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(User user, PlayerUid uid, SpiralAbyssSchedule schedule, CancellationToken token = default)
{
Response<SpiralAbyss.SpiralAbyss>? resp = await httpClient
.SetUser(user)
.UsingDynamicSecret2(options, ApiEndpoints.GameRecordSpiralAbyss(schedule, uid))
.SetUser(user, CookieType.Ltoken)
.UsingDynamicSecret2(SaltType.X4, options, ApiEndpoints.GameRecordSpiralAbyss(schedule, uid))
.GetFromJsonAsync<Response<SpiralAbyss.SpiralAbyss>>(token)
.ConfigureAwait(false);
return resp?.Data;
}
/// <summary>
/// 获取玩家角色详细信息
/// </summary>
/// <param name="user">用户</param>
/// <param name="playerInfo">玩家的基础信息</param>
/// <param name="token">取消令牌</param>
/// <returns>角色列表</returns>
public Task<List<Character>> GetCharactersAsync(User user, PlayerInfo playerInfo, CancellationToken token = default)
{
PlayerUid uid = (PlayerUid)Must.NotNull(user.SelectedUserGameRole!);
return GetCharactersAsync(user, uid, playerInfo, token);
}
/// <summary>
/// 异步获取角色基本信息
/// </summary>
/// <param name="user">用户</param>
/// <param name="uid">uid</param>
/// <param name="token">取消令牌</param>
/// <returns>角色基本信息</returns>
[ApiInformation(Cookie = CookieType.Ltoken, Salt = SaltType.X4)]
public async Task<BasicRoleInfo?> GetRoleBasicInfoAsync(User user, PlayerUid uid, CancellationToken token = default)
{
Response<BasicRoleInfo>? resp = await httpClient
.SetUser(user, CookieType.Ltoken)
.UsingDynamicSecret2(SaltType.X4, options, ApiEndpoints.GameRecordRoleBasicInfo(uid))
.TryCatchGetFromJsonAsync<Response<BasicRoleInfo>>(logger, token)
.ConfigureAwait(false);
return resp?.Data;
}
/// <summary>
/// 获取玩家角色详细信息
/// </summary>
@@ -153,13 +120,14 @@ internal class GameRecordClient
/// <param name="playerInfo">玩家的基础信息</param>
/// <param name="token">取消令牌</param>
/// <returns>角色列表</returns>
[ApiInformation(Cookie = CookieType.Ltoken, Salt = SaltType.X4)]
public async Task<List<Character>> GetCharactersAsync(User user, PlayerUid uid, PlayerInfo playerInfo, CancellationToken token = default)
{
CharacterData data = new(uid, playerInfo.Avatars.Select(x => x.Id));
Response<CharacterWrapper>? resp = await httpClient
.SetUser(user)
.UsingDynamicSecret2(options, ApiEndpoints.GameRecordCharacter, data)
.SetUser(user, CookieType.Ltoken)
.UsingDynamicSecret2(SaltType.X4, options, ApiEndpoints.GameRecordCharacter, data)
.TryCatchPostAsJsonAsync<Response<CharacterWrapper>>(logger, token)
.ConfigureAwait(false);

View File

@@ -12,7 +12,7 @@ public class PlayerInfo
/// 玩家的角色信息
/// </summary>
[JsonPropertyName("role")]
public PlayerRole Role { get; set; } = default!;
public BasicRoleInfo Role { get; set; } = default!;
/// <summary>
/// 持有的角色的信息

View File

@@ -175,21 +175,23 @@ internal class HomaClient
/// <returns>玩家记录</returns>
public async Task<SimpleRecord> GetPlayerRecordAsync(User user, CancellationToken token = default)
{
Must.NotNull(user.SelectedUserGameRole!);
PlayerInfo? playerInfo = await gameRecordClient
.GetPlayerInfoAsync(user, token)
.GetPlayerInfoAsync(user.Entity, user.SelectedUserGameRole, token)
.ConfigureAwait(false);
Must.NotNull(playerInfo!);
List<Character> characters = await gameRecordClient
.GetCharactersAsync(user, playerInfo, token)
.GetCharactersAsync(user.Entity, user.SelectedUserGameRole, playerInfo, token)
.ConfigureAwait(false);
SpiralAbyss? spiralAbyssInfo = await gameRecordClient
.GetSpiralAbyssAsync(user, SpiralAbyssSchedule.Current, token)
.GetSpiralAbyssAsync(user.Entity, user.SelectedUserGameRole, SpiralAbyssSchedule.Current, token)
.ConfigureAwait(false);
Must.NotNull(spiralAbyssInfo!);
return new(Must.NotNull(user.SelectedUserGameRole!).GameUid, characters, spiralAbyssInfo);
return new(user.SelectedUserGameRole.GameUid, characters, spiralAbyssInfo);
}
/// <summary>