diff --git a/src/Snap.Hutao/.editorconfig b/src/Snap.Hutao/.editorconfig index fc537b99..0bd4841e 100644 --- a/src/Snap.Hutao/.editorconfig +++ b/src/Snap.Hutao/.editorconfig @@ -108,7 +108,9 @@ dotnet_naming_style.pascal_case.capitalization = pascal_case dotnet_diagnostic.SA1629.severity = none dotnet_diagnostic.SA1642.severity = none +dotnet_diagnostic.IDE0005.severity = warning dotnet_diagnostic.IDE0060.severity = none +dotnet_diagnostic.IDE0290.severity = none # SA1208: System using directives should be placed before other using directives dotnet_diagnostic.SA1208.severity = none @@ -322,6 +324,8 @@ dotnet_diagnostic.CA2227.severity = suggestion # CA2251: 使用 “string.Equals” dotnet_diagnostic.CA2251.severity = suggestion +csharp_style_prefer_primary_constructors = true:suggestion +dotnet_diagnostic.SA1010.severity = none [*.vb] #### 命名样式 #### diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/AttributeGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/AttributeGenerator.cs index 48c1846c..da43e6d7 100644 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/AttributeGenerator.cs +++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/AttributeGenerator.cs @@ -140,6 +140,8 @@ internal sealed class AttributeGenerator : IIncrementalGenerator public InjectionAttribute(InjectAs injectAs, Type interfaceType) { } + + public object Key { get; set; } } """; context.AddSource("Snap.Hutao.Core.DependencyInjection.Annotation.Attributes.g.cs", coreDependencyInjectionAnnotations); diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/DependencyPropertyGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/DependencyPropertyGenerator.cs index 66744fc5..8d0d7748 100644 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/DependencyPropertyGenerator.cs +++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/DependencyPropertyGenerator.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Snap.Hutao.SourceGeneration.Primitive; using System.Collections.Generic; @@ -68,7 +69,8 @@ internal sealed class DependencyPropertyGenerator : IIncrementalGenerator string propertyName = (string)arguments[0].Value!; string propertyType = arguments[1].Value!.ToString(); - string defaultValue = GetLiteralString(arguments.ElementAtOrDefault(2)) ?? "default"; + string defaultValue = arguments.ElementAtOrDefault(2).ToCSharpString() ?? "default"; + defaultValue = defaultValue == "null" ? "default" : defaultValue; string propertyChangedCallback = arguments.ElementAtOrDefault(3) is { IsNull: false } arg3 ? $", {arg3.Value}" : string.Empty; string code; @@ -125,25 +127,4 @@ internal sealed class DependencyPropertyGenerator : IIncrementalGenerator production.AddSource($"{normalizedClassName}.{propertyName}.g.cs", code); } } - - private static string? GetLiteralString(TypedConstant typedConstant) - { - if (typedConstant.IsNull) - { - return default; - } - - if (typedConstant.Value is bool boolValue) - { - return boolValue ? "true" : "false"; - } - - string result = typedConstant.Value!.ToString(); - if (string.IsNullOrEmpty(result)) - { - return default; - } - - return result; - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/HttpClientGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/HttpClientGenerator.cs index 112993fd..5df9b4c1 100644 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/HttpClientGenerator.cs +++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/HttpClientGenerator.cs @@ -60,7 +60,6 @@ internal sealed class HttpClientGenerator : IIncrementalGenerator // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. - using Snap.Hutao.Web.Hoyolab.DynamicSecret; using System.Net.Http; namespace Snap.Hutao.Core.DependencyInjection; @@ -86,7 +85,7 @@ internal sealed class HttpClientGenerator : IIncrementalGenerator private static void FillUpWithAddHttpClient(StringBuilder sourceBuilder, SourceProductionContext production, ImmutableArray contexts) { - List lines = new(); + List lines = []; StringBuilder lineBuilder = new(); foreach (GeneratorSyntaxContext2 context in contexts.DistinctBy(c => c.Symbol.ToDisplayString())) diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/InjectionGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/InjectionGenerator.cs index 6d51142e..7aa07a88 100644 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/InjectionGenerator.cs +++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/InjectionGenerator.cs @@ -81,7 +81,7 @@ internal sealed class InjectionGenerator : IIncrementalGenerator private static void FillUpWithAddServices(StringBuilder sourceBuilder, SourceProductionContext production, ImmutableArray contexts) { - List lines = new(); + List lines = []; StringBuilder lineBuilder = new(); foreach (GeneratorSyntaxContext2 context in contexts.DistinctBy(c => c.Symbol.ToDisplayString())) @@ -92,17 +92,29 @@ internal sealed class InjectionGenerator : IIncrementalGenerator ImmutableArray arguments = injectionInfo.ConstructorArguments; string injectAsName = arguments[0].ToCSharpString(); - switch (injectAsName) + + bool hasKey = injectionInfo.TryGetNamedArgumentValue("Key", out TypedConstant key); + + switch (injectAsName, hasKey) { - case InjectAsSingletonName: + case (InjectAsSingletonName, false): lineBuilder.Append(" services.AddSingleton<"); break; - case InjectAsTransientName: + case (InjectAsSingletonName, true): + lineBuilder.Append(" services.AddKeyedSingleton<"); + break; + case (InjectAsTransientName, false): lineBuilder.Append(" services.AddTransient<"); break; - case InjectAsScopedName: + case (InjectAsTransientName, true): + lineBuilder.Append(" services.AddKeyedTransient<"); + break; + case (InjectAsScopedName, false): lineBuilder.Append(" services.AddScoped<"); break; + case (InjectAsScopedName, true): + lineBuilder.Append(" services.AddKeyedScoped<"); + break; default: production.ReportDiagnostic(Diagnostic.Create(invalidInjectionDescriptor, context.Context.Node.GetLocation(), injectAsName)); break; @@ -113,7 +125,14 @@ internal sealed class InjectionGenerator : IIncrementalGenerator lineBuilder.Append($"{arguments[1].Value}, "); } - lineBuilder.Append($"{context.Symbol.ToDisplayString()}>();"); + if (hasKey) + { + lineBuilder.Append($"{context.Symbol.ToDisplayString()}>({key.ToCSharpString()});"); + } + else + { + lineBuilder.Append($"{context.Symbol.ToDisplayString()}>();"); + } lines.Add(lineBuilder.ToString()); } diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/JsonParser.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/JsonParser.cs index 1aed7884..24d06d4e 100644 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/JsonParser.cs +++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/JsonParser.cs @@ -41,10 +41,10 @@ public static class JsonParser public static T? FromJson(this string json) { // Initialize, if needed, the ThreadStatic variables - propertyInfoCache ??= new Dictionary>(); - fieldInfoCache ??= new Dictionary>(); - stringBuilder ??= new StringBuilder(); - splitArrayPool ??= new Stack>(); + propertyInfoCache ??= []; + fieldInfoCache ??= []; + stringBuilder ??= new(); + splitArrayPool ??= []; // Remove all whitespace not within strings to make parsing simpler stringBuilder.Length = 0; @@ -99,7 +99,7 @@ public static class JsonParser // Splits { :, : } and [ , ] into a list of strings private static List Split(string json) { - List splitArray = splitArrayPool!.Count > 0 ? splitArrayPool.Pop() : new List(); + List splitArray = splitArrayPool!.Count > 0 ? splitArrayPool.Pop() : []; splitArray.Clear(); if (json.Length == 2) { diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/AttributeDataExtension.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/AttributeDataExtension.cs index 91e8b326..98496f2f 100644 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/AttributeDataExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/AttributeDataExtension.cs @@ -3,6 +3,7 @@ using Microsoft.CodeAnalysis; using System; +using System.Collections.Generic; using System.Linq; namespace Snap.Hutao.SourceGeneration.Primitive; @@ -13,4 +14,19 @@ internal static class AttributeDataExtension { return data.NamedArguments.Any(a => a.Key == key && predicate((TValue)a.Value.Value!)); } + + public static bool TryGetNamedArgumentValue(this AttributeData data, string key, out TypedConstant value) + { + foreach (KeyValuePair pair in data.NamedArguments) + { + if (pair.Key == key) + { + value = pair.Value; + return true; + } + } + + value = default; + return false; + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/EnumerableExtension.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/EnumerableExtension.cs index 05ee1f6a..544504a3 100644 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/EnumerableExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/EnumerableExtension.cs @@ -20,7 +20,7 @@ internal static class EnumerableExtension if (enumerator.MoveNext()) { - HashSet set = new(); + HashSet set = []; do { diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Resx/ResxGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Resx/ResxGenerator.cs index 17979d76..d9670fca 100644 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Resx/ResxGenerator.cs +++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Resx/ResxGenerator.cs @@ -39,44 +39,44 @@ public sealed class ResxGenerator : IIncrementalGenerator private static void Execute(SourceProductionContext context, AnalyzerConfigOptionsProvider options, string? assemblyName, bool supportNullableReferenceTypes, ImmutableArray files) { // Group additional file by resource kind ((a.resx, a.en.resx, a.en-us.resx), (b.resx, b.en-us.resx)) - List> resxGroups = files + IOrderedEnumerable> group = files .GroupBy(file => GetResourceName(file.Path), StringComparer.OrdinalIgnoreCase) - .OrderBy(x => x.Key, StringComparer.Ordinal) - .ToList(); + .OrderBy(x => x.Key, StringComparer.Ordinal); + List> resxGroups = [.. group]; - foreach (IGrouping? resxGroug in resxGroups) + foreach (IGrouping? resxGroup in resxGroups) { - string? rootNamespaceConfiguration = GetMetadataValue(context, options, "RootNamespace", resxGroug); - string? projectDirConfiguration = GetMetadataValue(context, options, "ProjectDir", resxGroug); - string? namespaceConfiguration = GetMetadataValue(context, options, "Namespace", "DefaultResourcesNamespace", resxGroug); - string? resourceNameConfiguration = GetMetadataValue(context, options, "ResourceName", globalName: null, resxGroug); - string? classNameConfiguration = GetMetadataValue(context, options, "ClassName", globalName: null, resxGroug); + string? rootNamespaceConfiguration = GetMetadataValue(context, options, "RootNamespace", resxGroup); + string? projectDirConfiguration = GetMetadataValue(context, options, "ProjectDir", resxGroup); + string? namespaceConfiguration = GetMetadataValue(context, options, "Namespace", "DefaultResourcesNamespace", resxGroup); + string? resourceNameConfiguration = GetMetadataValue(context, options, "ResourceName", globalName: null, resxGroup); + string? classNameConfiguration = GetMetadataValue(context, options, "ClassName", globalName: null, resxGroup); string rootNamespace = rootNamespaceConfiguration ?? assemblyName ?? ""; string projectDir = projectDirConfiguration ?? assemblyName ?? ""; - string? defaultResourceName = ComputeResourceName(rootNamespace, projectDir, resxGroug.Key); - string? defaultNamespace = ComputeNamespace(rootNamespace, projectDir, resxGroug.Key); + string? defaultResourceName = ComputeResourceName(rootNamespace, projectDir, resxGroup.Key); + string? defaultNamespace = ComputeNamespace(rootNamespace, projectDir, resxGroup.Key); string? ns = namespaceConfiguration ?? defaultNamespace; string? resourceName = resourceNameConfiguration ?? defaultResourceName; - string className = classNameConfiguration ?? ToCSharpNameIdentifier(Path.GetFileName(resxGroug.Key)); + string className = classNameConfiguration ?? ToCSharpNameIdentifier(Path.GetFileName(resxGroup.Key)); if (ns == null) { - context.ReportDiagnostic(Diagnostic.Create(InvalidPropertiesForNamespace, location: null, resxGroug.First().Path)); + context.ReportDiagnostic(Diagnostic.Create(InvalidPropertiesForNamespace, location: null, resxGroup.First().Path)); } if (resourceName == null) { - context.ReportDiagnostic(Diagnostic.Create(InvalidPropertiesForResourceName, location: null, resxGroug.First().Path)); + context.ReportDiagnostic(Diagnostic.Create(InvalidPropertiesForResourceName, location: null, resxGroup.First().Path)); } - List? entries = LoadResourceFiles(context, resxGroug); + List? entries = LoadResourceFiles(context, resxGroup); string content = $""" // Debug info: - // key: {resxGroug.Key} - // files: {string.Join(", ", resxGroug.Select(f => f.Path))} + // key: {resxGroup.Key} + // files: {string.Join(", ", resxGroup.Select(f => f.Path))} // RootNamespace (metadata): {rootNamespaceConfiguration} // ProjectDir (metadata): {projectDirConfiguration} // Namespace / DefaultResourcesNamespace (metadata): {namespaceConfiguration} @@ -97,7 +97,7 @@ public sealed class ResxGenerator : IIncrementalGenerator content += GenerateCode(ns, className, resourceName, entries, supportNullableReferenceTypes); } - context.AddSource($"{Path.GetFileName(resxGroug.Key)}.resx.g.cs", SourceText.From(content, Encoding.UTF8)); + context.AddSource($"{Path.GetFileName(resxGroup.Key)}.resx.g.cs", SourceText.From(content, Encoding.UTF8)); } } @@ -285,7 +285,10 @@ public sealed class ResxGenerator : IIncrementalGenerator if (!entry.IsFileRef) { - summary.Add(new XElement("para", $"Value: \"{entry.Value}\".")); + foreach((string? each, string locale) in entry.Values.Zip(entry.Locales,(x,y)=>(x,y))) + { + summary.Add(new XElement("para", $"{GetStringWithPadding(locale, 8)} Value: \"{each}\"")); + } } string comment = summary.ToString().Replace("\r\n", "\r\n /// ", StringComparison.Ordinal); @@ -299,9 +302,9 @@ public sealed class ResxGenerator : IIncrementalGenerator """); - if (entry.Value != null) + if (entry.Values.FirstOrDefault() is string value) { - int args = Regex.Matches(entry.Value, "\\{(?[0-9]+)(\\:[^}]*)?\\}", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant) + int args = Regex.Matches(value, "\\{(?[0-9]+)(\\:[^}]*)?\\}", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant) .Cast() .Select(m => int.Parse(m.Groups["num"].Value, CultureInfo.InvariantCulture)) .Distinct() @@ -360,6 +363,16 @@ public sealed class ResxGenerator : IIncrementalGenerator return sb.ToString(); } + private static string GetStringWithPadding(string source, int length) + { + if (source.Length >= length) + { + return source; + } + + return source + new string('_', length - source.Length); + } + private static string? ComputeResourceName(string rootNamespace, string projectDir, string resourcePath) { string fullProjectDir = EnsureEndSeparator(Path.GetFullPath(projectDir)); @@ -400,11 +413,11 @@ public sealed class ResxGenerator : IIncrementalGenerator private static List? LoadResourceFiles(SourceProductionContext context, IGrouping resxGroug) { - List entries = new(); + List entries = []; foreach (AdditionalText? entry in resxGroug.OrderBy(file => file.Path, StringComparer.Ordinal)) { SourceText? content = entry.GetText(context.CancellationToken); - if (content == null) + if (content is null) { continue; } @@ -423,10 +436,12 @@ public sealed class ResxGenerator : IIncrementalGenerator if (existingEntry != null) { existingEntry.Comment ??= comment; + existingEntry.Values.Add(value); + existingEntry.Locales.Add(GetLocaleName(entry.Path)); } else { - entries.Add(new ResxEntry { Name = name, Value = value, Comment = comment, Type = type }); + entries.Add(new() { Name = name, Values = [value], Locales = [GetLocaleName(entry.Path)], Comment = comment, Type = type }); } } } @@ -532,15 +547,28 @@ public sealed class ResxGenerator : IIncrementalGenerator return pathWithoutExtension; } - return Regex.IsMatch(pathWithoutExtension.Substring(indexOf + 1), "^[a-zA-Z]{2}(-[a-zA-Z]{2})?$", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant, TimeSpan.FromSeconds(1)) + return Regex.IsMatch(pathWithoutExtension.Substring(indexOf + 1), "^[a-zA-Z]{2}(-[a-zA-Z]{2,4})?$", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant, TimeSpan.FromSeconds(1)) ? pathWithoutExtension.Substring(0, indexOf) : pathWithoutExtension; } + private static string GetLocaleName(string path) + { + string fileName = Path.GetFileNameWithoutExtension(path); + int indexOf = fileName.LastIndexOf('.'); + if (indexOf < 0) + { + return "Neutral"; + } + + return fileName.Substring(indexOf + 1); + } + private sealed class ResxEntry { public string? Name { get; set; } - public string? Value { get; set; } + public List Values { get; set; } = default!; + public List Locales { get; set; } = default!; public string? Comment { get; set; } public string? Type { get; set; } @@ -553,9 +581,9 @@ public sealed class ResxGenerator : IIncrementalGenerator return true; } - if (Value != null) + if (Values.FirstOrDefault() is string value) { - string[] parts = Value.Split(';'); + string[] parts = value.Split(';'); if (parts.Length > 1) { string type = parts[1]; @@ -579,9 +607,9 @@ public sealed class ResxGenerator : IIncrementalGenerator return "string"; } - if (Value != null) + if (Values.FirstOrDefault() is string value) { - string[] parts = Value.Split(';'); + string[] parts = value.Split(';'); if (parts.Length > 1) { string type = parts[1]; diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration.csproj b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration.csproj index 62f03b54..5b81c9af 100644 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration.csproj +++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration.csproj @@ -8,6 +8,7 @@ x64 true Debug;Release + true diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/UniversalAnalyzer.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/UniversalAnalyzer.cs index 2c8d6f77..d09bbc6a 100644 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/UniversalAnalyzer.cs +++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/UniversalAnalyzer.cs @@ -57,23 +57,13 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer private static void CompilationStart(CompilationStartAnalysisContext context) { - SyntaxKind[] types = - { - SyntaxKind.ClassDeclaration, - SyntaxKind.InterfaceDeclaration, - SyntaxKind.StructDeclaration, - SyntaxKind.EnumDeclaration, - }; + SyntaxKind[] types = [SyntaxKind.ClassDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.EnumDeclaration,]; context.RegisterSyntaxNodeAction(HandleTypeShouldBeInternal, types); context.RegisterSyntaxNodeAction(HandleMethodParameterShouldUseRefLikeKeyword, SyntaxKind.MethodDeclaration); context.RegisterSyntaxNodeAction(HandleMethodReturnTypeShouldUseValueTaskInsteadOfTask, SyntaxKind.MethodDeclaration); context.RegisterSyntaxNodeAction(HandleConstructorParameterShouldUseRefLikeKeyword, SyntaxKind.ConstructorDeclaration); - SyntaxKind[] expressions = - { - SyntaxKind.EqualsExpression, - SyntaxKind.NotEqualsExpression, - }; + SyntaxKind[] expressions = [SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression,]; context.RegisterSyntaxNodeAction(HandleEqualsAndNotEqualsExpressionShouldUsePatternMatching, expressions); context.RegisterSyntaxNodeAction(HandleIsPatternShouldUseRecursivePattern, SyntaxKind.IsPatternExpression); context.RegisterSyntaxNodeAction(HandleArgumentNullExceptionThrowIfNull, SyntaxKind.SuppressNullableWarningExpression); diff --git a/src/Snap.Hutao/Snap.Hutao.Test/JsonSerializeTest.cs b/src/Snap.Hutao/Snap.Hutao.Test/JsonSerializeTest.cs index 98b818a1..2ec63fa2 100644 --- a/src/Snap.Hutao/Snap.Hutao.Test/JsonSerializeTest.cs +++ b/src/Snap.Hutao/Snap.Hutao.Test/JsonSerializeTest.cs @@ -7,6 +7,15 @@ namespace Snap.Hutao.Test; [TestClass] public class JsonSerializeTest { + private TestContext? testContext; + + public TestContext? TestContext { get => testContext; set => testContext = value; } + + private readonly JsonSerializerOptions AlowStringNumberOptions = new() + { + NumberHandling = JsonNumberHandling.AllowReadingFromString, + }; + private const string SmapleObjectJson = """ { "A" :1 @@ -44,13 +53,29 @@ public class JsonSerializeTest [TestMethod] public void NumberStringKeyCanSerializeAsKey() { - JsonSerializerOptions options = new() + Dictionary sample = JsonSerializer.Deserialize>(SmapleNumberKeyDictionaryJson, AlowStringNumberOptions)!; + Assert.AreEqual(sample[111], "12"); + } + + [TestMethod] + public void ByteArraySerializeAsBase64() + { + byte[] array = +#if NET8_0_OR_GREATER + [1, 2, 3, 4, 5]; +#else + { 1, 2, 3, 4, 5 }; +#endif + ByteArraySample sample = new() { - NumberHandling = JsonNumberHandling.AllowReadingFromString, + Array = array, }; - Dictionary sample = JsonSerializer.Deserialize>(SmapleNumberKeyDictionaryJson, options)!; - Assert.AreEqual(sample[111], "12"); + string result = JsonSerializer.Serialize(sample); + TestContext!.WriteLine($"ByteArray Serialize Result: {result}"); + Assert.AreEqual(result, """ + {"Array":"AQIDBAU="} + """); } private sealed class Sample @@ -64,4 +89,9 @@ public class JsonSerializeTest [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)] public int A { get; set; } } + + private sealed class ByteArraySample + { + public byte[]? Array { get; set; } + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.Test/RuntimeBehavior/ForEachRuntimeBehaviorTest.cs b/src/Snap.Hutao/Snap.Hutao.Test/RuntimeBehavior/ForEachRuntimeBehaviorTest.cs index 8011f30c..dc2bc7ba 100644 --- a/src/Snap.Hutao/Snap.Hutao.Test/RuntimeBehavior/ForEachRuntimeBehaviorTest.cs +++ b/src/Snap.Hutao/Snap.Hutao.Test/RuntimeBehavior/ForEachRuntimeBehaviorTest.cs @@ -9,11 +9,12 @@ public sealed class ForEachRuntimeBehaviorTest [TestMethod] public void ListOfStringCanEnumerateAsReadOnlySpanOfChar() { - List strings = new() - { - "a", "b", "c" - }; - + List strings = +#if NET8_0_OR_GREATER + ["a", "b", "c"]; +#else + new() { "a", "b", "c" }; +#endif int count = 0; foreach (ReadOnlySpan chars in strings) { diff --git a/src/Snap.Hutao/Snap.Hutao.Test/RuntimeBehavior/RangeRuntimeBehaviorTest.cs b/src/Snap.Hutao/Snap.Hutao.Test/RuntimeBehavior/RangeRuntimeBehaviorTest.cs index 9634543e..abb82eb1 100644 --- a/src/Snap.Hutao/Snap.Hutao.Test/RuntimeBehavior/RangeRuntimeBehaviorTest.cs +++ b/src/Snap.Hutao/Snap.Hutao.Test/RuntimeBehavior/RangeRuntimeBehaviorTest.cs @@ -8,8 +8,13 @@ public sealed class RangeRuntimeBehaviorTest [TestMethod] public void RangeTrimLastOne() { +#if NET8_0_OR_GREATER + int[] array = [1, 2, 3, 4]; + int[] test = [1, 2, 3]; +#else int[] array = { 1, 2, 3, 4 }; int[] test = { 1, 2, 3 }; +#endif int[] result = array[..^1]; Assert.AreEqual(3, result.Length); Assert.IsTrue(MemoryExtensions.SequenceEqual(test, result)); diff --git a/src/Snap.Hutao/Snap.Hutao.Test/RuntimeBehavior/UnsafeRuntimeBehaviorTest.cs b/src/Snap.Hutao/Snap.Hutao.Test/RuntimeBehavior/UnsafeRuntimeBehaviorTest.cs index 46c5db18..0d516772 100644 --- a/src/Snap.Hutao/Snap.Hutao.Test/RuntimeBehavior/UnsafeRuntimeBehaviorTest.cs +++ b/src/Snap.Hutao/Snap.Hutao.Test/RuntimeBehavior/UnsafeRuntimeBehaviorTest.cs @@ -6,7 +6,12 @@ public sealed class UnsafeRuntimeBehaviorTest [TestMethod] public unsafe void UInt32AllSetIs() { - byte[] bytes = { 0xFF, 0xFF, 0xFF, 0xFF, }; + byte[] bytes = +#if NET8_0_OR_GREATER + [0xFF, 0xFF, 0xFF, 0xFF]; +#else + { 0xFF, 0xFF, 0xFF, 0xFF, }; +#endif fixed (byte* pBytes = bytes) { diff --git a/src/Snap.Hutao/Snap.Hutao.Test/Snap.Hutao.Test.csproj b/src/Snap.Hutao/Snap.Hutao.Test/Snap.Hutao.Test.csproj index 3949e0f1..e440cdd6 100644 --- a/src/Snap.Hutao/Snap.Hutao.Test/Snap.Hutao.Test.csproj +++ b/src/Snap.Hutao/Snap.Hutao.Test/Snap.Hutao.Test.csproj @@ -1,7 +1,7 @@ - + - net7.0 + net7.0;net8.0 disable enable false diff --git a/src/Snap.Hutao/Snap.Hutao.Win32/Snap.Hutao.Win32.csproj b/src/Snap.Hutao/Snap.Hutao.Win32/Snap.Hutao.Win32.csproj index 3473f909..c499b94e 100644 --- a/src/Snap.Hutao/Snap.Hutao.Win32/Snap.Hutao.Win32.csproj +++ b/src/Snap.Hutao/Snap.Hutao.Win32/Snap.Hutao.Win32.csproj @@ -1,7 +1,7 @@ - net7.0-windows10.0.19041.0 + net8.0-windows10.0.19041.0 disable enable diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Animation/AnimationDurations.cs b/src/Snap.Hutao/Snap.Hutao/Control/Animation/ControlAnimationConstants.cs similarity index 70% rename from src/Snap.Hutao/Snap.Hutao/Control/Animation/AnimationDurations.cs rename to src/Snap.Hutao/Snap.Hutao/Control/Animation/ControlAnimationConstants.cs index 1601d6f3..e748911f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Animation/AnimationDurations.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Animation/ControlAnimationConstants.cs @@ -3,12 +3,18 @@ namespace Snap.Hutao.Control.Animation; -/// -/// 动画时长 -/// -[HighQuality] -internal static class AnimationDurations +internal static class ControlAnimationConstants { + /// + /// 1 + /// + public const string One = "1"; + + /// + /// 1.1 + /// + public const string OnePointOne = "1.1"; + /// /// 图片缩放动画 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomInAnimation.cs b/src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomInAnimation.cs index 44971340..e064a78f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomInAnimation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomInAnimation.cs @@ -19,10 +19,10 @@ internal sealed class ImageZoomInAnimation : ImplicitAnimation /// public ImageZoomInAnimation() { - Duration = AnimationDurations.ImageZoom; + Duration = ControlAnimationConstants.ImageZoom; EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseOut; EasingType = CommunityToolkit.WinUI.Animations.EasingType.Circle; - To = Core.StringLiterals.OnePointOne; + To = ControlAnimationConstants.OnePointOne; } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomOutAnimation.cs b/src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomOutAnimation.cs index f310f85a..abe51f58 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomOutAnimation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Animation/ImageZoomOutAnimation.cs @@ -19,10 +19,10 @@ internal sealed class ImageZoomOutAnimation : ImplicitAnimation /// public ImageZoomOutAnimation() { - Duration = AnimationDurations.ImageZoom; + Duration = ControlAnimationConstants.ImageZoom; EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseOut; EasingType = CommunityToolkit.WinUI.Animations.EasingType.Circle; - To = Core.StringLiterals.One; + To = ControlAnimationConstants.One; } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/CoreWebView2Extension.cs b/src/Snap.Hutao/Snap.Hutao/Control/Extension/WebView2Extension.cs similarity index 83% rename from src/Snap.Hutao/Snap.Hutao/Web/Bridge/CoreWebView2Extension.cs rename to src/Snap.Hutao/Snap.Hutao/Control/Extension/WebView2Extension.cs index 52e972d2..9d28ef31 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/CoreWebView2Extension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Extension/WebView2Extension.cs @@ -1,16 +1,17 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Microsoft.UI.Xaml.Controls; using Microsoft.Web.WebView2.Core; using System.Diagnostics; -namespace Snap.Hutao.Web.Bridge; +namespace Snap.Hutao.Control.Extension; /// /// Bridge 拓展 /// [HighQuality] -internal static class CoreWebView2Extension +internal static class WebView2Extension { [Conditional("RELEASE")] public static void DisableDevToolsForReleaseBuild(this CoreWebView2 webView) @@ -37,4 +38,9 @@ internal static class CoreWebView2Extension manager.DeleteCookie(item); } } + + public static bool IsDisposed(this WebView2 webView2) + { + return WinRTExtension.IsDisposed(webView2); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs index 132b87c1..9cbc5d20 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs @@ -110,7 +110,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co if (exception is HttpRequestException httpRequestException) { - infoBarService.Error(httpRequestException, SH.ControlImageCompositionImageHttpRequest.Format(uri)); + infoBarService.Error(httpRequestException, SH.FormatControlImageCompositionImageHttpRequest(uri)); } else { @@ -196,7 +196,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co { await AnimationBuilder .Create() - .Opacity(from: 0D, to: 1D, duration: AnimationDurations.ImageFadeIn) + .Opacity(from: 0D, to: 1D, duration: ControlAnimationConstants.ImageFadeIn) .StartAsync(this, token) .ConfigureAwait(true); } @@ -217,7 +217,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co { await AnimationBuilder .Create() - .Opacity(from: 1D, to: 0D, duration: AnimationDurations.ImageFadeOut) + .Opacity(from: 1D, to: 0D, duration: ControlAnimationConstants.ImageFadeOut) .StartAsync(this, token) .ConfigureAwait(true); } diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Layout/DefaultItemCollectionTransitionProvider.cs b/src/Snap.Hutao/Snap.Hutao/Control/Layout/DefaultItemCollectionTransitionProvider.cs index e10f2a65..1426af0e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Layout/DefaultItemCollectionTransitionProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Layout/DefaultItemCollectionTransitionProvider.cs @@ -27,9 +27,9 @@ internal sealed class DefaultItemCollectionTransitionProvider : ItemCollectionTr protected override void StartTransitions(IList transitions) { - List addTransitions = new(); - List removeTransitions = new(); - List moveTransitions = new(); + List addTransitions = []; + List removeTransitions = []; + List moveTransitions = []; foreach (ItemCollectionTransition transition in addTransitions) { diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Layout/UniformStaggeredLayout.cs b/src/Snap.Hutao/Snap.Hutao/Control/Layout/UniformStaggeredLayout.cs index ce1b24f9..e9e17beb 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Layout/UniformStaggeredLayout.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Layout/UniformStaggeredLayout.cs @@ -118,7 +118,7 @@ internal sealed partial class UniformStaggeredLayout : VirtualizingLayout Span columnHeights = new double[numberOfColumns]; Span itemsPerColumn = new int[numberOfColumns]; - HashSet deadColumns = new(); + HashSet deadColumns = []; for (int i = 0; i < context.ItemCount; i++) { @@ -131,7 +131,9 @@ internal sealed partial class UniformStaggeredLayout : VirtualizingLayout // https://github.com/DGP-Studio/Snap.Hutao/issues/1079 // The first element must be force refreshed otherwise // it will use the old one realized - ElementRealizationOptions options = i == 0 ? ElementRealizationOptions.ForceCreate : ElementRealizationOptions.None; + // https://github.com/DGP-Studio/Snap.Hutao/issues/1099 + // Now we need to refresh the first element of each column + ElementRealizationOptions options = i < numberOfColumns ? ElementRealizationOptions.ForceCreate : ElementRealizationOptions.None; // Item has not been measured yet. Get the element and store the values UIElement element = context.GetOrCreateElementAt(i, options); diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Layout/UniformStaggeredLayoutState.cs b/src/Snap.Hutao/Snap.Hutao/Control/Layout/UniformStaggeredLayoutState.cs index bb44b651..bd191648 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Layout/UniformStaggeredLayoutState.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Layout/UniformStaggeredLayoutState.cs @@ -9,9 +9,9 @@ namespace Snap.Hutao.Control.Layout; internal sealed class UniformStaggeredLayoutState { - private readonly List items = new(); + private readonly List items = []; private readonly VirtualizingLayoutContext context; - private readonly Dictionary columnLayout = new(); + private readonly Dictionary columnLayout = []; private double lastAverageHeight; public UniformStaggeredLayoutState(VirtualizingLayoutContext context) @@ -32,7 +32,7 @@ internal sealed class UniformStaggeredLayoutState { if (!this.columnLayout.TryGetValue(columnIndex, out UniformStaggeredColumnLayout? columnLayout)) { - columnLayout = new(); + columnLayout = []; this.columnLayout[columnIndex] = columnLayout; } diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Loading.cs b/src/Snap.Hutao/Snap.Hutao/Control/Loading.cs index a2cdec3f..bb443a05 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Loading.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Loading.cs @@ -11,7 +11,6 @@ internal class Loading : Microsoft.UI.Xaml.Controls.ContentControl { public static readonly DependencyProperty IsLoadingProperty = DependencyProperty.Register(nameof(IsLoading), typeof(bool), typeof(Loading), new PropertyMetadata(default(bool), IsLoadingPropertyChanged)); - [SuppressMessage("", "IDE0052")] private FrameworkElement? presenter; public Loading() diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs b/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs index 0625da8d..94150ea4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs @@ -4,6 +4,7 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.Core.IO; using System.Collections.Concurrent; +using System.Collections.Frozen; using System.IO; using System.Net; using System.Net.Http; @@ -24,17 +25,16 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation { private const string CacheFolderName = nameof(ImageCache); - // TODO: use FrozenDictionary - private static readonly Dictionary RetryCountToDelay = new() + private static readonly FrozenDictionary RetryCountToDelay = new Dictionary() { [0] = TimeSpan.FromSeconds(4), [1] = TimeSpan.FromSeconds(16), [2] = TimeSpan.FromSeconds(64), - }; + }.ToFrozenDictionary(); - private readonly ILogger logger; private readonly IHttpClientFactory httpClientFactory; private readonly IServiceProvider serviceProvider; + private readonly ILogger logger; private readonly ConcurrentDictionary concurrentTasks = new(); @@ -62,7 +62,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation /// public void Remove(Uri uriForCachedItem) { - Remove(new ReadOnlySpan(uriForCachedItem)); + Remove(new ReadOnlySpan(ref uriForCachedItem)); } /// @@ -76,7 +76,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation string folder = GetCacheFolder(); string[] files = Directory.GetFiles(folder); - List filesToDelete = new(); + List filesToDelete = []; foreach (ref readonly Uri uri in uriForCachedItems) { string filePath = Path.Combine(folder, GetCacheFileName(uri)); diff --git a/src/Snap.Hutao/Snap.Hutao/Core/CommandLineBuilder.cs b/src/Snap.Hutao/Snap.Hutao/Core/CommandLineBuilder.cs index 9837e368..ca8af27f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/CommandLineBuilder.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/CommandLineBuilder.cs @@ -12,7 +12,8 @@ namespace Snap.Hutao.Core; internal sealed class CommandLineBuilder { private const char WhiteSpace = ' '; - private readonly Dictionary options = new(); + + private readonly Dictionary options = []; /// /// 当符合条件时添加参数 diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Database/ScopedDbCurrent.cs b/src/Snap.Hutao/Snap.Hutao/Core/Database/ScopedDbCurrent.cs index 27a8c5c5..7c4655ca 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Database/ScopedDbCurrent.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Database/ScopedDbCurrent.cs @@ -38,7 +38,11 @@ internal sealed partial class ScopedDbCurrent return; } - // TODO: Troubeshooting why the serviceProvider will NRE + if (serviceProvider.IsDisposedSlow()) + { + return; + } + using (IServiceScope scope = serviceProvider.CreateScope()) { AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); @@ -92,7 +96,11 @@ internal sealed partial class ScopedDbCurrent return; } - // TODO: Troubeshooting why the serviceProvider will NRE + if (serviceProvider.IsDisposedSlow()) + { + return; + } + using (IServiceScope scope = serviceProvider.CreateScope()) { AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Abstraction/OverseaSupportFactory.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Abstraction/OverseaSupportFactory.cs index 1d3ef60d..49d10903 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Abstraction/OverseaSupportFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Abstraction/OverseaSupportFactory.cs @@ -3,6 +3,12 @@ namespace Snap.Hutao.Core.DependencyInjection.Abstraction; +/// +/// 由于 AddHttpClient 不支持 KeyedService, 所以使用工厂模式 +/// +/// 抽象类型 +/// 官服/米游社类型 +/// 国际/HoYoLAB类型 internal abstract class OverseaSupportFactory : IOverseaSupportFactory where TClientCN : notnull, TClient where TClientOS : notnull, TClient diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs index 5a709cb0..e9484f79 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs @@ -35,7 +35,6 @@ internal static class DependencyInjection // Discrete services .AddSingleton() - .BuildServiceProvider(true); Ioc.Default.ConfigureServices(serviceProvider); diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/EnumerableServiceExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/EnumerableServiceExtension.cs deleted file mode 100644 index 44ddf98d..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/EnumerableServiceExtension.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Snap.Hutao.Core.DependencyInjection.Abstraction; -using System.Runtime.CompilerServices; - -namespace Snap.Hutao.Core.DependencyInjection; - -/// -/// 服务集合扩展 -/// -internal static class EnumerableServiceExtension -{ - /// - /// 选择对应的服务 - /// - /// 服务类型 - /// 服务集合 - /// 是否为海外服/Hoyolab - /// 对应的服务 - [Obsolete("该方法会导致不必要的服务实例化")] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TService Pick(this IEnumerable services, bool isOversea) - where TService : IOverseaSupport - { - return services.Single(s => s.IsOversea == isOversea); - } - - /// - /// 选择对应的服务 - /// - /// 服务类型 - /// 服务提供器 - /// 是否为海外服/Hoyolab - /// 对应的服务 - [Obsolete("该方法会导致不必要的服务实例化")] - public static TService PickRequiredService(this IServiceProvider serviceProvider, bool isOversea) - where TService : IOverseaSupport - { - return serviceProvider.GetRequiredService>().Pick(isOversea); - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/ServiceProviderExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/ServiceProviderExtension.cs index f7d15aa4..bdaafd82 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/ServiceProviderExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/ServiceProviderExtension.cs @@ -16,4 +16,15 @@ internal static class ServiceProviderExtension { return ActivatorUtilities.CreateInstance(serviceProvider, parameters); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsDisposedSlow(this IServiceProvider? serviceProvider) + { + if (serviceProvider is null) + { + return true; + } + + return serviceProvider.GetType().GetField("_disposed")?.GetValue(serviceProvider) is true; + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/DatabaseCorruptedException.cs b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/DatabaseCorruptedException.cs index 1c31ff82..c015352d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/DatabaseCorruptedException.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/DatabaseCorruptedException.cs @@ -15,7 +15,7 @@ internal sealed class DatabaseCorruptedException : Exception /// 消息 /// 内部错误 public DatabaseCorruptedException(string message, Exception? innerException) - : base(SH.CoreExceptionServiceDatabaseCorruptedMessage.Format($"{message}\n{innerException?.Message}"), innerException) + : base(SH.FormatCoreExceptionServiceDatabaseCorruptedMessage($"{message}\n{innerException?.Message}"), innerException) { } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/UserdataCorruptedException.cs b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/UserdataCorruptedException.cs index ee6880d8..934753e1 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/UserdataCorruptedException.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/UserdataCorruptedException.cs @@ -15,7 +15,7 @@ internal sealed class UserdataCorruptedException : Exception /// 消息 /// 内部错误 public UserdataCorruptedException(string message, Exception? innerException) - : base(SH.CoreExceptionServiceUserdataCorruptedMessage.Format($"{message}\n{innerException?.Message}"), innerException) + : base(SH.FormatCoreExceptionServiceUserdataCorruptedMessage($"{message}\n{innerException?.Message}"), innerException) { } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/ClipboardInterop.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/ClipboardProvider.cs similarity index 90% rename from src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/ClipboardInterop.cs rename to src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/ClipboardProvider.cs index 44b3d3ec..49dee0fd 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/ClipboardInterop.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/ClipboardProvider.cs @@ -6,12 +6,9 @@ using Windows.Storage.Streams; namespace Snap.Hutao.Core.IO.DataTransfer; -/// -/// 剪贴板互操作 -/// [ConstructorGenerated] -[Injection(InjectAs.Transient, typeof(IClipboardInterop))] -internal sealed partial class ClipboardInterop : IClipboardInterop +[Injection(InjectAs.Transient, typeof(IClipboardProvider))] +internal sealed partial class ClipboardProvider : IClipboardProvider { private readonly JsonSerializerOptions options; private readonly ITaskContext taskContext; diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/IClipboardInterop.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/IClipboardProvider.cs similarity index 95% rename from src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/IClipboardInterop.cs rename to src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/IClipboardProvider.cs index 38b77041..b5fa7bc9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/IClipboardInterop.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/IClipboardProvider.cs @@ -8,7 +8,7 @@ namespace Snap.Hutao.Core.IO.DataTransfer; /// /// 剪贴板互操作 /// -internal interface IClipboardInterop +internal interface IClipboardProvider { /// /// 从剪贴板文本中反序列化 diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniSerializer.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniSerializer.cs index c8dfff1c..d51ddcfe 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniSerializer.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniSerializer.cs @@ -18,7 +18,7 @@ internal static class IniSerializer /// Ini 元素集合 public static List Deserialize(FileStream fileStream) { - List results = new(); + List results = []; using (StreamReader reader = new(fileStream)) { while (reader.ReadLine() is { } line) diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/PickerExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/PickerExtension.cs index 76cf669e..1dda04d6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/IO/PickerExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/PickerExtension.cs @@ -98,7 +98,7 @@ internal static class PickerExtension .GetRequiredService() .Warning( SH.CoreIOPickerExtensionPickerExceptionInfoBarTitle, - SH.CoreIOPickerExtensionPickerExceptionInfoBarMessage.Format(exception.Message)); + SH.FormatCoreIOPickerExtensionPickerExceptionInfoBarMessage(exception.Message)); } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/SeparatorCommaInt32EnumerableConverter.cs b/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/SeparatorCommaInt32EnumerableConverter.cs index bffefbb1..669e2e32 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/SeparatorCommaInt32EnumerableConverter.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/SeparatorCommaInt32EnumerableConverter.cs @@ -33,8 +33,7 @@ internal sealed class SeparatorCommaInt32EnumerableConverter : JsonConverter EnumerateNumbers(string source) { - // TODO: Use Collection Literals - foreach (StringSegment id in new StringTokenizer(source, new[] { Comma })) + foreach (StringSegment id in new StringTokenizer(source, [Comma])) { yield return int.Parse(id.AsSpan(), CultureInfo.CurrentCulture); } diff --git a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/AppInstanceExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/AppInstanceExtension.cs index 06f24566..d7f8b25c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/AppInstanceExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/AppInstanceExtension.cs @@ -37,10 +37,9 @@ internal static class AppInstanceExtension SetEvent(redirectEventHandle); }); - ReadOnlySpan handles = new(redirectEventHandle); + ReadOnlySpan handles = new(ref redirectEventHandle); CoWaitForMultipleObjects((uint)CWMO_FLAGS.CWMO_DEFAULT, INFINITE, handles, out uint _); - - // TODO: Release handle + CloseHandle(redirectEventHandle); } [SuppressMessage("", "SH007")] diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Random.cs b/src/Snap.Hutao/Snap.Hutao/Core/Random.cs new file mode 100644 index 00000000..74afc366 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Random.cs @@ -0,0 +1,22 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Core; + +internal static class Random +{ + public static string GetLowerHexString(int length) + { + return new(System.Random.Shared.GetItems("0123456789abcdef".AsSpan(), length)); + } + + public static string GetUpperAndNumberString(int length) + { + return new(System.Random.Shared.GetItems("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".AsSpan(), length)); + } + + public static string GetLowerAndNumberString(int length) + { + return new(System.Random.Shared.GetItems("0123456789abcdefghijklmnopqrstuvwxyz".AsSpan(), length)); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/StringLiterals.cs b/src/Snap.Hutao/Snap.Hutao/Core/StringLiterals.cs index 091f606a..1e719e3f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/StringLiterals.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/StringLiterals.cs @@ -11,26 +11,6 @@ namespace Snap.Hutao.Core; [HighQuality] internal static class StringLiterals { - /// - /// 1 - /// - public const string One = "1"; - - /// - /// 1.1 - /// - public const string OnePointOne = "1.1"; - - /// - /// True - /// - public const string True = "True"; - - /// - /// False - /// - public const string False = "False"; - /// /// CRLF 换行符 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/DispatcherQueueExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/DispatcherQueueExtension.cs index 01df78e1..692004e1 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Threading/DispatcherQueueExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/DispatcherQueueExtension.cs @@ -45,9 +45,7 @@ internal static class DispatcherQueueExtension }); blockEvent.Wait(); -#pragma warning disable CA1508 exceptionDispatchInfo?.Throw(); -#pragma warning restore CA1508 } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/UnsafeDateTimeOffset.cs b/src/Snap.Hutao/Snap.Hutao/Core/UnsafeDateTimeOffset.cs similarity index 95% rename from src/Snap.Hutao/Snap.Hutao/Extension/UnsafeDateTimeOffset.cs rename to src/Snap.Hutao/Snap.Hutao/Core/UnsafeDateTimeOffset.cs index 854b29eb..92b408dc 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/UnsafeDateTimeOffset.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/UnsafeDateTimeOffset.cs @@ -1,7 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -namespace Snap.Hutao.Extension; +namespace Snap.Hutao.Core; internal struct UnsafeDateTimeOffset { diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyController.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyController.cs index 4741e8a9..ffb070e6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyController.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyController.cs @@ -8,6 +8,7 @@ using static Windows.Win32.PInvoke; namespace Snap.Hutao.Core.Windowing.HotKey; [SuppressMessage("", "CA1001")] +[Injection(InjectAs.Singleton, typeof(IHotKeyController))] [ConstructorGenerated] internal sealed partial class HotKeyController : IHotKeyController { @@ -53,10 +54,10 @@ internal sealed partial class HotKeyController : IHotKeyController while (!token.IsCancellationRequested) { INPUT[] inputs = - { + [ CreateInputForMouseEvent(MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTDOWN), CreateInputForMouseEvent(MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTUP), - }; + ]; if (SendInput(inputs.AsSpan(), sizeof(INPUT)) is 0) { @@ -68,7 +69,7 @@ internal sealed partial class HotKeyController : IHotKeyController return; } - Thread.Sleep(Random.Shared.Next(100, 150)); + Thread.Sleep(System.Random.Shared.Next(100, 150)); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowController.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowController.cs index 706e7547..40bfc52b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowController.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowController.cs @@ -55,7 +55,7 @@ internal sealed class WindowController { RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService(); - window.AppWindow.Title = SH.AppNameAndVersion.Format(hutaoOptions.Version); + window.AppWindow.Title = SH.FormatAppNameAndVersion(hutaoOptions.Version); window.AppWindow.SetIcon(Path.Combine(hutaoOptions.InstalledLocation, "Assets/Logo.ico")); ExtendsContentIntoTitleBar(); @@ -204,6 +204,6 @@ internal sealed class WindowController // 48 is the navigation button leftInset RectInt32 dragRect = StructMarshal.RectInt32(48, 0, options.TitleBar.ActualSize).Scale(scale); - appTitleBar.SetDragRectangles(dragRect.ToArray()); + appTitleBar.SetDragRectangles([dragRect]); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowExtension.cs index 52f22eae..e6598b80 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowExtension.cs @@ -8,7 +8,7 @@ namespace Snap.Hutao.Core.Windowing; internal static class WindowExtension { - private static readonly ConditionalWeakTable WindowControllers = new(); + private static readonly ConditionalWeakTable WindowControllers = []; public static void InitializeController(this TWindow window, IServiceProvider serviceProvider) where TWindow : Window, IWindowOptionsSource diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclass.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclass.cs index 7a7aabbd..c3e9ac21 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclass.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclass.cs @@ -33,7 +33,8 @@ internal sealed class WindowSubclass : IDisposable this.window = window; this.options = options; this.serviceProvider = serviceProvider; - hotKeyController = new HotKeyController(serviceProvider); + + hotKeyController = serviceProvider.GetRequiredService(); } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.Dictionary.cs b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.Dictionary.cs index 555b285b..c6897d7f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.Dictionary.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.Dictionary.cs @@ -80,7 +80,7 @@ internal static partial class EnumerableExtension public static Dictionary ToDictionaryIgnoringDuplicateKeys(this IEnumerable source, Func keySelector) where TKey : notnull { - Dictionary dictionary = new(); + Dictionary dictionary = []; foreach (TSource value in source) { @@ -94,7 +94,7 @@ internal static partial class EnumerableExtension public static Dictionary ToDictionaryIgnoringDuplicateKeys(this IEnumerable source, Func keySelector, Func elementSelector) where TKey : notnull { - Dictionary dictionary = new(); + Dictionary dictionary = []; foreach (TSource value in source) { diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.List.cs b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.List.cs index 95a3adfa..68b066d5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.List.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.List.cs @@ -69,7 +69,7 @@ internal static partial class EnumerableExtension [MethodImpl(MethodImplOptions.AggressiveInlining)] public static List EmptyIfNull(this List? source) { - return source ?? new(); + return source ?? []; } public static List GetRange(this List list, in Range range) diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.NameValueCollection.cs b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.NameValueCollection.cs new file mode 100644 index 00000000..9d3840c0 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.NameValueCollection.cs @@ -0,0 +1,24 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Specialized; + +namespace Snap.Hutao.Extension; + +internal static partial class EnumerableExtension +{ + public static bool TryGetValue(this NameValueCollection collection, string name, [NotNullWhen(true)] out string? value) + { + if (collection.AllKeys.Contains(name)) + { + if (collection.GetValues(name) is [string single]) + { + value = single; + return true; + } + } + + value = string.Empty; + return false; + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs index c4976887..eaa5e998 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs @@ -30,20 +30,6 @@ internal static partial class EnumerableExtension return source ?? Enumerable.Empty(); } - /// - /// 将源转换为仅包含单个元素的枚举 - /// - /// 源的类型 - /// 源 - /// 集合 -#if NET8_0 - [Obsolete("Use C# 12 Collection Literal instead")] -#endif - public static IEnumerable Enumerate(this TSource source) - { - yield return source; - } - /// /// 寻找枚举中唯一的值,找不到时 /// 回退到首个或默认值 diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/ObjectExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/ObjectExtension.cs deleted file mode 100644 index 7c631bd3..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Extension/ObjectExtension.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using System.Runtime.CompilerServices; - -namespace Snap.Hutao.Extension; - -/// -/// 对象拓展 -/// -internal static class ObjectExtension -{ - /// - /// 转换到只有1长度的数组 - /// - /// 数据类型 - /// 源 - /// 数组 -#if NET8_0 - [Obsolete("Use C# 12 Collection Literals")] -#endif - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T[] ToArray(this T source) - { - // TODO: use C# 12 collection literals - // [ source ] - return new[] { source }; - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/StringExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/StringExtension.cs index 43f61ed4..02d523aa 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/StringExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/StringExtension.cs @@ -1,7 +1,6 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using System.Globalization; using System.Runtime.CompilerServices; namespace Snap.Hutao.Extension; @@ -28,22 +27,4 @@ internal static class StringExtension { return source.AsSpan().TrimEnd(value).ToString(); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string Format(this string value, object? arg) - { - return string.Format(CultureInfo.CurrentCulture, value, arg); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string Format(this string value, object? arg0, object? arg1) - { - return string.Format(CultureInfo.CurrentCulture, value, arg0, arg1); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string Format(this string value, object? arg0, object? arg1, object? arg2) - { - return string.Format(CultureInfo.CurrentCulture, value, arg0, arg1, arg2); - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/StructExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/StructExtension.cs index c8b7f949..3d19fe1a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/StructExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/StructExtension.cs @@ -67,4 +67,4 @@ internal static class StructExtension { return size.Width * size.Height; } -} \ No newline at end of file +} diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/WinRTExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/WinRTExtension.cs new file mode 100644 index 00000000..d88586a0 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Extension/WinRTExtension.cs @@ -0,0 +1,19 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using System.Runtime.CompilerServices; +using WinRT; + +namespace Snap.Hutao.Extension; + +internal static class WinRTExtension +{ + public static bool IsDisposed(this IWinRTObject obj) + { + return GetDisposed(obj.NativeObject); + } + + // protected bool disposed; + [UnsafeAccessor(UnsafeAccessorKind.Field, Name ="disposed")] + private static extern ref bool GetDisposed(IObjectReference objRef); +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/DailyNoteEntry.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/DailyNoteEntry.cs index 62e69ed8..c1cc069f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/DailyNoteEntry.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/DailyNoteEntry.cs @@ -64,7 +64,7 @@ internal sealed class DailyNoteEntry : ObservableObject, IMappingFrom [NotMapped] - public bool IsExpired { get => ExpireTime < DateTimeOffset.Now; } + public bool IsExpired { get => ExpireTime < DateTimeOffset.UtcNow; } /// /// 值字符串 diff --git a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Achievement/UIAF.cs b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Achievement/UIAF.cs index d5cc8023..301302fc 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Achievement/UIAF.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Achievement/UIAF.cs @@ -1,6 +1,8 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using System.Collections.Frozen; + namespace Snap.Hutao.Model.InterChange.Achievement; /// @@ -15,11 +17,7 @@ internal sealed class UIAF /// public const string CurrentVersion = "v1.1"; - // TODO use FrozenSet - private static readonly HashSet SupportedVersion = new() - { - CurrentVersion, - }; + private static readonly FrozenSet SupportedVersion = FrozenSet.ToFrozenSet([CurrentVersion]); /// /// 信息 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 7df5c9fc..e15c6147 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Achievement/UIAFInfo.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Achievement/UIAFInfo.cs @@ -49,7 +49,7 @@ internal sealed class UIAFInfo : IMappingFrom { return new() { - ExportTimestamp = DateTimeOffset.Now.ToUnixTimeSeconds(), + ExportTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), ExportApp = SH.AppName, ExportAppVersion = runtimeOptions.Version.ToString(), UIAFVersion = UIAF.CurrentVersion, diff --git a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGF.cs b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGF.cs index a29b8471..1733be16 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGF.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGF.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Core; using Snap.Hutao.Web.Hoyolab; using System.Runtime.InteropServices; 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 f6a0a9cc..0fbbe77f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGFInfo.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGFInfo.cs @@ -71,7 +71,7 @@ internal sealed class UIGFInfo : IMappingFrom public const string CurrentVersion = "v1.0"; - private static readonly ImmutableList SupportedVersion = new List() - { - CurrentVersion, - }.ToImmutableList(); + private static readonly FrozenSet SupportedVersion = FrozenSet.ToFrozenSet([CurrentVersion]); /// /// 信息 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 7ba2295c..a17ddfad 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Inventory/UIIFInfo.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Inventory/UIIFInfo.cs @@ -69,7 +69,7 @@ internal sealed class UIIFInfo return new() { Uid = uid, - ExportTimestamp = DateTimeOffset.Now.ToUnixTimeSeconds(), + ExportTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), ExportApp = SH.AppName, ExportAppVersion = hutaoOptions.Version.ToString(), UIIFVersion = UIIF.CurrentVersion, diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/FetterInfo.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/FetterInfo.cs index 49c8ac12..82a41c26 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/FetterInfo.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/FetterInfo.cs @@ -46,7 +46,7 @@ internal sealed class FetterInfo /// public string BirthFormatted { - get => SH.ModelMetadataFetterInfoBirthdayFormat.Format(BirthMonth, BirthDay); + get => SH.FormatModelMetadataFetterInfoBirthdayFormat(BirthMonth, BirthDay); } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Monster/MonsterBaseValue.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Monster/MonsterBaseValue.cs index 49be55d8..fe446a02 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Monster/MonsterBaseValue.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Monster/MonsterBaseValue.cs @@ -58,8 +58,8 @@ internal sealed class MonsterBaseValue : BaseValue { get { - return new() - { + return + [ FightPropertyFormat.ToNameValue(FightProperty.FIGHT_PROP_FIRE_SUB_HURT, FireSubHurt), FightPropertyFormat.ToNameValue(FightProperty.FIGHT_PROP_WATER_SUB_HURT, WaterSubHurt), FightPropertyFormat.ToNameValue(FightProperty.FIGHT_PROP_GRASS_SUB_HURT, GrassSubHurt), @@ -68,7 +68,7 @@ internal sealed class MonsterBaseValue : BaseValue FightPropertyFormat.ToNameValue(FightProperty.FIGHT_PROP_ICE_SUB_HURT, IceSubHurt), FightPropertyFormat.ToNameValue(FightProperty.FIGHT_PROP_ROCK_SUB_HURT, RockSubHurt), FightPropertyFormat.ToNameValue(FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT, PhysicalSubHurt), - }; + ]; } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Weapon/LevelDescription.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Weapon/LevelDescription.cs index f38a6727..a2ff06cf 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Weapon/LevelDescription.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Weapon/LevelDescription.cs @@ -19,7 +19,7 @@ internal sealed class LevelDescription /// 格式化的等级 /// [JsonIgnore] - public string LevelFormatted { get => SH.ModelWeaponAffixFormat.Format(Level + 1); } + public string LevelFormatted { get => SH.FormatModelWeaponAffixFormat(Level + 1); } /// /// 描述 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementDbService.cs index 1e7efec6..8440a8b9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementDbService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementDbService.cs @@ -129,10 +129,8 @@ internal sealed partial class AchievementDbService : IAchievementDbService using (IServiceScope scope = serviceProvider.CreateScope()) { AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - return appDbContext.Achievements - .AsNoTracking() - .Where(i => i.ArchiveId == archiveId) - .ToList(); + IQueryable result = appDbContext.Achievements.AsNoTracking().Where(i => i.ArchiveId == archiveId); + return [.. result]; } } @@ -154,7 +152,8 @@ internal sealed partial class AchievementDbService : IAchievementDbService using (IServiceScope scope = serviceProvider.CreateScope()) { AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - return appDbContext.AchievementArchives.AsNoTracking().ToList(); + IQueryable result = appDbContext.AchievementArchives.AsNoTracking(); + return [.. result]; } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementStatisticsService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementStatisticsService.cs index d7cf8deb..f588b786 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementStatisticsService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementStatisticsService.cs @@ -21,7 +21,7 @@ internal sealed partial class AchievementStatisticsService : IAchievementStatist { await taskContext.SwitchToBackgroundAsync(); - List results = new(); + List results = []; foreach (AchievementArchive archive in await achievementDbService.GetAchievementArchiveListAsync().ConfigureAwait(false)) { int finishedCount = await achievementDbService diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/ImportResult.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/ImportResult.cs index cd114b4a..aceaecf4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/ImportResult.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/ImportResult.cs @@ -40,6 +40,6 @@ internal readonly struct ImportResult /// public override string ToString() { - return SH.ServiceAchievementImportResultFormat.Format(Add, Update, Remove); + return SH.FormatServiceAchievementImportResultFormat(Add, Update, Remove); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs index 96121b9a..d1a2a707 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs @@ -21,14 +21,14 @@ internal sealed partial class AppOptions : DbStoreOptions { private readonly List> supportedBackdropTypesInner = CollectionsNameValue.ListFromEnum(); - private readonly List> supportedCulturesInner = new() - { + 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; @@ -126,7 +126,7 @@ internal sealed partial class AppOptions : DbStoreOptions string? paths = Environment.GetEnvironmentVariable("Path"); if (!string.IsNullOrEmpty(paths)) { - foreach (StringSegment path in new StringTokenizer(paths, ';'.ToArray())) + foreach (StringSegment path in new StringTokenizer(paths, [';'])) { if (path is { HasValue: true, Length: > 0 }) { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoDbBulkOperation.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoDbBulkOperation.cs index c16003bb..50750d87 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoDbBulkOperation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoDbBulkOperation.cs @@ -181,13 +181,13 @@ internal sealed partial class AvatarInfoDbBulkOperation if (entity is null) { entity = EntityAvatarInfo.From(uid, webInfo); - entity.ShowcaseRefreshTime = DateTimeOffset.Now; + entity.ShowcaseRefreshTime = DateTimeOffset.UtcNow; appDbContext.AvatarInfos.AddAndSave(entity); } else { entity.Info = webInfo; - entity.ShowcaseRefreshTime = DateTimeOffset.Now; + entity.ShowcaseRefreshTime = DateTimeOffset.UtcNow; appDbContext.AvatarInfos.UpdateAndSave(entity); } } @@ -200,7 +200,7 @@ internal sealed partial class AvatarInfoDbBulkOperation EnkaAvatarInfo avatarInfo = new() { AvatarId = avatarId }; transformer.Transform(ref avatarInfo, source); entity = EntityAvatarInfo.From(uid, avatarInfo); - entity.CalculatorRefreshTime = DateTimeOffset.Now; + entity.CalculatorRefreshTime = DateTimeOffset.UtcNow; appDbContext.AvatarInfos.AddAndSave(entity); } else @@ -208,7 +208,7 @@ internal sealed partial class AvatarInfoDbBulkOperation EnkaAvatarInfo avatarInfo = entity.Info; transformer.Transform(ref avatarInfo, source); entity.Info = avatarInfo; - entity.CalculatorRefreshTime = DateTimeOffset.Now; + entity.CalculatorRefreshTime = DateTimeOffset.UtcNow; appDbContext.AvatarInfos.UpdateAndSave(entity); } } @@ -221,7 +221,7 @@ internal sealed partial class AvatarInfoDbBulkOperation EnkaAvatarInfo avatarInfo = new() { AvatarId = avatarId }; transformer.Transform(ref avatarInfo, source); entity = EntityAvatarInfo.From(uid, avatarInfo); - entity.GameRecordRefreshTime = DateTimeOffset.Now; + entity.GameRecordRefreshTime = DateTimeOffset.UtcNow; appDbContext.AvatarInfos.AddAndSave(entity); } else @@ -229,7 +229,7 @@ internal sealed partial class AvatarInfoDbBulkOperation EnkaAvatarInfo avatarInfo = entity.Info; transformer.Transform(ref avatarInfo, source); entity.Info = avatarInfo; - entity.GameRecordRefreshTime = DateTimeOffset.Now; + entity.GameRecordRefreshTime = DateTimeOffset.UtcNow; appDbContext.AvatarInfos.UpdateAndSave(entity); } } @@ -243,7 +243,7 @@ internal sealed partial class AvatarInfoDbBulkOperation if (distinctCount < dbInfos.Count) { avatarInfoDbService.RemoveAvatarInfoRangeByUid(uid); - dbInfos = new(); + dbInfos = []; } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoDbService.cs index 0a0c4914..f8683628 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoDbService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoDbService.cs @@ -19,7 +19,8 @@ internal sealed partial class AvatarInfoDbService : IAvatarInfoDbService using (IServiceScope scope = serviceProvider.CreateScope()) { AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - return appDbContext.AvatarInfos.AsNoTracking().Where(i => i.Uid == uid).ToList(); + IQueryable result = appDbContext.AvatarInfos.AsNoTracking().Where(i => i.Uid == uid); + return [.. result]; } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs index 1cbb6c09..c9bd0500 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs @@ -80,13 +80,13 @@ internal sealed class SummaryAvatarFactory // times ShowcaseRefreshTimeFormat = showcaseRefreshTime == DateTimeOffsetExtension.DatebaseDefaultTime ? SH.ServiceAvatarInfoSummaryShowcaseNotRefreshed - : SH.ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat.Format(showcaseRefreshTime), + : SH.FormatServiceAvatarInfoSummaryShowcaseRefreshTimeFormat(showcaseRefreshTime.ToLocalTime()), GameRecordRefreshTimeFormat = gameRecordRefreshTime == DateTimeOffsetExtension.DatebaseDefaultTime ? SH.ServiceAvatarInfoSummaryGameRecordNotRefreshed - : SH.ServiceAvatarInfoSummaryGameRecordRefreshTimeFormat.Format(gameRecordRefreshTime), + : SH.FormatServiceAvatarInfoSummaryGameRecordRefreshTimeFormat(gameRecordRefreshTime.ToLocalTime()), CalculatorRefreshTimeFormat = calculatorRefreshTime == DateTimeOffsetExtension.DatebaseDefaultTime ? SH.ServiceAvatarInfoSummaryCalculatorNotRefreshed - : SH.ServiceAvatarInfoSummaryCalculatorRefreshTimeFormat.Format(calculatorRefreshTime), + : SH.FormatServiceAvatarInfoSummaryCalculatorRefreshTimeFormat(calculatorRefreshTime.ToLocalTime()), }; ApplyCostumeIconOrDefault(ref propertyAvatar, avatar); @@ -110,12 +110,12 @@ internal sealed class SummaryAvatarFactory } } - private ReliquaryAndWeapon ProcessEquip(List equipments) + private ReliquaryAndWeapon ProcessEquip(List equipments) { - List reliquaryList = new(); + List reliquaryList = []; PropertyWeapon? weapon = null; - foreach (Web.Enka.Model.Equip equip in equipments) + foreach (Equip equip in equipments) { switch (equip.Flat.ItemType) { @@ -132,7 +132,7 @@ internal sealed class SummaryAvatarFactory return new(reliquaryList, weapon); } - private PropertyWeapon CreateWeapon(Web.Enka.Model.Equip equip) + private PropertyWeapon CreateWeapon(Equip equip) { MetadataWeapon weapon = metadataContext.IdWeaponMap[equip.ItemId]; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarProperties.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarProperties.cs index b25632ad..0df0cc30 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarProperties.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarProperties.cs @@ -22,7 +22,7 @@ internal static class SummaryAvatarProperties { if (fightPropMap is null) { - return new(); + return []; } AvatarProperty hpProp = ToAvatarProperty(FightProperty.FIGHT_PROP_BASE_HP, fightPropMap); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs index 2937b9d0..bb0b1378 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs @@ -31,17 +31,15 @@ internal sealed partial class SummaryFactory : ISummaryFactory Reliquaries = await metadataService.GetReliquariesAsync(token).ConfigureAwait(false), }; + IOrderedEnumerable avatars = avatarInfos + .Where(a => !AvatarIds.IsPlayer(a.Info.AvatarId)) + .Select(a => new SummaryAvatarFactory(metadataContext, a).Create()) + .OrderByDescending(a => a.LevelNumber) + .ThenBy(a => a.Name); + return new() { - Avatars = avatarInfos - .Where(a => !AvatarIds.IsPlayer(a.Info.AvatarId)) - .Select(a => new SummaryAvatarFactory(metadataContext, a).Create()) - .OrderByDescending(a => a.LevelNumber) - .ThenBy(a => a.Name) - .ToList(), - - // .ThenByDescending(a => a.Quality) - // .ThenByDescending(a => a.ActivatedConstellationCount) + Avatars = [.. avatars], }; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryHelper.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryHelper.cs index 0c5ab37e..dfd73b85 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryHelper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryHelper.cs @@ -23,7 +23,7 @@ internal static class SummaryHelper /// 命之座 public static List CreateConstellations(List talents, List? talentIds) { - talentIds ??= new(); + talentIds ??= []; return talents.SelectList(talent => new ConstellationView() { @@ -45,7 +45,7 @@ internal static class SummaryHelper { if (skillLevelMap.IsNullOrEmpty()) { - return new(); + return []; } Dictionary skillExtraLeveledMap = new(skillLevelMap); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryReliquaryFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryReliquaryFactory.cs index 341f375e..90aa3565 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryReliquaryFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryReliquaryFactory.cs @@ -114,7 +114,7 @@ internal sealed class SummaryReliquaryFactory private List CreateComposedSubProperties(List appendProps) { - List infos = new(); + List infos = []; foreach (ref readonly ReliquarySubAffixId subAffixId in CollectionsMarshal.AsSpan(appendProps)) { ReliquarySubAffix subAffix = metadataContext.IdReliquarySubAffixMap[subAffixId]; @@ -128,7 +128,7 @@ internal sealed class SummaryReliquaryFactory ThrowHelper.InvalidOperation("无效的圣遗物数据"); } - List results = new(); + List results = []; foreach (ref readonly SummaryReliquarySubPropertyCompositionInfo info in CollectionsMarshal.AsSpan(infos)) { results.Add(info.ToReliquaryComposedSubProperty()); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Transformer/GameRecordCharacterAvatarInfoTransformer.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Transformer/GameRecordCharacterAvatarInfoTransformer.cs index 699751a4..cd6cfeff 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Transformer/GameRecordCharacterAvatarInfoTransformer.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Transformer/GameRecordCharacterAvatarInfoTransformer.cs @@ -22,7 +22,7 @@ internal sealed class GameRecordCharacterAvatarInfoTransformer : IAvatarInfoTran avatarInfo.FetterInfo.ExpLevel = source.Fetter; // update level - avatarInfo.PropMap ??= new Dictionary(); + avatarInfo.PropMap ??= []; avatarInfo.PropMap[PlayerProperty.PROP_LEVEL] = new(PlayerProperty.PROP_LEVEL, source.Level); // update constellations diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationDbService.cs index c73cc773..cba52ed6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationDbService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationDbService.cs @@ -20,10 +20,8 @@ internal sealed partial class CultivationDbService : ICultivationDbService using (IServiceScope scope = serviceProvider.CreateScope()) { AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - return appDbContext.InventoryItems - .AsNoTracking() - .Where(a => a.ProjectId == projectId) - .ToList(); + IQueryable result = appDbContext.InventoryItems.AsNoTracking().Where(a => a.ProjectId == projectId); + return [.. result]; } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs index 490548ec..67b1ecb8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs @@ -38,7 +38,7 @@ internal sealed partial class CultivationService : ICultivationService Guid projectId = cultivateProject.InnerId; List entities = cultivationDbService.GetInventoryItemListByProjectId(projectId); - List results = new(); + List results = []; foreach (Material meta in metadata.Where(m => m.IsInventoryItem()).OrderBy(m => m.Id.Value)) { InventoryItem entity = entities.SingleOrDefault(e => e.ItemId == meta.Id) ?? InventoryItem.From(projectId, meta.Id); @@ -64,7 +64,7 @@ internal sealed partial class CultivationService : ICultivationService List resultEntries = new(entries.Count); foreach (CultivateEntry entry in entries) { - List entryItems = new(); + List entryItems = []; foreach (CultivateItem item in await cultivationDbService.GetCultivateItemListByEntryIdAsync(entry.InnerId).ConfigureAwait(false)) { entryItems.Add(new(item, materials.Single(m => m.Id == item.ItemId))); @@ -94,7 +94,7 @@ internal sealed partial class CultivationService : ICultivationService CancellationToken token) { await taskContext.SwitchToBackgroundAsync(); - List resultItems = new(); + List resultItems = []; Guid projectId = cultivateProject.InnerId; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteDbService.cs index c537e3d7..5349086a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteDbService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteDbService.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Query; using Snap.Hutao.Core.Database; using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity.Database; @@ -64,7 +65,8 @@ internal sealed partial class DailyNoteDbService : IDailyNoteDbService using (IServiceScope scope = serviceProvider.CreateScope()) { AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - return appDbContext.DailyNotes.AsNoTracking().Include(n => n.User).ToList(); + IIncludableQueryable result = appDbContext.DailyNotes.AsNoTracking().Include(n => n.User); + return [.. result]; } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteNotificationOperation.cs b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteNotificationOperation.cs index 1c5890ce..cf96c8ab 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteNotificationOperation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteNotificationOperation.cs @@ -35,7 +35,7 @@ internal sealed partial class DailyNoteNotificationOperation return; } - List notifyInfos = new(); + List notifyInfos = []; CheckNotifySuppressed(entry, notifyInfos); @@ -139,7 +139,7 @@ internal sealed partial class DailyNoteNotificationOperation SH.ServiceDailyNoteNotifierResin, Web.HutaoEndpoints.StaticFile("ItemIcon", "UI_ItemIcon_210.png"), $"{entry.DailyNote.CurrentResin}", - SH.ServiceDailyNoteNotifierResinCurrent.Format(entry.DailyNote.CurrentResin))); + SH.FormatServiceDailyNoteNotifierResinCurrent(entry.DailyNote.CurrentResin))); entry.ResinNotifySuppressed = true; } } @@ -160,7 +160,7 @@ internal sealed partial class DailyNoteNotificationOperation SH.ServiceDailyNoteNotifierHomeCoin, Web.HutaoEndpoints.StaticFile("ItemIcon", "UI_ItemIcon_204.png"), $"{entry.DailyNote.CurrentHomeCoin}", - SH.ServiceDailyNoteNotifierHomeCoinCurrent.Format(entry.DailyNote.CurrentHomeCoin))); + SH.FormatServiceDailyNoteNotifierHomeCoinCurrent(entry.DailyNote.CurrentHomeCoin))); entry.HomeCoinNotifySuppressed = true; } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteOptions.cs index 3786ef2e..265aa1a3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteOptions.cs @@ -20,6 +20,15 @@ internal sealed partial class DailyNoteOptions : DbStoreOptions { private const int OneMinute = 60; + private readonly List> refreshTimes = + [ + new(SH.ViewModelDailyNoteRefreshTime4, OneMinute * 4), + new(SH.ViewModelDailyNoteRefreshTime8, OneMinute * 8), + new(SH.ViewModelDailyNoteRefreshTime30, OneMinute * 30), + new(SH.ViewModelDailyNoteRefreshTime40, OneMinute * 40), + new(SH.ViewModelDailyNoteRefreshTime60, OneMinute * 60), + ]; + private readonly RuntimeOptions runtimeOptions; private readonly IServiceProvider serviceProvider; private readonly IScheduleTaskInterop scheduleTaskInterop; @@ -32,14 +41,7 @@ internal sealed partial class DailyNoteOptions : DbStoreOptions /// /// 刷新时间 /// - public List> RefreshTimes { get; } = new() - { - new(SH.ViewModelDailyNoteRefreshTime4, OneMinute * 4), - new(SH.ViewModelDailyNoteRefreshTime8, OneMinute * 8), - new(SH.ViewModelDailyNoteRefreshTime30, OneMinute * 30), - new(SH.ViewModelDailyNoteRefreshTime40, OneMinute * 40), - new(SH.ViewModelDailyNoteRefreshTime60, OneMinute * 60), - }; + public List> RefreshTimes { get => refreshTimes; } public bool IsAutoRefreshEnabled { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsExtension.cs index cc34c7ef..7259e33f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsExtension.cs @@ -50,10 +50,10 @@ internal static class GachaStatisticsExtension public static List ToStatisticsList(this Dictionary dict) where TItem : IStatisticsItemSource { - return dict + IOrderedEnumerable result = dict .Select(kvp => kvp.Key.ToStatisticsItem(kvp.Value)) - .OrderByDescending(item => item.Count) - .ToList(); + .OrderByDescending(item => item.Count); + return [.. result]; } [SuppressMessage("", "IDE0057")] diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs index 4100b4b0..48bda3db 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs @@ -54,11 +54,11 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory TypedWishSummaryBuilderContext weaponContext = TypedWishSummaryBuilderContext.WeaponEventWish(taskContext, gachaLogClient); TypedWishSummaryBuilder weaponWishBuilder = new(weaponContext); - Dictionary orangeAvatarCounter = new(); - Dictionary purpleAvatarCounter = new(); - Dictionary orangeWeaponCounter = new(); - Dictionary purpleWeaponCounter = new(); - Dictionary blueWeaponCounter = new(); + Dictionary orangeAvatarCounter = []; + Dictionary purpleAvatarCounter = []; + Dictionary orangeWeaponCounter = []; + Dictionary purpleWeaponCounter = []; + Dictionary blueWeaponCounter = []; // Pre group builders Dictionary> historyWishBuilderMap = historyWishBuilders @@ -132,7 +132,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory default: // ItemId string length not correct. - ThrowHelper.UserdataCorrupted(SH.ServiceGachaStatisticsFactoryItemIdInvalid.Format(item.ItemId), default!); + ThrowHelper.UserdataCorrupted(SH.FormatServiceGachaStatisticsFactoryItemIdInvalid(item.ItemId), default!); break; } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HistoryWishBuilder.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HistoryWishBuilder.cs index 6feac754..043a43bb 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HistoryWishBuilder.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HistoryWishBuilder.cs @@ -16,11 +16,11 @@ internal sealed class HistoryWishBuilder { private readonly GachaEvent gachaEvent; - private readonly Dictionary orangeUpCounter = new(); - private readonly Dictionary purpleUpCounter = new(); - private readonly Dictionary orangeCounter = new(); - private readonly Dictionary purpleCounter = new(); - private readonly Dictionary blueCounter = new(); + private readonly Dictionary orangeUpCounter = []; + private readonly Dictionary purpleUpCounter = []; + private readonly Dictionary orangeCounter = []; + private readonly Dictionary purpleCounter = []; + private readonly Dictionary blueCounter = []; private int totalCountTracker; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HutaoStatisticsFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HutaoStatisticsFactory.cs index ad7f6b9f..feae8b55 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HutaoStatisticsFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HutaoStatisticsFactory.cs @@ -26,7 +26,7 @@ internal sealed class HutaoStatisticsFactory // TODO: when in new verion // due to lack of newer metadata // this can crash - DateTimeOffset now = DateTimeOffset.Now; + DateTimeOffset now = DateTimeOffset.UtcNow; avatarEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaConfigType.AvatarEventWish); avatarEvent2 = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaConfigType.AvatarEventWish2); weaponEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaConfigType.WeaponEventWish); @@ -44,10 +44,10 @@ internal sealed class HutaoStatisticsFactory private HutaoWishSummary CreateWishSummary(GachaEvent gachaEvent, List items) { - List upItems = new(); - List orangeItems = new(); - List purpleItems = new(); - List blueItems = new(); + List upItems = []; + List orangeItems = []; + List purpleItems = []; + List blueItems = []; foreach (ref readonly ItemCount item in CollectionsMarshal.AsSpan(items)) { @@ -55,7 +55,7 @@ internal sealed class HutaoStatisticsFactory { 8U => context.IdAvatarMap[item.Item], 5U => context.IdWeaponMap[item.Item], - _ => throw ThrowHelper.UserdataCorrupted(SH.ServiceGachaStatisticsFactoryItemIdInvalid.Format(item.Item), default!), + _ => throw ThrowHelper.UserdataCorrupted(SH.FormatServiceGachaStatisticsFactoryItemIdInvalid(item.Item), default!), }; StatisticsItem statisticsItem = source.ToStatisticsItem(unchecked((int)item.Count)); @@ -80,10 +80,10 @@ internal sealed class HutaoStatisticsFactory return new() { Event = gachaEvent, - UpItems = upItems.OrderByDescending(i => i.Quality).ThenByDescending(i => i.Count).ToList(), - OrangeItems = orangeItems.OrderByDescending(i => i.Count).ToList(), - PurpleItems = purpleItems.OrderByDescending(i => i.Count).ToList(), - BlueItems = blueItems.OrderByDescending(i => i.Count).ToList(), + UpItems = [.. upItems.OrderByDescending(i => i.Quality).ThenByDescending(i => i.Count)], + OrangeItems = [.. orangeItems.OrderByDescending(i => i.Count)], + PurpleItems = [.. purpleItems.OrderByDescending(i => i.Count)], + BlueItems = [.. blueItems.OrderByDescending(i => i.Count)], }; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/TypedWishSummaryBuilder.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/TypedWishSummaryBuilder.cs index e6392eee..db25c690 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/TypedWishSummaryBuilder.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/TypedWishSummaryBuilder.cs @@ -32,9 +32,9 @@ internal sealed class TypedWishSummaryBuilder private readonly TypedWishSummaryBuilderContext context; - private readonly List averageOrangePullTracker = new(); - private readonly List averageUpOrangePullTracker = new(); - private readonly List summaryItems = new(); + private readonly List averageOrangePullTracker = []; + private readonly List averageUpOrangePullTracker = []; + private readonly List summaryItems = []; private int maxOrangePullTracker; private int minOrangePullTracker; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLog.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLog.cs index 7ace4b6a..81de38be 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLog.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLog.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo; -using System.Collections.Immutable; +using System.Collections.Frozen; namespace Snap.Hutao.Service.GachaLog; @@ -14,11 +14,11 @@ internal static class GachaLog /// /// 查询类型 /// - public static readonly ImmutableList QueryTypes = new List - { + public static readonly FrozenSet QueryTypes = FrozenSet.ToFrozenSet( + [ GachaConfigType.NoviceWish, GachaConfigType.StandardWish, GachaConfigType.AvatarEventWish, GachaConfigType.WeaponEventWish, - }.ToImmutableList(); // TODO: FrozenSet + ]); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogDbService.cs index dc4c3ce8..cfa27516 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogDbService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogDbService.cs @@ -30,7 +30,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService } catch (SqliteException ex) { - string message = SH.ServiceGachaLogArchiveCollectionUserdataCorruptedMessage.Format(ex.Message); + string message = SH.FormatServiceGachaLogArchiveCollectionUserdataCorruptedMessage(ex.Message); throw ThrowHelper.UserdataCorrupted(message, ex); } } @@ -40,11 +40,11 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService using (IServiceScope scope = serviceProvider.CreateScope()) { AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - return appDbContext.GachaItems + IOrderedQueryable result = appDbContext.GachaItems .AsNoTracking() .Where(i => i.ArchiveId == archiveId) - .OrderBy(i => i.Id) - .ToList(); + .OrderBy(i => i.Id); + return [.. result]; } } @@ -271,7 +271,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService using (IServiceScope scope = serviceProvider.CreateScope()) { AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - return appDbContext.GachaItems + IQueryable result = appDbContext.GachaItems .AsNoTracking() .Where(i => i.ArchiveId == archiveId) .Where(i => i.QueryType == queryType) @@ -286,8 +286,8 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService ItemId = i.ItemId, Time = i.Time, Id = i.Id, - }) - .ToList(); + }); + return [..result]; } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogFetchContext.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogFetchContext.cs index e243efe7..5d7448eb 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogFetchContext.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogFetchContext.cs @@ -70,7 +70,7 @@ internal struct GachaLogFetchContext { DbEndId = null; CurrentType = configType; - ItemsToAdd = new(); + ItemsToAdd = []; FetchStatus = new(configType); QueryOptions = new(query, configType); } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogFetchStatus.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogFetchStatus.cs index c77de33c..a01c15c4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogFetchStatus.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogFetchStatus.cs @@ -41,7 +41,7 @@ internal sealed class GachaLogFetchStatus { return AuthKeyTimeout ? SH.ViewDialogGachaLogRefreshProgressAuthkeyTimeout - : SH.ViewDialogGachaLogRefreshProgressDescription.Format(ConfigType.GetLocalizedDescription()); + : SH.FormatViewDialogGachaLogRefreshProgressDescription(ConfigType.GetLocalizedDescription()); } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogHutaoCloudService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogHutaoCloudService.cs index 2ea24ed2..5b50ca21 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogHutaoCloudService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogHutaoCloudService.cs @@ -39,7 +39,7 @@ internal sealed partial class GachaLogHutaoCloudService : IGachaLogHutaoCloudSer string uid = gachaArchive.Uid; if (await GetEndIdsFromCloudAsync(uid, token).ConfigureAwait(false) is { } endIds) { - List items = new(); + List items = []; foreach ((GachaConfigType type, long endId) in endIds) { List part = await gachaLogDbService diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs index e6daeaf2..403046ce 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs @@ -99,7 +99,7 @@ internal sealed partial class GachaLogService : IGachaLogService await InitializeAsync(token).ConfigureAwait(false); ArgumentNullException.ThrowIfNull(ArchiveCollection); - List statistics = new(); + List statistics = []; foreach (GachaArchive archive in ArchiveCollection) { List items = await gachaLogDbService.GetGachaItemListByArchiveIdAsync(archive.InnerId).ConfigureAwait(false); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogServiceMetadataContext.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogServiceMetadataContext.cs index d783f5b9..749903fc 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogServiceMetadataContext.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogServiceMetadataContext.cs @@ -18,7 +18,7 @@ internal readonly struct GachaLogServiceMetadataContext /// /// 物品缓存 /// - public readonly Dictionary ItemCache = new(); + public readonly Dictionary ItemCache = []; /// /// Id 角色 映射 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 2a2bbc38..a146c2d3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryManualInputProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryManualInputProvider.cs @@ -4,7 +4,8 @@ using Snap.Hutao.Factory.ContentDialog; using Snap.Hutao.Service.Metadata; using Snap.Hutao.View.Dialog; -using Snap.Hutao.Web.Request.QueryString; +using System.Collections.Specialized; +using System.Web; namespace Snap.Hutao.Service.GachaLog.QueryProvider; @@ -27,18 +28,18 @@ internal sealed partial class GachaLogQueryManualInputProvider : IGachaLogQueryP if (isOk) { - QueryString query = QueryString.Parse(queryString); + NameValueCollection query = HttpUtility.ParseQueryString(queryString); + if (query.TryGetValue("auth_appid", out string? appId) && appId is "webview_gacha") { - string queryLanguageCode = query["lang"]; + string? queryLanguageCode = query["lang"]; if (metadataOptions.IsCurrentLocale(queryLanguageCode)) { return new(true, new(queryString)); } else { - string message = SH.ServiceGachaLogUrlProviderUrlLanguageNotMatchCurrentLocale - .Format(queryLanguageCode, metadataOptions.LanguageCode); + string message = SH.FormatServiceGachaLogUrlProviderUrlLanguageNotMatchCurrentLocale(queryLanguageCode, metadataOptions.LanguageCode); return new(false, message); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQuerySTokenProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQuerySTokenProvider.cs index 0f76f308..32e1c549 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQuerySTokenProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQuerySTokenProvider.cs @@ -4,9 +4,10 @@ using Snap.Hutao.Service.Metadata; using Snap.Hutao.Service.User; using Snap.Hutao.ViewModel.User; -using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo; using Snap.Hutao.Web.Hoyolab.Takumi.Binding; +using Snap.Hutao.Web.Request; using Snap.Hutao.Web.Response; +using System.Collections.Specialized; namespace Snap.Hutao.Service.GachaLog.QueryProvider; @@ -37,7 +38,7 @@ internal sealed partial class GachaLogQuerySTokenProvider : IGachaLogQueryProvid if (authkeyResponse.IsOk()) { - return new(true, new(GachaLogQueryOptions.ToQueryString(data, authkeyResponse.Data, metadataOptions.LanguageCode))); + return new(true, new(ComposeQueryString(data, authkeyResponse.Data, metadataOptions.LanguageCode))); } else { @@ -49,4 +50,16 @@ internal sealed partial class GachaLogQuerySTokenProvider : IGachaLogQueryProvid return new(false, SH.MustSelectUserAndUid); } } + + private static string ComposeQueryString(GenAuthKeyData genAuthKeyData, GameAuthKey gameAuthKey, string lang) + { + NameValueCollection collection = []; + collection.Set("lang", lang); + collection.Set("auth_appid", genAuthKeyData.AuthAppId); + collection.Set("authkey", gameAuthKey.AuthKey); + collection.Set("authkey_ver", $"{gameAuthKey.AuthKeyVersion:D}"); + collection.Set("sign_type", $"{gameAuthKey.SignType:D}"); + + return collection.ToQueryString(); + } } \ No newline at end of file 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 3b37e5f9..b36ce4a4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryWebCacheProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryWebCacheProvider.cs @@ -4,10 +4,12 @@ using Snap.Hutao.Core.IO; using Snap.Hutao.Service.Game; using Snap.Hutao.Service.Metadata; -using Snap.Hutao.Web.Request.QueryString; +using System.Collections.Specialized; +using System.Globalization; using System.IO; using System.Text; using System.Text.RegularExpressions; +using System.Web; namespace Snap.Hutao.Service.GachaLog.QueryProvider; @@ -69,7 +71,8 @@ internal sealed partial class GachaLogQueryWebCacheProvider : IGachaLogQueryProv { if (!tempFile.TryGetValue(out TempFile file)) { - return new(false, Regex.Unescape(SH.ServiceGachaLogUrlProviderCachePathNotFound).Format(cacheFile)); + string unescaped = Regex.Unescape(SH.ServiceGachaLogUrlProviderCachePathNotFound); + return new(false, string.Format(CultureInfo.CurrentCulture, unescaped, cacheFile)); } using (FileStream fileStream = new(file.Path, FileMode.Open, FileAccess.Read, FileShare.Read)) @@ -84,16 +87,15 @@ internal sealed partial class GachaLogQueryWebCacheProvider : IGachaLogQueryProv return new(false, SH.ServiceGachaLogUrlProviderCacheUrlNotFound); } - QueryString query = QueryString.Parse(result.TrimEnd("#/log")); - string queryLanguageCode = query["lang"]; + NameValueCollection query = HttpUtility.ParseQueryString(result.TrimEnd("#/log")); + string? queryLanguageCode = query["lang"]; if (metadataOptions.IsCurrentLocale(queryLanguageCode)) { return new(true, new(result)); } - string message = SH.ServiceGachaLogUrlProviderUrlLanguageNotMatchCurrentLocale - .Format(queryLanguageCode, metadataOptions.LanguageCode); + string message = SH.FormatServiceGachaLogUrlProviderUrlLanguageNotMatchCurrentLocale(queryLanguageCode, metadataOptions.LanguageCode); return new(false, message); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UIGFImportService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UIGFImportService.cs index 45186002..ed63e016 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UIGFImportService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UIGFImportService.cs @@ -39,13 +39,13 @@ internal sealed partial class UIGFImportService : IUIGFImportService { if (!metadataOptions.IsCurrentLocale(uigf.Info.Language)) { - string message = SH.ServiceGachaUIGFImportLanguageNotMatch.Format(uigf.Info.Language, metadataOptions.LanguageCode); + string message = SH.FormatServiceGachaUIGFImportLanguageNotMatch(uigf.Info.Language, metadataOptions.LanguageCode); ThrowHelper.InvalidOperation(message); } if (!uigf.IsMajor2Minor2OrLowerListValid(out long id)) { - string message = SH.ServiceGachaLogUIGFImportItemInvalidFormat.Format(id); + string message = SH.FormatServiceGachaLogUIGFImportItemInvalidFormat(id); ThrowHelper.InvalidOperation(message); } } @@ -54,7 +54,7 @@ internal sealed partial class UIGFImportService : IUIGFImportService { if (!uigf.IsMajor2Minor3OrHigherListValid(out long id)) { - string message = SH.ServiceGachaLogUIGFImportItemInvalidFormat.Format(id); + string message = SH.FormatServiceGachaLogUIGFImportItemInvalidFormat(id); ThrowHelper.InvalidOperation(message); } } @@ -62,7 +62,7 @@ internal sealed partial class UIGFImportService : IUIGFImportService GachaArchiveOperation.GetOrAdd(gachaLogDbService, taskContext, uigf.Info.Uid, archives, out GachaArchive? archive); Guid archiveId = archive.InnerId; - List fullItems = new(); + List fullItems = []; foreach (GachaConfigType queryType in GachaLog.QueryTypes) { long trimId = gachaLogDbService.GetOldestGachaItemIdByArchiveIdAndQueryType(archiveId, queryType); @@ -98,7 +98,7 @@ internal sealed partial class UIGFImportService : IUIGFImportService // 因此从尾部开始查找 if (currentTypeToAdd.LastOrDefault(item => item.ItemId is 0U) is { } item) { - ThrowHelper.InvalidOperation(SH.ServiceGachaLogUIGFImportItemInvalidFormat.Format(item.Id)); + ThrowHelper.InvalidOperation(SH.FormatServiceGachaLogUIGFImportItemInvalidFormat(item.Id)); } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/GameAccountService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/GameAccountService.cs index f8363be1..e3ef96f9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/GameAccountService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/GameAccountService.cs @@ -14,7 +14,6 @@ namespace Snap.Hutao.Service.Game.Account; internal sealed partial class GameAccountService : IGameAccountService { private readonly IContentDialogFactory contentDialogFactory; - private readonly IServiceProvider serviceProvider; private readonly IGameDbService gameDbService; private readonly ITaskContext taskContext; private readonly AppOptions appOptions; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs index 20db8658..80fd68d3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs @@ -48,16 +48,16 @@ internal sealed partial class GameChannelOptionsService : IGameChannelOptionsSer { using (FileStream readStream = File.OpenRead(configPath)) { - elements = IniSerializer.Deserialize(readStream).ToList(); + elements = [.. IniSerializer.Deserialize(readStream)]; } } catch (FileNotFoundException ex) { - ThrowHelper.GameFileOperation(SH.ServiceGameSetMultiChannelConfigFileNotFound.Format(configPath), ex); + ThrowHelper.GameFileOperation(SH.FormatServiceGameSetMultiChannelConfigFileNotFound(configPath), ex); } catch (DirectoryNotFoundException ex) { - ThrowHelper.GameFileOperation(SH.ServiceGameSetMultiChannelConfigFileNotFound.Format(configPath), ex); + ThrowHelper.GameFileOperation(SH.FormatServiceGameSetMultiChannelConfigFileNotFound(configPath), ex); } catch (UnauthorizedAccessException ex) { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileOperationException.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileOperationException.cs index d15a618e..cc5c6fc4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileOperationException.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileOperationException.cs @@ -15,7 +15,7 @@ internal sealed class GameFileOperationException : Exception /// 消息 /// 内部错误 public GameFileOperationException(string message, Exception? innerException) - : base(SH.ServiceGameFileOperationExceptionMessage.Format(message), innerException) + : base(SH.FormatServiceGameFileOperationExceptionMessage(message), innerException) { } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs index 28d1e743..aa265ef3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs @@ -141,7 +141,7 @@ internal sealed class LaunchOptions : DbStoreOptions /// /// 所有监视器 /// - public List> Monitors { get; } = new(); + public List> Monitors { get; } = []; /// /// 目标帧率 @@ -165,11 +165,11 @@ internal sealed class LaunchOptions : DbStoreOptions set => SetOption(ref isMonitorEnabled, SettingEntry.LaunchIsMonitorEnabled, value); } - public List AspectRatios { get; } = new() - { + public List AspectRatios { get; } = + [ new(2560, 1440), new(1920, 1080), - }; + ]; public AspectRatio? SelectedAspectRatio { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverter.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverter.cs index 3e39bfd9..65098687 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverter.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverter.cs @@ -8,6 +8,7 @@ using Snap.Hutao.Core.IO; using Snap.Hutao.Core.IO.Hashing; using Snap.Hutao.Service.Game.Scheme; using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher; +using System.Globalization; using System.IO; using System.IO.Compression; using System.Net.Http; @@ -178,11 +179,11 @@ internal sealed partial class PackageConverter private async ValueTask> GetVersionItemsAsync(Stream stream) { - Dictionary results = new(); + Dictionary results = []; using (StreamReader reader = new(stream)) { Regex dataFolderRegex = DataFolderRegex(); - while (await reader.ReadLineAsync().ConfigureAwait(false) is { } row && !string.IsNullOrEmpty(row)) + while (await reader.ReadLineAsync().ConfigureAwait(false) is { Length: > 0 } row) { VersionItem? item = JsonSerializer.Deserialize(row, options); ArgumentNullException.ThrowIfNull(item); @@ -236,7 +237,7 @@ internal sealed partial class PackageConverter private async ValueTask SkipOrDownloadAsync(ItemOperationInfo info, PackageConvertContext context, IProgress progress) { // 还原正确的远程地址 - string remoteName = info.Remote.RelativePath.Format(context.ToDataFolderName); + string remoteName = string.Format(CultureInfo.CurrentCulture, info.Remote.RelativePath, context.ToDataFolderName); string cacheFile = context.GetServerCacheTargetFilePath(remoteName); if (File.Exists(cacheFile)) @@ -283,7 +284,7 @@ internal sealed partial class PackageConverter { // System.IO.IOException: The response ended prematurely. // System.IO.IOException: Received an unexpected EOF or 0 bytes from the transport stream. - ThrowHelper.PackageConvert(SH.ServiceGamePackageRequestScatteredFileFailed.Format(remoteName), ex); + ThrowHelper.PackageConvert(SH.FormatServiceGamePackageRequestScatteredFileFailed(remoteName), ex); } } } @@ -306,8 +307,8 @@ internal sealed partial class PackageConverter // 先备份 if (moveToBackup) { - string localFileName = info.Local.RelativePath.Format(context.FromDataFolderName); - progress.Report(new(SH.ServiceGamePackageConvertMoveFileBackupFormat.Format(localFileName))); + string localFileName = string.Format(CultureInfo.CurrentCulture, info.Local.RelativePath, context.FromDataFolderName); + progress.Report(new(SH.FormatServiceGamePackageConvertMoveFileBackupFormat(localFileName))); string localFilePath = context.GetGameFolderFilePath(localFileName); string cacheFilePath = context.GetServerCacheBackupFilePath(localFileName); @@ -322,8 +323,8 @@ internal sealed partial class PackageConverter // 后替换 if (moveToTarget) { - string targetFileName = info.Remote.RelativePath.Format(context.ToDataFolderName); - progress.Report(new(SH.ServiceGamePackageConvertMoveFileRestoreFormat.Format(targetFileName))); + string targetFileName = string.Format(CultureInfo.CurrentCulture, info.Remote.RelativePath, context.ToDataFolderName); + progress.Report(new(SH.FormatServiceGamePackageConvertMoveFileRestoreFormat(targetFileName))); string targetFilePath = context.GetGameFolderFilePath(targetFileName); string? targetFileDirectory = Path.GetDirectoryName(targetFilePath); @@ -339,7 +340,7 @@ internal sealed partial class PackageConverter // 重命名 _Data 目录 try { - progress.Report(new(SH.ServiceGamePackageConvertMoveFileRenameFormat.Format(context.FromDataFolderName, context.ToDataFolderName))); + progress.Report(new(SH.FormatServiceGamePackageConvertMoveFileRenameFormat(context.FromDataFolderName, context.ToDataFolderName))); DirectoryOperation.Move(context.FromDataFolder, context.ToDataFolder); } catch (IOException ex) diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Process/GameProcessService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Process/GameProcessService.cs index 13780b2d..c1dbf7d4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Process/GameProcessService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Process/GameProcessService.cs @@ -30,8 +30,8 @@ internal sealed partial class GameProcessService : IGameProcessService return false; } - return System.Diagnostics.Process.GetProcessesByName(YuanShenProcessName).Any() - || System.Diagnostics.Process.GetProcessesByName(GenshinImpactProcessName).Any(); + return System.Diagnostics.Process.GetProcessesByName(YuanShenProcessName).Length > 0 + || System.Diagnostics.Process.GetProcessesByName(GenshinImpactProcessName).Length > 0; } public async ValueTask LaunchAsync(IProgress progress) @@ -126,7 +126,9 @@ internal sealed partial class GameProcessService : IGameProcessService private ValueTask UnlockFpsAsync(System.Diagnostics.Process game, IProgress progress, CancellationToken token = default) { +#pragma warning disable CA1859 IGameFpsUnlocker unlocker = serviceProvider.CreateInstance(game); +#pragma warning restore CA1859 UnlockTimingOptions options = new(100, 20000, 3000); Progress lockerProgress = new(unlockStatus => progress.Report(LaunchStatus.FromUnlockStatus(unlockStatus))); return unlocker.UnlockAsync(options, lockerProgress, token); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/KnownLaunchSchemes.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/KnownLaunchSchemes.cs index c1373917..1240c893 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/KnownLaunchSchemes.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/KnownLaunchSchemes.cs @@ -28,8 +28,9 @@ internal static class KnownLaunchSchemes /// 已知的启动方案 public static List Get() { - return new List() - { + return + [ + // 官服 ServerChineseChannelDefaultSubChannelDefaultCompat, ServerChineseChannelOfficialSubChannelDefault, @@ -47,6 +48,6 @@ internal static class KnownLaunchSchemes ServerGlobalChannelOfficialSubChannelOfficial, ServerGlobalChannelOfficialSubChannelEpic, ServerGlobalChannelOfficialSubChannelGoogle, - }; + ]; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs index 05132861..978ebf55 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs @@ -135,8 +135,8 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker { // E8 ?? ?? ?? ?? 85 C0 7E 07 E8 ?? ?? ?? ?? EB 05 int second = 0; - ReadOnlySpan secondPart = stackalloc byte[] { 0x85, 0xC0, 0x7E, 0x07, 0xE8, }; - ReadOnlySpan thirdPart = stackalloc byte[] { 0xEB, 0x05, }; + ReadOnlySpan secondPart = [0x85, 0xC0, 0x7E, 0x07, 0xE8,]; + ReadOnlySpan thirdPart = [0xEB, 0x05,]; while (second >= 0 && second < memory.Length) { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoAsAService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoAsAService.cs index dddc3381..40963af4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoAsAService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoAsAService.cs @@ -39,7 +39,7 @@ internal sealed partial class HutaoAsAService : IHutaoAsAService } else { - return new(); + return []; } } @@ -54,13 +54,13 @@ internal sealed partial class HutaoAsAService : IHutaoAsAService foreach ((string key, object value) in excludedIds) { - if (value is DateTimeOffset time && time < DateTimeOffset.Now - TimeSpan.FromDays(AnnouncementDuration)) + if (value is DateTimeOffset time && time < DateTimeOffset.UtcNow - TimeSpan.FromDays(AnnouncementDuration)) { excludedIds.Remove(key); } } - excludedIds.TryAdd($"{announcement.Id}", DateTimeOffset.Now + TimeSpan.FromDays(AnnouncementDuration)); + excludedIds.TryAdd($"{announcement.Id}", DateTimeOffset.UtcNow + TimeSpan.FromDays(AnnouncementDuration)); LocalSetting.Set(SettingKeys.ExcludedAnnouncementIds, excludedIds); announcements.Remove(announcement); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs index 9ed938c0..c5c049da 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs @@ -184,7 +184,7 @@ internal sealed partial class HutaoCache : IHutaoCache AvatarAppearanceRanks = avatarAppearanceRanksRaw.SortByDescending(r => r.Floor).SelectList(rank => new AvatarRankView { - Floor = SH.ModelBindingHutaoComplexRankFloor.Format(rank.Floor), + Floor = SH.FormatModelBindingHutaoComplexRankFloor(rank.Floor), Avatars = rank.Ranks.SortByDescending(r => r.Rate).SelectList(rank => new AvatarView(idAvatarMap[rank.Item], rank.Rate)), }); } @@ -201,7 +201,7 @@ internal sealed partial class HutaoCache : IHutaoCache AvatarUsageRanks = avatarUsageRanksRaw.SortByDescending(r => r.Floor).SelectList(rank => new AvatarRankView { - Floor = SH.ModelBindingHutaoComplexRankFloor.Format(rank.Floor), + Floor = SH.FormatModelBindingHutaoComplexRankFloor(rank.Floor), Avatars = rank.Ranks.SortByDescending(r => r.Rate).SelectList(rank => new AvatarView(idAvatarMap[rank.Item], rank.Rate)), }); } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoUserOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoUserOptions.cs index f8db2272..87074a42 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoUserOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoUserOptions.cs @@ -5,6 +5,7 @@ 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; @@ -118,9 +119,10 @@ internal sealed class HutaoUserOptions : ObservableObject, IOptions DateTimeOffset.Now; + IsCloudServiceAllowed = IsLicensedDeveloper || userInfo.GachaLogExpireAt > DateTimeOffset.UtcNow; } public async ValueTask GetTokenAsync() diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/ObjectCacheDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/ObjectCacheDbService.cs index 8e61bf18..f93c7eeb 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/ObjectCacheDbService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/ObjectCacheDbService.cs @@ -25,7 +25,7 @@ internal sealed partial class ObjectCacheDbService : IObjectCacheDbService await appDbContext.ObjectCache.AddAndSaveAsync(new() { Key = key, - ExpireTime = DateTimeOffset.Now.Add(expire), + ExpireTime = DateTimeOffset.UtcNow.Add(expire), Value = JsonSerializer.Serialize(data, jsonSerializerOptions), }).ConfigureAwait(false); } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataOptions.cs index ac89edb7..2e3eba2d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataOptions.cs @@ -108,7 +108,7 @@ internal sealed partial class MetadataOptions : IOptions /// /// 语言代码 /// 是否为当前语言名称 - public bool IsCurrentLocale(string languageCode) + public bool IsCurrentLocale(string? languageCode) { if (string.IsNullOrEmpty(languageCode)) { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Indexing.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Indexing.cs index f6b0c259..64dddd00 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Indexing.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Indexing.cs @@ -84,7 +84,6 @@ internal sealed partial class MetadataService Dictionary displays = await FromCacheAsDictionaryAsync(FileNameDisplayItem, a => a.Id, token).ConfigureAwait(false); Dictionary materials = await GetIdToMaterialMapAsync(token).ConfigureAwait(false); - // TODO: Cache this Dictionary results = new(displays); foreach ((MaterialId id, DisplayItem material) in materials) diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs index 66eb91d0..62e99f30 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs @@ -9,7 +9,6 @@ using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Core.IO.Hashing; using Snap.Hutao.Core.Setting; using Snap.Hutao.Service.Notification; -using System.Globalization; using System.IO; using System.Net; using System.Net.Http; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Navigation/NavigationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Navigation/NavigationService.cs index 0d8f10dc..8729e996 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Navigation/NavigationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Navigation/NavigationService.cs @@ -168,6 +168,23 @@ internal sealed class NavigationService : INavigationService, INavigationInitial }); } + private static IEnumerable EnumerateMenuItems(IList items) + { + foreach (NavigationViewItem item in items.OfType()) + { + yield return item; + + // Suppress recursion method call if possible + if (item.MenuItems.Count > 0) + { + foreach (NavigationViewItem subItem in EnumerateMenuItems(item.MenuItems)) + { + yield return subItem; + } + } + } + } + private bool SyncSelectedNavigationViewItemWith(Type? pageType) { if (NavigationView is null || pageType is null) @@ -191,23 +208,6 @@ internal sealed class NavigationService : INavigationService, INavigationInitial return true; } - private IEnumerable EnumerateMenuItems(IList items) - { - foreach (NavigationViewItem item in items.OfType()) - { - yield return item; - - // Suppress recursion method call if possible - if (item.MenuItems.Count > 0) - { - foreach (NavigationViewItem subItem in EnumerateMenuItems(item.MenuItems)) - { - yield return subItem; - } - } - } - } - private void OnItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args) { selected = NavigationView?.SelectedItem as NavigationViewItem; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Notification/InfoBarService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Notification/InfoBarService.cs index 1f69dae3..f5ebe8f9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Notification/InfoBarService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Notification/InfoBarService.cs @@ -31,7 +31,7 @@ internal sealed class InfoBarService : IInfoBarService /// public ObservableCollection Collection { - get => collection ??= new(); + get => collection ??= []; } /// @@ -121,7 +121,7 @@ internal sealed class InfoBarService : IInfoBarService Title = title, Message = message, IsOpen = true, - Transitions = new() { new AddDeleteThemeTransition() }, + Transitions = [new AddDeleteThemeTransition()], }; infoBar.Closed += infobarClosedEventHandler; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/SignIn/SignInService.cs b/src/Snap.Hutao/Snap.Hutao/Service/SignIn/SignInService.cs index 62d23daf..18d690c4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/SignIn/SignInService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/SignIn/SignInService.cs @@ -33,7 +33,7 @@ internal sealed partial class SignInService : ISignInService { int index = infoResponse.Data.TotalSignDay - 1; Award award = rewardResponse.Data.Awards[index]; - return new(true, SH.ServiceSignInSuccessRewardFormat.Format(award.Name, award.Count)); + return new(true, SH.FormatServiceSignInSuccessRewardFormat(award.Name, award.Count)); } else { @@ -54,7 +54,7 @@ internal sealed partial class SignInService : ISignInService message = $"RiskCode: {resultResponse.Data?.RiskCode}"; } - return new(false, SH.ServiceSignInClaimRewardFailedFormat.Format(message)); + return new(false, SH.FormatServiceSignInClaimRewardFailedFormat(message)); } } else diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserFingerprintService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserFingerprintService.cs index 2d8b1004..4a2be536 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserFingerprintService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserFingerprintService.cs @@ -4,7 +4,6 @@ using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab.PublicData.DeviceFp; using Snap.Hutao.Web.Response; -using System.Text; namespace Snap.Hutao.Service.User; @@ -27,7 +26,7 @@ internal sealed partial class UserFingerprintService : IUserFingerprintService return; } - string model = GetRandomStringOfLength(6); + string model = Core.Random.GetUpperAndNumberString(6); Dictionary extendProperties = new() { { "cpuType", "arm64-v8a" }, @@ -66,48 +65,18 @@ internal sealed partial class UserFingerprintService : IUserFingerprintService DeviceFpData data = new() { - DeviceId = GetRandomHexStringOfLength(16), + DeviceId = Core.Random.GetLowerHexString(16), SeedId = $"{Guid.NewGuid()}", Platform = "2", - SeedTime = $"{DateTimeOffset.Now.ToUnixTimeMilliseconds()}", + SeedTime = $"{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}", ExtFields = JsonSerializer.Serialize(extendProperties), AppName = "bbs_cn", BbsDeviceId = HoyolabOptions.DeviceId, - DeviceFp = string.IsNullOrEmpty(user.Fingerprint) ? GetRandomHexStringOfLength(13) : user.Fingerprint, + DeviceFp = string.IsNullOrEmpty(user.Fingerprint) ? Core.Random.GetLowerHexString(13) : user.Fingerprint, }; Response response = await deviceFpClient.GetFingerprintAsync(data, token).ConfigureAwait(false); user.Fingerprint = response.IsOk() ? response.Data.DeviceFp : string.Empty; user.NeedDbUpdateAfterResume = true; } - - private static string GetRandomHexStringOfLength(int length) - { - const string RandomRange = "0123456789abcdef"; - - StringBuilder sb = new(length); - - for (int i = 0; i < length; i++) - { - int pos = Random.Shared.Next(0, RandomRange.Length); - sb.Append(RandomRange[pos]); - } - - return sb.ToString(); - } - - private static string GetRandomStringOfLength(int length) - { - const string RandomRange = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - - StringBuilder sb = new(length); - - for (int i = 0; i < length; i++) - { - int pos = Random.Shared.Next(0, RandomRange.Length); - sb.Append(RandomRange[pos]); - } - - return sb.ToString(); - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs index 2d2dfb7c..83816b05 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs @@ -24,7 +24,7 @@ internal sealed partial class UserInitializationService : IUserInitializationSer if (!await InitializeUserAsync(user, token).ConfigureAwait(false)) { user.UserInfo = new() { Nickname = SH.ModelBindingUserInitializationFailed }; - user.UserGameRoles = new(); + user.UserGameRoles = []; } return user; @@ -176,7 +176,7 @@ internal sealed partial class UserInitializationService : IUserInitializationSer if (userGameRolesResponse.IsOk()) { user.UserGameRoles = userGameRolesResponse.Data.List; - return user.UserGameRoles.Any(); + return user.UserGameRoles.Count > 0; } else { diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj index 7ac0e919..0604b25b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj +++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj @@ -1,14 +1,16 @@  WinExe - net7.0-windows10.0.19041.0 + net8.0-windows10.0.22621.0 10.0.19041.0 Snap.Hutao app.manifest x64 x64 - win10-x64 - win10-x64 + + true + win-x64 + win-x64 win10-$(Platform).pubxml true False @@ -269,21 +271,22 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -302,10 +305,15 @@ + + + + + diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/AnnouncementContentViewer.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Control/AnnouncementContentViewer.xaml.cs index 159074db..afa1d250 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Control/AnnouncementContentViewer.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Control/AnnouncementContentViewer.xaml.cs @@ -4,8 +4,8 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.Web.WebView2.Core; +using Snap.Hutao.Control.Extension; using Snap.Hutao.Control.Theme; -using Snap.Hutao.Web.Bridge; using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement; using System.Text; using System.Text.RegularExpressions; diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/WebViewer.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Control/WebViewer.xaml.cs index 1f53bba9..26a63db1 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Control/WebViewer.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Control/WebViewer.xaml.cs @@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.Messaging; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.Web.WebView2.Core; +using Snap.Hutao.Control.Extension; using Snap.Hutao.Message; using Snap.Hutao.Service.Notification; using Snap.Hutao.Service.User; @@ -94,53 +95,52 @@ internal partial class WebViewer : UserControl, IRecipient return; } - // TODO: replace with .NET 8 UnsafeAccessor - try + if (WebView.IsDisposed()) { - CoreWebView2? coreWebView2 = WebView?.CoreWebView2; - - if (coreWebView2 is null) - { - return; - } - - if (SourceProvider is not null) - { - if (UserAndUid.TryFromUser(user, out UserAndUid? userAndUid)) - { - string source = SourceProvider.GetSource(userAndUid); - if (!string.IsNullOrEmpty(source)) - { - try - { - await coreWebView2.Profile.ClearBrowsingDataAsync(); - } - catch (InvalidCastException) - { - infoBarService.Warning(SH.ViewControlWebViewerCoreWebView2ProfileQueryInterfaceFailed); - await coreWebView2.DeleteCookiesAsync(userAndUid.IsOversea).ConfigureAwait(true); - } - - CoreWebView2Navigator navigator = new(coreWebView2); - await navigator.NavigateAsync("about:blank").ConfigureAwait(true); - - coreWebView2 - .SetCookie(user.CookieToken, user.LToken, userAndUid.IsOversea) - .SetMobileUserAgent(userAndUid.IsOversea); - jsBridge?.Detach(); - jsBridge = SourceProvider.CreateJSBridge(serviceProvider, coreWebView2, userAndUid); - - await navigator.NavigateAsync(source).ConfigureAwait(true); - } - } - else - { - infoBarService.Warning(SH.MustSelectUserAndUid); - } - } + return; } - catch (ObjectDisposedException) + + CoreWebView2? coreWebView2 = WebView?.CoreWebView2; + + if (coreWebView2 is null) { + return; + } + + if (SourceProvider is null) + { + return; + } + + if (!UserAndUid.TryFromUser(user, out UserAndUid? userAndUid)) + { + infoBarService.Warning(SH.MustSelectUserAndUid); + return; + } + + string source = SourceProvider.GetSource(userAndUid); + if (!string.IsNullOrEmpty(source)) + { + try + { + await coreWebView2.Profile.ClearBrowsingDataAsync(); + } + catch (InvalidCastException) + { + infoBarService.Warning(SH.ViewControlWebViewerCoreWebView2ProfileQueryInterfaceFailed); + await coreWebView2.DeleteCookiesAsync(userAndUid.IsOversea).ConfigureAwait(true); + } + + CoreWebView2Navigator navigator = new(coreWebView2); + await navigator.NavigateAsync("about:blank").ConfigureAwait(true); + + coreWebView2 + .SetCookie(user.CookieToken, user.LToken, userAndUid.IsOversea) + .SetMobileUserAgent(userAndUid.IsOversea); + jsBridge?.Detach(); + jsBridge = SourceProvider.CreateJSBridge(serviceProvider, coreWebView2, userAndUid); + + await navigator.NavigateAsync(source).ConfigureAwait(true); } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/ISupportLoginByWebView.cs b/src/Snap.Hutao/Snap.Hutao/View/Page/ISupportLoginByWebView.cs index 203ca5a1..a6101a73 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/ISupportLoginByWebView.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/ISupportLoginByWebView.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Microsoft.UI.Xaml.Controls; +using Snap.Hutao.Control.Extension; using Snap.Hutao.Service.Navigation; using Snap.Hutao.Service.Notification; using Snap.Hutao.Service.User; diff --git a/src/Snap.Hutao/Snap.Hutao/View/TitleView.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/TitleView.xaml.cs index 945f5f13..3eea5f49 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/TitleView.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/TitleView.xaml.cs @@ -25,20 +25,16 @@ internal sealed partial class TitleView : UserControl /// /// 标题 /// - [SuppressMessage("", "CA1822")] public string Title { + [SuppressMessage("", "IDE0027")] get { - Core.RuntimeOptions hutaoOptions = Ioc.Default.GetRequiredService(); - - string format = #if DEBUG - SH.AppDevNameAndVersion; + return SH.FormatAppDevNameAndVersion(RuntimeOptions.Version); #else - SH.AppNameAndVersion; + return SH.FormatAppNameAndVersion(RuntimeOptions.Version); #endif - return format.Format(hutaoOptions.Version); } } @@ -50,15 +46,7 @@ internal sealed partial class TitleView : UserControl get => DragableGrid; } - [SuppressMessage("", "CA1822")] - public RuntimeOptions RuntimeOptions - { - get => Ioc.Default.GetRequiredService(); - } + public RuntimeOptions RuntimeOptions { get; } = Ioc.Default.GetRequiredService(); - [SuppressMessage("", "CA1822")] - public HotKeyOptions HotKeyOptions - { - get => Ioc.Default.GetRequiredService(); - } + public HotKeyOptions HotKeyOptions { get; } = Ioc.Default.GetRequiredService(); } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementImporter.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementImporter.cs index e5a23ee0..a2937b94 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementImporter.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementImporter.cs @@ -26,7 +26,7 @@ internal sealed partial class AchievementImporter { private readonly IContentDialogFactory contentDialogFactory; private readonly IAchievementService achievementService; - private readonly IClipboardInterop clipboardInterop; + private readonly IClipboardProvider clipboardInterop; private readonly IInfoBarService infoBarService; private readonly IPickerFactory pickerFactory; private readonly JsonSerializerOptions options; diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementView.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementView.cs index a3b0bdbf..d35b851e 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementView.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementView.cs @@ -54,7 +54,7 @@ internal sealed class AchievementView : ObservableObject, IEntityWithMetadata public string Time { - get => $"{Entity.Time:yyyy.MM.dd HH:mm:ss}"; + get => $"{Entity.Time.ToLocalTime():yyyy.MM.dd HH:mm:ss}"; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs index 35aecde4..f4fd36c3 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs @@ -206,13 +206,13 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav case ArchiveAddResult.Added: await taskContext.SwitchToMainThreadAsync(); SelectedArchive = achievementService.CurrentArchive; - infoBarService.Success(SH.ViewModelAchievementArchiveAdded.Format(name)); + infoBarService.Success(SH.FormatViewModelAchievementArchiveAdded(name)); break; case ArchiveAddResult.InvalidName: infoBarService.Warning(SH.ViewModelAchievementArchiveInvalidName); break; case ArchiveAddResult.AlreadyExists: - infoBarService.Warning(SH.ViewModelAchievementArchiveAlreadyExists.Format(name)); + infoBarService.Warning(SH.FormatViewModelAchievementArchiveAlreadyExists(name)); break; default: throw Must.NeverHappen(); @@ -226,7 +226,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav { if (Archives is not null && SelectedArchive is not null) { - string title = SH.ViewModelAchievementRemoveArchiveTitle.Format(SelectedArchive.Name); + string title = SH.FormatViewModelAchievementRemoveArchiveTitle(SelectedArchive.Name); string content = SH.ViewModelAchievementRemoveArchiveContent; ContentDialogResult result = await contentDialogFactory .CreateForConfirmCancelAsync(title, content) @@ -260,7 +260,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav string fileName = $"{achievementService.CurrentArchive?.Name}.json"; Dictionary> fileTypes = new() { - [SH.ViewModelAchievementExportFileType] = ".json".Enumerate().ToList(), + [SH.ViewModelAchievementExportFileType] = [".json"], }; FileSavePicker picker = pickerFactory diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarProperty.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarProperty.cs index fd4ad553..6c35eba6 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarProperty.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarProperty.cs @@ -6,6 +6,7 @@ using Microsoft.UI.Xaml.Media; using Snap.Hutao.Control.Collection.Alternating; using Snap.Hutao.Model; using Snap.Hutao.Model.Intrinsic; +using System.Collections.Frozen; using System.Collections.Immutable; namespace Snap.Hutao.ViewModel.AvatarProperty; @@ -16,8 +17,7 @@ namespace Snap.Hutao.ViewModel.AvatarProperty; [HighQuality] internal sealed class AvatarProperty : ObservableObject, INameIcon, IAlternatingItem { - // TODO: use FrozenDictionary - private static readonly ImmutableDictionary PropertyIcons = new Dictionary() + private static readonly FrozenDictionary PropertyIcons = new Dictionary() { [FightProperty.FIGHT_PROP_SKILL_CD_MINUS_RATIO] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_CDReduce.png").ToUri(), [FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_ChargeEfficiency.png").ToUri(), @@ -37,7 +37,7 @@ internal sealed class AvatarProperty : ObservableObject, INameIcon, IAlternating [FightProperty.FIGHT_PROP_MAX_HP] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_MaxHp.png").ToUri(), [FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_PhysicalAttackUp.png").ToUri(), [FightProperty.FIGHT_PROP_SHIELD_COST_MINUS_RATIO] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_ShieldCostMinus.png").ToUri(), - }.ToImmutableDictionary(); + }.ToFrozenDictionary(); private Brush? background; diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs index 5f2181b5..cf249276 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs @@ -42,7 +42,7 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I private readonly IAppResourceProvider appResourceProvider; private readonly ICultivationService cultivationService; private readonly IAvatarInfoService avatarInfoService; - private readonly IClipboardInterop clipboardInterop; + private readonly IClipboardProvider clipboardInterop; private readonly CalculatorClient calculatorClient; private readonly IInfoBarService infoBarService; private readonly ITaskContext taskContext; @@ -322,11 +322,11 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I if (result.Interrupted) { infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning); - infoBarService.Warning(SH.ViewModelCultivationBatchAddIncompletedFormat.Format(result.SucceedCount, result.SkippedCount)); + infoBarService.Warning(SH.FormatViewModelCultivationBatchAddIncompletedFormat(result.SucceedCount, result.SkippedCount)); } else { - infoBarService.Success(SH.ViewModelCultivationBatchAddCompletedFormat.Format(result.SucceedCount, result.SkippedCount)); + infoBarService.Success(SH.FormatViewModelCultivationBatchAddCompletedFormat(result.SucceedCount, result.SkippedCount)); } } } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/WeaponView.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/WeaponView.cs index 8bfb46c7..495b0b9d 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/WeaponView.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/WeaponView.cs @@ -26,7 +26,7 @@ internal sealed class WeaponView : Equip, ICalculableSource /// /// 精炼属性 /// - public string AffixLevel { get => SH.ModelBindingAvatarPropertyWeaponAffixFormat.Format(AffixLevelNumber); } + public string AffixLevel { get => SH.FormatModelBindingAvatarPropertyWeaponAffixFormat(AffixLevelNumber); } /// /// 精炼名称 diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Complex/Team.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Complex/Team.cs index 4340042c..37006714 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Complex/Team.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Complex/Team.cs @@ -23,14 +23,13 @@ internal sealed class Team : List public Team(ItemRate team, Dictionary idAvatarMap) : base(4) { - // TODO use Collection Literials - foreach (StringSegment item in new StringTokenizer(team.Item, new char[] { ',' })) + foreach (StringSegment item in new StringTokenizer(team.Item, [','])) { uint id = uint.Parse(item.AsSpan(), CultureInfo.InvariantCulture); Add(new(idAvatarMap[id])); } - Rate = SH.ModelBindingHutaoTeamUpCountFormat.Format(team.Rate); + Rate = SH.FormatModelBindingHutaoTeamUpCountFormat(team.Rate); } /// diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Complex/TeamAppearanceView.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Complex/TeamAppearanceView.cs index c914b6ed..58ff9c2c 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Complex/TeamAppearanceView.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Complex/TeamAppearanceView.cs @@ -20,7 +20,7 @@ internal sealed class TeamAppearanceView /// 映射 public TeamAppearanceView(TeamAppearance teamRank, Dictionary idAvatarMap) { - Floor = SH.ModelBindingHutaoComplexRankFloor.Format(teamRank.Floor); + Floor = SH.FormatModelBindingHutaoComplexRankFloor(teamRank.Floor); Up = teamRank.Up.SelectList(teamRate => new Team(teamRate, idAvatarMap)); Down = teamRank.Down.SelectList(teamRate => new Team(teamRate, idAvatarMap)); } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNote/DailyNoteViewModelSlim.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNote/DailyNoteViewModelSlim.cs index 07fc74b3..a3e785e9 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNote/DailyNoteViewModelSlim.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNote/DailyNoteViewModelSlim.cs @@ -38,7 +38,7 @@ internal sealed partial class DailyNoteViewModelSlim : Abstraction.ViewModelSlim // 此处使用浅拷贝的列表以避免当导航到实时便笺页面后 // 由于主页尚未卸载,添加或删除便笺可能会崩溃的问题 - List entryList = entries.ToList(); + List entryList = [.. entries]; await taskContext.SwitchToMainThreadAsync(); DailyNoteEntries = entryList; diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNote/DailyNoteWebViewerSource.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNote/DailyNoteWebViewerSource.cs index 830c060b..4207da74 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNote/DailyNoteWebViewerSource.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNote/DailyNoteWebViewerSource.cs @@ -6,7 +6,6 @@ using Snap.Hutao.View.Control; using Snap.Hutao.ViewModel.User; using Snap.Hutao.Web.Bridge; using Snap.Hutao.Web.Hoyolab; -using Snap.Hutao.Web.Request.QueryString; namespace Snap.Hutao.ViewModel.DailyNote; @@ -19,7 +18,7 @@ internal sealed class DailyNoteWebViewerSource : IWebViewerSource public string GetSource(UserAndUid userAndUid) { - QueryString query = userAndUid.Uid.ToQueryString(); + string query = userAndUid.Uid.ToQueryString(); return $"https://webstatic.mihoyo.com/app/community-game-records/index.html?bbs_presentation_style=fullscreen#/ys/daily/?{query}"; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs index 6dbf21d8..478ba2e7 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs @@ -238,7 +238,7 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel { Dictionary> fileTypes = new() { - [SH.ViewModelGachaLogExportFileType] = ".json".Enumerate().ToList(), + [SH.ViewModelGachaLogExportFileType] = [".json"], }; FileSavePicker picker = pickerFactory.GetFileSavePicker( @@ -270,7 +270,7 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel if (Archives is not null && SelectedArchive is not null) { ContentDialogResult result = await contentDialogFactory - .CreateForConfirmCancelAsync(SH.ViewModelGachaLogRemoveArchiveTitle.Format(SelectedArchive.Uid), SH.ViewModelGachaLogRemoveArchiveDescription) + .CreateForConfirmCancelAsync(SH.FormatViewModelGachaLogRemoveArchiveTitle(SelectedArchive.Uid), SH.ViewModelGachaLogRemoveArchiveDescription) .ConfigureAwait(false); if (result == ContentDialogResult.Primary) diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/TypedWishSummary.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/TypedWishSummary.cs index b74c20e2..3e9202e5 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/TypedWishSummary.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/TypedWishSummary.cs @@ -28,7 +28,7 @@ internal sealed partial class TypedWishSummary : Wish /// public string MaxOrangePullFormatted { - get => SH.ModelBindingGachaTypedWishSummaryMaxOrangePullFormat.Format(MaxOrangePull); + get => SH.FormatModelBindingGachaTypedWishSummaryMaxOrangePullFormat(MaxOrangePull); } /// @@ -36,7 +36,7 @@ internal sealed partial class TypedWishSummary : Wish /// public string MinOrangePullFormatted { - get => SH.ModelBindingGachaTypedWishSummaryMinOrangePullFormat.Format(MinOrangePull); + get => SH.FormatModelBindingGachaTypedWishSummaryMinOrangePullFormat(MinOrangePull); } /// @@ -88,7 +88,7 @@ internal sealed partial class TypedWishSummary : Wish /// public string AverageOrangePullFormatted { - get => SH.ModelBindingGachaTypedWishSummaryAveragePullFormat.Format(AverageOrangePull); + get => SH.FormatModelBindingGachaTypedWishSummaryAveragePullFormat(AverageOrangePull); } /// @@ -101,7 +101,7 @@ internal sealed partial class TypedWishSummary : Wish /// public string AverageUpOrangePullFormatted { - get => SH.ModelBindingGachaTypedWishSummaryAveragePullFormat.Format(AverageUpOrangePull); + get => SH.FormatModelBindingGachaTypedWishSummaryAveragePullFormat(AverageUpOrangePull); } /// @@ -109,7 +109,7 @@ internal sealed partial class TypedWishSummary : Wish /// public string PredictedPullLeftToOrangeFormatted { - get => SH.ViewModelGachaLogPredictedPullLeftToOrange.Format(PredictedPullLeftToOrange, ProbabilityOfPredictedPullLeftToOrange); + get => SH.FormatViewModelGachaLogPredictedPullLeftToOrange(PredictedPullLeftToOrange, ProbabilityOfPredictedPullLeftToOrange); } /// @@ -117,7 +117,7 @@ internal sealed partial class TypedWishSummary : Wish /// public string ProbabilityOfNextPullIsOrangeFormatted { - get => SH.ViewModelGachaLogProbabilityOfNextPullIsOrange.Format(ProbabilityOfNextPullIsOrange); + get => SH.FormatViewModelGachaLogProbabilityOfNextPullIsOrange(ProbabilityOfNextPullIsOrange); } /// diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/Wish.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/Wish.cs index fc5278c0..978e81af 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/Wish.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/Wish.cs @@ -29,7 +29,7 @@ internal abstract class Wish /// public string TotalCountFormatted { - get => SH.ModelBindingGachaWishBaseTotalCountFormat.Format(TotalCount); + get => SH.FormatModelBindingGachaWishBaseTotalCountFormat(TotalCount); } /// diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs index 02a5e442..c80f600a 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs @@ -138,7 +138,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel } else { - infoBarService.Warning(SH.ViewModelLaunchGameMultiChannelReadFail.Format(options.ConfigFilePath)); + infoBarService.Warning(SH.FormatViewModelLaunchGameMultiChannelReadFail(options.ConfigFilePath)); } ObservableCollection accounts = gameService.GameAccountCollection; diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/GuideViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/GuideViewModel.cs index 3832b069..e8b5718d 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/GuideViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/GuideViewModel.cs @@ -160,7 +160,7 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel private async ValueTask DownloadStaticResourceAsync() { - HashSet downloadSummaries = new(); + HashSet downloadSummaries = []; HashSet categories = StaticResource.GetUnfulfilledCategorySet(); diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/StaticResource.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/StaticResource.cs index 911a14ef..3d88e5d2 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/StaticResource.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/StaticResource.cs @@ -109,7 +109,7 @@ internal static class StaticResource public static HashSet GetUnfulfilledCategorySet() { - HashSet result = new(); + HashSet result = []; ApplicationDataCompositeValue map = LocalSetting.Get(ContractMap, DefaultResourceVersionMap); foreach ((string key, object value) in LatestResourceVersionMap) { diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Home/AnnouncementViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Home/AnnouncementViewModel.cs index 6b5bcbd0..adf63276 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Home/AnnouncementViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Home/AnnouncementViewModel.cs @@ -107,14 +107,14 @@ internal sealed partial class AnnouncementViewModel : Abstraction.ViewModel } else if (rand == 1) { - GreetingText = SH.ViewPageHomeGreetingTextCommon2.Format(LocalSetting.Get(SettingKeys.LaunchTimes, 0)); + GreetingText = SH.FormatViewPageHomeGreetingTextCommon2(LocalSetting.Get(SettingKeys.LaunchTimes, 0)); } } } private void InitializeDashboard() { - List result = new(); + List result = []; if (LocalSetting.Get(SettingKeys.IsHomeCardLaunchGamePresented, true)) { diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs index b57ee8d9..57ffa664 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; using Microsoft.UI.Xaml.Controls; using Microsoft.Windows.AppLifecycle; using Snap.Hutao.Core; @@ -46,7 +45,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel private readonly IContentDialogFactory contentDialogFactory; private readonly IGameLocatorFactory gameLocatorFactory; private readonly INavigationService navigationService; - private readonly IClipboardInterop clipboardInterop; + private readonly IClipboardProvider clipboardInterop; private readonly IShellLinkInterop shellLinkInterop; private readonly HutaoUserOptions hutaoUserOptions; private readonly IInfoBarService infoBarService; @@ -180,7 +179,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel } else { - infoBarService.Warning(SH.ViewModelSettingClearWebCachePathInvalid.Format(cacheFolder)); + infoBarService.Warning(SH.FormatViewModelSettingClearWebCachePathInvalid(cacheFolder)); } } } @@ -230,15 +229,15 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel } [Command("OpenCacheFolderCommand")] - private Task OpenCacheFolderAsync() + private async Task OpenCacheFolderAsync() { - return Launcher.LaunchFolderPathAsync(runtimeOptions.LocalCache).AsTask(); + await Launcher.LaunchFolderPathAsync(runtimeOptions.LocalCache); } [Command("OpenDataFolderCommand")] - private Task OpenDataFolderAsync() + private async Task OpenDataFolderAsync() { - return Launcher.LaunchFolderPathAsync(runtimeOptions.DataFolder).AsTask(); + await Launcher.LaunchFolderPathAsync(runtimeOptions.DataFolder); } [Command("DeleteUsersCommand")] diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/FloorView.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/FloorView.cs index 865b2126..9a5e6e3d 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/FloorView.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/FloorView.cs @@ -14,7 +14,7 @@ internal sealed class FloorView : IMappingFrom diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/SpiralAbyssView.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/SpiralAbyssView.cs index 68244651..b96d9b1c 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/SpiralAbyssView.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/SpiralAbyssView.cs @@ -60,7 +60,7 @@ internal sealed class SpiralAbyssView : IEntityOnly, /// /// 视图 中使用的计划 Id 字符串 /// - public string Schedule { get => SH.ModelEntitySpiralAbyssScheduleFormat.Format(ScheduleId); } + public string Schedule { get => SH.FormatModelEntitySpiralAbyssScheduleFormat(ScheduleId); } public SpiralAbyssEntry? Entity { get => entity; } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs index 41fb8a3d..f3c31406 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs @@ -85,7 +85,7 @@ internal sealed partial class UserViewModel : ObservableObject SelectedUser = Users.Single(); } - infoBarService.Success(SH.ViewModelUserAdded.Format(uid)); + infoBarService.Success(SH.FormatViewModelUserAdded(uid)); break; case UserOptionResult.Incomplete: infoBarService.Information(SH.ViewModelUserIncomplete); @@ -94,7 +94,7 @@ internal sealed partial class UserViewModel : ObservableObject infoBarService.Information(SH.ViewModelUserInvalid); break; case UserOptionResult.Updated: - infoBarService.Success(SH.ViewModelUserUpdated.Format(uid)); + infoBarService.Success(SH.FormatViewModelUserUpdated(uid)); break; default: throw Must.NeverHappen(); @@ -181,7 +181,7 @@ internal sealed partial class UserViewModel : ObservableObject try { await userService.RemoveUserAsync(user).ConfigureAwait(false); - infoBarService.Success(SH.ViewModelUserRemoved.Format(user.UserInfo?.Nickname)); + infoBarService.Success(SH.FormatViewModelUserRemoved(user.UserInfo?.Nickname)); } catch (UserdataCorruptedException ex) { @@ -203,10 +203,10 @@ internal sealed partial class UserViewModel : ObservableObject .AppendIf(user.LToken is not null, ';') .Append(user.CookieToken) .ToString(); - serviceProvider.GetRequiredService().SetText(cookieString); + serviceProvider.GetRequiredService().SetText(cookieString); ArgumentNullException.ThrowIfNull(user.UserInfo); - infoBarService.Success(SH.ViewModelUserCookieCopied.Format(user.UserInfo.Nickname)); + infoBarService.Success(SH.FormatViewModelUserCookieCopied(user.UserInfo.Nickname)); } catch (Exception ex) { diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/AvatarFilter.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/AvatarFilter.cs index 33e67f79..abbe8ef6 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/AvatarFilter.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/AvatarFilter.cs @@ -24,10 +24,8 @@ internal static class AvatarFilter private static bool DoFilter(string input, Avatar avatar) { - List matches = new(); - - // TODO: use Collection Literals - foreach (StringSegment segment in new StringTokenizer(input, new char[] { ' ' })) + List matches = []; + foreach (StringSegment segment in new StringTokenizer(input, [' '])) { string value = segment.ToString(); diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WeaponFilter.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WeaponFilter.cs index 93331755..b07ffc46 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WeaponFilter.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WeaponFilter.cs @@ -24,9 +24,9 @@ internal static class WeaponFilter private static bool DoFilter(string input, Weapon weapon) { - List matches = new(); + List matches = []; - foreach (StringSegment segment in new StringTokenizer(input, ' '.Enumerate().ToArray())) + foreach (StringSegment segment in new StringTokenizer(input, [' '])) { string value = segment.ToString(); diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs index d38ca327..9804b221 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs @@ -92,15 +92,15 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel Dictionary idMaterialMap = await metadataService.GetIdToMaterialMapAsync().ConfigureAwait(false); List avatars = await metadataService.GetAvatarsAsync().ConfigureAwait(false); - List sorted = avatars + IOrderedEnumerable sorted = avatars .OrderByDescending(avatar => avatar.BeginTime) - .ThenByDescending(avatar => avatar.Sort) - .ToList(); + .ThenByDescending(avatar => avatar.Sort); + List list = [.. sorted]; - await CombineComplexDataAsync(sorted, idMaterialMap).ConfigureAwait(false); + await CombineComplexDataAsync(list, idMaterialMap).ConfigureAwait(false); await taskContext.SwitchToMainThreadAsync(); - Avatars = new AdvancedCollectionView(sorted, true); + Avatars = new AdvancedCollectionView(list, true); Selected = Avatars.Cast().FirstOrDefault(); return true; } @@ -192,13 +192,13 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel Dictionary avatarGrowCurve = avatar.GrowCurves.ToDictionary(g => g.Type, g => g.Value); FightProperty promoteProperty = avatarPromoteMap[0].AddProperties.Last().Type; - List propertyCurveValues = new() - { + List propertyCurveValues = + [ new(FightProperty.FIGHT_PROP_BASE_HP, avatarGrowCurve, avatar.BaseValue), new(FightProperty.FIGHT_PROP_BASE_ATTACK, avatarGrowCurve, avatar.BaseValue), new(FightProperty.FIGHT_PROP_BASE_DEFENSE, avatarGrowCurve, avatar.BaseValue), new(promoteProperty, avatarGrowCurve, avatar.BaseValue), - }; + ]; ArgumentNullException.ThrowIfNull(levelAvatarCurveMap); BaseValueInfo = new(avatar.MaxLevel, propertyCurveValues, levelAvatarCurveMap, avatarPromoteMap); diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs index dbdd95d8..fe709b05 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs @@ -87,17 +87,17 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel Dictionary idMaterialMap = await metadataService.GetIdToMaterialMapAsync().ConfigureAwait(false); List weapons = await metadataService.GetWeaponsAsync().ConfigureAwait(false); - List sorted = weapons + IEnumerable sorted = weapons .OrderByDescending(weapon => weapon.RankLevel) .ThenBy(weapon => weapon.WeaponType) - .ThenByDescending(weapon => weapon.Id.Value) - .ToList(); + .ThenByDescending(weapon => weapon.Id.Value); + List list = [.. sorted]; - await CombineComplexDataAsync(sorted, idMaterialMap).ConfigureAwait(false); + await CombineComplexDataAsync(list, idMaterialMap).ConfigureAwait(false); await taskContext.SwitchToMainThreadAsync(); - Weapons = new AdvancedCollectionView(sorted, true); + Weapons = new AdvancedCollectionView(list, true); Selected = Weapons.Cast().FirstOrDefault(); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/HoyolabCoreWebView2Extension.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/HoyolabCoreWebView2Extension.cs index 7131fdcc..a9d97c86 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/HoyolabCoreWebView2Extension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/HoyolabCoreWebView2Extension.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Microsoft.Web.WebView2.Core; +using Snap.Hutao.Control.Extension; using Snap.Hutao.Web.Hoyolab; namespace Snap.Hutao.Web.Bridge; diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSBridge.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSBridge.cs index 2630976f..381189bf 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSBridge.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSBridge.cs @@ -9,7 +9,7 @@ using Snap.Hutao.ViewModel.User; using Snap.Hutao.Web.Bridge.Model; using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab.Bbs.User; -using Snap.Hutao.Web.Hoyolab.DynamicSecret; +using Snap.Hutao.Web.Hoyolab.DataSigning; using Snap.Hutao.Web.Hoyolab.Takumi.Auth; using Snap.Hutao.Web.Response; using System.Runtime.InteropServices; @@ -23,7 +23,6 @@ namespace Snap.Hutao.Web.Bridge; /// [HighQuality] [SuppressMessage("", "CA1001")] -[SuppressMessage("", "CA1308")] internal class MiHoYoJSBridge { private const string InitializeJsInterfaceScript2 = """ @@ -191,28 +190,14 @@ internal class MiHoYoJSBridge /// 响应 protected virtual JsResult> GetDynamicSecrectV1(JsParam param) { - string salt = HoyolabOptions.Salts[SaltType.LK2]; - long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - string r = GetRandomString(); - - string check = Core.Convert.ToMd5HexString($"salt={salt}&t={t}&r={r}").ToLowerInvariant(); - - return new() { Data = new() { ["DS"] = $"{t},{r},{check}", }, }; - - static string GetRandomString() + DataSignOptions options = DataSignOptions.CreateForGeneration1(SaltType.LK2, true); + return new() { - const string RandomRange = "abcdefghijklmnopqrstuvwxyz1234567890"; - - StringBuilder sb = new(6); - - for (int i = 0; i < 6; i++) + Data = new() { - int pos = Random.Shared.Next(0, RandomRange.Length); - sb.Append(RandomRange[pos]); - } - - return sb.ToString(); - } + ["DS"] = DataSignAlgorithm.GetDataSign(options), + }, + }; } /// @@ -222,20 +207,14 @@ internal class MiHoYoJSBridge /// 响应 protected virtual JsResult> GetDynamicSecrectV2(JsParam param) { - string salt = HoyolabOptions.Salts[SaltType.X4]; - long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - int r = GetRandom(); - string b = param.Payload.Body; - string q = param.Payload.GetQueryParam(); - string check = Core.Convert.ToMd5HexString($"salt={salt}&t={t}&r={r}&b={b}&q={q}").ToLowerInvariant(); - - return new() { Data = new() { ["DS"] = $"{t},{r},{check}" } }; - - static int GetRandom() + DataSignOptions options = DataSignOptions.CreateForGeneration2(SaltType.X4, false, param.Payload.Body, param.Payload.GetQueryParam()); + return new() { - int rand = Random.Shared.Next(100000, 200000); - return rand == 100000 ? 642367 : rand; - } + Data = new() + { + ["DS"] = DataSignAlgorithm.GetDataSign(options), + }, + }; } /// @@ -461,7 +440,7 @@ internal class MiHoYoJSBridge } [SuppressMessage("", "CA2254")] - private IJsResult? LogUnhandledMessage([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string message, params object?[] param) + private IJsResult? LogUnhandledMessage(string message, params object?[] param) { logger.LogWarning(message, param); return default; @@ -490,7 +469,7 @@ internal class MiHoYoJSBridge "pushPage" => await PushPageAsync(param).ConfigureAwait(false), "share" => Share(param), "showLoading" => null, - _ => LogUnhandledMessage("Unhandled Message Type: {method}", param.Method), + _ => LogUnhandledMessage("Unhandled Message Type: {Method}", param.Method), }; } catch (ObjectDisposedException) @@ -512,8 +491,6 @@ internal class MiHoYoJSBridge ReadOnlySpan uriHostSpan = uriHost.AsSpan(); if (uriHostSpan.EndsWith("mihoyo.com") || uriHostSpan.EndsWith("hoyolab.com")) { - // Execute this solve issue: When open same site second time,there might be no bridge init. - // coreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(InitializeJsInterfaceScript2).AsTask().SafeForget(logger); coreWebView2.ExecuteScriptAsync(InitializeJsInterfaceScript2).AsTask().SafeForget(logger); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Enka/EnkaClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Enka/EnkaClient.cs index 5d318fe1..0d362873 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Enka/EnkaClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Enka/EnkaClient.cs @@ -6,6 +6,7 @@ using Snap.Hutao.Web.Enka.Model; using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Request.Builder; using Snap.Hutao.Web.Request.Builder.Abstraction; +using System.Globalization; using System.IO; using System.Net; using System.Net.Http; @@ -37,7 +38,7 @@ internal sealed partial class EnkaClient /// Enka API 响应 public ValueTask GetForwardDataAsync(in PlayerUid playerUid, CancellationToken token = default) { - return TryGetEnkaResponseCoreAsync(EnkaAPIHutaoForward.Format(playerUid.Value), token); + return TryGetEnkaResponseCoreAsync(string.Format(CultureInfo.CurrentCulture, EnkaAPIHutaoForward, playerUid.Value), token); } /// @@ -48,7 +49,7 @@ internal sealed partial class EnkaClient /// Enka API 响应 public ValueTask GetDataAsync(in PlayerUid playerUid, CancellationToken token = default) { - return TryGetEnkaResponseCoreAsync(EnkaAPI.Format(playerUid.Value), token); + return TryGetEnkaResponseCoreAsync(string.Format(CultureInfo.CurrentCulture, EnkaAPI, playerUid.Value), token); } private async ValueTask TryGetEnkaResponseCoreAsync(string url, CancellationToken token = default) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Annotation/ApiInformationAttribute.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Annotation/ApiInformationAttribute.cs index 00f54be0..83a7fa01 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Annotation/ApiInformationAttribute.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Annotation/ApiInformationAttribute.cs @@ -1,7 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Snap.Hutao.Web.Hoyolab.DynamicSecret; +using Snap.Hutao.Web.Hoyolab.DataSigning; namespace Snap.Hutao.Web.Hoyolab.Annotation; diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/App/Account/AccountClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/App/Account/AccountClient.cs index 538dc199..2e7f3b89 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/App/Account/AccountClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/App/Account/AccountClient.cs @@ -4,7 +4,7 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.Model.Entity; using Snap.Hutao.Web.Hoyolab.Annotation; -using Snap.Hutao.Web.Hoyolab.DynamicSecret; +using Snap.Hutao.Web.Hoyolab.DataSigning; using Snap.Hutao.Web.Hoyolab.Takumi.Binding; using Snap.Hutao.Web.Request.Builder; using Snap.Hutao.Web.Request.Builder.Abstraction; @@ -41,7 +41,7 @@ internal sealed partial class AccountClient .SetReferer(ApiEndpoints.AppMihoyoReferer) .PostJson(data); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen1, SaltType.K2, false).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen1, SaltType.K2, false).ConfigureAwait(false); Response? resp = await builder .TryCatchSendAsync>(httpClient, logger, token) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs index bcf01984..64309812 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs @@ -18,7 +18,7 @@ internal sealed partial class Cookie /// 构造一个空白的Cookie /// public Cookie() - : this(new()) + : this([]) { } @@ -44,7 +44,7 @@ internal sealed partial class Cookie /// 新的Cookie对象 public static Cookie Parse(string cookieString) { - SortedDictionary cookieMap = new(); + SortedDictionary cookieMap = []; cookieString = cookieString.Replace(" ", string.Empty, StringComparison.Ordinal); string[] values = cookieString.Split(';', StringSplitOptions.RemoveEmptyEntries); foreach (string[] parts in values.Select(c => c.Split('=', 2))) @@ -59,7 +59,7 @@ internal sealed partial class Cookie public static Cookie FromCoreWebView2Cookies(IReadOnlyList webView2Cookies) { - SortedDictionary cookieMap = new(); + SortedDictionary cookieMap = []; foreach (CoreWebView2Cookie cookie in webView2Cookies) { diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DataSigning/DataSignAlgorithm.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DataSigning/DataSignAlgorithm.cs new file mode 100644 index 00000000..48914216 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DataSigning/DataSignAlgorithm.cs @@ -0,0 +1,27 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Web.Hoyolab.DataSigning; + +internal static class DataSignAlgorithm +{ + public static string GetDataSign(DataSignOptions options) + { + long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + string r = options.RandomString; + + string dsContent = $"salt={options.Salt}&t={t}&r={options.RandomString}"; + + // ds2 b & q process + if (options.RequiresBodyAndQuery) + { + dsContent = $"{dsContent}&b={options.Body}&q={options.Query}"; + } + +#pragma warning disable CA1308 + string check = Core.Convert.ToMd5HexString(dsContent).ToLowerInvariant(); +#pragma warning restore CA1308 + + return $"{t},{r},{check}"; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretVersion.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DataSigning/DataSignAlgorithmVersion.cs similarity index 78% rename from src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretVersion.cs rename to src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DataSigning/DataSignAlgorithmVersion.cs index b2a7b935..20b425d4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretVersion.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DataSigning/DataSignAlgorithmVersion.cs @@ -1,13 +1,13 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -namespace Snap.Hutao.Web.Hoyolab.DynamicSecret; +namespace Snap.Hutao.Web.Hoyolab.DataSigning; /// /// 动态密钥版本 /// [HighQuality] -internal enum DynamicSecretVersion +internal enum DataSignAlgorithmVersion { /// /// 一代 diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DataSigning/DataSignHttpRequestMessageBuilderExtension.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DataSigning/DataSignHttpRequestMessageBuilderExtension.cs new file mode 100644 index 00000000..21c7bf1a --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DataSigning/DataSignHttpRequestMessageBuilderExtension.cs @@ -0,0 +1,15 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Web.Request.Builder; + +namespace Snap.Hutao.Web.Hoyolab.DataSigning; + +internal static class DataSignHttpRequestMessageBuilderExtension +{ + public static async ValueTask SignDataAsync(this HttpRequestMessageBuilder builder, DataSignAlgorithmVersion version, SaltType saltType, bool includeChars) + { + DataSignOptions options = await DataSignOptions.CreateFromHttpRequestMessageBuilderAsync(builder, saltType, includeChars, version).ConfigureAwait(false); + builder.SetHeader("DS", DataSignAlgorithm.GetDataSign(options)); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DataSigning/DataSignOptions.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DataSigning/DataSignOptions.cs new file mode 100644 index 00000000..4c7c2e95 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DataSigning/DataSignOptions.cs @@ -0,0 +1,58 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Web.Request.Builder; +using System.Net.Http; + +namespace Snap.Hutao.Web.Hoyolab.DataSigning; + +internal sealed class DataSignOptions +{ + private DataSignOptions(SaltType type, bool includeChars, DataSignAlgorithmVersion version, string? body, string query) + { + Salt = HoyolabOptions.Salts[type]; + RandomString = includeChars ? Core.Random.GetLowerAndNumberString(6) : GetRandomNumberString(); + RequiresBodyAndQuery = version >= DataSignAlgorithmVersion.Gen2; + string defaultBody = type is SaltType.PROD ? "{}" : string.Empty; + Body = body ?? defaultBody; + Query = query; + } + + public string Salt { get; } + + public string RandomString { get; } + + public bool RequiresBodyAndQuery { get; } + + public string Body { get; } + + public string Query { get; } + + public static DataSignOptions CreateForGeneration1(SaltType type, bool includeChars) + { + return new(type, includeChars, DataSignAlgorithmVersion.Gen1, default, default!); + } + + public static DataSignOptions CreateForGeneration2(SaltType type, bool includeChars, string? body, string query) + { + return new(type, includeChars, DataSignAlgorithmVersion.Gen2, body, query); + } + + public static async ValueTask CreateFromHttpRequestMessageBuilderAsync(HttpRequestMessageBuilder builder, SaltType type, bool includeChars, DataSignAlgorithmVersion version) + { + HttpContent? content = builder.Content; + string? body = content is not null ? await content.ReadAsStringAsync().ConfigureAwait(false) : default; + + ArgumentNullException.ThrowIfNull(builder.RequestUri); + string[] queries = Uri.UnescapeDataString(builder.RequestUri.Query).Split('?', 2); // queries[0] is always empty + string query = queries.Length is 2 ? string.Join('&', queries[1].Split('&').OrderBy(x => x)) : string.Empty; + + return new(type, includeChars, version, body, query); + } + + private static string GetRandomNumberString() + { + int rand = Random.Shared.Next(100000, 200000); + return $"{(rand == 100000 ? 642367 : rand)}"; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/SaltType.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DataSigning/SaltType.cs similarity index 94% rename from src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/SaltType.cs rename to src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DataSigning/SaltType.cs index af6f2931..83cf3a41 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/SaltType.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DataSigning/SaltType.cs @@ -1,7 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -namespace Snap.Hutao.Web.Hoyolab.DynamicSecret; +namespace Snap.Hutao.Web.Hoyolab.DataSigning; /// /// Salt's type diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretHttpRequestMessageBuilderExtension.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretHttpRequestMessageBuilderExtension.cs deleted file mode 100644 index c4a262bd..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretHttpRequestMessageBuilderExtension.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Snap.Hutao.Web.Request.Builder; -using System.Net.Http; -using System.Text; - -namespace Snap.Hutao.Web.Hoyolab.DynamicSecret; - -internal static class DynamicSecretHttpRequestMessageBuilderExtension -{ - private const string RandomRange = "abcdefghijklmnopqrstuvwxyz1234567890"; - - public static async ValueTask SetDynamicSecretAsync(this HttpRequestMessageBuilder builder, DynamicSecretVersion version, SaltType saltType, bool includeChars) - { - string salt = HoyolabOptions.Salts[saltType]; - long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - string r = includeChars ? GetRandomStringWithChars() : GetRandomStringNoChars(); - - string dsContent = $"salt={salt}&t={t}&r={r}"; - - // ds2 b & q process - if (version is DynamicSecretVersion.Gen2) - { - HttpContent? content = builder.Content; - string b = content is not null - ? await content.ReadAsStringAsync().ConfigureAwait(false) - : saltType is SaltType.PROD ? "{}" : string.Empty; // PROD's default value is {} - - ArgumentNullException.ThrowIfNull(builder.RequestUri); - string[] queries = Uri.UnescapeDataString(builder.RequestUri.Query).Split('?', 2); // queries[0] is always empty - string q = queries.Length is 2 ? string.Join('&', queries[1].Split('&').OrderBy(x => x)) : string.Empty; - - dsContent = $"{dsContent}&b={b}&q={q}"; - } - -#pragma warning disable CA1308 - string check = Core.Convert.ToMd5HexString(dsContent).ToLowerInvariant(); -#pragma warning restore CA1308 - builder.SetHeader("DS", $"{t},{r},{check}"); - } - - private static string GetRandomStringWithChars() - { - StringBuilder sb = new(6); - - for (int i = 0; i < 6; i++) - { - int pos = Random.Shared.Next(0, RandomRange.Length); - sb.Append(RandomRange[pos]); - } - - return sb.ToString(); - } - - private static string GetRandomStringNoChars() - { - int rand = Random.Shared.Next(100000, 200000); - return $"{(rand == 100000 ? 642367 : rand)}"; - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/Announcement.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/Announcement.cs index cc3429fd..389caf90 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/Announcement.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/Announcement.cs @@ -35,20 +35,20 @@ internal sealed class Announcement : AnnouncementContent TimeSpan span = StartTime - now; if (span.TotalDays <= 1) { - return SH.WebAnnouncementTimeHoursBeginFormat.Format((int)span.TotalHours); + return SH.FormatWebAnnouncementTimeHoursBeginFormat((int)span.TotalHours); } - return SH.WebAnnouncementTimeDaysBeginFormat.Format((int)span.TotalDays); + return SH.FormatWebAnnouncementTimeDaysBeginFormat((int)span.TotalDays); } else { TimeSpan span = EndTime - now; if (span.TotalDays <= 1) { - return SH.WebAnnouncementTimeHoursEndFormat.Format((int)span.TotalHours); + return SH.FormatWebAnnouncementTimeHoursEndFormat((int)span.TotalHours); } - return SH.WebAnnouncementTimeDaysEndFormat.Format((int)span.TotalDays); + return SH.FormatWebAnnouncementTimeDaysEndFormat((int)span.TotalDays); } } } @@ -68,9 +68,7 @@ internal sealed class Announcement : AnnouncementContent { get { - // TODO: validate correctness - // UTC+8 - DateTimeOffset currentTime = DateTimeOffset.UtcNow.AddHours(8); + DateTimeOffset currentTime = DateTimeOffset.UtcNow; TimeSpan current = currentTime - StartTime; TimeSpan total = EndTime - StartTime; return current / total; diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementWrapper.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementWrapper.cs index ce675358..0d32391e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementWrapper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementWrapper.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Core; using Snap.Hutao.Web.Response; namespace Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement; diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaLogQueryOptions.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaLogQueryOptions.cs index 5352e0c6..11d31ea4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaLogQueryOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaLogQueryOptions.cs @@ -2,8 +2,8 @@ // Licensed under the MIT license. using Snap.Hutao.Service.GachaLog.QueryProvider; -using Snap.Hutao.Web.Hoyolab.Takumi.Binding; -using Snap.Hutao.Web.Request.QueryString; +using System.Collections.Specialized; +using System.Web; namespace Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo; @@ -45,7 +45,7 @@ internal struct GachaLogQueryOptions /// size /// end_id /// - private readonly QueryString innerQuery; + private readonly NameValueCollection innerQuery; /// /// 构造一个新的祈愿记录请求配置 @@ -60,36 +60,17 @@ internal struct GachaLogQueryOptions // 对于每个类型我们需要单独创建 // 对应类型的 GachaLogQueryOptions Type = queryType; - innerQuery = QueryString.Parse(query.Query); - - // innerQuery.Set("lang", "zh-cn"); - innerQuery.Set("gacha_type", (int)queryType); - innerQuery.Set("size", Size); - } - - /// - /// 转换到查询字符串 - /// - /// 生成信息 - /// 验证包装 - /// 语言 - /// 查询 - public static string ToQueryString(GenAuthKeyData genAuthKeyData, GameAuthKey gameAuthKey, string lang) - { - QueryString queryString = new(); - queryString.Set("lang", lang); - queryString.Set("auth_appid", genAuthKeyData.AuthAppId); - queryString.Set("authkey", Uri.EscapeDataString(gameAuthKey.AuthKey)); - queryString.Set("authkey_ver", gameAuthKey.AuthKeyVersion); - queryString.Set("sign_type", gameAuthKey.SignType); - - return queryString.ToString(); + innerQuery = HttpUtility.ParseQueryString(query.Query); + innerQuery.Set("gacha_type", $"{queryType:D}"); + innerQuery.Set("size", $"{Size}"); } public readonly string ToQueryString() { // Make the cached end id into query. - innerQuery.Set("end_id", EndId); - return innerQuery.ToString(); + innerQuery.Set("end_id", $"{EndId:D}"); + string? query = innerQuery.ToString(); + ArgumentException.ThrowIfNullOrEmpty(query); + return query; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabOptions.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabOptions.cs index 2ebb11ba..70bc2b45 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabOptions.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. using Microsoft.Extensions.Options; -using Snap.Hutao.Web.Hoyolab.DynamicSecret; +using Snap.Hutao.Web.Hoyolab.DataSigning; using System.Collections.Immutable; namespace Snap.Hutao.Web.Hoyolab; diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.cs index 41c8cddf..8f98219d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.cs @@ -4,7 +4,7 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.Model.Entity; using Snap.Hutao.Web.Hoyolab.Annotation; -using Snap.Hutao.Web.Hoyolab.DynamicSecret; +using Snap.Hutao.Web.Hoyolab.DataSigning; using Snap.Hutao.Web.Request.Builder; using Snap.Hutao.Web.Request.Builder.Abstraction; using Snap.Hutao.Web.Response; @@ -38,7 +38,7 @@ internal sealed partial class PassportClient : IPassportClient .SetUserCookieAndFpHeader(user, CookieType.SToken) .Get(); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen2, SaltType.PROD, true).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.PROD, true).ConfigureAwait(false); Response? resp = await builder .TryCatchSendAsync>(httpClient, logger, token) @@ -61,7 +61,7 @@ internal sealed partial class PassportClient : IPassportClient .SetUserCookieAndFpHeader(user, CookieType.SToken) .Get(); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen2, SaltType.PROD, true).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.PROD, true).ConfigureAwait(false); Response? resp = await builder .TryCatchSendAsync>(httpClient, logger, token) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs index 60bb4246..f6e3713f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs @@ -4,7 +4,7 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.Model.Entity; using Snap.Hutao.Web.Hoyolab.Annotation; -using Snap.Hutao.Web.Hoyolab.DynamicSecret; +using Snap.Hutao.Web.Hoyolab.DataSigning; using Snap.Hutao.Web.Request.Builder; using Snap.Hutao.Web.Request.Builder.Abstraction; using Snap.Hutao.Web.Response; @@ -59,7 +59,7 @@ internal sealed partial class PassportClient2 .SetHeader("Cookie", stokenV1.ToString()) .Post(); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen2, SaltType.PROD, true).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.PROD, true).ConfigureAwait(false); Response? resp = await builder .TryCatchSendAsync>(httpClient, logger, token) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUidExtension.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUidExtension.cs index 4dcb9eda..ffabae36 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUidExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUidExtension.cs @@ -1,18 +1,19 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Snap.Hutao.Web.Request.QueryString; +using Snap.Hutao.Web.Request; +using System.Collections.Specialized; namespace Snap.Hutao.Web.Hoyolab; internal static class PlayerUidExtension { - public static QueryString ToQueryString(this in PlayerUid playerUid) + public static string ToQueryString(this in PlayerUid playerUid) { - QueryString queryString = new(); - queryString.Set("role_id", playerUid.Value); - queryString.Set("server", playerUid.Region); + NameValueCollection collection = []; + collection.Set("role_id", playerUid.Value); + collection.Set("server", playerUid.Region); - return queryString; + return collection.ToQueryString(); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/SdkStatic/Hk4e/Launcher/PathMd5.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/SdkStatic/Hk4e/Launcher/PathMd5.cs index 6bed5d1a..f1cf5fde 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/SdkStatic/Hk4e/Launcher/PathMd5.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/SdkStatic/Hk4e/Launcher/PathMd5.cs @@ -33,7 +33,7 @@ internal partial class PathMd5 private void CopyPathToClipboard() { IServiceProvider serviceProvider = Ioc.Default; - serviceProvider.GetRequiredService().SetText(Path); + serviceProvider.GetRequiredService().SetText(Path); serviceProvider.GetRequiredService().Success(SH.WebGameResourcePathCopySucceed); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Auth/AuthClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Auth/AuthClient.cs index bf941f7d..50820381 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Auth/AuthClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Auth/AuthClient.cs @@ -4,7 +4,7 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.Model.Entity; using Snap.Hutao.Web.Hoyolab.Annotation; -using Snap.Hutao.Web.Hoyolab.DynamicSecret; +using Snap.Hutao.Web.Hoyolab.DataSigning; using Snap.Hutao.Web.Hoyolab.Takumi.Binding; using Snap.Hutao.Web.Request.Builder; using Snap.Hutao.Web.Request.Builder.Abstraction; @@ -36,7 +36,7 @@ internal sealed partial class AuthClient .SetUserCookieAndFpHeader(user, CookieType.SToken) .Get(); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen1, SaltType.K2, true).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen1, SaltType.K2, true).ConfigureAwait(false); Response? resp = await builder .TryCatchSendAsync>(httpClient, logger, token) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient2.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient2.cs index 9d1f4e3a..fdf4232a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient2.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient2.cs @@ -4,7 +4,7 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.Model.Entity; using Snap.Hutao.Web.Hoyolab.Annotation; -using Snap.Hutao.Web.Hoyolab.DynamicSecret; +using Snap.Hutao.Web.Hoyolab.DataSigning; using Snap.Hutao.Web.Request.Builder; using Snap.Hutao.Web.Request.Builder.Abstraction; using Snap.Hutao.Web.Response; @@ -40,7 +40,7 @@ internal sealed partial class BindingClient2 .SetReferer(ApiEndpoints.AppMihoyoReferer) .Get(); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false); Response>? resp = await builder .TryCatchSendAsync>>(httpClient, logger, token) @@ -66,7 +66,7 @@ internal sealed partial class BindingClient2 .SetReferer(ApiEndpoints.AppMihoyoReferer) .PostJson(data); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false); Response? resp = await builder .TryCatchSendAsync>(httpClient, logger, token) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInClient.cs index 0ef4e4c8..8095921c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInClient.cs @@ -3,7 +3,7 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.ViewModel.User; -using Snap.Hutao.Web.Hoyolab.DynamicSecret; +using Snap.Hutao.Web.Hoyolab.DataSigning; using Snap.Hutao.Web.Hutao.Geetest; using Snap.Hutao.Web.Request.Builder; using Snap.Hutao.Web.Request.Builder.Abstraction; @@ -32,7 +32,7 @@ internal sealed partial class SignInClient : ISignInClient .SetUserCookieAndFpHeader(userAndUid, CookieType.CookieToken) .Get(); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false); Response? resp = await builder .TryCatchSendAsync>(httpClient, logger, token) @@ -48,7 +48,7 @@ internal sealed partial class SignInClient : ISignInClient .SetUserCookieAndFpHeader(userAndUid, CookieType.CookieToken) .Get(); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false); Response? resp = await builder .TryCatchSendAsync>(httpClient, logger, token) @@ -78,7 +78,7 @@ internal sealed partial class SignInClient : ISignInClient .SetUserCookieAndFpHeader(userAndUid, CookieType.CookieToken) .PostJson(new SignInData(userAndUid.Uid, false)); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false); Response? resp = await builder .TryCatchSendAsync>(httpClient, logger, token) @@ -94,7 +94,7 @@ internal sealed partial class SignInClient : ISignInClient .SetUserCookieAndFpHeader(userAndUid, CookieType.CookieToken) .PostJson(new SignInData(userAndUid.Uid, false)); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false); Response? resp = await builder .TryCatchSendAsync>(httpClient, logger, token) @@ -112,7 +112,7 @@ internal sealed partial class SignInClient : ISignInClient .SetXrpcChallenge(challenge, validate) .PostJson(new SignInData(userAndUid.Uid, false)); - await verifiedBuilder.SetDynamicSecretAsync(DynamicSecretVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false); + await verifiedBuilder.SignDataAsync(DataSignAlgorithmVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false); resp = await verifiedBuilder .TryCatchSendAsync>(httpClient, logger, token) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/AvatarPromotionDelta.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/AvatarPromotionDelta.cs index c59e88dc..39578208 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/AvatarPromotionDelta.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/AvatarPromotionDelta.cs @@ -53,12 +53,12 @@ internal sealed class AvatarPromotionDelta return new() { AvatarLevelTarget = LocalSetting.Get(SettingKeys.CultivationAvatarLevelTarget, 90U), - SkillList = new() - { + SkillList = + [ new() { LevelTarget = LocalSetting.Get(SettingKeys.CultivationAvatarSkillATarget, 10U), }, new() { LevelTarget = LocalSetting.Get(SettingKeys.CultivationAvatarSkillETarget, 10U), }, new() { LevelTarget = LocalSetting.Get(SettingKeys.CultivationAvatarSkillQTarget, 10U), }, - }, + ], Weapon = new() { LevelTarget = LocalSetting.Get(SettingKeys.CultivationWeapon90LevelTarget, 90U), }, }; } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/CalculateClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/CalculateClient.cs index 79c6c126..da9af651 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/CalculateClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/CalculateClient.cs @@ -57,7 +57,7 @@ internal sealed partial class CalculateClient int currentPage = 1; SyncAvatarFilter filter = new() { Uid = userAndUid.Uid.Value, Region = userAndUid.Uid.Region }; - List avatars = new(); + List avatars = []; Response>? resp; do @@ -166,10 +166,10 @@ internal sealed partial class CalculateClient private class SyncAvatarFilter { [JsonPropertyName("element_attr_ids")] - public List? ElementAttrIds { get; set; } = new(); + public List? ElementAttrIds { get; set; } = []; [JsonPropertyName("weapon_cat_ids")] - public List? WeaponCatIds { get; set; } = new(); + public List? WeaponCatIds { get; set; } = []; [JsonPropertyName("page")] public int Page { get; set; } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/CardClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/CardClient.cs index 28eb3b4e..ecf863d5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/CardClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/CardClient.cs @@ -4,7 +4,7 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.Model.Entity; using Snap.Hutao.Web.Hoyolab.Annotation; -using Snap.Hutao.Web.Hoyolab.DynamicSecret; +using Snap.Hutao.Web.Hoyolab.DataSigning; using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Verification; using Snap.Hutao.Web.Request.Builder; using Snap.Hutao.Web.Request.Builder.Abstraction; @@ -38,7 +38,7 @@ internal sealed partial class CardClient .SetUserCookieAndFpHeader(user, CookieType.LToken) .Get(); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); Response? resp = await builder .TryCatchSendAsync>(httpClient, logger, token) @@ -60,7 +60,7 @@ internal sealed partial class CardClient .SetRequestUri(ApiEndpoints.CardVerifyVerification) .PostJson(new VerificationData(challenge, validate)); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); Response? resp = await builder .TryCatchSendAsync>(httpClient, logger, token) @@ -83,7 +83,7 @@ internal sealed partial class CardClient .SetUserCookieAndFpHeader(user, CookieType.SToken) .Get(); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen2, SaltType.X6, false).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X6, false).ConfigureAwait(false); Response? resp = await builder .TryCatchSendAsync>(httpClient, logger, token) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/DailyNote.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/DailyNote.cs index a3223d51..f2038ee6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/DailyNote.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/DailyNote.cs @@ -38,10 +38,10 @@ internal sealed class DailyNote : DailyNoteCommon 0 => SH.WebDailyNoteRecoveryTimeDay0, 1 => SH.WebDailyNoteRecoveryTimeDay1, 2 => SH.WebDailyNoteRecoveryTimeDay2, - _ => SH.WebDailyNoteRecoveryTimeDayFormat.Format(totalDays), + _ => SH.FormatWebDailyNoteRecoveryTimeDayFormat(totalDays), }; - return SH.WebDailyNoteResinRecoveryFormat.Format(day, reach); + return SH.FormatWebDailyNoteResinRecoveryFormat(day, reach); } } @@ -129,9 +129,9 @@ internal sealed class DailyNote : DailyNoteCommon 0 => SH.WebDailyNoteRecoveryTimeDay0, 1 => SH.WebDailyNoteRecoveryTimeDay1, 2 => SH.WebDailyNoteRecoveryTimeDay2, - _ => SH.WebDailyNoteRecoveryTimeDayFormat.Format(totalDays), + _ => SH.FormatWebDailyNoteRecoveryTimeDayFormat(totalDays), }; - return SH.WebDailyNoteHomeCoinRecoveryFormat.Format(day, reach); + return SH.FormatWebDailyNoteHomeCoinRecoveryFormat(day, reach); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/Expedition.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/Expedition.cs index beb9c7bf..1981bc47 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/Expedition.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/Expedition.cs @@ -1,7 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using System.Collections.Immutable; +using System.Collections.Frozen; namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote; @@ -17,10 +17,7 @@ internal sealed class Expedition private const string Keqing = "https://upload-bbs.mihoyo.com/game_record/genshin/character_side_icon/UI_AvatarIcon_Side_Keqing.png"; private const string Sara = "https://upload-bbs.mihoyo.com/game_record/genshin/character_side_icon/UI_AvatarIcon_Side_Sara.png"; - private static readonly ImmutableList ShortExpeditionTimeAvatars = new List() - { - Bennett, Chongyun, Fischl, Keqing, Sara, - }.ToImmutableList(); + private static readonly FrozenSet ShortExpeditionTimeAvatars = FrozenSet.ToFrozenSet([Bennett, Chongyun, Fischl, Keqing, Sara]); /// /// 图标 @@ -84,8 +81,8 @@ internal sealed class Expedition TimeSpan ts = new(0, 0, RemainedTime); return ts.Hours > 0 - ? SH.WebDailyNoteExpeditionRemainHoursFormat.Format(ts.Hours) - : SH.WebDailyNoteExpeditionRemainMinutesFormat.Format(ts.Minutes); + ? SH.FormatWebDailyNoteExpeditionRemainHoursFormat(ts.Hours) + : SH.FormatWebDailyNoteExpeditionRemainMinutesFormat(ts.Minutes); } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/RecoveryTime.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/RecoveryTime.cs index eb526cad..723c94b9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/RecoveryTime.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/RecoveryTime.cs @@ -67,10 +67,10 @@ internal sealed class RecoveryTime else { return new StringBuilder() - .AppendIf(Day > 0, SH.WebDailyNoteTransformerDaysFormat.Format(Day)) - .AppendIf(Hour > 0, SH.WebDailyNoteTransformerHoursFormat.Format(Hour)) - .AppendIf(Minute > 0, SH.WebDailyNoteTransformerMinutesFormat.Format(Minute)) - .AppendIf(Second > 0, SH.WebDailyNoteTransformerSecondsFormat.Format(Second)) + .AppendIf(Day > 0, SH.FormatWebDailyNoteTransformerDaysFormat(Day)) + .AppendIf(Hour > 0, SH.FormatWebDailyNoteTransformerHoursFormat(Hour)) + .AppendIf(Minute > 0, SH.FormatWebDailyNoteTransformerMinutesFormat(Minute)) + .AppendIf(Second > 0, SH.FormatWebDailyNoteTransformerSecondsFormat(Second)) .Append(SH.WebDailyNoteTransformerAppend) .ToString(); } @@ -92,10 +92,10 @@ internal sealed class RecoveryTime else { return new StringBuilder() - .AppendIf(Day > 0, SH.WebDailyNoteTransformerDaysFormat.Format(Day)) - .AppendIf(Hour > 0, SH.WebDailyNoteTransformerHoursFormat.Format(Hour)) - .AppendIf(Minute > 0, SH.WebDailyNoteTransformerMinutesFormat.Format(Minute)) - .AppendIf(Second > 0, SH.WebDailyNoteTransformerSecondsFormat.Format(Second)) + .AppendIf(Day > 0, SH.FormatWebDailyNoteTransformerDaysFormat(Day)) + .AppendIf(Hour > 0, SH.FormatWebDailyNoteTransformerHoursFormat(Hour)) + .AppendIf(Minute > 0, SH.FormatWebDailyNoteTransformerMinutesFormat(Minute)) + .AppendIf(Second > 0, SH.FormatWebDailyNoteTransformerSecondsFormat(Second)) .ToString(); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs index a12c3088..dcd36a43 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs @@ -4,7 +4,7 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.ViewModel.User; using Snap.Hutao.Web.Hoyolab.Annotation; -using Snap.Hutao.Web.Hoyolab.DynamicSecret; +using Snap.Hutao.Web.Hoyolab.DataSigning; using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar; using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Verification; using Snap.Hutao.Web.Request.Builder; @@ -42,7 +42,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient .SetUserCookieAndFpHeader(userAndUid, CookieType.Cookie) .Get(); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); Response? resp = await builder .TryCatchSendAsync>(httpClient, logger, token) @@ -53,7 +53,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient { // Replace message resp.Message = SH.WebDailyNoteVerificationFailed; - IGeetestCardVerifier verifier = serviceProvider.GetRequiredService(); + IGeetestCardVerifier verifier = serviceProvider.GetRequiredKeyedService(GeetestCardVerifierType.Custom); if (await verifier.TryValidateXrpcChallengeAsync(userAndUid.User, token).ConfigureAwait(false) is { } challenge) { @@ -63,7 +63,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient .SetXrpcChallenge(challenge) .Get(); - await verifiedbuilder.SetDynamicSecretAsync(DynamicSecretVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); + await verifiedbuilder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); resp = await verifiedbuilder .TryCatchSendAsync>(httpClient, logger, token) @@ -88,7 +88,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient .SetUserCookieAndFpHeader(userAndUid, CookieType.Cookie) .Get(); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); Response? resp = await builder .TryCatchSendAsync>(httpClient, logger, token) @@ -99,7 +99,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient { // Replace message resp.Message = SH.WebIndexOrSpiralAbyssVerificationFailed; - IGeetestCardVerifier verifier = serviceProvider.GetRequiredService(); + IGeetestCardVerifier verifier = serviceProvider.GetRequiredKeyedService(GeetestCardVerifierType.Custom); if (await verifier.TryValidateXrpcChallengeAsync(userAndUid.User, token).ConfigureAwait(false) is { } challenge) { @@ -109,7 +109,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient .SetXrpcChallenge(challenge) .Get(); - await verifiedbuilder.SetDynamicSecretAsync(DynamicSecretVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); + await verifiedbuilder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); resp = await verifiedbuilder .TryCatchSendAsync>(httpClient, logger, token) @@ -135,7 +135,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient .SetUserCookieAndFpHeader(userAndUid, CookieType.Cookie) .Get(); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); Response? resp = await builder .TryCatchSendAsync>(httpClient, logger, token) @@ -146,7 +146,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient { // Replace message resp.Message = SH.WebIndexOrSpiralAbyssVerificationFailed; - IGeetestCardVerifier verifier = serviceProvider.GetRequiredService(); + IGeetestCardVerifier verifier = serviceProvider.GetRequiredKeyedService(GeetestCardVerifierType.Custom); if (await verifier.TryValidateXrpcChallengeAsync(userAndUid.User, token).ConfigureAwait(false) is { } challenge) { @@ -156,9 +156,9 @@ internal sealed partial class GameRecordClient : IGameRecordClient .SetXrpcChallenge(challenge) .Get(); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); + await verifiedbuilder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); - resp = await builder + resp = await verifiedbuilder .TryCatchSendAsync>(httpClient, logger, token) .ConfigureAwait(false); } @@ -181,7 +181,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient .SetUserCookieAndFpHeader(userAndUid, CookieType.Cookie) .Get(); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); Response? resp = await builder .TryCatchSendAsync>(httpClient, logger, token) @@ -205,7 +205,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient .SetUserCookieAndFpHeader(userAndUid, CookieType.Cookie) .PostJson(new CharacterData(userAndUid.Uid, playerInfo.Avatars.Select(x => x.Id))); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); Response? resp = await builder .TryCatchSendAsync>(httpClient, logger, token) @@ -216,7 +216,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient { // Replace message resp.Message = SH.WebIndexOrSpiralAbyssVerificationFailed; - IGeetestCardVerifier verifier = serviceProvider.GetRequiredService(); + IGeetestCardVerifier verifier = serviceProvider.GetRequiredKeyedService(GeetestCardVerifierType.Custom); if (await verifier.TryValidateXrpcChallengeAsync(userAndUid.User, token).ConfigureAwait(false) is { } challenge) { @@ -226,7 +226,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient .SetXrpcChallenge(challenge) .PostJson(new CharacterData(userAndUid.Uid, playerInfo.Avatars.Select(x => x.Id))); - await verifiedBuilder.SetDynamicSecretAsync(DynamicSecretVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); + await verifiedBuilder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false); resp = await verifiedBuilder .TryCatchSendAsync>(httpClient, logger, token) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClientOversea.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClientOversea.cs index 7c5005ab..a1ca3068 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClientOversea.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClientOversea.cs @@ -4,7 +4,7 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.ViewModel.User; using Snap.Hutao.Web.Hoyolab.Annotation; -using Snap.Hutao.Web.Hoyolab.DynamicSecret; +using Snap.Hutao.Web.Hoyolab.DataSigning; using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar; using Snap.Hutao.Web.Request.Builder; using Snap.Hutao.Web.Request.Builder.Abstraction; @@ -39,7 +39,7 @@ internal sealed partial class GameRecordClientOversea : IGameRecordClient .SetUserCookieAndFpHeader(userAndUid, CookieType.Cookie) .Get(); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen2, SaltType.OSX4, false).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.OSX4, false).ConfigureAwait(false); Response? resp = await builder .TryCatchSendAsync>(httpClient, logger, token) @@ -62,7 +62,7 @@ internal sealed partial class GameRecordClientOversea : IGameRecordClient .SetUserCookieAndFpHeader(userAndUid, CookieType.Cookie) .Get(); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen2, SaltType.OSX4, false).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.OSX4, false).ConfigureAwait(false); Response? resp = await builder .TryCatchSendAsync>(httpClient, logger, token) @@ -86,7 +86,7 @@ internal sealed partial class GameRecordClientOversea : IGameRecordClient .SetUserCookieAndFpHeader(userAndUid, CookieType.Cookie) .Get(); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen2, SaltType.OSX4, false).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.OSX4, false).ConfigureAwait(false); Response? resp = await builder .TryCatchSendAsync>(httpClient, logger, token) @@ -110,7 +110,7 @@ internal sealed partial class GameRecordClientOversea : IGameRecordClient .SetUserCookieAndFpHeader(userAndUid, CookieType.Cookie) .PostJson(new CharacterData(userAndUid.Uid, playerInfo.Avatars.Select(x => x.Id))); - await builder.SetDynamicSecretAsync(DynamicSecretVersion.Gen2, SaltType.OSX4, false).ConfigureAwait(false); + await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.OSX4, false).ConfigureAwait(false); Response? resp = await builder .TryCatchSendAsync>(httpClient, logger, token) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Verification/GeetestCardVerifierType.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Verification/GeetestCardVerifierType.cs new file mode 100644 index 00000000..f8dedec3 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Verification/GeetestCardVerifierType.cs @@ -0,0 +1,9 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Verification; + +internal enum GeetestCardVerifierType +{ + Custom, +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Verification/HomaGeetestCardVerifier.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Verification/HomaGeetestCardVerifier.cs index 634c73ec..789d9f7c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Verification/HomaGeetestCardVerifier.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Verification/HomaGeetestCardVerifier.cs @@ -7,7 +7,7 @@ using Snap.Hutao.Web.Hutao.Geetest; namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Verification; [ConstructorGenerated] -[Injection(InjectAs.Transient)] +[Injection(InjectAs.Transient, Key = GeetestCardVerifierType.Custom)] internal sealed partial class HomaGeetestCardVerifier : IGeetestCardVerifier { private readonly CardClient cardClient; diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Geetest/HomaGeetestClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Geetest/HomaGeetestClient.cs index a36802d3..4f9ba6be 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Geetest/HomaGeetestClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Geetest/HomaGeetestClient.cs @@ -5,6 +5,7 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.Service; using Snap.Hutao.Web.Request.Builder; using Snap.Hutao.Web.Request.Builder.Abstraction; +using System.Globalization; using System.Net.Http; namespace Snap.Hutao.Web.Hutao.Geetest; @@ -25,7 +26,7 @@ internal sealed partial class HomaGeetestClient string url; try { - url = template.Format(gt, challenge); + url = string.Format(CultureInfo.CurrentCulture, template, gt, challenge); } catch (FormatException) { diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HutaoResponse.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HutaoResponse.cs index 56397a7f..e73529e0 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HutaoResponse.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HutaoResponse.cs @@ -20,20 +20,20 @@ internal sealed class HutaoResponse : Response.Response, ILocalizableResponse public static HutaoResponse DefaultIfNull(HutaoResponse? response, [CallerMemberName] string callerName = default!) { // 0x26F19335 is a magic number that hashed from "Snap.Hutao" - response ??= new(InternalFailure, SH.WebResponseRequestExceptionFormat.Format(callerName, null), default); + response ??= new(InternalFailure, SH.FormatWebResponseRequestExceptionFormat(callerName, null), default); return response; } public static HutaoResponse DefaultIfNull(HutaoResponse? response, [CallerMemberName] string callerName = default!) { // 0x26F19335 is a magic number that hashed from "Snap.Hutao" - response ??= new(InternalFailure, SH.WebResponseRequestExceptionFormat.Format(callerName, typeof(TData).Name), default, default); - return response ?? new(InternalFailure, SH.WebResponseRequestExceptionFormat.Format(callerName, typeof(TData).Name), default, default); + response ??= new(InternalFailure, SH.FormatWebResponseRequestExceptionFormat(callerName, typeof(TData).Name), default, default); + return response ?? new(InternalFailure, SH.FormatWebResponseRequestExceptionFormat(callerName, typeof(TData).Name), default, default); } public override string ToString() { - return SH.WebResponseFormat.Format(ReturnCode, this.GetLocalizationMessageOrDefault()); + return SH.FormatWebResponseFormat(ReturnCode, this.GetLocalizationMessageOrDefault()); } } @@ -52,6 +52,6 @@ internal sealed class HutaoResponse : Response.Response, ILocaliza public override string ToString() { - return SH.WebResponseFormat.Format(ReturnCode, this.GetLocalizationMessageOrDefault()); + return SH.FormatWebResponseFormat(ReturnCode, this.GetLocalizationMessageOrDefault()); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Log/HomaLogUploadClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Log/HomaLogUploadClient.cs index 7574e59b..30d110e4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Log/HomaLogUploadClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Log/HomaLogUploadClient.cs @@ -46,7 +46,7 @@ internal sealed partial class HomaLogUploadClient return new() { Id = runtimeOptions.DeviceId, - Time = DateTimeOffset.Now.ToUnixTimeMilliseconds(), + Time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), Info = Core.ExceptionService.ExceptionFormat.Format(exception), }; } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/SpiralAbyss/Converter/ReliquarySetsConverter.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/SpiralAbyss/Converter/ReliquarySetsConverter.cs index 9627299a..89b60540 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/SpiralAbyss/Converter/ReliquarySetsConverter.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/SpiralAbyss/Converter/ReliquarySetsConverter.cs @@ -18,8 +18,8 @@ internal sealed class ReliquarySetsConverter : JsonConverter { if (reader.GetString() is { } source) { - List sets = new(); - foreach (StringSegment segment in new StringTokenizer(source, Separator.ToArray())) + List sets = []; + foreach (StringSegment segment in new StringTokenizer(source, [Separator])) { if (segment is { HasValue: true, Length: > 0 }) { diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Request/Builder/JsonHttpContentSerializer.cs b/src/Snap.Hutao/Snap.Hutao/Web/Request/Builder/JsonHttpContentSerializer.cs index 20d9caf4..9dbfd363 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Request/Builder/JsonHttpContentSerializer.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Request/Builder/JsonHttpContentSerializer.cs @@ -62,7 +62,7 @@ internal class JsonHttpContentSerializer : HttpContentSerializer return JsonSerializer.Deserialize(json, contentType, JsonSerializerOptions); } - private HttpContent? SerializeUtf8(object? content, Type contentType) + private ByteArrayContent? SerializeUtf8(object? content, Type contentType) { byte[] bytes = JsonSerializer.SerializeToUtf8Bytes(content, contentType, JsonSerializerOptions); ByteArrayContent httpContent = new(bytes); @@ -75,7 +75,7 @@ internal class JsonHttpContentSerializer : HttpContentSerializer return httpContent; } - private HttpContent? SerializeOtherEncoding(object? content, Type contentType, Encoding encoding) + private StringContent? SerializeOtherEncoding(object? content, Type contentType, Encoding encoding) { string str = JsonSerializer.Serialize(content, contentType, JsonSerializerOptions); return new StringContent(str, encoding, MediaType.ApplicationJson); diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Request/NameValueCollectionExtension.cs b/src/Snap.Hutao/Snap.Hutao/Web/Request/NameValueCollectionExtension.cs new file mode 100644 index 00000000..660a28c0 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Request/NameValueCollectionExtension.cs @@ -0,0 +1,41 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Specialized; +using System.Text; +using System.Web; + +namespace Snap.Hutao.Web.Request; + +internal static class NameValueCollectionExtension +{ + public static string ToQueryString(this NameValueCollection collection) + { + int count = collection.Count; + if (count == 0) + { + return string.Empty; + } + + StringBuilder sb = new(); + string?[] keys = collection.AllKeys; + for (int i = 0; i < count; i++) + { + string? key = keys[i]; + if (collection.GetValues(key) is { } values) + { + foreach (string value in values) + { + if (!string.IsNullOrEmpty(key)) + { + sb.Append(key).Append('='); + } + + sb.Append(HttpUtility.UrlEncode(value)).Append('&'); + } + } + } + + return sb.Length > 0 ? sb.ToString(0, sb.Length - 1) : string.Empty; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Request/QueryString/QueryString.cs b/src/Snap.Hutao/Snap.Hutao/Web/Request/QueryString/QueryString.cs deleted file mode 100644 index dd22f7d0..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Web/Request/QueryString/QueryString.cs +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -namespace Snap.Hutao.Web.Request.QueryString; - -/// -/// querystring serializer/deserializer -/// -[HighQuality] -internal readonly struct QueryString -{ - private readonly Dictionary> dictionary = new(); - - /// - /// Nothing - /// - public QueryString() - { - } - - /// - /// Gets the first value of the first parameter with the matching name. - /// Throws if a parameter with a matching name could not be found. - /// O(n) where n = Count of the current object. - /// - /// The parameter name to find. - /// query - public string this[string name] - { - get - { - if (TryGetValue(name, out string? value)) - { - return value; - } - - throw new KeyNotFoundException($"A parameter with name '{name}' could not be found."); - } - } - -#if NET8_0 - - private static QueryString Parse(ReadOnlySpan value) - { - // TODO: .NET 8 ReadOnlySpan Split - return default; - } -#endif - - /// - /// Parses a query string into a object. Keys/values are automatically URL decoded. - /// - /// - /// The query string to deserialize. - /// Valid input would be something like "a=1&b=5". - /// URL decoding of keys/values is automatically performed. - /// Also supports query strings that are serialized using ; instead of &, like "a=1;b=5" - /// A new QueryString represents the url query - public static QueryString Parse(string? queryString) - { - if (string.IsNullOrWhiteSpace(queryString)) - { - return new QueryString(); - } - - int questionMarkIndex = queryString.AsSpan().IndexOf('?'); - queryString = queryString[(questionMarkIndex + 1)..]; - - string[] pairs = queryString.Split('&', ';'); - QueryString answer = new(); - foreach (string pair in pairs) - { - string name; - string? value; - int indexOfEquals = pair.IndexOf('=', StringComparison.Ordinal); - if (indexOfEquals == -1) - { - name = pair; - value = string.Empty; - } - else - { - name = pair[..indexOfEquals]; - value = pair[(indexOfEquals + 1)..]; - } - - answer.Add(name, value); - } - - return answer; - } - - /// - /// Gets the first value of the first parameter with the matching name. If no parameter with a matching name exists, returns false. - /// - /// The parameter name to find. - /// The parameter's value will be written here once found. - /// value - public bool TryGetValue(string name, [NotNullWhen(true)] out string? value) - { - if (dictionary.TryGetValue(name, out List? values)) - { - value = values.First(); - return true; - } - - value = null; - return false; - } - - /// - /// Gets the values of the parameter with the matching name. If no parameter with a matching name exists, sets to null and returns false. - /// - /// The parameter name to find. - /// The parameter's values will be written here once found. - /// values - public bool TryGetValues(string name, [NotNullWhen(true)] out string?[]? values) - { - if (dictionary.TryGetValue(name, out List? storedValues)) - { - values = storedValues.ToArray(); - return true; - } - - values = null; - return false; - } - - /// - /// Returns the count of parameters in the current query string. - /// - /// count of the queries - public int Count() - { - return dictionary.Select(i => i.Value.Count).Sum(); - } - - /// - /// Adds a query string parameter to the query string. - /// - /// The name of the parameter. - /// The optional value of the parameter. - public void Add(string name, string value) - { - if (!dictionary.TryGetValue(name, out List? values)) - { - values = new List(); - dictionary[name] = values; - } - - values.Add(value); - } - - /// - public void Set(string name, object value) - { - Set(name, value.ToString() ?? string.Empty); - } - - /// - /// Sets a query string parameter. If there are existing parameters with the same name, they are removed. - /// - /// The name of the parameter. - /// The optional value of the parameter. - public void Set(string name, string value) - { - dictionary[name] = new() { value }; - } - - /// - /// Determines if the query string contains at least one parameter with the specified name. - /// - /// The parameter name to look for. - /// True if the query string contains at least one parameter with the specified name, else false. - public bool Contains(string name) - { - return dictionary.ContainsKey(name); - } - - /// - /// Determines if the query string contains a parameter with the specified name and value. - /// - /// The parameter name to look for. - /// The value to look for when the name has been matched. - /// True if the query string contains a parameter with the specified name and value, else false. - public bool Contains(string name, string value) - { - return dictionary.TryGetValue(name, out List? values) && values.Contains(value); - } - - /// - /// Removes the first parameter with the specified name. - /// - /// The name of parameter to remove. - /// True if the parameters were removed, else false. - public bool Remove(string name) - { - if (dictionary.TryGetValue(name, out List? values)) - { - if (values.Count == 1) - { - dictionary.Remove(name); - } - else - { - values.RemoveAt(0); - } - - return true; - } - - return false; - } - - /// - /// Removes all parameters with the specified name. - /// - /// The name of parameters to remove. - /// True if the parameters were removed, else false. - public bool RemoveAll(string name) - { - return dictionary.Remove(name); - } - - /// - /// Removes the first parameter with the specified name and value. - /// - /// The name of the parameter to remove. - /// value - /// True if parameter was removed, else false. - public bool Remove(string name, string value) - { - if (dictionary.TryGetValue(name, out List? values)) - { - if (values.RemoveFirstWhere(i => Equals(i, value))) - { - // If removed last value, remove the key - if (values.Count == 0) - { - dictionary.Remove(name); - } - - return true; - } - } - - return false; - } - - /// - /// Removes all parameters with the specified name and value. - /// - /// The name of parameters to remove. - /// The value to match when deciding whether to remove. - /// The count of parameters removed. - public int RemoveAll(string name, string value) - { - if (dictionary.TryGetValue(name, out List? values)) - { - int countRemoved = values.RemoveAll(i => Equals(i, value)); - - // If removed last value, remove the key - if (values.Count == 0) - { - dictionary.Remove(name); - } - - return countRemoved; - } - - return 0; - } - - /// - /// Serializes the key-value pairs into a query string, using the default & separator. - /// Produces something like "a=1&b=5". - /// URL encoding of keys/values is automatically performed. - /// Null values are not written (only their key is written). - /// - /// query - public override string ToString() - { - return string.Join('&', GetParameters()); - } - - private IEnumerable GetParameters() - { - foreach ((string name, List values) in dictionary) - { - foreach (string value in values) - { - yield return new QueryStringParameter(name, value); - } - } - } -} diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Request/QueryString/QueryStringParameter.cs b/src/Snap.Hutao/Snap.Hutao/Web/Request/QueryString/QueryStringParameter.cs deleted file mode 100644 index 4c652b12..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Web/Request/QueryString/QueryStringParameter.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -namespace Snap.Hutao.Web.Request.QueryString; - -/// -/// A single query string parameter (name and value pair). -/// -[HighQuality] -internal struct QueryStringParameter -{ - /// - /// The name of the parameter. Cannot be null. - /// - public string Name; - - /// - /// The value of the parameter (or null if there's no value). - /// - public string Value; - - /// - /// Initializes a new query string parameter with the specified name and optional value. - /// - /// The name of the parameter. Cannot be null. - /// The optional value of the parameter. - internal QueryStringParameter(string name, string value) - { - Name = name; - Value = value; - } - - /// - public override readonly string ToString() - { - return $"{Name}={Value}"; - } -} diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs b/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs index 30697bca..4f0bc8b8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs @@ -56,11 +56,11 @@ internal class Response public static Response DefaultIfNull(Response? response, [CallerMemberName] string callerName = default!) { // 0x26F19335 is a magic number that hashed from "Snap.Hutao" - response ??= new(InternalFailure, SH.WebResponseRequestExceptionFormat.Format(callerName, null)); + response ??= new(InternalFailure, SH.FormatWebResponseRequestExceptionFormat(callerName, null)); if (((KnownReturnCode)response.ReturnCode) is KnownReturnCode.PleaseLogin or KnownReturnCode.RET_TOKEN_INVALID) { - response.Message = SH.WebResponseRefreshCookieHintFormat.Format(response.Message); + response.Message = SH.FormatWebResponseRefreshCookieHintFormat(response.Message); } return response; @@ -76,14 +76,14 @@ internal class Response public static Response DefaultIfNull(Response? response, [CallerMemberName] string callerName = default!) { // 0x26F19335 is a magic number that hashed from "Snap.Hutao" - response ??= new(InternalFailure, SH.WebResponseRequestExceptionFormat.Format(callerName, typeof(TData).Name), default); + response ??= new(InternalFailure, SH.FormatWebResponseRequestExceptionFormat(callerName, typeof(TData).Name), default); if (((KnownReturnCode)response.ReturnCode) is KnownReturnCode.PleaseLogin or KnownReturnCode.RET_TOKEN_INVALID) { - response.Message = SH.WebResponseRefreshCookieHintFormat.Format(response.Message); + response.Message = SH.FormatWebResponseRefreshCookieHintFormat(response.Message); } - return response ?? new(InternalFailure, SH.WebResponseRequestExceptionFormat.Format(callerName, typeof(TData).Name), default); + return response ?? new(InternalFailure, SH.FormatWebResponseRequestExceptionFormat(callerName, typeof(TData).Name), default); } /// @@ -104,7 +104,7 @@ internal class Response else { // Magic number that hashed from "Snap.Hutao" - return new(InternalFailure, SH.WebResponseRequestExceptionFormat.Format(callerName, typeof(TData).Name), default); + return new(InternalFailure, SH.FormatWebResponseRequestExceptionFormat(callerName, typeof(TData).Name), default); } } @@ -129,7 +129,7 @@ internal class Response /// public override string ToString() { - return SH.WebResponseFormat.Format(ReturnCode, Message); + return SH.FormatWebResponseFormat(ReturnCode, Message); } } diff --git a/src/Snap.Hutao/Temp.txt b/src/Snap.Hutao/Temp.txt new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/src/Snap.Hutao/Temp.txt @@ -0,0 +1 @@ + \ No newline at end of file