From a97aa26d795918ff4d2ee7ebb60c1c234c62c0bb Mon Sep 17 00:00:00 2001 From: Lightczx <1686188646@qq.com> Date: Wed, 6 Dec 2023 15:41:13 +0800 Subject: [PATCH] refactor options --- .../Snap.Hutao/Core/RuntimeOptions.cs | 175 +++++++----------- .../Extension/DateTimeOffsetExtension.cs | 44 ++--- .../Extension/EnumerableExtension.cs | 13 -- .../Snap.Hutao/Extension/NullableExtension.cs | 18 ++ .../Snap.Hutao/Extension/SpanExtension.cs | 4 +- .../Model/InterChange/Achievement/UIAFInfo.cs | 2 +- .../Model/InterChange/GachaLog/UIGFInfo.cs | 2 +- .../Model/InterChange/Inventory/UIIFInfo.cs | 2 +- .../Snap.Hutao/Resource/Localization/SH.resx | 2 +- .../Snap.Hutao/Service/AppOptions.cs | 106 +++-------- .../GachaLogQueryManualInputProvider.cs | 2 +- .../GachaLogQueryWebCacheProvider.cs | 2 +- .../Service/GachaLog/UIGFImportService.cs | 2 +- .../Snap.Hutao/Service/Game/LaunchOptions.cs | 95 ++++------ .../Service/Hutao/HutaoUserOptions.cs | 121 +----------- .../Hutao/HutaoUserOptionsExtension.cs | 88 +++++++++ .../Service/Hutao/HutaoUserService.cs | 4 +- .../Service/Metadata/MetadataOptions.cs | 80 +------- .../Metadata/MetadataOptionsExtension.cs | 47 +++++ .../Snap.Hutao/Service/SupportedCultures.cs | 29 +++ .../Setting/HutaoPassportViewModel.cs | 4 +- .../Snap.Hutao/Web/Hutao/IPInformation.cs | 2 +- .../SpiralAbyss/HutaoSpiralAbyssClient.cs | 2 +- 23 files changed, 360 insertions(+), 486 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoUserOptionsExtension.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataOptionsExtension.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/SupportedCultures.cs diff --git a/src/Snap.Hutao/Snap.Hutao/Core/RuntimeOptions.cs b/src/Snap.Hutao/Snap.Hutao/Core/RuntimeOptions.cs index 49c50cab..9dd3079a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/RuntimeOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/RuntimeOptions.cs @@ -1,7 +1,6 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Microsoft.Extensions.Options; using Microsoft.Web.WebView2.Core; using Microsoft.Win32; using Snap.Hutao.Core.Setting; @@ -12,28 +11,16 @@ using Windows.Storage; namespace Snap.Hutao.Core; -/// -/// 存储环境相关的选项 -/// 运行时运算得到的选项,无数据库交互 -/// [Injection(InjectAs.Singleton)] -internal sealed class RuntimeOptions : IOptions +internal sealed class RuntimeOptions { - private readonly ILogger logger; - private readonly bool isWebView2Supported; private readonly string webView2Version = SH.CoreWebView2HelperVersionUndetected; private bool? isElevated; - /// - /// 构造一个新的胡桃选项 - /// - /// 日志器 public RuntimeOptions(ILogger logger) { - this.logger = logger; - AppLaunchTime = DateTimeOffset.UtcNow; DataFolder = GetDataFolderPath(); @@ -45,117 +32,95 @@ internal sealed class RuntimeOptions : IOptions UserAgent = $"Snap Hutao/{Version}"; DeviceId = GetUniqueUserId(); - DetectWebView2Environment(ref webView2Version, ref isWebView2Supported); + DetectWebView2Environment(logger, out webView2Version, out isWebView2Supported); + + static string GetDataFolderPath() + { + string preferredPath = LocalSetting.Get(SettingKeys.DataFolderPath, string.Empty); + + if (!string.IsNullOrEmpty(preferredPath)) + { + Directory.CreateDirectory(preferredPath); + return preferredPath; + } + + // Fallback to MyDocuments + string myDocuments = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); + +#if RELEASE + // 将测试版与正式版的文件目录分离 + string folderName = Package.Current.PublisherDisplayName == "DGP Studio CI" ? "HutaoAlpha" : "Hutao"; +#else + // 使得迁移能正常生成 + string folderName = "Hutao"; +#endif + string path = Path.GetFullPath(Path.Combine(myDocuments, folderName)); + Directory.CreateDirectory(path); + return path; + } + + static string GetUniqueUserId() + { + string userName = Environment.UserName; + object? machineGuid = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\", "MachineGuid", userName); + return Convert.ToMd5HexString($"{userName}{machineGuid}"); + } + + static void DetectWebView2Environment(ILogger logger, out string webView2Version, out bool isWebView2Supported) + { + try + { + webView2Version = CoreWebView2Environment.GetAvailableBrowserVersionString(); + isWebView2Supported = true; + } + catch (FileNotFoundException ex) + { + webView2Version = SH.CoreWebView2HelperVersionUndetected; + isWebView2Supported = false; + logger.LogError(ex, "WebView2 Runtime not installed."); + } + } } - /// - /// 当前版本 - /// public Version Version { get; } - /// - /// 标准UA - /// public string UserAgent { get; } - /// - /// 安装位置 - /// public string InstalledLocation { get; } - /// - /// 数据文件夹路径 - /// public string DataFolder { get; } - /// - /// 本地缓存 - /// public string LocalCache { get; } - /// - /// 包家族名称 - /// public string FamilyName { get; } - /// - /// 设备Id - /// public string DeviceId { get; } - /// - /// WebView2 版本 - /// public string WebView2Version { get => webView2Version; } - /// - /// 是否支持 WebView2 - /// public bool IsWebView2Supported { get => isWebView2Supported; } - /// - /// 是否为提升的权限 - /// - public bool IsElevated { get => isElevated ??= GetElevated(); } + public bool IsElevated + { + get + { + return isElevated ??= GetElevated(); + + static bool GetElevated() + { + if (LocalSetting.Get(SettingKeys.OverrideElevationRequirement, false)) + { + return true; + } + + using (WindowsIdentity identity = WindowsIdentity.GetCurrent()) + { + WindowsPrincipal principal = new(identity); + return principal.IsInRole(WindowsBuiltInRole.Administrator); + } + } + } + } public DateTimeOffset AppLaunchTime { get; } - - /// - public RuntimeOptions Value { get => this; } - - private static string GetDataFolderPath() - { - string preferredPath = LocalSetting.Get(SettingKeys.DataFolderPath, string.Empty); - - if (!string.IsNullOrEmpty(preferredPath) && Directory.Exists(preferredPath)) - { - return preferredPath; - } - - string myDocument = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); -#if RELEASE - // 将测试版与正式版的文件目录分离 - string folderName = Package.Current.PublisherDisplayName == "DGP Studio CI" ? "HutaoAlpha" : "Hutao"; -#else - // 使得迁移能正常生成 - string folderName = "Hutao"; -#endif - string path = Path.GetFullPath(Path.Combine(myDocument, folderName)); - Directory.CreateDirectory(path); - return path; - } - - private static string GetUniqueUserId() - { - string userName = Environment.UserName; - object? machineGuid = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\", "MachineGuid", userName); - return Convert.ToMd5HexString($"{userName}{machineGuid}"); - } - - private static bool GetElevated() - { - if (LocalSetting.Get(SettingKeys.OverrideElevationRequirement, false)) - { - return true; - } - - using (WindowsIdentity identity = WindowsIdentity.GetCurrent()) - { - WindowsPrincipal principal = new(identity); - return principal.IsInRole(WindowsBuiltInRole.Administrator); - } - } - - private void DetectWebView2Environment(ref string webView2Version, ref bool isWebView2Supported) - { - try - { - webView2Version = CoreWebView2Environment.GetAvailableBrowserVersionString(); - isWebView2Supported = true; - } - catch (FileNotFoundException ex) - { - logger.LogError(ex, "WebView2 Runtime not installed."); - } - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/DateTimeOffsetExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/DateTimeOffsetExtension.cs index 867ef104..f31d6af8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/DateTimeOffsetExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/DateTimeOffsetExtension.cs @@ -11,35 +11,27 @@ internal static class DateTimeOffsetExtension { public static readonly DateTimeOffset DatebaseDefaultTime = new(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)); - /// - /// 从Unix时间戳转换 - /// - /// 时间戳 - /// 默认值 - /// 转换的时间 - public static DateTimeOffset FromUnixTime(long? timestamp, in DateTimeOffset defaultValue) + public static DateTimeOffset UnsafeRelaxedFromUnixTime(long? timestamp, in DateTimeOffset defaultValue) { - if (timestamp is { } value) - { - try - { - return DateTimeOffset.FromUnixTimeSeconds(value); - } - catch (ArgumentOutOfRangeException) - { - try - { - return DateTimeOffset.FromUnixTimeMilliseconds(value); - } - catch (ArgumentOutOfRangeException) - { - return defaultValue; - } - } - } - else + if (timestamp is not { } value) { return defaultValue; } + + try + { + return DateTimeOffset.FromUnixTimeSeconds(value); + } + catch (ArgumentOutOfRangeException) + { + try + { + return DateTimeOffset.FromUnixTimeMilliseconds(value); + } + catch (ArgumentOutOfRangeException) + { + return defaultValue; + } + } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs index eaa5e998..3ff7c070 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs @@ -17,19 +17,6 @@ internal static partial class EnumerableExtension return source.ElementAtOrDefault(index) ?? source.LastOrDefault(); } - /// - /// 如果传入集合不为空则原路返回, - /// 如果传入集合为空返回一个集合的空集 - /// - /// 源类型 - /// 源 - /// 源集合或空集 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IEnumerable EmptyIfNull(this IEnumerable? source) - { - return source ?? Enumerable.Empty(); - } - /// /// 寻找枚举中唯一的值,找不到时 /// 回退到首个或默认值 diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/NullableExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/NullableExtension.cs index 85f31b64..7c25a349 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/NullableExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/NullableExtension.cs @@ -17,4 +17,22 @@ internal static class NullableExtension value = default; return false; } + + public static string ToStringOrEmpty(this in T? nullable) + where T : struct + { + string? result = default; + + if (nullable.HasValue) + { + result = nullable.Value.ToString(); + } + + if (string.IsNullOrEmpty(result)) + { + result = string.Empty; + } + + return result; + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/SpanExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/SpanExtension.cs index dee1a93c..67435eb5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/SpanExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/SpanExtension.cs @@ -18,9 +18,9 @@ internal static class SpanExtension /// Span /// 最大值的下标 public static int IndexOfMax(this in ReadOnlySpan span) - where T : INumber + where T : INumber, IMinMaxValue { - T max = T.Zero; + T max = T.MinValue; int maxIndex = 0; for (int i = 0; i < span.Length; i++) { diff --git a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Achievement/UIAFInfo.cs b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Achievement/UIAFInfo.cs index e15c6147..0155e807 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Achievement/UIAFInfo.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Achievement/UIAFInfo.cs @@ -30,7 +30,7 @@ internal sealed class UIAFInfo : IMappingFrom [JsonIgnore] public DateTimeOffset ExportDateTime { - get => DateTimeOffsetExtension.FromUnixTime(ExportTimestamp, DateTimeOffset.MinValue); + get => DateTimeOffsetExtension.UnsafeRelaxedFromUnixTime(ExportTimestamp, DateTimeOffset.MinValue); } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGFInfo.cs b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGFInfo.cs index b5302513..f0eeae6e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGFInfo.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGFInfo.cs @@ -38,7 +38,7 @@ internal sealed class UIGFInfo : IMappingFrom DateTimeOffsetExtension.FromUnixTime(ExportTimestamp, DateTimeOffset.MinValue); + get => DateTimeOffsetExtension.UnsafeRelaxedFromUnixTime(ExportTimestamp, DateTimeOffset.MinValue); } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Inventory/UIIFInfo.cs b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Inventory/UIIFInfo.cs index a17ddfad..1ef79580 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Inventory/UIIFInfo.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Inventory/UIIFInfo.cs @@ -35,7 +35,7 @@ internal sealed class UIIFInfo [JsonIgnore] public DateTimeOffset ExportDateTime { - get => DateTimeOffsetExtension.FromUnixTime(ExportTimestamp, DateTimeOffset.MinValue); + get => DateTimeOffsetExtension.UnsafeRelaxedFromUnixTime(ExportTimestamp, DateTimeOffset.MinValue); } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx index f6d73cbe..e79abdea 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx @@ -2250,7 +2250,7 @@ 设备 ID - IP:{0},归属:{1} + IP:{0} 归属服务器:{1} 设备 IP diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs index 4b0f32d8..e02352d9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs @@ -11,25 +11,10 @@ using System.IO; namespace Snap.Hutao.Service; -/// -/// 应用程序选项 -/// 存储服务相关的选项 -/// [ConstructorGenerated(CallBaseConstructor = true)] [Injection(InjectAs.Singleton)] internal sealed partial class AppOptions : DbStoreOptions { - private readonly List> supportedBackdropTypesInner = CollectionsNameValue.FromEnum(); - - private readonly List> supportedCulturesInner = - [ - ToNameValue(CultureInfo.GetCultureInfo("zh-Hans")), - ToNameValue(CultureInfo.GetCultureInfo("zh-Hant")), - ToNameValue(CultureInfo.GetCultureInfo("en")), - ToNameValue(CultureInfo.GetCultureInfo("ko")), - ToNameValue(CultureInfo.GetCultureInfo("ja")), - ]; - private string? gamePath; private string? powerShellPath; private bool? isEmptyHistoryWishVisible; @@ -38,75 +23,67 @@ internal sealed partial class AppOptions : DbStoreOptions private bool? isAdvancedLaunchOptionsEnabled; private string? geetestCustomCompositeUrl; - /// - /// 游戏路径 - /// public string GamePath { get => GetOption(ref gamePath, SettingEntry.GamePath); set => SetOption(ref gamePath, SettingEntry.GamePath, value); } - /// - /// PowerShell 路径 - /// public string PowerShellPath { - get => GetOption(ref powerShellPath, SettingEntry.PowerShellPath, GetPowerShellLocationOrEmpty); + get + { + return GetOption(ref powerShellPath, SettingEntry.PowerShellPath, GetDefaultPowerShellLocationOrEmpty); + + static string GetDefaultPowerShellLocationOrEmpty() + { + string? paths = Environment.GetEnvironmentVariable("Path"); + if (!string.IsNullOrEmpty(paths)) + { + foreach (StringSegment path in new StringTokenizer(paths, [';'])) + { + if (path is { HasValue: true, Length: > 0 }) + { + if (path.Value.Contains("WindowsPowerShell", StringComparison.OrdinalIgnoreCase)) + { + return Path.Combine(path.Value, "powershell.exe"); + } + } + } + } + + return string.Empty; + } + } set => SetOption(ref powerShellPath, SettingEntry.PowerShellPath, value); } - /// - /// 游戏路径 - /// public bool IsEmptyHistoryWishVisible { get => GetOption(ref isEmptyHistoryWishVisible, SettingEntry.IsEmptyHistoryWishVisible); set => SetOption(ref isEmptyHistoryWishVisible, SettingEntry.IsEmptyHistoryWishVisible, value); } - /// - /// 所有支持的背景样式 - /// - public List> BackdropTypes { get => supportedBackdropTypesInner; } + public List> BackdropTypes { get; } = CollectionsNameValue.FromEnum(); - /// - /// 背景类型 默认 Mica - /// public BackdropType BackdropType { get => GetOption(ref backdropType, SettingEntry.SystemBackdropType, v => Enum.Parse(v), BackdropType.Mica).Value; - set => SetOption(ref backdropType, SettingEntry.SystemBackdropType, value, value => value.ToString()!); + set => SetOption(ref backdropType, SettingEntry.SystemBackdropType, value, value => value.ToStringOrEmpty()); } - /// - /// 所有支持的语言 - /// - public List> Cultures { get => supportedCulturesInner; } + public List> Cultures { get; } = SupportedCultures.Get(); - /// - /// 初始化前的语言 - /// 通过设置与获取此属性,就可以获取到与系统同步的语言 - /// - public CultureInfo PreviousCulture { get; set; } = default!; - - /// - /// 当前语言 - /// 默认为系统语言 - /// public CultureInfo CurrentCulture { get => GetOption(ref currentCulture, SettingEntry.Culture, CultureInfo.GetCultureInfo, CultureInfo.CurrentCulture); set => SetOption(ref currentCulture, SettingEntry.Culture, value, value => value.Name); } - /// - /// 是否启用高级功能 - /// DO NOT MOVE TO OTHER CLASS - /// We are binding this property in SettingPage - /// public bool IsAdvancedLaunchOptionsEnabled { + // DO NOT MOVE TO OTHER CLASS + // We use this property in SettingPage binding get => GetOption(ref isAdvancedLaunchOptionsEnabled, SettingEntry.IsAdvancedLaunchOptionsEnabled); set => SetOption(ref isAdvancedLaunchOptionsEnabled, SettingEntry.IsAdvancedLaunchOptionsEnabled, value); } @@ -117,28 +94,5 @@ internal sealed partial class AppOptions : DbStoreOptions set => SetOption(ref geetestCustomCompositeUrl, SettingEntry.GeetestCustomCompositeUrl, value); } - private static NameValue ToNameValue(CultureInfo info) - { - return new(info.NativeName, info); - } - - private static string GetPowerShellLocationOrEmpty() - { - string? paths = Environment.GetEnvironmentVariable("Path"); - if (!string.IsNullOrEmpty(paths)) - { - foreach (StringSegment path in new StringTokenizer(paths, [';'])) - { - if (path is { HasValue: true, Length: > 0 }) - { - if (path.Value.Contains("WindowsPowerShell", StringComparison.OrdinalIgnoreCase)) - { - return Path.Combine(path.Value, "powershell.exe"); - } - } - } - } - - return string.Empty; - } + internal CultureInfo PreviousCulture { get; set; } = default!; } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryManualInputProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryManualInputProvider.cs index a146c2d3..9e7b98fc 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryManualInputProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryManualInputProvider.cs @@ -33,7 +33,7 @@ internal sealed partial class GachaLogQueryManualInputProvider : IGachaLogQueryP if (query.TryGetValue("auth_appid", out string? appId) && appId is "webview_gacha") { string? queryLanguageCode = query["lang"]; - if (metadataOptions.IsCurrentLocale(queryLanguageCode)) + if (metadataOptions.LanguageCodeFitsCurrentLocale(queryLanguageCode)) { return new(true, new(queryString)); } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryWebCacheProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryWebCacheProvider.cs index b36ce4a4..247fe149 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryWebCacheProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryWebCacheProvider.cs @@ -90,7 +90,7 @@ internal sealed partial class GachaLogQueryWebCacheProvider : IGachaLogQueryProv NameValueCollection query = HttpUtility.ParseQueryString(result.TrimEnd("#/log")); string? queryLanguageCode = query["lang"]; - if (metadataOptions.IsCurrentLocale(queryLanguageCode)) + if (metadataOptions.LanguageCodeFitsCurrentLocale(queryLanguageCode)) { return new(true, new(result)); } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UIGFImportService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UIGFImportService.cs index ed63e016..6f82560e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UIGFImportService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UIGFImportService.cs @@ -37,7 +37,7 @@ internal sealed partial class UIGFImportService : IUIGFImportService // v2.1 only support CHS if (version is UIGFVersion.Major2Minor2OrLower) { - if (!metadataOptions.IsCurrentLocale(uigf.Info.Language)) + if (!metadataOptions.LanguageCodeFitsCurrentLocale(uigf.Info.Language)) { string message = SH.FormatServiceGachaUIGFImportLanguageNotMatch(uigf.Info.Language, metadataOptions.LanguageCode); ThrowHelper.InvalidOperation(message); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs index 5876b870..4241bba3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs @@ -40,10 +40,6 @@ internal sealed class LaunchOptions : DbStoreOptions private bool? useStarwardPlayTimeStatistics; private bool? setDiscordActivityWhenPlaying; - /// - /// 构造一个新的启动游戏选项 - /// - /// 服务提供器 public LaunchOptions(IServiceProvider serviceProvider) : base(serviceProvider) { @@ -53,47 +49,66 @@ internal sealed class LaunchOptions : DbStoreOptions InitializeMonitors(Monitors); InitializeScreenFps(out primaryScreenFps); + + static void InitializeMonitors(List> monitors) + { + try + { + // This list can't use foreach + // https://github.com/microsoft/CsWinRT/issues/747 + IReadOnlyList displayAreas = DisplayArea.FindAll(); + for (int i = 0; i < displayAreas.Count; i++) + { + DisplayArea displayArea = displayAreas[i]; + int index = i + 1; + monitors.Add(new($"{displayArea.DisplayId.Value:X8}:{index}", index)); + } + } + catch + { + monitors.Clear(); + } + } + + static void InitializeScreenFps(out int fps) + { + HDC hDC = default; + try + { + hDC = GetDC(HWND.Null); + fps = GetDeviceCaps(hDC, GET_DEVICE_CAPS_INDEX.VREFRESH); + } + finally + { + _ = ReleaseDC(HWND.Null, hDC); + } + } } - /// - /// 是否启用启动参数 - /// public bool IsEnabled { get => GetOption(ref isEnabled, SettingEntry.LaunchIsLaunchOptionsEnabled, true); set => SetOption(ref isEnabled, SettingEntry.LaunchIsLaunchOptionsEnabled, value); } - /// - /// 是否全屏 - /// public bool IsFullScreen { get => GetOption(ref isFullScreen, SettingEntry.LaunchIsFullScreen); set => SetOption(ref isFullScreen, SettingEntry.LaunchIsFullScreen, value); } - /// - /// 是否无边框 - /// public bool IsBorderless { get => GetOption(ref isBorderless, SettingEntry.LaunchIsBorderless); set => SetOption(ref isBorderless, SettingEntry.LaunchIsBorderless, value); } - /// - /// 是否独占全屏 - /// public bool IsExclusive { get => GetOption(ref isExclusive, SettingEntry.LaunchIsExclusive); set => SetOption(ref isExclusive, SettingEntry.LaunchIsExclusive, value); } - /// - /// 屏幕宽度 - /// public int ScreenWidth { get => GetOption(ref screenWidth, SettingEntry.LaunchScreenWidth, primaryScreenWidth); @@ -106,9 +121,6 @@ internal sealed class LaunchOptions : DbStoreOptions set => SetOption(ref isScreenWidthEnabled, SettingEntry.LaunchIsScreenWidthEnabled, value); } - /// - /// 屏幕高度 - /// public int ScreenHeight { get => GetOption(ref screenHeight, SettingEntry.LaunchScreenHeight, primaryScreenHeight); @@ -121,32 +133,20 @@ internal sealed class LaunchOptions : DbStoreOptions set => SetOption(ref isScreenHeightEnabled, SettingEntry.LaunchIsScreenHeightEnabled, value); } - /// - /// 是否全屏 - /// public bool UnlockFps { get => GetOption(ref unlockFps, SettingEntry.LaunchUnlockFps); set => SetOption(ref unlockFps, SettingEntry.LaunchUnlockFps, value); } - /// - /// 目标帧率 - /// public int TargetFps { get => GetOption(ref targetFps, SettingEntry.LaunchTargetFps, primaryScreenFps); set => SetOption(ref targetFps, SettingEntry.LaunchTargetFps, value); } - /// - /// 所有监视器 - /// public List> Monitors { get; } = []; - /// - /// 目标帧率 - /// [AllowNull] public NameValue Monitor { @@ -196,31 +196,4 @@ internal sealed class LaunchOptions : DbStoreOptions get => GetOption(ref setDiscordActivityWhenPlaying, SettingEntry.LaunchSetDiscordActivityWhenPlaying, true); set => SetOption(ref setDiscordActivityWhenPlaying, SettingEntry.LaunchSetDiscordActivityWhenPlaying, value); } - - private static void InitializeMonitors(List> monitors) - { - // This list can't use foreach - // https://github.com/microsoft/CsWinRT/issues/747 - IReadOnlyList displayAreas = DisplayArea.FindAll(); - for (int i = 0; i < displayAreas.Count; i++) - { - DisplayArea displayArea = displayAreas[i]; - int index = i + 1; - monitors.Add(new($"{displayArea.DisplayId.Value:X8}:{index}", index)); - } - } - - private static void InitializeScreenFps(out int fps) - { - HDC hDC = default; - try - { - hDC = GetDC(HWND.Null); - fps = GetDeviceCaps(hDC, GET_DEVICE_CAPS_INDEX.VREFRESH); - } - finally - { - _ = ReleaseDC(HWND.Null, hDC); - } - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoUserOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoUserOptions.cs index 71988df7..71fabc3c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoUserOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoUserOptions.cs @@ -2,141 +2,38 @@ // Licensed under the MIT license. using CommunityToolkit.Mvvm.ComponentModel; -using Microsoft.Extensions.Options; -using Snap.Hutao.Core.Setting; -using Snap.Hutao.Web.Hutao; -using System.Globalization; -using System.Text.RegularExpressions; namespace Snap.Hutao.Service.Hutao; -/// -/// 胡桃用户选项 -/// [Injection(InjectAs.Singleton)] -internal sealed class HutaoUserOptions : ObservableObject, IOptions +internal sealed class HutaoUserOptions : ObservableObject { - private readonly TaskCompletionSource initializedTaskCompletionSource = new(); + private readonly TaskCompletionSource initialization = new(); + private string? userName = SH.ViewServiceHutaoUserLoginOrRegisterHint; - private string? token; private bool isLoggedIn; - private bool isHutaoCloudServiceAllowed; + private bool isCloudServiceAllowed; private bool isLicensedDeveloper; + private bool isMaintainer; private string? gachaLogExpireAt; private string? gachaLogExpireAtSlim; - private bool isMaintainer; + private string? token; - /// - /// 用户名 - /// public string? UserName { get => userName; set => SetProperty(ref userName, value); } - /// - /// 真正的用户名 - /// - public string? ActualUserName { get => IsLoggedIn ? UserName : null; } - - /// - /// 是否已登录 - /// public bool IsLoggedIn { get => isLoggedIn; set => SetProperty(ref isLoggedIn, value); } - /// - /// 胡桃云服务是否可用 - /// - public bool IsCloudServiceAllowed { get => isHutaoCloudServiceAllowed; set => SetProperty(ref isHutaoCloudServiceAllowed, value); } + public bool IsCloudServiceAllowed { get => isCloudServiceAllowed; set => SetProperty(ref isCloudServiceAllowed, value); } - /// - /// 是否为开发者 - /// public bool IsLicensedDeveloper { get => isLicensedDeveloper; set => SetProperty(ref isLicensedDeveloper, value); } public bool IsMaintainer { get => isMaintainer; set => SetProperty(ref isMaintainer, value); } - /// - /// 祈愿记录服务到期时间 - /// public string? GachaLogExpireAt { get => gachaLogExpireAt; set => SetProperty(ref gachaLogExpireAt, value); } public string? GachaLogExpireAtSlim { get => gachaLogExpireAtSlim; set => SetProperty(ref gachaLogExpireAtSlim, value); } - /// - public HutaoUserOptions Value { get => this; } + internal string? Token { get => token; set => token = value; } - public async ValueTask PostLoginSucceedAsync(HutaoPassportClient passportClient, ITaskContext taskContext, string username, string password, string? token) - { - LocalSetting.Set(SettingKeys.PassportUserName, username); - LocalSetting.Set(SettingKeys.PassportPassword, password); - - await taskContext.SwitchToMainThreadAsync(); - UserName = username; - this.token = token; - IsLoggedIn = true; - initializedTaskCompletionSource.TrySetResult(); - - await taskContext.SwitchToBackgroundAsync(); - Web.Response.Response userInfoResponse = await passportClient.GetUserInfoAsync(default).ConfigureAwait(false); - if (userInfoResponse.IsOk()) - { - await taskContext.SwitchToMainThreadAsync(); - UpdateUserInfo(userInfoResponse.Data); - return true; - } - - return false; - } - - public void LogoutOrUnregister() - { - LocalSetting.Set(SettingKeys.PassportUserName, string.Empty); - LocalSetting.Set(SettingKeys.PassportPassword, string.Empty); - - UserName = null; - token = null; - IsLoggedIn = false; - ClearUserInfo(); - } - - /// - /// 登录失败 - /// - public void LoginFailed() - { - UserName = SH.ViewServiceHutaoUserLoginFailHint; - initializedTaskCompletionSource.TrySetResult(); - } - - public void SkipLogin() - { - initializedTaskCompletionSource.TrySetResult(); - } - - /// - /// 刷新用户信息 - /// - /// 用户信息 - public void UpdateUserInfo(UserInfo userInfo) - { - IsLicensedDeveloper = userInfo.IsLicensedDeveloper; - IsMaintainer = userInfo.IsMaintainer; - string unescaped = Regex.Unescape(SH.ServiceHutaoUserGachaLogExpiredAt); - GachaLogExpireAt = string.Format(CultureInfo.CurrentCulture, unescaped, userInfo.GachaLogExpireAt); - GachaLogExpireAtSlim = $"{userInfo.GachaLogExpireAt:yyyy.MM.dd HH:mm:ss}"; - IsCloudServiceAllowed = IsLicensedDeveloper || userInfo.GachaLogExpireAt > DateTimeOffset.UtcNow; - } - - public async ValueTask GetTokenAsync() - { - await initializedTaskCompletionSource.Task.ConfigureAwait(false); - return token; - } - - private void ClearUserInfo() - { - IsLicensedDeveloper = false; - IsMaintainer = false; - GachaLogExpireAt = null; - GachaLogExpireAtSlim = null; - IsCloudServiceAllowed = false; - } + internal TaskCompletionSource Initialization { get => initialization; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoUserOptionsExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoUserOptionsExtension.cs new file mode 100644 index 00000000..d7bf4d84 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoUserOptionsExtension.cs @@ -0,0 +1,88 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core.Setting; +using Snap.Hutao.Web.Hutao; +using System.Globalization; +using System.Text.RegularExpressions; + +namespace Snap.Hutao.Service.Hutao; + +internal static class HutaoUserOptionsExtension +{ + public static string? GetActualUserName(this HutaoUserOptions options) + { + return options.IsLoggedIn ? options.UserName : null; + } + + public static async ValueTask GetTokenAsync(this HutaoUserOptions options) + { + await options.Initialization.Task.ConfigureAwait(false); + return options.Token; + } + + public static async ValueTask PostLoginSucceedAsync(this HutaoUserOptions options, HutaoPassportClient passportClient, ITaskContext taskContext, string username, string password, string? token) + { + LocalSetting.Set(SettingKeys.PassportUserName, username); + LocalSetting.Set(SettingKeys.PassportPassword, password); + + await taskContext.SwitchToMainThreadAsync(); + options.UserName = username; + options.Token = token; + options.IsLoggedIn = true; + options.Initialization.TrySetResult(); + + await taskContext.SwitchToBackgroundAsync(); + Web.Response.Response userInfoResponse = await passportClient.GetUserInfoAsync(default).ConfigureAwait(false); + if (userInfoResponse.IsOk()) + { + await taskContext.SwitchToMainThreadAsync(); + UpdateUserInfo(options, userInfoResponse.Data); + return true; + } + + return false; + + static void UpdateUserInfo(HutaoUserOptions options, UserInfo userInfo) + { + options.IsLicensedDeveloper = userInfo.IsLicensedDeveloper; + options.IsMaintainer = userInfo.IsMaintainer; + + options.IsCloudServiceAllowed = options.IsLicensedDeveloper || userInfo.GachaLogExpireAt > DateTimeOffset.UtcNow; + string unescaped = Regex.Unescape(SH.ServiceHutaoUserGachaLogExpiredAt); + options.GachaLogExpireAt = string.Format(CultureInfo.CurrentCulture, unescaped, userInfo.GachaLogExpireAt); + options.GachaLogExpireAtSlim = $"{userInfo.GachaLogExpireAt:yyyy.MM.dd HH:mm:ss}"; + } + } + + public static void PostLogoutOrUnregister(this HutaoUserOptions options) + { + LocalSetting.Set(SettingKeys.PassportUserName, string.Empty); + LocalSetting.Set(SettingKeys.PassportPassword, string.Empty); + + options.UserName = null; + options.Token = null; + options.IsLoggedIn = false; + ClearUserInfo(options); + + static void ClearUserInfo(HutaoUserOptions options) + { + options.IsLicensedDeveloper = false; + options.IsMaintainer = false; + options.GachaLogExpireAt = null; + options.GachaLogExpireAtSlim = null; + options.IsCloudServiceAllowed = false; + } + } + + public static void PostLoginFailed(this HutaoUserOptions options) + { + options.UserName = SH.ViewServiceHutaoUserLoginFailHint; + options.Initialization.TrySetResult(); + } + + public static void PostLoginSkipped(this HutaoUserOptions options) + { + options.Initialization.TrySetResult(); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoUserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoUserService.cs index 1156614b..34527a60 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoUserService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoUserService.cs @@ -36,7 +36,7 @@ internal sealed partial class HutaoUserService : IHutaoUserService, IHutaoUserSe if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password)) { - options.SkipLogin(); + options.PostLoginSkipped(); } else { @@ -52,7 +52,7 @@ internal sealed partial class HutaoUserService : IHutaoUserService, IHutaoUserSe else { await taskContext.SwitchToMainThreadAsync(); - options.LoginFailed(); + options.PostLoginFailed(); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataOptions.cs index 5fe5a13b..3bb1b91c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataOptions.cs @@ -1,19 +1,14 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Microsoft.Extensions.Options; using Snap.Hutao.Core; -using System.Globalization; using System.IO; namespace Snap.Hutao.Service.Metadata; -/// -/// 元数据选项 -/// [ConstructorGenerated] [Injection(InjectAs.Singleton)] -internal sealed partial class MetadataOptions : IOptions +internal sealed partial class MetadataOptions { private readonly AppOptions appOptions; private readonly RuntimeOptions hutaoOptions; @@ -22,9 +17,6 @@ internal sealed partial class MetadataOptions : IOptions private string? fallbackDataFolder; private string? localizedDataFolder; - /// - /// 中文数据文件夹 - /// public string FallbackDataFolder { get @@ -39,9 +31,6 @@ internal sealed partial class MetadataOptions : IOptions } } - /// - /// 本地化数据文件夹 - /// public string LocalizedDataFolder { get @@ -56,17 +45,11 @@ internal sealed partial class MetadataOptions : IOptions } } - /// - /// 当前使用的元数据本地化名称 - /// public string LocaleName { - get => localeName ??= GetLocaleName(appOptions.CurrentCulture); + get => localeName ??= MetadataOptionsExtension.GetLocaleName(appOptions.CurrentCulture); } - /// - /// 当前语言代码 - /// public string LanguageCode { get @@ -79,63 +62,4 @@ internal sealed partial class MetadataOptions : IOptions throw new KeyNotFoundException($"Invalid localeName: '{LocaleName}'"); } } - - /// - public MetadataOptions Value { get => this; } - - /// - /// 获取语言名称 - /// - /// 语言信息 - /// 元数据语言名称 - public static string GetLocaleName(CultureInfo cultureInfo) - { - while (true) - { - if (LocaleNames.TryGetLocaleNameFromLanguageName(cultureInfo.Name, out string? localeName)) - { - return localeName; - } - else - { - cultureInfo = cultureInfo.Parent; - } - } - } - - /// - /// 检查是否为当前语言名称 - /// - /// 语言代码 - /// 是否为当前语言名称 - public bool IsCurrentLocale(string? languageCode) - { - if (string.IsNullOrEmpty(languageCode)) - { - return false; - } - - CultureInfo cultureInfo = CultureInfo.GetCultureInfo(languageCode); - return GetLocaleName(cultureInfo) == LocaleName; - } - - /// - /// 获取本地的本地化元数据文件 - /// - /// 文件名 - /// 本地的本地化元数据文件 - public string GetLocalizedLocalFile(string fileNameWithExtension) - { - return Path.Combine(LocalizedDataFolder, fileNameWithExtension); - } - - /// - /// 获取服务器上的本地化元数据文件 - /// - /// 文件名 - /// 服务器上的本地化元数据文件 - public string GetLocalizedRemoteFile(string fileNameWithExtension) - { - return Web.HutaoEndpoints.Metadata(LocaleName, fileNameWithExtension); - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataOptionsExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataOptionsExtension.cs new file mode 100644 index 00000000..59c17e93 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataOptionsExtension.cs @@ -0,0 +1,47 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using System.Globalization; +using System.IO; + +namespace Snap.Hutao.Service.Metadata; + +internal static class MetadataOptionsExtension +{ + public static string GetLocalizedLocalFile(this MetadataOptions options, string fileNameWithExtension) + { + return Path.Combine(options.LocalizedDataFolder, fileNameWithExtension); + } + + public static string GetLocalizedRemoteFile(this MetadataOptions options, string fileNameWithExtension) + { + return Web.HutaoEndpoints.Metadata(options.LocaleName, fileNameWithExtension); + } + + public static bool LanguageCodeFitsCurrentLocale(this MetadataOptions options, string? languageCode) + { + if (string.IsNullOrEmpty(languageCode)) + { + return false; + } + + // We want to make sure code fits in 1 of 15 metadata locales + CultureInfo cultureInfo = CultureInfo.GetCultureInfo(languageCode); + return GetLocaleName(cultureInfo) == options.LocaleName; + } + + internal static string GetLocaleName(CultureInfo cultureInfo) + { + while (true) + { + if (LocaleNames.TryGetLocaleNameFromLanguageName(cultureInfo.Name, out string? localeName)) + { + return localeName; + } + else + { + cultureInfo = cultureInfo.Parent; + } + } + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/SupportedCultures.cs b/src/Snap.Hutao/Snap.Hutao/Service/SupportedCultures.cs new file mode 100644 index 00000000..8e352ef8 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/SupportedCultures.cs @@ -0,0 +1,29 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model; +using System.Globalization; + +namespace Snap.Hutao.Service; + +internal static class SupportedCultures +{ + private static readonly List> Cultures = + [ + ToNameValue(CultureInfo.GetCultureInfo("zh-Hans")), + ToNameValue(CultureInfo.GetCultureInfo("zh-Hant")), + ToNameValue(CultureInfo.GetCultureInfo("en")), + ToNameValue(CultureInfo.GetCultureInfo("ko")), + ToNameValue(CultureInfo.GetCultureInfo("ja")), + ]; + + public static List> Get() + { + return Cultures; + } + + private static NameValue ToNameValue(CultureInfo info) + { + return new(info.NativeName, info); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/HutaoPassportViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/HutaoPassportViewModel.cs index 22ce7a2b..e06e0bc7 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/HutaoPassportViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/HutaoPassportViewModel.cs @@ -77,7 +77,7 @@ internal sealed partial class HutaoPassportViewModel : Abstraction.ViewModel infoBarService.Information(response.GetLocalizationMessageOrMessage()); await taskContext.SwitchToMainThreadAsync(); - hutaoUserOptions.LogoutOrUnregister(); + hutaoUserOptions.PostLogoutOrUnregister(); } } } @@ -110,7 +110,7 @@ internal sealed partial class HutaoPassportViewModel : Abstraction.ViewModel [Command("LogoutCommand")] private void LogoutAsync() { - hutaoUserOptions.LogoutOrUnregister(); + hutaoUserOptions.PostLogoutOrUnregister(); } [Command("ResetPasswordCommand")] diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/IPInformation.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/IPInformation.cs index 1ce1379d..8546fc7c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/IPInformation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/IPInformation.cs @@ -8,7 +8,7 @@ internal sealed class IPInformation public static IPInformation Default { get; } = new() { Ip = "Unknown", - Division = "Unknown" + Division = "Unknown", }; [JsonPropertyName("ip")] diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/SpiralAbyss/HutaoSpiralAbyssClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/SpiralAbyss/HutaoSpiralAbyssClient.cs index 5283549c..d4aa1546 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/SpiralAbyss/HutaoSpiralAbyssClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/SpiralAbyss/HutaoSpiralAbyssClient.cs @@ -233,7 +233,7 @@ internal sealed partial class HutaoSpiralAbyssClient if (spiralAbyssResponse.IsOk()) { HutaoUserOptions options = serviceProvider.GetRequiredService(); - return new(userAndUid.Uid.Value, charactersResponse.Data.Avatars, spiralAbyssResponse.Data, options.ActualUserName); + return new(userAndUid.Uid.Value, charactersResponse.Data.Avatars, spiralAbyssResponse.Data, options.GetActualUserName()); } } }