mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
fix up oversea launcher support
This commit is contained in:
@@ -14,9 +14,9 @@ csharp_style_expression_bodied_accessors = when_on_single_line:silent
|
|||||||
csharp_style_expression_bodied_lambdas = when_on_single_line:silent
|
csharp_style_expression_bodied_lambdas = when_on_single_line:silent
|
||||||
csharp_style_expression_bodied_local_functions = false:silent
|
csharp_style_expression_bodied_local_functions = false:silent
|
||||||
csharp_style_conditional_delegate_call = true:suggestion
|
csharp_style_conditional_delegate_call = true:suggestion
|
||||||
csharp_style_var_for_built_in_types = false:silent
|
csharp_style_var_for_built_in_types = false:warning
|
||||||
csharp_style_var_when_type_is_apparent = false:silent
|
csharp_style_var_when_type_is_apparent = false:warning
|
||||||
csharp_style_var_elsewhere = false:silent
|
csharp_style_var_elsewhere = false:warning
|
||||||
csharp_prefer_simple_using_statement = false:suggestion
|
csharp_prefer_simple_using_statement = false:suggestion
|
||||||
csharp_prefer_braces = true:silent
|
csharp_prefer_braces = true:silent
|
||||||
csharp_style_namespace_declarations = file_scoped:silent
|
csharp_style_namespace_declarations = file_scoped:silent
|
||||||
@@ -24,11 +24,11 @@ csharp_prefer_static_local_function = false:suggestion
|
|||||||
|
|
||||||
[*.{cs,vb}]
|
[*.{cs,vb}]
|
||||||
end_of_line = crlf
|
end_of_line = crlf
|
||||||
dotnet_style_qualification_for_field = false:silent
|
dotnet_style_qualification_for_field = false:suggestion
|
||||||
dotnet_style_qualification_for_property = false:silent
|
dotnet_style_qualification_for_property = false:suggestion
|
||||||
dotnet_style_qualification_for_method = false:silent
|
dotnet_style_qualification_for_method = false:suggestion
|
||||||
dotnet_style_qualification_for_event = false:silent
|
dotnet_style_qualification_for_event = false:suggestion
|
||||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
|
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
||||||
dotnet_code_quality_unused_parameters = non_public:suggestion
|
dotnet_code_quality_unused_parameters = non_public:suggestion
|
||||||
dotnet_style_readonly_field = true:suggestion
|
dotnet_style_readonly_field = true:suggestion
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
@@ -47,8 +47,8 @@ dotnet_style_prefer_auto_properties = false:silent
|
|||||||
dotnet_style_object_initializer = true:suggestion
|
dotnet_style_object_initializer = true:suggestion
|
||||||
dotnet_style_collection_initializer = true:suggestion
|
dotnet_style_collection_initializer = true:suggestion
|
||||||
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
||||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
|
||||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
|
||||||
dotnet_style_explicit_tuple_names = true:suggestion
|
dotnet_style_explicit_tuple_names = true:suggestion
|
||||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||||
@@ -165,6 +165,7 @@ dotnet_diagnostic.CA1805.severity = suggestion
|
|||||||
dotnet_diagnostic.VSTHRD111.severity = suggestion
|
dotnet_diagnostic.VSTHRD111.severity = suggestion
|
||||||
csharp_style_prefer_top_level_statements = true:silent
|
csharp_style_prefer_top_level_statements = true:silent
|
||||||
csharp_style_prefer_readonly_struct = true:suggestion
|
csharp_style_prefer_readonly_struct = true:suggestion
|
||||||
|
csharp_style_prefer_utf8_string_literals = true:suggestion
|
||||||
|
|
||||||
[*.vb]
|
[*.vb]
|
||||||
#### 命名样式 ####
|
#### 命名样式 ####
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.SourceGeneration;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 高亮TODO
|
|
||||||
/// </summary>
|
|
||||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
|
||||||
internal class TodoAnalyzer : DiagnosticAnalyzer
|
|
||||||
{
|
|
||||||
private static readonly DiagnosticDescriptor Descriptor =
|
|
||||||
new("SH0001", "TODO 项尚未实现", "此 TODO 项需要实现", "Standard", DiagnosticSeverity.Info, true);
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get => ImmutableArray.Create(Descriptor); }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void Initialize(AnalysisContext context)
|
|
||||||
{
|
|
||||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
|
||||||
context.EnableConcurrentExecution();
|
|
||||||
|
|
||||||
context.RegisterSyntaxTreeAction(HandleSyntaxTree);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void HandleSyntaxTree(SyntaxTreeAnalysisContext context)
|
|
||||||
{
|
|
||||||
SyntaxNode root = context.Tree.GetCompilationUnitRoot(context.CancellationToken);
|
|
||||||
foreach (SyntaxTrivia node in root.DescendantTrivia(descendIntoTrivia: true))
|
|
||||||
{
|
|
||||||
switch (node.Kind())
|
|
||||||
{
|
|
||||||
case SyntaxKind.SingleLineCommentTrivia:
|
|
||||||
case SyntaxKind.MultiLineCommentTrivia:
|
|
||||||
string text = node.ToString().ToLowerInvariant();
|
|
||||||
if (text.Contains("todo:"))
|
|
||||||
{
|
|
||||||
string hint = node.ToString().Substring(text.IndexOf("todo:") + 6);
|
|
||||||
DiagnosticDescriptor descriptor = new("SH0001", "TODO 项尚未实现", hint, "Standard", DiagnosticSeverity.Info, true);
|
|
||||||
context.ReportDiagnostic(Diagnostic.Create(descriptor, node.GetLocation()));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -153,10 +153,11 @@ public class User : ObservableObject
|
|||||||
|
|
||||||
using (IServiceScope scope = Ioc.Default.CreateScope())
|
using (IServiceScope scope = Ioc.Default.CreateScope())
|
||||||
{
|
{
|
||||||
UserInfo = await scope.ServiceProvider
|
Web.Response.Response<UserFullInfoWrapper> response = await scope.ServiceProvider
|
||||||
.GetRequiredService<UserClient2>()
|
.GetRequiredService<UserClient2>()
|
||||||
.GetUserFullInfoAsync(Entity, token)
|
.GetUserFullInfoAsync(Entity, token)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
UserInfo = response.Data?.UserInfo;
|
||||||
|
|
||||||
// 自动填充 Ltoken
|
// 自动填充 Ltoken
|
||||||
if (Ltoken == null)
|
if (Ltoken == null)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Snap.Hutao.Service.Abstraction;
|
using Snap.Hutao.Service.Abstraction;
|
||||||
using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
|
using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
|
||||||
|
using Snap.Hutao.Web.Response;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace Snap.Hutao.Service;
|
namespace Snap.Hutao.Service;
|
||||||
@@ -34,29 +35,39 @@ internal partial class AnnouncementService : IAnnouncementService
|
|||||||
// 缓存中存在记录,直接返回
|
// 缓存中存在记录,直接返回
|
||||||
if (memoryCache.TryGetValue(CacheKey, out object? cache))
|
if (memoryCache.TryGetValue(CacheKey, out object? cache))
|
||||||
{
|
{
|
||||||
return Must.NotNull((AnnouncementWrapper)cache!);
|
return (AnnouncementWrapper)cache!;
|
||||||
}
|
}
|
||||||
|
|
||||||
await ThreadHelper.SwitchToBackgroundAsync();
|
await ThreadHelper.SwitchToBackgroundAsync();
|
||||||
AnnouncementWrapper? wrapper = await announcementClient
|
Response<AnnouncementWrapper> announcementWrapperResponse = await announcementClient
|
||||||
.GetAnnouncementsAsync(cancellationToken)
|
.GetAnnouncementsAsync(cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
List<AnnouncementContent> contents = await announcementClient
|
|
||||||
.GetAnnouncementContentsAsync(cancellationToken)
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
|
|
||||||
Dictionary<int, string> contentMap = contents
|
if (announcementWrapperResponse.IsOk())
|
||||||
.ToDictionary(id => id.AnnId, content => content.Content);
|
{
|
||||||
|
AnnouncementWrapper wrapper = announcementWrapperResponse.Data;
|
||||||
|
Response<ListWrapper<AnnouncementContent>> announcementContentResponse = await announcementClient
|
||||||
|
.GetAnnouncementContentsAsync(cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
Must.NotNull(wrapper!);
|
if (announcementContentResponse.IsOk())
|
||||||
|
{
|
||||||
|
List<AnnouncementContent> contents = announcementContentResponse.Data.List;
|
||||||
|
|
||||||
// 将活动公告置于上方
|
Dictionary<int, string> contentMap = contents
|
||||||
wrapper.List.Reverse();
|
.ToDictionary(id => id.AnnId, content => content.Content);
|
||||||
|
|
||||||
// 将公告内容联入公告列表
|
// 将活动公告置于上方
|
||||||
JoinAnnouncements(contentMap, wrapper.List);
|
wrapper.List.Reverse();
|
||||||
|
|
||||||
return memoryCache.Set(CacheKey, wrapper, TimeSpan.FromMinutes(30));
|
// 将公告内容联入公告列表
|
||||||
|
JoinAnnouncements(contentMap, wrapper.List);
|
||||||
|
|
||||||
|
return memoryCache.Set(CacheKey, wrapper, TimeSpan.FromMinutes(30));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void JoinAnnouncements(Dictionary<int, string> contentMap, List<AnnouncementListWrapper> announcementListWrappers)
|
private static void JoinAnnouncements(Dictionary<int, string> contentMap, List<AnnouncementListWrapper> announcementListWrappers)
|
||||||
|
|||||||
@@ -36,16 +36,10 @@ internal class GachaLogUrlWebCacheProvider : IGachaLogUrlProvider
|
|||||||
public static string GetCacheFile(string path)
|
public static string GetCacheFile(string path)
|
||||||
{
|
{
|
||||||
string folder = Path.GetDirectoryName(path) ?? string.Empty;
|
string folder = Path.GetDirectoryName(path) ?? string.Empty;
|
||||||
var cacheDataPath = Path.Combine(folder, @"YuanShen_Data\webCaches\Cache\Cache_Data\data_2");
|
string cacheDataPathChinese = Path.Combine(folder, @"YuanShen_Data\webCaches\Cache\Cache_Data\data_2");
|
||||||
var cacheDataPathIntl = Path.Combine(folder, @"GenshinImpact_Data\webCaches\Cache\Cache_Data\data_2");
|
string cacheDataPathOversea = Path.Combine(folder, @"GenshinImpact_Data\webCaches\Cache\Cache_Data\data_2");
|
||||||
if (File.Exists(cacheDataPath))
|
|
||||||
{
|
return File.Exists(cacheDataPathChinese) ? cacheDataPathChinese : cacheDataPathOversea;
|
||||||
return cacheDataPath;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return cacheDataPathIntl;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
@@ -200,6 +200,7 @@ internal class GameService : IGameService, IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
[SuppressMessage("", "IDE0046")]
|
||||||
public bool IsGameRunning()
|
public bool IsGameRunning()
|
||||||
{
|
{
|
||||||
if (gameSemaphore.CurrentCount == 0)
|
if (gameSemaphore.CurrentCount == 0)
|
||||||
@@ -207,7 +208,8 @@ internal class GameService : IGameService, IDisposable
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Process.GetProcessesByName("YuanShen.exe").Any() || Process.GetProcessesByName("GenshinImpact.exe").Any();
|
return Process.GetProcessesByName("YuanShen.exe").Any()
|
||||||
|
|| Process.GetProcessesByName("GenshinImpact.exe").Any();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Core.IO;
|
||||||
using Snap.Hutao.Factory.Abstraction;
|
using Snap.Hutao.Factory.Abstraction;
|
||||||
using Windows.Storage;
|
using Windows.Storage;
|
||||||
using Windows.Storage.Pickers;
|
using Windows.Storage.Pickers;
|
||||||
@@ -30,26 +31,21 @@ internal class ManualGameLocator : IGameLocator
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Task<ValueResult<bool, string>> LocateGamePathAsync()
|
public Task<ValueResult<bool, string>> LocateGamePathAsync()
|
||||||
{
|
{
|
||||||
List<string> filenames = new List<string>()
|
List<string> filenames = new(2) { "YuanShen.exe", "GenshinImpact.exe", };
|
||||||
{
|
|
||||||
"YuanShen.exe",
|
|
||||||
"GenshinImpact.exe",
|
|
||||||
};
|
|
||||||
return LocateInternalAsync(filenames);
|
return LocateInternalAsync(filenames);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ValueResult<bool, string>> LocateInternalAsync(List<string> fileNames)
|
private async Task<ValueResult<bool, string>> LocateInternalAsync(List<string> fileNames)
|
||||||
{
|
{
|
||||||
FileOpenPicker picker = pickerFactory.GetFileOpenPicker(PickerLocationId.Desktop, "选择游戏本体", ".exe");
|
FileOpenPicker picker = pickerFactory.GetFileOpenPicker(PickerLocationId.Desktop, "选择游戏本体", ".exe");
|
||||||
if (await picker.PickSingleFileAsync() is StorageFile file)
|
(bool isPickerOk, FilePath file) = await picker.TryPickSingleFileAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (isPickerOk)
|
||||||
{
|
{
|
||||||
string path = file.Path;
|
string fileName = System.IO.Path.GetFileName(file);
|
||||||
foreach (string fileName in fileNames)
|
if (fileNames.Contains(fileName))
|
||||||
{
|
{
|
||||||
if (path.Contains(fileName))
|
return new(true, file);
|
||||||
{
|
|
||||||
return new(true, path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,10 +21,13 @@ internal partial class UnityLogGameLocator : IGameLocator
|
|||||||
{
|
{
|
||||||
await ThreadHelper.SwitchToBackgroundAsync();
|
await ThreadHelper.SwitchToBackgroundAsync();
|
||||||
string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||||
string logFilePath = Path.Combine(appDataPath, @"..\LocalLow\miHoYo\原神\output_log.txt");
|
string logFilePathChinese = Path.Combine(appDataPath, @"..\LocalLow\miHoYo\原神\output_log.txt");
|
||||||
string logFilePathIntl = Path.Combine(appDataPath, @"..\LocalLow\miHoYo\Genshin Impact\output_log.txt");
|
string logFilePathOvsesea = Path.Combine(appDataPath, @"..\LocalLow\miHoYo\Genshin Impact\output_log.txt");
|
||||||
|
|
||||||
using (TempFile? tempFile = TempFile.CreateFromFileCopy(logFilePath), tempFileIntl = TempFile.CreateFromFileCopy(logFilePathIntl))
|
// We need to fallback the the cn server rather than os server.
|
||||||
|
string logFilePathFinal = File.Exists(logFilePathOvsesea) ? logFilePathOvsesea : logFilePathChinese;
|
||||||
|
|
||||||
|
using (TempFile? tempFile = TempFile.CreateFromFileCopy(logFilePathFinal))
|
||||||
{
|
{
|
||||||
if (tempFile != null)
|
if (tempFile != null)
|
||||||
{
|
{
|
||||||
@@ -40,30 +43,13 @@ internal partial class UnityLogGameLocator : IGameLocator
|
|||||||
string fullPath = Path.GetFullPath(Path.Combine(matchResult.Value, "..", entryName));
|
string fullPath = Path.GetFullPath(Path.Combine(matchResult.Value, "..", entryName));
|
||||||
return new(true, fullPath);
|
return new(true, fullPath);
|
||||||
}
|
}
|
||||||
else if (tempFileIntl != null)
|
|
||||||
{
|
|
||||||
string content = File.ReadAllText(tempFileIntl.Path);
|
|
||||||
|
|
||||||
Match matchResult = WarmupFileLineIntl().Match(content);
|
|
||||||
if (!matchResult.Success)
|
|
||||||
{
|
|
||||||
return new(false, $"在 Unity 日志文件中找不到游戏路径");
|
|
||||||
}
|
|
||||||
|
|
||||||
string entryName = matchResult.Groups[0].Value.Replace("_Data", ".exe");
|
|
||||||
string fullPath = Path.GetFullPath(Path.Combine(matchResult.Value, "..", entryName));
|
|
||||||
return new(true, fullPath);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return new(false, $"找不到 Unity 日志文件:\n{logFilePath}\n{logFilePathIntl}");
|
return new(false, $"找不到 Unity 日志文件");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[GeneratedRegex(@"(?m).:/.+YuanShen_Data")]
|
[GeneratedRegex(@"(?m).:/.+(GenshinImpact_Data|YuanShen_Data)")]
|
||||||
private static partial Regex WarmupFileLine();
|
private static partial Regex WarmupFileLine();
|
||||||
|
|
||||||
[GeneratedRegex(@"(?m).:/.+GenshinImpact_Data")]
|
|
||||||
private static partial Regex WarmupFileLineIntl();
|
|
||||||
}
|
}
|
||||||
@@ -87,7 +87,7 @@ internal class AnnouncementViewModel : ObservableObject, ISupportCancellation
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
||||||
infoBarService.Warning("尚未安装 WebView2 运行时。");
|
infoBarService.Warning("尚未安装 WebView2 Runtime");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ internal class SettingViewModel : ObservableObject
|
|||||||
|
|
||||||
Experimental = experimental;
|
Experimental = experimental;
|
||||||
|
|
||||||
isEmptyHistoryWishVisibleEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.IsEmptyHistoryWishVisible, true.ToString());
|
isEmptyHistoryWishVisibleEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.IsEmptyHistoryWishVisible, SettingEntryHelper.TrueString);
|
||||||
IsEmptyHistoryWishVisible = bool.Parse(isEmptyHistoryWishVisibleEntry.Value!);
|
IsEmptyHistoryWishVisible = bool.Parse(isEmptyHistoryWishVisibleEntry.Value!);
|
||||||
|
|
||||||
selectedBackdropTypeEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.SystemBackdropType, BackdropType.Mica.ToString());
|
selectedBackdropTypeEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.SystemBackdropType, BackdropType.Mica.ToString());
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ internal class EnkaClient
|
|||||||
/// <param name="playerUid">玩家Uid</param>
|
/// <param name="playerUid">玩家Uid</param>
|
||||||
/// <param name="token">取消令牌</param>
|
/// <param name="token">取消令牌</param>
|
||||||
/// <returns>Enka API 响应</returns>
|
/// <returns>Enka API 响应</returns>
|
||||||
public Task<EnkaResponse?> GetForwardDataAsync(PlayerUid playerUid, CancellationToken token)
|
public Task<EnkaResponse?> GetForwardDataAsync(PlayerUid playerUid, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
return httpClient.TryCatchGetFromJsonAsync<EnkaResponse>(string.Format(EnkaAPIHutaoForward, playerUid.Value), options, logger, token);
|
return httpClient.TryCatchGetFromJsonAsync<EnkaResponse>(string.Format(EnkaAPIHutaoForward, playerUid.Value), options, logger, token);
|
||||||
}
|
}
|
||||||
@@ -51,8 +51,8 @@ internal class EnkaClient
|
|||||||
/// <param name="playerUid">玩家Uid</param>
|
/// <param name="playerUid">玩家Uid</param>
|
||||||
/// <param name="token">取消令牌</param>
|
/// <param name="token">取消令牌</param>
|
||||||
/// <returns>Enka API 响应</returns>
|
/// <returns>Enka API 响应</returns>
|
||||||
public Task<EnkaResponse?> GetDataAsync(PlayerUid playerUid, CancellationToken token)
|
public Task<EnkaResponse?> GetDataAsync(PlayerUid playerUid, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
return httpClient.TryCatchGetFromJsonAsync<EnkaResponse>(string.Format(EnkaAPI, playerUid.Value), options, logger, token);
|
return httpClient.TryCatchGetFromJsonAsync<EnkaResponse>(string.Format(EnkaAPI, playerUid.Value), options, logger, token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ internal class AccountClient
|
|||||||
/// <param name="token">取消令牌</param>
|
/// <param name="token">取消令牌</param>
|
||||||
/// <returns>用户角色信息</returns>
|
/// <returns>用户角色信息</returns>
|
||||||
[ApiInformation(Cookie = CookieType.Stoken, Salt = SaltType.K2)]
|
[ApiInformation(Cookie = CookieType.Stoken, Salt = SaltType.K2)]
|
||||||
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
|
Response<GameAuthKey>? resp = await httpClient
|
||||||
.SetUser(user, CookieType.Stoken)
|
.SetUser(user, CookieType.Stoken)
|
||||||
@@ -53,6 +53,6 @@ internal class AccountClient
|
|||||||
.TryCatchPostAsJsonAsync<GenAuthKeyData, Response<GameAuthKey>>(ApiEndpoints.AppAuthGenAuthKey, data, options, logger, token)
|
.TryCatchPostAsJsonAsync<GenAuthKeyData, Response<GameAuthKey>>(ApiEndpoints.AppAuthGenAuthKey, data, options, logger, token)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
return resp?.Data;
|
return Response.Response.DefaultIfNull(resp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
|
||||||
using Snap.Hutao.Web.Hoyolab.Annotation;
|
|
||||||
using Snap.Hutao.Web.Response;
|
|
||||||
using System.Net.Http;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Web.Hoyolab.Bbs.User;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 用户信息客户端
|
|
||||||
/// </summary>
|
|
||||||
[HttpClient(HttpClientConfigration.XRpc)]
|
|
||||||
internal class UserClient
|
|
||||||
{
|
|
||||||
private readonly HttpClient httpClient;
|
|
||||||
private readonly JsonSerializerOptions options;
|
|
||||||
private readonly ILogger<UserClient> logger;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造一个新的用户信息客户端
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="httpClient">http客户端</param>
|
|
||||||
/// <param name="options">Json序列化选项</param>
|
|
||||||
/// <param name="logger">日志器</param>
|
|
||||||
public UserClient(HttpClient httpClient, JsonSerializerOptions options, ILogger<UserClient> logger)
|
|
||||||
{
|
|
||||||
this.httpClient = httpClient;
|
|
||||||
this.options = options;
|
|
||||||
this.logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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) // Otherwise HTTP 403
|
|
||||||
.TryCatchGetFromJsonAsync<Response<UserFullInfoWrapper>>(ApiEndpoints.UserFullInfo, 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?> GetUserFullInfoByUidAsync(Model.Entity.User user, CancellationToken token = default)
|
|
||||||
{
|
|
||||||
Response<UserFullInfoWrapper>? resp = await httpClient
|
|
||||||
|
|
||||||
// .SetUser(user, CookieType.Cookie)
|
|
||||||
.SetReferer(ApiEndpoints.BbsReferer) // Otherwise HTTP 403
|
|
||||||
.TryCatchGetFromJsonAsync<Response<UserFullInfoWrapper>>(ApiEndpoints.UserFullInfoQuery(user.Aid!), options, logger, token)
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
|
|
||||||
return resp?.Data?.UserInfo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -40,7 +40,7 @@ internal class UserClient2
|
|||||||
/// <param name="token">取消令牌</param>
|
/// <param name="token">取消令牌</param>
|
||||||
/// <returns>详细信息</returns>
|
/// <returns>详细信息</returns>
|
||||||
[ApiInformation(Cookie = CookieType.Stoken, Salt = SaltType.K2)]
|
[ApiInformation(Cookie = CookieType.Stoken, Salt = SaltType.K2)]
|
||||||
public async Task<UserInfo?> GetUserFullInfoAsync(Model.Entity.User user, CancellationToken token = default)
|
public async Task<Response<UserFullInfoWrapper>> GetUserFullInfoAsync(Model.Entity.User user, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
Response<UserFullInfoWrapper>? resp = await httpClient
|
Response<UserFullInfoWrapper>? resp = await httpClient
|
||||||
.SetUser(user, CookieType.Stoken)
|
.SetUser(user, CookieType.Stoken)
|
||||||
@@ -49,6 +49,6 @@ internal class UserClient2
|
|||||||
.TryCatchGetFromJsonAsync<Response<UserFullInfoWrapper>>(ApiEndpoints.UserFullInfoQuery(user.Aid!), options, logger, token)
|
.TryCatchGetFromJsonAsync<Response<UserFullInfoWrapper>>(ApiEndpoints.UserFullInfoQuery(user.Aid!), options, logger, token)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
return resp?.Data?.UserInfo;
|
return Response.Response.DefaultIfNull(resp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ namespace Snap.Hutao.Web.Hoyolab.DynamicSecret;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 指示此客户端使用动态密钥
|
/// 指示此客户端使用动态密钥
|
||||||
|
/// 会为依附类自动生成相关代码
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
internal class UseDynamicSecretAttribute : Attribute
|
internal class UseDynamicSecretAttribute : Attribute
|
||||||
|
|||||||
@@ -36,13 +36,13 @@ internal class AnnouncementClient
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cancellationToken">取消令牌</param>
|
/// <param name="cancellationToken">取消令牌</param>
|
||||||
/// <returns>公告列表</returns>
|
/// <returns>公告列表</returns>
|
||||||
public async Task<AnnouncementWrapper?> GetAnnouncementsAsync(CancellationToken cancellationToken = default)
|
public async Task<Response<AnnouncementWrapper>> GetAnnouncementsAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
Response<AnnouncementWrapper>? resp = await httpClient
|
Response<AnnouncementWrapper>? resp = await httpClient
|
||||||
.TryCatchGetFromJsonAsync<Response<AnnouncementWrapper>>(ApiEndpoints.AnnList, jsonSerializerOptions, logger, cancellationToken)
|
.TryCatchGetFromJsonAsync<Response<AnnouncementWrapper>>(ApiEndpoints.AnnList, jsonSerializerOptions, logger, cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
return resp?.Data;
|
return Response.Response.DefaultIfNull(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -50,13 +50,12 @@ internal class AnnouncementClient
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cancellationToken">取消令牌</param>
|
/// <param name="cancellationToken">取消令牌</param>
|
||||||
/// <returns>公告内容列表</returns>
|
/// <returns>公告内容列表</returns>
|
||||||
public async Task<List<AnnouncementContent>> GetAnnouncementContentsAsync(CancellationToken cancellationToken = default)
|
public async Task<Response<ListWrapper<AnnouncementContent>>> GetAnnouncementContentsAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
|
||||||
Response<ListWrapper<AnnouncementContent>>? resp = await httpClient
|
Response<ListWrapper<AnnouncementContent>>? resp = await httpClient
|
||||||
.TryCatchGetFromJsonAsync<Response<ListWrapper<AnnouncementContent>>>(ApiEndpoints.AnnContent, jsonSerializerOptions, logger, cancellationToken)
|
.TryCatchGetFromJsonAsync<Response<ListWrapper<AnnouncementContent>>>(ApiEndpoints.AnnContent, jsonSerializerOptions, logger, cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
return EnumerableExtension.EmptyIfNull(resp?.Data?.List);
|
return Response.Response.DefaultIfNull(resp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ internal static class HttpClientExtensions
|
|||||||
logger.LogWarning(EventIds.HttpException, ex, "请求异常已忽略");
|
logger.LogWarning(EventIds.HttpException, ex, "请求异常已忽略");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
catch (SocketException ex)
|
catch (IOException ex)
|
||||||
{
|
{
|
||||||
logger.LogWarning(EventIds.HttpException, ex, "请求异常已忽略");
|
logger.LogWarning(EventIds.HttpException, ex, "请求异常已忽略");
|
||||||
return null;
|
return null;
|
||||||
@@ -38,7 +38,7 @@ internal static class HttpClientExtensions
|
|||||||
logger.LogWarning(EventIds.HttpException, ex, "请求异常已忽略");
|
logger.LogWarning(EventIds.HttpException, ex, "请求异常已忽略");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
catch (IOException ex)
|
catch (SocketException ex)
|
||||||
{
|
{
|
||||||
logger.LogWarning(EventIds.HttpException, ex, "请求异常已忽略");
|
logger.LogWarning(EventIds.HttpException, ex, "请求异常已忽略");
|
||||||
return null;
|
return null;
|
||||||
@@ -59,7 +59,7 @@ internal static class HttpClientExtensions
|
|||||||
logger.LogWarning(EventIds.HttpException, ex, "请求异常已忽略");
|
logger.LogWarning(EventIds.HttpException, ex, "请求异常已忽略");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
catch (SocketException ex)
|
catch (IOException ex)
|
||||||
{
|
{
|
||||||
logger.LogWarning(EventIds.HttpException, ex, "请求异常已忽略");
|
logger.LogWarning(EventIds.HttpException, ex, "请求异常已忽略");
|
||||||
return null;
|
return null;
|
||||||
@@ -69,7 +69,7 @@ internal static class HttpClientExtensions
|
|||||||
logger.LogWarning(EventIds.HttpException, ex, "请求异常已忽略");
|
logger.LogWarning(EventIds.HttpException, ex, "请求异常已忽略");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
catch (IOException ex)
|
catch (SocketException ex)
|
||||||
{
|
{
|
||||||
logger.LogWarning(EventIds.HttpException, ex, "请求异常已忽略");
|
logger.LogWarning(EventIds.HttpException, ex, "请求异常已忽略");
|
||||||
return null;
|
return null;
|
||||||
@@ -89,7 +89,7 @@ internal static class HttpClientExtensions
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
catch (SocketException)
|
catch (IOException)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -97,7 +97,7 @@ internal static class HttpClientExtensions
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
catch (IOException)
|
catch (SocketException)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,13 +22,9 @@ public class Response : ISupportValidation
|
|||||||
{
|
{
|
||||||
ReturnCode = returnCode;
|
ReturnCode = returnCode;
|
||||||
Message = message;
|
Message = message;
|
||||||
|
#if DEBUG
|
||||||
if (!Validate())
|
|
||||||
{
|
|
||||||
Ioc.Default.GetRequiredService<IInfoBarService>().Error(ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ioc.Default.GetRequiredService<ILogger<Response>>().LogInformation("Response [{resp}]", ToString());
|
Ioc.Default.GetRequiredService<ILogger<Response>>().LogInformation("Response [{resp}]", ToString());
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -43,6 +39,18 @@ public class Response : ISupportValidation
|
|||||||
[JsonPropertyName("message")]
|
[JsonPropertyName("message")]
|
||||||
public string Message { get; set; } = default!;
|
public string Message { get; set; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 返回本体或带有消息提示的默认值
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TData">类型</typeparam>
|
||||||
|
/// <param name="response">本体</param>
|
||||||
|
/// <returns>本体或默认值,当本体为 null 时 返回默认值</returns>
|
||||||
|
public static Response<TData> DefaultIfNull<TData>(Response<TData>? response)
|
||||||
|
{
|
||||||
|
// 0x26F19335 is a magic number that hashed from "Snap.Hutao"
|
||||||
|
return response ?? new(0x26F19335, "请求异常", default);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool Validate()
|
public bool Validate()
|
||||||
{
|
{
|
||||||
@@ -90,7 +98,17 @@ public class Response<TData> : Response, IJsResult
|
|||||||
[MemberNotNullWhen(true, nameof(Data))]
|
[MemberNotNullWhen(true, nameof(Data))]
|
||||||
public bool IsOk()
|
public bool IsOk()
|
||||||
{
|
{
|
||||||
return ReturnCode == 0;
|
if (ReturnCode == 0)
|
||||||
|
{
|
||||||
|
#pragma warning disable CS8775
|
||||||
|
return true;
|
||||||
|
#pragma warning restore CS8775
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Ioc.Default.GetRequiredService<IInfoBarService>().Error(ToString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
@@ -9,20 +9,22 @@ namespace Snap.Hutao.Win32;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 内存拓展 for <see cref="Memory{T}"/> and <see cref="Span{T}"/>
|
/// 内存拓展 for <see cref="Memory{T}"/> and <see cref="Span{T}"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class MemoryExtensions
|
internal static class MemoryExtension
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 将 __CHAR_256 转换到 字符串
|
/// 将 __CHAR_256 转换到 字符串
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="char256">目标字符数组</param>
|
/// <param name="char256">目标字符数组</param>
|
||||||
/// <returns>结果字符串</returns>
|
/// <returns>结果字符串</returns>
|
||||||
public static unsafe string AsString(this __CHAR_256 char256)
|
public static unsafe string AsString(this ref __CHAR_256 char256)
|
||||||
{
|
{
|
||||||
byte* pszModule = (byte*)&char256;
|
fixed (CHAR* pszModule = &char256._0)
|
||||||
return Encoding.UTF8.GetString(pszModule, StringLength(pszModule));
|
{
|
||||||
|
return Encoding.UTF8.GetString((byte*)pszModule, StringLength(pszModule));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static unsafe int StringLength(byte* pszStr)
|
private static unsafe int StringLength(CHAR* pszStr)
|
||||||
{
|
{
|
||||||
int len = 0;
|
int len = 0;
|
||||||
while (*pszStr++ != 0)
|
while (*pszStr++ != 0)
|
||||||
@@ -63,14 +63,4 @@ internal static class StructMarshal
|
|||||||
{
|
{
|
||||||
return moduleEntry32.dwSize == 0;
|
return moduleEntry32.dwSize == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 判断结构实例是否为默认结构
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="uid">待测试的结构</param>
|
|
||||||
/// <returns>是否为默认结构</returns>
|
|
||||||
public static bool IsDefault(PlayerUid uid)
|
|
||||||
{
|
|
||||||
return uid.Value == default;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user