diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DedendencyInjection/HttpClientGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DedendencyInjection/HttpClientGenerator.cs
new file mode 100644
index 00000000..562da130
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DedendencyInjection/HttpClientGenerator.cs
@@ -0,0 +1,138 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Text;
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+
+namespace Snap.Hutao.SourceGeneration.DedendencyInjection;
+
+///
+/// 注入HttpClient代码生成器
+/// 旨在使用源生成器提高注入效率
+/// 防止在运行时动态查找注入类型
+///
+[Generator]
+public class HttpClientGenerator : ISourceGenerator
+{
+ private const string DefaultName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientConfigration.Default";
+ private const string XRpcName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientConfigration.XRpc";
+
+ private const string PrimaryHttpMessageHandlerAttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.PrimaryHttpMessageHandlerAttribute";
+
+ ///
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ // Register a syntax receiver that will be created for each generation pass
+ context.RegisterForSyntaxNotifications(() => new HttpClientSyntaxContextReceiver());
+ }
+
+ ///
+ public void Execute(GeneratorExecutionContext context)
+ {
+ // retrieve the populated receiver
+ if (context.SyntaxContextReceiver is not HttpClientSyntaxContextReceiver receiver)
+ {
+ return;
+ }
+
+ string toolName = this.GetGeneratorType().FullName;
+
+ StringBuilder sourceCodeBuilder = new();
+ sourceCodeBuilder.Append($@"// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+// This class is generated by Snap.Hutao.SourceGeneration
+
+using Microsoft.Extensions.DependencyInjection;
+using System.Net.Http;
+
+namespace Snap.Hutao.Core.DependencyInjection;
+
+internal static partial class IocHttpClientConfiguration
+{{
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""{toolName}"",""1.0.0.0"")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ public static partial IServiceCollection AddHttpClients(this IServiceCollection services)
+ {{");
+
+ FillWithInjectionServices(receiver, sourceCodeBuilder);
+ sourceCodeBuilder.Append(@"
+ return services;
+ }
+}");
+
+ context.AddSource("IocHttpClientConfiguration.g.cs", SourceText.From(sourceCodeBuilder.ToString(), Encoding.UTF8));
+ }
+
+ private static void FillWithInjectionServices(HttpClientSyntaxContextReceiver receiver, StringBuilder sourceCodeBuilder)
+ {
+ List lines = new();
+ StringBuilder lineBuilder = new();
+
+ foreach (INamedTypeSymbol classSymbol in receiver.Classes)
+ {
+ lineBuilder
+ .Clear()
+ .Append("\r\n");
+
+ lineBuilder.Append(@" services.AddHttpClient<");
+ lineBuilder.Append($"{classSymbol.ToDisplayString()}>(");
+
+ AttributeData httpClientInfo = classSymbol
+ .GetAttributes()
+ .Single(attr => attr.AttributeClass!.ToDisplayString() == HttpClientSyntaxContextReceiver.AttributeName);
+ ImmutableArray arguments = httpClientInfo.ConstructorArguments;
+
+ TypedConstant injectAs = arguments[0];
+
+ string injectAsName = injectAs.ToCSharpString();
+ switch (injectAsName)
+ {
+ case DefaultName:
+ lineBuilder.Append(@"DefaultConfiguration)");
+ break;
+ case XRpcName:
+ lineBuilder.Append(@"XRpcConfiguration)");
+ break;
+ default:
+ throw new InvalidOperationException($"非法的HttpClientConfigration值: [{injectAsName}]");
+ }
+
+ AttributeData? handlerInfo = classSymbol
+ .GetAttributes()
+ .SingleOrDefault(attr => attr.AttributeClass!.ToDisplayString() == PrimaryHttpMessageHandlerAttributeName);
+
+ if (handlerInfo != null)
+ {
+ ImmutableArray> properties = handlerInfo.NamedArguments;
+ lineBuilder.Append(@".ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() {");
+
+ foreach (KeyValuePair property in properties)
+ {
+ lineBuilder.Append(" ");
+ lineBuilder.Append(property.Key);
+ lineBuilder.Append(" = ");
+ lineBuilder.Append(property.Value.ToCSharpString());
+ lineBuilder.Append(",");
+ }
+
+ lineBuilder.Append(" })");
+ }
+
+ lineBuilder.Append(";");
+
+ lines.Add(lineBuilder.ToString());
+ }
+
+ foreach (string line in lines.OrderBy(x => x))
+ {
+ sourceCodeBuilder.Append(line);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DedendencyInjection/HttpClientSyntaxContextReceiver.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DedendencyInjection/HttpClientSyntaxContextReceiver.cs
new file mode 100644
index 00000000..f80d8f94
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DedendencyInjection/HttpClientSyntaxContextReceiver.cs
@@ -0,0 +1,42 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Snap.Hutao.SourceGeneration.DedendencyInjection;
+
+///
+/// Created on demand before each generation pass
+///
+public class HttpClientSyntaxContextReceiver : ISyntaxContextReceiver
+{
+ ///
+ /// 注入特性的名称
+ ///
+ public const string AttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientAttribute";
+
+ ///
+ /// 所有需要注入的类型符号
+ ///
+ public List Classes { get; } = new();
+
+ ///
+ public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
+ {
+ // any class with at least one attribute is a candidate for injection generation
+ if (context.Node is ClassDeclarationSyntax classDeclarationSyntax && classDeclarationSyntax.AttributeLists.Count > 0)
+ {
+ // get as named type symbol
+ if (context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax) is INamedTypeSymbol classSymbol)
+ {
+ if (classSymbol.GetAttributes().Any(ad => ad.AttributeClass!.ToDisplayString() == AttributeName))
+ {
+ Classes.Add(classSymbol);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DedendencyInjection/InjectionGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DedendencyInjection/InjectionGenerator.cs
index 40154b3e..9a00c488 100644
--- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DedendencyInjection/InjectionGenerator.cs
+++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DedendencyInjection/InjectionGenerator.cs
@@ -20,8 +20,8 @@ namespace Snap.Hutao.SourceGeneration.DedendencyInjection;
[Generator]
public class InjectionGenerator : ISourceGenerator
{
- private const string InjectAsSingletonName = "Snap.Hutao.Core.DependencyInjection.InjectAs.Singleton";
- private const string InjectAsTransientName = "Snap.Hutao.Core.DependencyInjection.InjectAs.Transient";
+ private const string InjectAsSingletonName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Singleton";
+ private const string InjectAsTransientName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Transient";
///
public void Initialize(GeneratorInitializationContext context)
@@ -39,8 +39,10 @@ public class InjectionGenerator : ISourceGenerator
return;
}
+ string toolName = this.GetGeneratorType().FullName;
+
StringBuilder sourceCodeBuilder = new();
- sourceCodeBuilder.Append(@"// Copyright (c) DGP Studio. All rights reserved.
+ sourceCodeBuilder.Append($@"// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
// This class is generated by Snap.Hutao.SourceGeneration
@@ -50,15 +52,15 @@ using Microsoft.Extensions.DependencyInjection;
namespace Snap.Hutao.Core.DependencyInjection;
internal static partial class ServiceCollectionExtensions
-{
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Snap.Hutao.SourceGeneration.DedendencyInjection.InjectionGenerator"","" 1.0.0.0"")]
+{{
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""{toolName}"",""1.0.0.0"")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public static partial IServiceCollection AddInjections(this IServiceCollection services)
- {
- return services");
+ {{");
FillWithInjectionServices(receiver, sourceCodeBuilder);
- sourceCodeBuilder.Append(@";
+ sourceCodeBuilder.Append(@"
+ return services;
}
}");
@@ -87,13 +89,13 @@ internal static partial class ServiceCollectionExtensions
switch (injectAsName)
{
case InjectAsSingletonName:
- lineBuilder.Append(@" .AddSingleton(");
+ lineBuilder.Append(@" services.AddSingleton(");
break;
case InjectAsTransientName:
- lineBuilder.Append(@" .AddTransient(");
+ lineBuilder.Append(@" services.AddTransient(");
break;
default:
- throw new InvalidOperationException($"非法的InjectAs值: [{injectAsName}]。");
+ throw new InvalidOperationException($"非法的InjectAs值: [{injectAsName}]");
}
if (arguments.Length == 2)
@@ -102,7 +104,7 @@ internal static partial class ServiceCollectionExtensions
lineBuilder.Append($"{interfaceType.ToCSharpString()}, ");
}
- lineBuilder.Append($"typeof({classSymbol.ToDisplayString()}))");
+ lineBuilder.Append($"typeof({classSymbol.ToDisplayString()}));");
lines.Add(lineBuilder.ToString());
}
diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DedendencyInjection/InjectionSyntaxContextReceiver.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DedendencyInjection/InjectionSyntaxContextReceiver.cs
index 09b1d460..2a9fd846 100644
--- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DedendencyInjection/InjectionSyntaxContextReceiver.cs
+++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DedendencyInjection/InjectionSyntaxContextReceiver.cs
@@ -39,4 +39,4 @@ public class InjectionSyntaxContextReceiver : ISyntaxContextReceiver
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Snap.Hutao/Snap.Hutao.Win32/Foundation/HWND.cs b/src/Snap.Hutao/Snap.Hutao.Win32/Foundation/HWND.cs
deleted file mode 100644
index 804f9404..00000000
--- a/src/Snap.Hutao/Snap.Hutao.Win32/Foundation/HWND.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Windows.Win32.Foundation;
-
-public readonly partial struct HWND
-{
- public static HWND Zero => (HWND)IntPtr.Zero;
-}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.txt b/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.txt
index c56775e8..50ff08a6 100644
--- a/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.txt
+++ b/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.txt
@@ -16,4 +16,4 @@ RemoveWindowSubclass
// User32
FindWindowEx
-GetDpiForWindow
+GetDpiForWindow
\ No newline at end of file
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 9081fd0f..10c2cd7a 100644
--- a/src/Snap.Hutao/Snap.Hutao.Win32/Snap.Hutao.Win32.csproj
+++ b/src/Snap.Hutao/Snap.Hutao.Win32/Snap.Hutao.Win32.csproj
@@ -11,7 +11,7 @@
all
-
+
diff --git a/src/Snap.Hutao/Snap.Hutao.Win32/Unsafe.cs b/src/Snap.Hutao/Snap.Hutao.Win32/Unsafe.cs
index 3006942d..1c57ab21 100644
--- a/src/Snap.Hutao/Snap.Hutao.Win32/Unsafe.cs
+++ b/src/Snap.Hutao/Snap.Hutao.Win32/Unsafe.cs
@@ -1,6 +1,10 @@
using Windows.Win32.UI.WindowsAndMessaging;
namespace Snap.Hutao.Win32;
+
+///
+/// 包装不安全的代码
+///
public class Unsafe
{
///
@@ -9,7 +13,7 @@ public class Unsafe
/// lParam
/// 最小宽度
/// 最小高度
- public static unsafe void SetMinTrackSize(nint lParam, float minWidth, float minHeight)
+ public static unsafe void SetMinTrackSize(nint lParam, double minWidth, double minHeight)
{
MINMAXINFO* info = (MINMAXINFO*)lParam;
info->ptMinTrackSize.x = (int)Math.Max(minWidth, info->ptMinTrackSize.x);
diff --git a/src/Snap.Hutao/Snap.Hutao/App.xaml.cs b/src/Snap.Hutao/Snap.Hutao/App.xaml.cs
index aafb025c..6321b25d 100644
--- a/src/Snap.Hutao/Snap.Hutao/App.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/App.xaml.cs
@@ -12,6 +12,7 @@ using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Metadata;
using System.Diagnostics;
using Windows.Storage;
+using Windows.UI.ViewManagement;
namespace Snap.Hutao;
@@ -52,17 +53,13 @@ public partial class App : Application
get => (App)Application.Current;
}
- ///
///
- ///
public static StorageFolder CacheFolder
{
get => ApplicationData.Current.TemporaryFolder;
}
- ///
///
- ///
public static ApplicationDataContainer Settings
{
get => ApplicationData.Current.LocalSettings;
@@ -78,11 +75,12 @@ public partial class App : Application
if (firstInstance.IsCurrent)
{
firstInstance.Activated += OnActivated;
-
Window = Ioc.Default.GetRequiredService();
logger.LogInformation(EventIds.CommonLog, "Cache folder : {folder}", CacheFolder.Path);
+ OnActivated(firstInstance, activatedEventArgs);
+
Ioc.Default
.GetRequiredService()
.ImplictAs()?
@@ -109,25 +107,31 @@ public partial class App : Application
// Hutao extensions
.AddInjections()
- .AddDatebase()
.AddHttpClients()
+ .AddDatebase()
.AddJsonSerializerOptions()
// Discrete services
.AddSingleton(WeakReferenceMessenger.Default)
+ .AddSingleton(new UISettings())
.BuildServiceProvider();
Ioc.Default.ConfigureServices(services);
}
- private void OnActivated(object? sender, AppActivationArguments args)
+ [SuppressMessage("", "VSTHRD100")]
+ private async void OnActivated(object? sender, AppActivationArguments args)
{
+ IInfoBarService infoBarService = Ioc.Default.GetRequiredService();
+ await infoBarService.WaitInitializationAsync();
+ infoBarService.Information("OnActivated");
+
if (args.Kind == ExtendedActivationKind.Protocol)
{
if (args.TryGetProtocolActivatedUri(out Uri? uri))
{
- Ioc.Default.GetRequiredService().Information(uri.ToString());
+ infoBarService.Information(uri.ToString());
}
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Context/Database/LogDbContext.cs b/src/Snap.Hutao/Snap.Hutao/Context/Database/LogDbContext.cs
index cbc4021d..c1d9fbcd 100644
--- a/src/Snap.Hutao/Snap.Hutao/Context/Database/LogDbContext.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Context/Database/LogDbContext.cs
@@ -8,6 +8,8 @@ namespace Snap.Hutao.Context.Database;
///
/// 日志数据库上下文
+/// 由于写入日志的行为需要锁定数据库上下文
+/// 所以将日志单独分离出来进行读写
///
public class LogDbContext : DbContext
{
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs
index 1e6d5eac..124b4362 100644
--- a/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs
@@ -132,6 +132,10 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
{
imageSurface = await LoadImageSurfaceAsync(storageFile, token);
}
+ catch (COMException ex) when (ex.Is(COMError.STG_E_FILENOTFOUND))
+ {
+ // Image file not found.
+ }
catch (COMException ex) when (ex.Is(COMError.WINCODEC_ERR_COMPONENTNOTFOUND))
{
// Image is broken, remove it
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs b/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs
index 8dd0f2a4..a844cdfa 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Media.Imaging;
+using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using System.Collections.Generic;
using System.Net.Http;
using Windows.Storage;
@@ -14,6 +15,8 @@ namespace Snap.Hutao.Core.Caching;
/// The class's name will become the cache folder's name
///
[Injection(InjectAs.Singleton, typeof(IImageCache))]
+[HttpClient(HttpClientConfigration.Default)]
+[PrimaryHttpMessageHandler(MaxConnectionsPerServer = 20)]
public class ImageCache : CacheBase, IImageCache
{
private const string DateAccessedProperty = "System.DateAccessed";
@@ -24,9 +27,9 @@ public class ImageCache : CacheBase, IImageCache
/// Initializes a new instance of the class.
///
/// 日志器
- /// http客户端
- public ImageCache(ILogger logger, HttpClient httpClient)
- : base(logger, httpClient)
+ /// http客户端工厂
+ public ImageCache(ILogger logger, IHttpClientFactory httpClientFactory)
+ : base(logger, httpClientFactory.CreateClient(nameof(ImageCache)))
{
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs
index e885bff0..83edd12f 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs
@@ -13,6 +13,27 @@ namespace Snap.Hutao.Core;
///
internal static class CoreEnvironment
{
+ ///
+ /// 动态密钥1的盐
+ ///
+ public const string DynamicSecret1Salt = "9nQiU3AV0rJSIBWgdynfoGMGKaklfbM7";
+
+ ///
+ /// 动态密钥2的盐
+ /// 计算过程:https://gist.github.com/Lightczx/373c5940b36e24b25362728b52dec4fd
+ ///
+ public const string DynamicSecret2Salt = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs";
+
+ ///
+ /// 米游社请求UA
+ ///
+ public const string HoyolabUA = $"miHoYoBBS/2.34.1";
+
+ ///
+ /// 标准UA
+ ///
+ public static readonly string CommonUA;
+
///
/// 当前版本
///
@@ -23,13 +44,20 @@ internal static class CoreEnvironment
///
public static readonly string DeviceId;
+ ///
+ /// 米游社设备Id
+ ///
+ public static readonly string HoyolabDeviceId;
+
private const string CryptographyKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography";
private const string MachineGuidValue = "MachineGuid";
static CoreEnvironment()
{
Version = Package.Current.Id.Version.ToVersion();
+ CommonUA = $"Snap Hutao/{Version}";
DeviceId = GetDeviceId();
+ HoyolabDeviceId = Guid.NewGuid().ToString();
}
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/HttpClient/HttpClientAttribute.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/HttpClient/HttpClientAttribute.cs
new file mode 100644
index 00000000..b93b5506
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/HttpClient/HttpClientAttribute.cs
@@ -0,0 +1,20 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
+
+///
+/// 指示被标注的类型可注入 HttpClient
+/// 由源生成器生成注入代码
+///
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
+public class HttpClientAttribute : Attribute
+{
+ ///
+ /// 构造一个新的特性
+ ///
+ /// 配置
+ public HttpClientAttribute(HttpClientConfigration configration)
+ {
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/HttpClient/HttpClientConfigration.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/HttpClient/HttpClientConfigration.cs
new file mode 100644
index 00000000..b86c2970
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/HttpClient/HttpClientConfigration.cs
@@ -0,0 +1,20 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
+
+///
+/// Http客户端配置
+///
+public enum HttpClientConfigration
+{
+ ///
+ /// 默认配置
+ ///
+ Default,
+
+ ///
+ /// 米游社请求配置
+ ///
+ XRpc,
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/HttpClient/PrimaryHttpMessageHandlerAttribute.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/HttpClient/PrimaryHttpMessageHandlerAttribute.cs
new file mode 100644
index 00000000..b53dc184
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/HttpClient/PrimaryHttpMessageHandlerAttribute.cs
@@ -0,0 +1,14 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
+
+///
+/// 配置首选Http消息处理器特性
+///
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
+public class PrimaryHttpMessageHandlerAttribute : Attribute
+{
+ ///
+ public int MaxConnectionsPerServer { get; set; }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/InjectAs.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/InjectAs.cs
similarity index 85%
rename from src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/InjectAs.cs
rename to src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/InjectAs.cs
index a9b0adcd..a9f36cfc 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/InjectAs.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/InjectAs.cs
@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-namespace Snap.Hutao.Core.DependencyInjection;
+namespace Snap.Hutao.Core.DependencyInjection.Annotation;
///
/// 注入方法
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/InjectionAttribute.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/InjectionAttribute.cs
index ffc1cbbb..741f2dfe 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/InjectionAttribute.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/InjectionAttribute.cs
@@ -26,4 +26,4 @@ public class InjectionAttribute : Attribute
public InjectionAttribute(InjectAs injectAs, Type interfaceType)
{
}
-}
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/IocConfiguration.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocConfiguration.cs
similarity index 89%
rename from src/Snap.Hutao/Snap.Hutao/IocConfiguration.cs
rename to src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocConfiguration.cs
index ca806d8c..7039f8ce 100644
--- a/src/Snap.Hutao/Snap.Hutao/IocConfiguration.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocConfiguration.cs
@@ -10,7 +10,7 @@ using System.Linq;
using System.Text.Encodings.Web;
using System.Text.Json.Serialization;
-namespace Snap.Hutao;
+namespace Snap.Hutao.Core.DependencyInjection;
///
/// 配置
@@ -18,7 +18,7 @@ namespace Snap.Hutao;
internal static class IocConfiguration
{
///
- /// 添加默认的 配置
+ /// 添加默认的
///
/// 集合
/// 可继续操作的集合
@@ -56,7 +56,6 @@ internal static class IocConfiguration
}
}
- return services
- .AddDbContextPool(builder => builder.UseSqlite(sqlConnectionString));
+ return services.AddDbContextPool(builder => builder.UseSqlite(sqlConnectionString));
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocHttpClientConfiguration.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocHttpClientConfiguration.cs
new file mode 100644
index 00000000..b9a10389
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocHttpClientConfiguration.cs
@@ -0,0 +1,52 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.Extensions.DependencyInjection;
+using Snap.Hutao.Core.Caching;
+using Snap.Hutao.Service.Metadata;
+using Snap.Hutao.Web.Enka;
+using Snap.Hutao.Web.Hoyolab.Bbs.User;
+using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
+using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
+using Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
+using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
+using Snap.Hutao.Web.Hutao;
+using System.Net.Http;
+
+namespace Snap.Hutao.Core.DependencyInjection;
+
+///
+/// 与 配置
+///
+internal static partial class IocHttpClientConfiguration
+{
+ ///
+ /// 添加
+ ///
+ /// 集合
+ /// 可继续操作的集合
+ public static partial IServiceCollection AddHttpClients(this IServiceCollection services);
+
+ ///
+ /// 默认配置
+ ///
+ /// 配置后的客户端
+ private static void DefaultConfiguration(HttpClient client)
+ {
+ client.Timeout = Timeout.InfiniteTimeSpan;
+ client.DefaultRequestHeaders.UserAgent.ParseAdd(CoreEnvironment.CommonUA);
+ }
+
+ ///
+ /// 对于需要添加动态密钥的客户端使用此配置
+ ///
+ /// 配置后的客户端
+ private static void XRpcConfiguration(HttpClient client)
+ {
+ client.Timeout = Timeout.InfiniteTimeSpan;
+ client.DefaultRequestHeaders.UserAgent.ParseAdd(CoreEnvironment.HoyolabUA);
+ client.DefaultRequestHeaders.Add("x-rpc-app_version", "2.34.1");
+ client.DefaultRequestHeaders.Add("x-rpc-client_type", "5");
+ client.DefaultRequestHeaders.Add("x-rpc-device_id", CoreEnvironment.HoyolabDeviceId);
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Exception/COMError.cs b/src/Snap.Hutao/Snap.Hutao/Core/Exception/COMError.cs
index d09245c5..d1df0a42 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Exception/COMError.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Exception/COMError.cs
@@ -8,6 +8,11 @@ namespace Snap.Hutao.Core.Exception;
///
public enum COMError : uint
{
+ ///
+ /// could not be found.
+ ///
+ STG_E_FILENOTFOUND = 0x80030002,
+
///
/// The component cannot be found.
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Logging/DatebaseLogger.cs b/src/Snap.Hutao/Snap.Hutao/Core/Logging/DatebaseLogger.cs
index fa3d5771..d0167cd9 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Logging/DatebaseLogger.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Logging/DatebaseLogger.cs
@@ -36,7 +36,6 @@ internal sealed partial class DatebaseLogger : ILogger
///
public bool IsEnabled(LogLevel logLevel)
{
- // If the filter is null, everything is enabled
return logLevel != LogLevel.None;
}
@@ -55,6 +54,7 @@ internal sealed partial class DatebaseLogger : ILogger
return;
}
+ // DbContext is not a thread safe class, so we have to lock the wirte procedure
lock (logDbContextLock)
{
logDbContext.Logs.Add(new LogEntry
@@ -73,7 +73,7 @@ internal sealed partial class DatebaseLogger : ILogger
///
/// An empty scope without any logic
///
- private struct NullScope : IDisposable
+ private class NullScope : IDisposable
{
public NullScope()
{
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ProcessHelper.cs b/src/Snap.Hutao/Snap.Hutao/Core/ProcessHelper.cs
index 47db1914..749f172d 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/ProcessHelper.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/ProcessHelper.cs
@@ -52,4 +52,4 @@ public static class ProcessHelper
{
return Start(uri.AbsolutePath, useShellExecute);
}
-}
\ No newline at end of file
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ThemeHelper.cs b/src/Snap.Hutao/Snap.Hutao/Core/ThemeHelper.cs
new file mode 100644
index 00000000..02518511
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/ThemeHelper.cs
@@ -0,0 +1,60 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.UI.Composition.SystemBackdrops;
+using Microsoft.UI.Xaml;
+
+namespace Snap.Hutao.Core;
+
+///
+/// 主题帮助工具类
+///
+public static class ThemeHelper
+{
+ ///
+ /// 判断主题是否相等
+ ///
+ /// 应用主题
+ /// 元素主题
+ /// 主题是否相等
+ public static bool Equals(ApplicationTheme applicationTheme, ElementTheme elementTheme)
+ {
+ return (applicationTheme, elementTheme) switch
+ {
+ (ApplicationTheme.Light, ElementTheme.Light) => true,
+ (ApplicationTheme.Dark, ElementTheme.Dark) => true,
+ _ => false,
+ };
+ }
+
+ ///
+ /// 从 转换到
+ ///
+ /// 应用主题
+ /// 元素主题
+ public static ElementTheme ApplicationToElement(ApplicationTheme applicationTheme)
+ {
+ return applicationTheme switch
+ {
+ ApplicationTheme.Light => ElementTheme.Light,
+ ApplicationTheme.Dark => ElementTheme.Dark,
+ _ => throw Must.NeverHappen(),
+ };
+ }
+
+ ///
+ /// 从 转换到
+ ///
+ /// 元素主题
+ /// 背景主题
+ public static SystemBackdropTheme ElementToSystemBackdrop(ElementTheme elementTheme)
+ {
+ return elementTheme switch
+ {
+ ElementTheme.Default => SystemBackdropTheme.Default,
+ ElementTheme.Light => SystemBackdropTheme.Light,
+ ElementTheme.Dark => SystemBackdropTheme.Dark,
+ _ => throw Must.NeverHappen(),
+ };
+}
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs b/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs
index fadfb946..9c79c058 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs
@@ -10,6 +10,16 @@ namespace Snap.Hutao.Core.Validation;
///
public static class Must
{
+ ///
+ /// Unconditionally throws an .
+ ///
+ /// Nothing. This method always throws.
+ [DoesNotReturn]
+ public static System.Exception NeverHappen()
+ {
+ throw new NotSupportedException("该行为不应发生,请联系开发者进一步确认");
+ }
+
///
/// Throws an if the specified parameter's value is null.
///
@@ -25,25 +35,20 @@ public static class Must
}
///
- /// Unconditionally throws an .
+ /// Throws an if the specified parameter's value is IntPtr.Zero.
///
- /// Nothing. This method always throws.
- [DoesNotReturn]
- public static System.Exception NeverHappen()
+ /// The value of the argument.
+ /// The name of the parameter to include in any thrown exception.
+ /// The value of the parameter.
+ /// Thrown if is .
+ public static Windows.Win32.Foundation.HWND NotNull(Windows.Win32.Foundation.HWND value, [CallerArgumentExpression("value")] string? parameterName = null)
{
- throw new NotSupportedException("该行为不应发生,请联系开发者进一步确认");
- }
+ if (value == default)
+ {
+ throw new ArgumentNullException(parameterName);
+ }
- ///
- /// Unconditionally throws an .
- ///
- /// The type that the method should be typed to return (although it never returns anything).
- /// Nothing. This method always throws.
- [DoesNotReturn]
- [return: MaybeNull]
- public static T NeverHappen()
- {
- throw new NotSupportedException("该行为不应发生,请联系开发者进一步确认");
+ return value;
}
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/SystemBackdrop.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/SystemBackdrop.cs
index 1bca1719..70d0edca 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/SystemBackdrop.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/SystemBackdrop.cs
@@ -19,7 +19,7 @@ public class SystemBackdrop
private DispatcherQueueHelper? dispatcherQueueHelper;
private MicaController? backdropController;
- private SystemBackdropConfiguration? configurationSource;
+ private SystemBackdropConfiguration? configuration;
///
/// 构造一个新的系统背景帮助类
@@ -42,35 +42,34 @@ public class SystemBackdrop
}
else
{
- dispatcherQueueHelper = new DispatcherQueueHelper();
- dispatcherQueueHelper.EnsureWindowsSystemDispatcherQueueController();
+ dispatcherQueueHelper = new();
+ dispatcherQueueHelper.Ensure();
// Hooking up the policy object
- configurationSource = new SystemBackdropConfiguration();
- window.Activated += WindowActivated;
- window.Closed += WindowClosed;
- ((FrameworkElement)window.Content).ActualThemeChanged += WindowThemeChanged;
+ configuration = new();
+ window.Activated += OnWindowActivated;
+ window.Closed += OnWindowClosed;
+ ((FrameworkElement)window.Content).ActualThemeChanged += OnWindowThemeChanged;
// Initial configuration state.
- configurationSource.IsInputActive = true;
- SetConfigurationSourceTheme();
+ configuration.IsInputActive = true;
+ SetConfigurationSourceTheme(configuration);
- backdropController = new MicaController();
+ backdropController = new();
backdropController.AddSystemBackdropTarget(window.As());
- backdropController.SetSystemBackdropConfiguration(configurationSource);
+ backdropController.SetSystemBackdropConfiguration(configuration);
return true;
}
}
- private void WindowActivated(object sender, WindowActivatedEventArgs args)
+ private void OnWindowActivated(object sender, WindowActivatedEventArgs args)
{
- Must.NotNull(configurationSource!);
- configurationSource.IsInputActive = args.WindowActivationState != WindowActivationState.Deactivated;
+ Must.NotNull(configuration!).IsInputActive = args.WindowActivationState != WindowActivationState.Deactivated;
}
- private void WindowClosed(object sender, WindowEventArgs args)
+ private void OnWindowClosed(object sender, WindowEventArgs args)
{
// Make sure any Mica/Acrylic controller is disposed so it doesn't try to
// use this closed window.
@@ -80,27 +79,21 @@ public class SystemBackdrop
backdropController = null;
}
- window.Activated -= WindowActivated;
- configurationSource = null;
+ window.Activated -= OnWindowActivated;
+ configuration = null;
}
- private void WindowThemeChanged(FrameworkElement sender, object args)
+ private void OnWindowThemeChanged(FrameworkElement sender, object args)
{
- if (configurationSource != null)
+ if (configuration != null)
{
- SetConfigurationSourceTheme();
+ SetConfigurationSourceTheme(configuration);
}
}
- private void SetConfigurationSourceTheme()
+ private void SetConfigurationSourceTheme(SystemBackdropConfiguration configuration)
{
- Must.NotNull(configurationSource!).Theme = ((FrameworkElement)window.Content).ActualTheme switch
- {
- ElementTheme.Default => SystemBackdropTheme.Default,
- ElementTheme.Light => SystemBackdropTheme.Light,
- ElementTheme.Dark => SystemBackdropTheme.Dark,
- _ => throw Must.NeverHappen(),
- };
+ configuration.Theme = ThemeHelper.ElementToSystemBackdrop(((FrameworkElement)window.Content).ActualTheme);
}
private class DispatcherQueueHelper
@@ -110,7 +103,7 @@ public class SystemBackdrop
///
/// 确保系统调度队列控制器存在
///
- public void EnsureWindowsSystemDispatcherQueueController()
+ public void Ensure()
{
if (DispatcherQueue.GetForCurrentThread() != null)
{
@@ -122,7 +115,7 @@ public class SystemBackdrop
{
DispatcherQueueOptions options = new()
{
- DwSize = Marshal.SizeOf(typeof(DispatcherQueueOptions)),
+ DwSize = Marshal.SizeOf(),
ThreadType = 2, // DQTYPE_THREAD_CURRENT
ApartmentType = 2, // DQTAT_COM_STA
};
@@ -136,7 +129,6 @@ public class SystemBackdrop
[In] DispatcherQueueOptions options,
[In, Out, MarshalAs(UnmanagedType.IUnknown)] ref object? dispatcherQueueController);
- [StructLayout(LayoutKind.Sequential)]
private struct DispatcherQueueOptions
{
internal int DwSize;
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclassManager.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclassManager.cs
index 26821782..29d4a3d6 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclassManager.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclassManager.cs
@@ -33,6 +33,7 @@ internal class WindowSubclassManager : IDisposable
/// 是否为经典标题栏区域
public WindowSubclassManager(HWND hwnd, bool isLegacyDragBar)
{
+ Must.NotNull(hwnd);
this.hwnd = hwnd;
this.isLegacyDragBar = isLegacyDragBar;
}
@@ -44,20 +45,23 @@ internal class WindowSubclassManager : IDisposable
public bool TrySetWindowSubclass()
{
windowProc = new(OnSubclassProcedure);
- bool minSize = SetWindowSubclass(hwnd, windowProc, WindowSubclassId, 0);
+ bool windowHooked = SetWindowSubclass(hwnd, windowProc, WindowSubclassId, 0);
- bool hideSystemMenu = true;
+ bool titleBarHooked = true;
// only hook up drag bar proc when use legacy Window.ExtendsContentIntoTitleBar
if (isLegacyDragBar)
{
- dragBarProc = new(OnDragBarProcedure);
- hwndDragBar = FindWindowEx(hwnd, HWND.Zero, "DRAG_BAR_WINDOW_CLASS", string.Empty);
+ hwndDragBar = FindWindowEx(hwnd, default, "DRAG_BAR_WINDOW_CLASS", string.Empty);
- hideSystemMenu = SetWindowSubclass(hwndDragBar, dragBarProc, DragBarSubclassId, 0);
+ if (!hwndDragBar.IsNull)
+ {
+ dragBarProc = new(OnDragBarProcedure);
+ titleBarHooked = SetWindowSubclass(hwndDragBar, dragBarProc, DragBarSubclassId, 0);
+ }
}
- return minSize && hideSystemMenu;
+ return windowHooked && titleBarHooked;
}
///
@@ -79,7 +83,7 @@ internal class WindowSubclassManager : IDisposable
{
case WM_GETMINMAXINFO:
{
- float scalingFactor = (float)Persistence.GetScaleForWindow(hwnd);
+ double scalingFactor = Persistence.GetScaleForWindow(hwnd);
Win32.Unsafe.SetMinTrackSize(lParam, MinWidth * scalingFactor, MinHeight * scalingFactor);
break;
}
@@ -87,7 +91,7 @@ internal class WindowSubclassManager : IDisposable
case WM_NCRBUTTONDOWN:
case WM_NCRBUTTONUP:
{
- return (LRESULT)IntPtr.Zero;
+ return new(0);
}
}
@@ -101,7 +105,7 @@ internal class WindowSubclassManager : IDisposable
case WM_NCRBUTTONDOWN:
case WM_NCRBUTTONUP:
{
- return (LRESULT)IntPtr.Zero;
+ return new(0);
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/IocHttpClientConfiguration.cs b/src/Snap.Hutao/Snap.Hutao/IocHttpClientConfiguration.cs
deleted file mode 100644
index 0e18fd63..00000000
--- a/src/Snap.Hutao/Snap.Hutao/IocHttpClientConfiguration.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (c) DGP Studio. All rights reserved.
-// Licensed under the MIT license.
-
-using Microsoft.Extensions.DependencyInjection;
-using Snap.Hutao.Core.Caching;
-using Snap.Hutao.Service.Metadata;
-using Snap.Hutao.Web.Enka;
-using Snap.Hutao.Web.Hoyolab.Bbs.User;
-using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
-using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
-using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
-using Snap.Hutao.Web.Hutao;
-using System.Net.Http;
-
-namespace Snap.Hutao;
-
-///
-/// 与 配置
-///
-internal static class IocHttpClientConfiguration
-{
- private static readonly string CommonUA = $"Snap Hutao/{Core.CoreEnvironment.Version}";
-
- ///
- /// 添加
- ///
- /// 集合
- /// 可继续操作的集合
- public static IServiceCollection AddHttpClients(this IServiceCollection services)
- {
- // services
- services.AddHttpClient(DefaultConfiguration);
- services.AddHttpClient(DefaultConfiguration)
- .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() { MaxConnectionsPerServer = 20 });
-
- // normal clients
- services.AddHttpClient(DefaultConfiguration);
- services.AddHttpClient(DefaultConfiguration);
- services.AddHttpClient(DefaultConfiguration);
- services.AddHttpClient(DefaultConfiguration);
-
- // x-rpc clients
- services.AddHttpClient(XRpcConfiguration);
- services.AddHttpClient(XRpcConfiguration);
-
- return services;
- }
-
- ///
- /// 默认配置
- ///
- /// 配置后的客户端
- private static void DefaultConfiguration(this HttpClient client)
- {
- client.Timeout = Timeout.InfiniteTimeSpan;
- client.DefaultRequestHeaders.UserAgent.ParseAdd(CommonUA);
- }
-
- ///
- /// 对于需要添加动态密钥的客户端使用此配置
- ///
- /// 配置后的客户端
- private static void XRpcConfiguration(this HttpClient client)
- {
- client.DefaultConfiguration();
- client.DefaultRequestHeaders.Add("x-rpc-app_version", "2.30.1");
- client.DefaultRequestHeaders.Add("x-rpc-client_type", "5");
- }
-}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml
index d5036a55..7c525ec4 100644
--- a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml
@@ -13,6 +13,7 @@
Margin="48,0,0,0"
Height="44"
x:Name="TitleBarView"/>
+
diff --git a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs
index 3ce2257e..d2f7be1a 100644
--- a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs
@@ -16,6 +16,8 @@ public sealed partial class MainWindow : Window
private readonly AppDbContext appDbContext;
private readonly WindowManager windowManager;
+ private readonly TaskCompletionSource initializaionCompletionSource = new();
+
///
/// 构造一个新的主窗体
///
@@ -26,8 +28,12 @@ public sealed partial class MainWindow : Window
this.appDbContext = appDbContext;
InitializeComponent();
windowManager = new WindowManager(this, TitleBarView.DragableArea);
+
+ initializaionCompletionSource.TrySetResult();
}
+
+
private void MainWindowClosed(object sender, WindowEventArgs args)
{
windowManager?.Dispose();
diff --git a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
index 0457ea18..ce80236f 100644
--- a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
+++ b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
@@ -9,7 +9,7 @@
+ Version="1.0.26.0" />
胡桃
@@ -18,8 +18,8 @@
-
-
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IInfoBarService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IInfoBarService.cs
index 86d35622..3d8f8210 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IInfoBarService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IInfoBarService.cs
@@ -76,6 +76,13 @@ public interface IInfoBarService
/// 关闭延迟
void Success(string title, string message, int delay = 5000);
+ ///
+ /// 异步等待加载完成
+ ///
+ /// 取消令牌
+ /// 任务
+ Task WaitInitializationAsync(CancellationToken token = default);
+
///
/// 显示警告信息
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs b/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs
index 718d05da..ad62f22e 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs
@@ -10,12 +10,25 @@ namespace Snap.Hutao.Service;
[Injection(InjectAs.Singleton, typeof(IInfoBarService))]
internal class InfoBarService : IInfoBarService
{
+ private readonly TaskCompletionSource initializaionCompletionSource = new();
private StackPanel? infoBarStack;
+
///
public void Initialize(StackPanel container)
{
infoBarStack = container;
+ initializaionCompletionSource.TrySetResult();
+ }
+
+ ///
+ /// 异步等待主窗体加载完成
+ ///
+ /// 取消令牌
+ /// 任务
+ public Task WaitInitializationAsync(CancellationToken token = default)
+ {
+ return initializaionCompletionSource.Task;
}
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataInitializer.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataInitializer.cs
index 1427fbbc..72eec155 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataInitializer.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataInitializer.cs
@@ -1,6 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
+using Snap.Hutao.Core.Abstraction;
+
namespace Snap.Hutao.Service.Metadata;
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs
index 331984de..4e3a7fe5 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs
@@ -4,6 +4,7 @@
using Microsoft.Extensions.Caching.Memory;
using Snap.Hutao.Context.FileSystem;
using Snap.Hutao.Core.Abstraction;
+using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Core.Diagnostics;
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Model.Metadata.Achievement;
@@ -23,6 +24,7 @@ namespace Snap.Hutao.Service.Metadata;
/// 元数据服务
///
[Injection(InjectAs.Singleton, typeof(IMetadataService))]
+[HttpClient(HttpClientConfigration.Default)]
internal class MetadataService : IMetadataService, IMetadataInitializer, ISupportAsyncInitialization
{
private const string MetaAPIHost = "http://hutao-metadata.snapgenshin.com";
@@ -46,25 +48,25 @@ internal class MetadataService : IMetadataService, IMetadataInitializer, ISuppor
/// 构造一个新的元数据服务
///
/// 信息条服务
- /// http客户端
+ /// http客户端工厂
/// 我的文档上下文
/// json序列化选项
/// 日志器
/// 内存缓存
public MetadataService(
IInfoBarService infoBarService,
- HttpClient httpClient,
+ IHttpClientFactory httpClientFactory,
MetadataContext metadataContext,
JsonSerializerOptions options,
ILogger logger,
IMemoryCache memoryCache)
{
this.infoBarService = infoBarService;
- this.httpClient = httpClient;
this.metadataContext = metadataContext;
this.options = options;
this.logger = logger;
this.memoryCache = memoryCache;
+ httpClient = httpClientFactory.CreateClient(nameof(MetadataService));
}
///
@@ -73,63 +75,63 @@ internal class MetadataService : IMetadataService, IMetadataInitializer, ISuppor
///
public async ValueTask InitializeAsync(CancellationToken token = default)
{
- await initializeCompletionSource.Task;
+ await initializeCompletionSource.Task.ConfigureAwait(false);
return IsInitialized;
}
///
public async Task InitializeInternalAsync(CancellationToken token = default)
{
- logger.LogInformation(EventIds.MetadataInitialization, "Metadata initializaion begin");
ValueStopwatch stopwatch = ValueStopwatch.StartNew();
+ logger.LogInformation(EventIds.MetadataInitialization, "Metadata initializaion begin");
+
IsInitialized = await TryUpdateMetadataAsync(token)
.ConfigureAwait(false);
initializeCompletionSource.SetResult();
-
- logger.LogInformation(EventIds.MetadataInitialization, "Metadata initializaion completed");
+ logger.LogInformation(EventIds.MetadataInitialization, "Metadata initializaion completed in {time}ms", stopwatch.GetElapsedTime().TotalMilliseconds);
}
///
public ValueTask> GetAchievementGoalsAsync(CancellationToken token = default)
{
- return GetMetadataAsync>("AchievementGoal", token);
+ return FromCacheOrFileAsync>("AchievementGoal", token);
}
///
public ValueTask> GetAchievementsAsync(CancellationToken token = default)
{
- return GetMetadataAsync>("Achievement", token);
+ return FromCacheOrFileAsync>("Achievement", token);
}
///
public ValueTask> GetAvatarsAsync(CancellationToken token = default)
{
- return GetMetadataAsync>("Avatar", token);
+ return FromCacheOrFileAsync>("Avatar", token);
}
///
public ValueTask> GetReliquariesAsync(CancellationToken token = default)
{
- return GetMetadataAsync>("Reliquary", token);
+ return FromCacheOrFileAsync>("Reliquary", token);
}
///
public ValueTask> GetReliquaryAffixesAsync(CancellationToken token = default)
{
- return GetMetadataAsync>("ReliquaryAffix", token);
+ return FromCacheOrFileAsync>("ReliquaryAffix", token);
}
///
public ValueTask> GetReliquaryMainAffixesAsync(CancellationToken token = default)
{
- return GetMetadataAsync>("ReliquaryMainAffix", token);
+ return FromCacheOrFileAsync>("ReliquaryMainAffix", token);
}
///
public ValueTask> GetWeaponsAsync(CancellationToken token = default)
{
- return GetMetadataAsync>("Weapon", token);
+ return FromCacheOrFileAsync>("Weapon", token);
}
private async Task TryUpdateMetadataAsync(CancellationToken token = default)
@@ -171,20 +173,18 @@ internal class MetadataService : IMetadataService, IMetadataInitializer, ISuppor
{
(string fileName, string md5) = pair;
string fileFullName = $"{fileName}.json";
- bool skip = false;
+ bool skip = false;
if (metadataContext.FileExists(fileFullName))
{
- skip = md5 == await GetFileMd5Async(fileFullName, token)
- .ConfigureAwait(false);
+ skip = md5 == await GetFileMd5Async(fileFullName, token).ConfigureAwait(false);
}
if (!skip)
{
- logger.LogInformation(EventIds.MetadataFileMD5Check, "MD5 of {file} not matched", fileFullName);
+ logger.LogInformation(EventIds.MetadataFileMD5Check, "MD5 of {file} not matched, begin downloading", fileFullName);
- await DownloadMetadataAsync(fileFullName, token)
- .ConfigureAwait(false);
+ await DownloadMetadataAsync(fileFullName, token).ConfigureAwait(false);
}
});
}
@@ -214,8 +214,8 @@ internal class MetadataService : IMetadataService, IMetadataInitializer, ISuppor
{
while (await streamReader.ReadLineAsync().ConfigureAwait(false) is string line)
{
- Func writeMethod = streamReader.EndOfStream ? streamWriter.WriteAsync : streamWriter.WriteLineAsync;
- await writeMethod(line).ConfigureAwait(false);
+ Func write = streamReader.EndOfStream ? streamWriter.WriteAsync : streamWriter.WriteLineAsync;
+ await write(line).ConfigureAwait(false);
}
}
}
@@ -223,7 +223,7 @@ internal class MetadataService : IMetadataService, IMetadataInitializer, ISuppor
logger.LogInformation("Download {file} completed", fileFullName);
}
- private async ValueTask GetMetadataAsync(string fileName, CancellationToken token)
+ private async ValueTask FromCacheOrFileAsync(string fileName, CancellationToken token)
where T : class
{
Verify.Operation(IsInitialized, "元数据服务尚未初始化,或初始化失败");
@@ -234,10 +234,10 @@ internal class MetadataService : IMetadataService, IMetadataInitializer, ISuppor
return Must.NotNull((value as T)!);
}
- T? result = await JsonSerializer
- .DeserializeAsync(metadataContext.OpenRead($"{fileName}.json"), options, token)
- .ConfigureAwait(false);
-
- return memoryCache.Set(cacheKey, Must.NotNull(result!));
+ using (Stream fileStream = metadataContext.OpenRead($"{fileName}.json"))
+ {
+ T? result = await JsonSerializer.DeserializeAsync(fileStream, options, token).ConfigureAwait(false);
+ return memoryCache.Set(cacheKey, Must.NotNull(result!));
+ }
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
index b5d861dc..f60bf593 100644
--- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
+++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
@@ -24,7 +24,13 @@
Never
0
Snap.Hutao.Program
- DISABLE_XAML_GENERATED_MAIN;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;DISABLE_XAML_GENERATED_BINDING_DEBUG_OUTPUT
+ $(DefineConstants);DISABLE_XAML_GENERATED_MAIN;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;DISABLE_XAML_GENERATED_BINDING_DEBUG_OUTPUT
+
+
+ embedded
+
+
+ embedded
diff --git a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml
index 0ffb7ebb..349e2f87 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml
@@ -16,9 +16,8 @@
@@ -37,9 +36,9 @@
shvh:NavHelper.NavigateTo="shvp:WikiAvatarPage"
Icon="{cwu:BitmapIcon ShowAsMonochrome=True,Source=ms-appx:///Resource/Icon/UI_BagTabIcon_Avatar.png}"/>
-
+
-
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs
index 65581190..9cc4db1e 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs
@@ -3,6 +3,7 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
+using Snap.Hutao.Core;
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Navigation;
@@ -27,7 +28,8 @@ public sealed partial class MainView : UserControl
{
InitializeComponent();
- uISettings = new();
+ // 由于 PopupRoot 的 BUG, 需要手动响应主题色更改
+ uISettings = Ioc.Default.GetRequiredService();
uISettings.ColorValuesChanged += OnUISettingsColorValuesChanged;
infoBarService = Ioc.Default.GetRequiredService();
@@ -46,20 +48,13 @@ public sealed partial class MainView : UserControl
private void UpdateTheme()
{
- if (RequestedTheme.ToString() == App.Current.RequestedTheme.ToString())
+ if (!ThemeHelper.Equals(App.Current.RequestedTheme, RequestedTheme))
{
- return;
+ ILogger logger = Ioc.Default.GetRequiredService>();
+ logger.LogInformation(EventIds.CommonLog, "Element Theme [{element}] App Theme [{app}]", RequestedTheme, App.Current.RequestedTheme);
+
+ // Update controls' theme which presents in the PopupRoot
+ RequestedTheme = ThemeHelper.ApplicationToElement(App.Current.RequestedTheme);
}
-
- ILogger logger = Ioc.Default.GetRequiredService>();
- logger.LogInformation(EventIds.CommonLog, "Element Theme [{element}] App Theme [{app}]", RequestedTheme, App.Current.RequestedTheme);
-
- // Update controls' theme which presents in the PopupRoot
- RequestedTheme = App.Current.RequestedTheme switch
- {
- ApplicationTheme.Light => ElementTheme.Light,
- ApplicationTheme.Dark => ElementTheme.Dark,
- _ => throw Must.NeverHappen(),
- };
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml
index d1b39138..2b45b625 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml
@@ -41,7 +41,8 @@
+ Header="打开 数据 文件夹"
+ Description="用户数据/日志/元数据在此处存放">
@@ -49,11 +50,21 @@
+ Header="打开 缓存 文件夹"
+ Description="图片缓存在此处存放">
+
+
+
+
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml.cs
index b81bd4bb..1e3da3b4 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml.cs
@@ -13,8 +13,6 @@ namespace Snap.Hutao.View;
///
public sealed partial class UserView : UserControl
{
- private static readonly DependencyProperty IsExpandedProperty = Property.Depend(nameof(IsExpanded), true);
-
///
/// 构造一个新的用户视图
///
@@ -23,13 +21,4 @@ public sealed partial class UserView : UserControl
InitializeComponent();
DataContext = Ioc.Default.GetRequiredService();
}
-
- ///
- /// 当前用户控件是否处于展开状态
- ///
- public bool IsExpanded
- {
- get => (bool)GetValue(IsExpandedProperty);
- set => SetValue(IsExpandedProperty, value);
- }
}
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/ExperimentalFeaturesViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/ExperimentalFeaturesViewModel.cs
index 0c64aa1e..4dd8d39c 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/ExperimentalFeaturesViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/ExperimentalFeaturesViewModel.cs
@@ -4,6 +4,10 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Context.FileSystem.Location;
using Snap.Hutao.Factory.Abstraction;
+using Snap.Hutao.Service.Abstraction;
+using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
+using Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
+using Snap.Hutao.Web.Response;
using Windows.System;
namespace Snap.Hutao.ViewModel;
@@ -15,18 +19,33 @@ namespace Snap.Hutao.ViewModel;
internal class ExperimentalFeaturesViewModel : ObservableObject
{
private readonly IFileSystemLocation hutaoLocation;
+ private readonly IUserService userService;
+ private readonly SignClient signClient;
+ private readonly IInfoBarService infoBarService;
///
/// 构造一个新的实验性功能视图模型
///
/// 异步命令工厂
/// 数据文件夹
- public ExperimentalFeaturesViewModel(IAsyncRelayCommandFactory asyncRelayCommandFactory, HutaoLocation hutaoLocation)
+ /// 用户服务
+ /// 签到客户端
+ /// 信息栏服务
+ public ExperimentalFeaturesViewModel(
+ IAsyncRelayCommandFactory asyncRelayCommandFactory,
+ HutaoLocation hutaoLocation,
+ IUserService userService,
+ SignClient signClient,
+ IInfoBarService infoBarService)
{
this.hutaoLocation = hutaoLocation;
+ this.userService = userService;
+ this.signClient = signClient;
+ this.infoBarService = infoBarService;
OpenCacheFolderCommand = asyncRelayCommandFactory.Create(OpenCacheFolderAsync);
OpenDataFolderCommand = asyncRelayCommandFactory.Create(OpenDataFolderAsync);
+ SignAllUserGameRolesCommand = asyncRelayCommandFactory.Create(SignAllUserGameRolesAsync);
}
///
@@ -39,13 +58,35 @@ internal class ExperimentalFeaturesViewModel : ObservableObject
///
public ICommand OpenDataFolderCommand { get; }
- private Task OpenCacheFolderAsync(CancellationToken token)
+ ///
+ /// 签到全部角色命令
+ ///
+ public ICommand SignAllUserGameRolesCommand { get; }
+
+ private Task OpenCacheFolderAsync()
{
- return Launcher.LaunchFolderAsync(App.CacheFolder).AsTask(token);
+ return Launcher.LaunchFolderAsync(App.CacheFolder).AsTask();
}
- private Task OpenDataFolderAsync(CancellationToken token)
+ private Task OpenDataFolderAsync()
{
- return Launcher.LaunchFolderPathAsync(hutaoLocation.GetPath()).AsTask(token);
+ return Launcher.LaunchFolderPathAsync(hutaoLocation.GetPath()).AsTask();
+ }
+
+ private async Task SignAllUserGameRolesAsync()
+ {
+ foreach (Model.Binding.User user in await userService.GetUserCollectionAsync())
+ {
+ foreach (UserGameRole role in user.UserGameRoles)
+ {
+ Response? result = await signClient.SignAsync(user, role);
+ if (result != null)
+ {
+ infoBarService.Information(result.Message);
+ }
+
+ await Task.Delay(TimeSpan.FromSeconds(15));
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Enka/EnkaClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Enka/EnkaClient.cs
index d24f964f..86280e20 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Enka/EnkaClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Enka/EnkaClient.cs
@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
+using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Web.Enka.Model;
using Snap.Hutao.Web.Hoyolab;
using System.Net.Http;
@@ -11,7 +12,7 @@ namespace Snap.Hutao.Web.Enka;
///
/// Enka API 客户端
///
-[Injection(InjectAs.Transient)]
+[HttpClient(HttpClientConfigration.Default)]
internal class EnkaClient
{
private const string EnkaAPI = "https://enka.shinshin.moe/u/{0}/__data.json";
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ApiEndpoints.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ApiEndpoints.cs
index a6424c6c..d0450e9d 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ApiEndpoints.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ApiEndpoints.cs
@@ -19,6 +19,11 @@ internal static class ApiEndpoints
///
public const string AnnContent = $"{Hk4eApi}/common/hk4e_cn/announcement/api/getAnnContent?{AnnouncementQuery}";
+ ///
+ /// 角色信息
+ ///
+ public const string GameRecordCharacter = $"{ApiTakumiRecordApi}/character";
+
///
/// 游戏记录主页
///
@@ -33,17 +38,53 @@ internal static class ApiEndpoints
///
/// 深渊信息
///
- public const string SpiralAbyss = $"{ApiTakumiRecordApi}/spiralAbyss?schedule_type={{0}}&role_id={{1}}&server={{2}}";
+ /// 深渊类型
+ /// Uid
+ /// 深渊信息字符串
+ public static string GameRecordSpiralAbyss(Takumi.GameRecord.SpiralAbyssSchedule scheduleType, PlayerUid uid)
+ {
+ return $"{ApiTakumiRecordApi}/spiralAbyss?schedule_type={(int)scheduleType}&role_id={uid.Value}&server={uid.Region}";
+ }
///
- /// 角色信息
+ /// 签到活动Id
///
- public const string Character = $"{ApiTakumiRecordApi}/character";
+ public const string SignInRewardActivityId = "e202009291139501";
///
- /// 用户游戏角色
+ /// 签到
///
- public const string UserGameRoles = $"{ApiTakumi}/binding/api/getUserGameRolesByCookie?game_biz=hk4e_cn";
+ public const string SignInRewardHome = $"{ApiTakumi}/event/bbs_sign_reward/home?act_id={SignInRewardActivityId}";
+
+ ///
+ /// 签到信息
+ ///
+ /// uid
+ /// 签到信息字符串
+ public static string SignInRewardInfo(PlayerUid uid)
+ {
+ return $"{ApiTakumi}/event/bbs_sign_reward/info?act_id={SignInRewardActivityId}®ion={uid.Region}&uid={uid.Value}";
+ }
+
+ ///
+ /// 签到
+ ///
+ public const string SignInRewardReSign = $"{ApiTakumi}/event/bbs_sign_reward/resign";
+
+ ///
+ /// 补签信息
+ ///
+ /// uid
+ /// 补签信息字符串
+ public static string SignInRewardResignInfo(PlayerUid uid)
+ {
+ return $"{ApiTakumi}/event/bbs_sign_reward/resign_info?act_id=e202009291139501®ion={uid.Region}&uid={uid.Value}";
+ }
+
+ ///
+ /// 签到
+ ///
+ public const string SignInRewardSign = $"{ApiTakumi}/event/bbs_sign_reward/sign";
///
/// 用户详细信息
@@ -53,8 +94,19 @@ internal static class ApiEndpoints
///
/// 查询其他用户详细信息
///
- public const string UserFullInfoQuery = $"{BbsApiUserApi}/getUserFullInfo?uid={{0}}&gids=2";
+ /// bbs Uid
+ /// 查询其他用户详细信息字符串
+ public static string UserFullInfoQuery(string bbsUid)
+ {
+ return $"{BbsApiUserApi}/getUserFullInfo?uid={bbsUid}&gids=2";
+ }
+ ///
+ /// 用户游戏角色
+ ///
+ public const string UserGameRoles = $"{ApiTakumi}/binding/api/getUserGameRolesByCookie?game_biz=hk4e_cn";
+
+ // consts
private const string ApiTakumi = "https://api-takumi.mihoyo.com";
private const string ApiTakumiRecord = "https://api-takumi-record.mihoyo.com";
private const string ApiTakumiRecordApi = $"{ApiTakumiRecord}/game_record/app/genshin/api";
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient.cs
index 86a04145..aa57db0d 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient.cs
@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
+using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
using Snap.Hutao.Web.Response;
using System.Net.Http;
@@ -11,7 +12,7 @@ namespace Snap.Hutao.Web.Hoyolab.Bbs.User;
///
/// 用户信息客户端
///
-[Injection(InjectAs.Transient)]
+[HttpClient(HttpClientConfigration.XRpc)]
internal class UserClient
{
private readonly HttpClient httpClient;
@@ -20,7 +21,6 @@ internal class UserClient
///
/// 构造一个新的用户信息客户端
///
- /// 用户服务
/// http客户端
/// Json序列化选项
public UserClient(HttpClient httpClient, JsonSerializerOptions jsonSerializerOptions)
@@ -47,17 +47,18 @@ internal class UserClient
}
///
- /// 获取当前用户详细信息
+ /// 获取其他用户详细信息
///
+ /// 当前用户
/// 米游社Uid
/// 取消令牌
/// 详细信息
- public async Task GetUserFullInfoAsync(string uid, CancellationToken token = default)
+ public async Task GetUserFullInfoAsync(Model.Binding.User user, string uid, CancellationToken token = default)
{
Response? resp = await httpClient
.UsingDynamicSecret()
- /*.SetUser(userService.CurrentUser)*/
- .GetFromJsonAsync>(string.Format(ApiEndpoints.UserFullInfoQuery, uid), jsonSerializerOptions, token)
+ .SetUser(user)
+ .GetFromJsonAsync>(ApiEndpoints.UserFullInfoQuery(uid), jsonSerializerOptions, token)
.ConfigureAwait(false);
return resp?.Data?.UserInfo;
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider.cs
index d4eb034e..219b90f0 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider.cs
@@ -11,9 +11,6 @@ namespace Snap.Hutao.Web.Hoyolab.DynamicSecret;
///
internal abstract class DynamicSecretProvider : Md5Convert
{
- // @Azure99 respect original author
- private static readonly string Salt = "4a8knnbk5pbjqsrudp3dq484m9axoc5g";
-
///
/// 创建动态密钥
///
@@ -22,8 +19,10 @@ internal abstract class DynamicSecretProvider : Md5Convert
{
// unix timestamp
long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
+
string r = GetRandomString();
- string check = ToHexString($"salt={Salt}&t={t}&r={r}");
+
+ string check = ToHexString($"salt={Core.CoreEnvironment.DynamicSecret1Salt}&t={t}&r={r}").ToLowerInvariant();
return $"{t},{r},{check}";
}
@@ -31,22 +30,17 @@ internal abstract class DynamicSecretProvider : Md5Convert
private static string GetRandomString()
{
StringBuilder sb = new(6);
- Random random = new();
for (int i = 0; i < 6; i++)
{
- int offset = random.Next(0, 32768) % 26;
-
- // 实际上只能取到前16个小写字母
- int target = 'a' - 10;
-
- // 取数字
- if (offset < 10)
+ int v8 = Random.Shared.Next(0, 32768) % 26;
+ int v9 = 87;
+ if (v8 < 10)
{
- target = '0';
+ v9 = 48;
}
- _ = sb.Append((char)(offset + target));
+ _ = sb.Append((char)(v8 + v9));
}
return sb.ToString();
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider2.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider2.cs
index f6ce4d12..9f605926 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider2.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretProvider2.cs
@@ -11,19 +11,6 @@ namespace Snap.Hutao.Web.Hoyolab.DynamicSecret;
///
internal abstract class DynamicSecretProvider2 : Md5Convert
{
- ///
- /// salt
- ///
- public const string AppVersion = "2.34.1";
-
- ///
- /// 米游社的盐
- /// 计算过程:https://gist.github.com/Lightczx/373c5940b36e24b25362728b52dec4fd
- /// libxxxx.so
- ///
- private static readonly string Salt = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs";
- private static readonly Random Random = new();
-
///
/// 创建动态密钥
///
@@ -43,15 +30,10 @@ internal abstract class DynamicSecretProvider2 : Md5Convert
string b = postBody is null ? string.Empty : JsonSerializer.Serialize(postBody, options);
// query
- string q = string.Empty;
- string? query = new UriBuilder(queryUrl).Query;
- if (!string.IsNullOrEmpty(query))
- {
- q = string.Join("&", query.Split('&').OrderBy(x => x));
- }
+ string q = string.Join("&", new UriBuilder(queryUrl).Query.Split('&').OrderBy(x => x));
// check
- string check = ToHexString($"salt={Salt}&t={t}&r={r}&b={b}&q={q}");
+ string check = ToHexString($"salt={Core.CoreEnvironment.DynamicSecret2Salt}&t={t}&r={r}&b={b}&q={q}").ToLowerInvariant();
return $"{t},{r},{check}";
}
@@ -66,7 +48,7 @@ internal abstract class DynamicSecretProvider2 : Md5Convert
// v18 = v17;
// else
// v18 = v17 + 542367;
- int rand = Random.Next(100000, 200000);
+ int rand = Random.Shared.Next(100000, 200000);
if (rand == 100000)
{
rand = 642367;
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/Http/DynamicSecret2HttpClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/Http/DynamicSecret2HttpClient.cs
new file mode 100644
index 00000000..9f4bd3fa
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/Http/DynamicSecret2HttpClient.cs
@@ -0,0 +1,77 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Net.Http;
+using System.Net.Http.Json;
+
+namespace Snap.Hutao.Web.Hoyolab.DynamicSecret.Http;
+
+///
+/// 使用动态密钥2的Http客户端默认实现
+///
+/// 请求提交的数据的的格式
+internal class DynamicSecret2HttpClient : IDynamicSecret2HttpClient
+{
+ private readonly HttpClient httpClient;
+ private readonly JsonSerializerOptions options;
+ private readonly string url;
+
+ ///
+ /// 构造一个新的使用动态密钥2的Http客户端默认实现的实例
+ ///
+ /// 请求使用的客户端
+ /// Json序列化选项
+ /// url
+ /// 请求的数据
+ public DynamicSecret2HttpClient(HttpClient httpClient, JsonSerializerOptions options, string url)
+ {
+ this.httpClient = httpClient;
+ this.options = options;
+ this.url = url;
+
+ httpClient.DefaultRequestHeaders.Set("DS", DynamicSecretProvider2.Create(options, url, null));
+ }
+
+ ///
+ public Task GetFromJsonAsync(CancellationToken token)
+ {
+ return httpClient.GetFromJsonAsync(url, options, token);
+ }
+}
+
+///
+/// 使用动态密钥2的Http客户端默认实现
+///
+/// 请求提交的数据的的格式
+[SuppressMessage("", "SA1402")]
+internal class DynamicSecret2HttpClient : IDynamicSecret2HttpClient
+ where TValue : class
+{
+ private readonly HttpClient httpClient;
+ private readonly JsonSerializerOptions options;
+ private readonly string url;
+ private readonly TValue? data = null;
+
+ ///
+ /// 构造一个新的使用动态密钥2的Http客户端默认实现的实例
+ ///
+ /// 请求使用的客户端
+ /// Json序列化选项
+ /// url
+ /// 请求的数据
+ public DynamicSecret2HttpClient(HttpClient httpClient, JsonSerializerOptions options, string url, TValue? data)
+ {
+ this.httpClient = httpClient;
+ this.options = options;
+ this.url = url;
+ this.data = data;
+
+ httpClient.DefaultRequestHeaders.Set("DS", DynamicSecretProvider2.Create(options, url, data));
+ }
+
+ ///
+ public Task PostAsJsonAsync(CancellationToken token)
+ {
+ return httpClient.PostAsJsonAsync(url, data, options, token);
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/Http/IDynamicSecret2HttpClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/Http/IDynamicSecret2HttpClient.cs
new file mode 100644
index 00000000..ddfe3376
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/Http/IDynamicSecret2HttpClient.cs
@@ -0,0 +1,35 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Net.Http;
+
+namespace Snap.Hutao.Web.Hoyolab.DynamicSecret.Http;
+
+///
+/// 使用动态密钥2的Http客户端抽象
+///
+internal interface IDynamicSecret2HttpClient
+{
+ ///
+ /// Sends a GET request to the specified Uri and returns the value that results from deserializing the response body as JSON in an asynchronous operation.
+ ///
+ /// The target type to deserialize to.
+ /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
+ /// The task object representing the asynchronous operation.
+ Task GetFromJsonAsync(CancellationToken token);
+}
+
+///
+/// 使用动态密钥2的Http客户端抽象
+///
+/// 请求数据的类型
+internal interface IDynamicSecret2HttpClient
+ where TValue : class
+{
+ ///
+ /// Sends a POST request to the specified Uri containing the value serialized as JSON in the request body.
+ ///
+ /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
+ /// The task object representing the asynchronous operation.
+ Task PostAsJsonAsync(CancellationToken token);
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/HttpClientDynamicSecretExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/HttpClientDynamicSecretExtensions.cs
index 26206390..41f16e28 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/HttpClientDynamicSecretExtensions.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/HttpClientDynamicSecretExtensions.cs
@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
+using Snap.Hutao.Web.Hoyolab.DynamicSecret.Http;
using System.Net.Http;
namespace Snap.Hutao.Web.Hoyolab.DynamicSecret;
@@ -29,9 +30,24 @@ internal static class HttpClientDynamicSecretExtensions
/// 地址
/// post数据
/// 响应
- public static HttpClient UsingDynamicSecret2(this HttpClient httpClient, JsonSerializerOptions options, string url, object? data = null)
+ public static IDynamicSecret2HttpClient UsingDynamicSecret2(this HttpClient httpClient, JsonSerializerOptions options, string url)
+ {
+ return new DynamicSecret2HttpClient(httpClient, options, url);
+ }
+
+ ///
+ /// 使用二代动态密钥执行 GET 操作
+ ///
+ /// 请求数据的类型
+ /// 请求器
+ /// 选项
+ /// 地址
+ /// post数据
+ /// 响应
+ public static IDynamicSecret2HttpClient UsingDynamicSecret2(this HttpClient httpClient, JsonSerializerOptions options, string url, TValue data)
+ where TValue : class
{
httpClient.DefaultRequestHeaders.Set("DS", DynamicSecretProvider2.Create(options, url, data));
- return httpClient;
+ return new DynamicSecret2HttpClient(httpClient, options, url, data);
}
-}
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementClient.cs
index d677fdef..aaa24901 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementClient.cs
@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
+using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Extension;
using Snap.Hutao.Web.Response;
using System.Collections.Generic;
@@ -12,7 +13,7 @@ namespace Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
///
/// 公告客户端
///
-[Injection(InjectAs.Transient)]
+[HttpClient(HttpClientConfigration.Default)]
internal class AnnouncementClient
{
private readonly HttpClient httpClient;
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRole.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRole.cs
index e0ca6238..a57f0fc0 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRole.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRole.cs
@@ -8,7 +8,7 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.Binding;
///
/// 用户游戏角色
///
-public record UserGameRole
+public class UserGameRole
{
///
/// hk4e_cn for Genshin Impact
@@ -68,16 +68,7 @@ public record UserGameRole
public static explicit operator PlayerUid(UserGameRole userGameRole)
{
- return userGameRole.AsPlayerUid();
- }
-
- ///
- /// 转化为
- ///
- /// 一个等价的 实例
- public PlayerUid AsPlayerUid()
- {
- return new PlayerUid(GameUid, Region);
+ return new PlayerUid(userGameRole.GameUid, userGameRole.Region);
}
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRoleClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRoleClient.cs
index 6464e9c2..b156e8cd 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRoleClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRoleClient.cs
@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
+using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Extension;
using Snap.Hutao.Model.Binding;
using Snap.Hutao.Web.Response;
@@ -13,7 +14,7 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.Binding;
///
/// 用户游戏角色提供器
///
-[Injection(InjectAs.Transient)]
+[HttpClient(HttpClientConfigration.Default)]
internal class UserGameRoleClient
{
private readonly HttpClient httpClient;
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/Award.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/Award.cs
new file mode 100644
index 00000000..1cb80951
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/Award.cs
@@ -0,0 +1,30 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
+
+///
+/// 奖励物品
+///
+public class Award
+{
+ ///
+ /// 图标
+ ///
+ [JsonPropertyName("icon")]
+ public string Icon { get; set; } = default!;
+
+ ///
+ /// 名称
+ ///
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = default!;
+
+ ///
+ /// 个数
+ ///
+ [JsonPropertyName("cnt")]
+ public string Count { get; set; } = default!;
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/Reward.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/Reward.cs
new file mode 100644
index 00000000..416a7365
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/Reward.cs
@@ -0,0 +1,28 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
+
+public class Reward
+{
+ ///
+ /// 月份
+ ///
+ [JsonPropertyName("month")]
+ public string? Month { get; set; }
+
+ ///
+ /// 奖励列表
+ ///
+ [JsonPropertyName("awards")]
+ public List? Awards { get; set; }
+
+ ///
+ /// 支持补签
+ ///
+ [JsonPropertyName("resign")]
+ public bool Resign { get; set; }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignClient.cs
new file mode 100644
index 00000000..d7cc6e25
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignClient.cs
@@ -0,0 +1,129 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
+using Snap.Hutao.Model.Binding;
+using Snap.Hutao.Web.Hoyolab.DynamicSecret;
+using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
+using Snap.Hutao.Web.Response;
+using System.Net.Http;
+using System.Net.Http.Json;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
+
+///
+/// 签到客户端
+///
+[HttpClient(HttpClientConfigration.XRpc)]
+internal class SignClient
+{
+ private readonly HttpClient httpClient;
+ private readonly JsonSerializerOptions options;
+
+ ///
+ /// 构造一个新的签到客户端
+ ///
+ /// http客户端
+ /// 选项
+ public SignClient(HttpClient httpClient, JsonSerializerOptions options)
+ {
+ this.httpClient = httpClient;
+ this.options = options;
+ }
+
+ ///
+ /// 异步获取签到信息
+ ///
+ /// 用户
+ /// 角色
+ /// 取消令牌
+ /// 签到信息
+ public async Task GetInfoAsync(User user, UserGameRole role, CancellationToken token = default)
+ {
+ Response? resp = await httpClient
+ .SetUser(user)
+ .UsingDynamicSecret()
+ .GetFromJsonAsync>(ApiEndpoints.SignInRewardInfo((PlayerUid)role), options, token)
+ .ConfigureAwait(false);
+
+ return resp?.Data;
+ }
+
+ ///
+ /// 异步获取签到信息
+ ///
+ /// 用户
+ /// 角色
+ /// 取消令牌
+ /// 签到信息
+ public async Task GetResignInfoAsync(User user, UserGameRole role, CancellationToken token = default)
+ {
+ Response? resp = await httpClient
+ .SetUser(user)
+ .UsingDynamicSecret()
+ .GetFromJsonAsync>(ApiEndpoints.SignInRewardResignInfo((PlayerUid)role), options, token)
+ .ConfigureAwait(false);
+
+ return resp?.Data;
+ }
+
+ ///
+ /// 获取签到奖励
+ ///
+ /// 用户
+ /// 取消令牌
+ /// 奖励信息
+ public async Task GetRewardAsync(User user, CancellationToken token = default)
+ {
+ Response? resp = await httpClient
+ .SetUser(user)
+ .GetFromJsonAsync>(ApiEndpoints.SignInRewardHome, options, token)
+ .ConfigureAwait(false);
+
+ return resp?.Data;
+ }
+
+ ///
+ /// 补签
+ ///
+ /// 用户
+ /// 角色
+ /// 取消令牌
+ /// 签到消息
+ public async Task?> ReSignAsync(User user, UserGameRole role, CancellationToken token = default)
+ {
+ SignInData data = new((PlayerUid)role);
+
+ HttpResponseMessage response = await httpClient
+ .SetUser(user)
+ .UsingDynamicSecret()
+ .PostAsJsonAsync(ApiEndpoints.SignInRewardReSign, data, options, token)
+ .ConfigureAwait(false);
+ Response? resp = await response.Content
+ .ReadFromJsonAsync>(options, token)
+ .ConfigureAwait(false);
+
+ return resp;
+ }
+
+ ///
+ /// 签到
+ ///
+ /// 用户
+ /// 角色
+ /// 取消令牌
+ /// 签到消息
+ public async Task?> SignAsync(User user, UserGameRole role, CancellationToken token = default)
+ {
+ HttpResponseMessage response = await httpClient
+ .SetUser(user)
+ .UsingDynamicSecret()
+ .PostAsJsonAsync(ApiEndpoints.SignInRewardSign, new SignInData((PlayerUid)role), options, token)
+ .ConfigureAwait(false);
+ Response? resp = await response.Content
+ .ReadFromJsonAsync>(options, token)
+ .ConfigureAwait(false);
+
+ return resp;
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInData.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInData.cs
new file mode 100644
index 00000000..c861dc77
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInData.cs
@@ -0,0 +1,40 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
+
+///
+/// 签到提交数据
+///
+public class SignInData
+{
+ ///
+ /// 构造一个新的签到提交数据
+ ///
+ /// uid
+ public SignInData(PlayerUid uid)
+ {
+ Region = uid.Region;
+ Uid = uid.Value;
+ }
+
+ ///
+ /// 活动Id
+ ///
+ [JsonPropertyName("act_id")]
+ public string ActivityId { get; } = ApiEndpoints.SignInRewardActivityId;
+
+ ///
+ /// 地区代码
+ ///
+ [JsonPropertyName("region")]
+ public string Region { get; }
+
+ ///
+ /// Uid
+ ///
+ [JsonPropertyName("uid")]
+ public string Uid { get; }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInResult.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInResult.cs
new file mode 100644
index 00000000..1d473885
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInResult.cs
@@ -0,0 +1,42 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
+
+///
+/// 签到结果
+///
+public class SignInResult
+{
+ ///
+ /// 通常是 ""
+ ///
+ [JsonPropertyName("code")]
+ public string Code { get; set; } = default!;
+
+ ///
+ /// 通常是 ""
+ ///
+ [JsonPropertyName("risk_code")]
+ public int RiskCode { get; set; }
+
+ ///
+ /// 通常是 ""
+ ///
+ [JsonPropertyName("gt")]
+ public string Gt { get; set; } = default!;
+
+ ///
+ /// 通常是 ""
+ ///
+ [JsonPropertyName("challenge")]
+ public string Challenge { get; set; } = default!;
+
+ ///
+ /// 通常是 ""
+ ///
+ [JsonPropertyName("success")]
+ public int Success { get; set; }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInRewardInfo.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInRewardInfo.cs
new file mode 100644
index 00000000..b9a8ab18
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInRewardInfo.cs
@@ -0,0 +1,54 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
+
+///
+/// 签到信息
+///
+public class SignInRewardInfo
+{
+ ///
+ /// 累积签到天数
+ ///
+ [JsonPropertyName("total_sign_day")]
+ public int TotalSignDay { get; set; }
+
+ ///
+ /// yyyy-MM-dd
+ ///
+ [JsonPropertyName("today")]
+ public string? Today { get; set; }
+
+ ///
+ /// 今日是否已签到
+ ///
+ [JsonPropertyName("is_sign")]
+ public bool IsSign { get; set; }
+
+ ///
+ /// ?
+ ///
+ [JsonPropertyName("is_sub")]
+ public bool IsSub { get; set; }
+
+ ///
+ /// 是否首次绑定
+ ///
+ [JsonPropertyName("first_bind")]
+ public bool FirstBind { get; set; }
+
+ ///
+ /// 是否为当月第一次
+ ///
+ [JsonPropertyName("month_first")]
+ public bool MonthFirst { get; set; }
+
+ ///
+ /// 漏签天数
+ ///
+ [JsonPropertyName("sign_cnt_missed")]
+ public bool SignCountMissed { get; set; }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInRewardReSignInfo.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInRewardReSignInfo.cs
new file mode 100644
index 00000000..845c5cd8
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInRewardReSignInfo.cs
@@ -0,0 +1,60 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Text.Json.Serialization;
+
+namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
+
+///
+/// 补签说明
+///
+public class SignInRewardReSignInfo
+{
+ ///
+ /// 当日补签次数
+ ///
+ [JsonPropertyName("resign_cnt_daily")]
+ public bool ResignCountDaily { get; set; }
+
+ ///
+ /// 当月补签次数
+ ///
+ [JsonPropertyName("resign_cnt_monthly")]
+ public bool ResignCountMonthly { get; set; }
+
+ ///
+ /// 当日补签次数限制
+ ///
+ [JsonPropertyName("resign_limit_daily")]
+ public bool ResignLimitDaily { get; set; }
+
+ ///
+ /// 当月补签次数限制
+ ///
+ [JsonPropertyName("resign_limit_monthly")]
+ public bool ResignLimitMonthly { get; set; }
+
+ ///
+ /// 漏签次数
+ ///
+ [JsonPropertyName("sign_cnt_missed")]
+ public bool SignCountMissed { get; set; }
+
+ ///
+ /// 米游币个数
+ ///
+ [JsonPropertyName("coin_cnt")]
+ public bool CoinCount { get; set; }
+
+ ///
+ /// 补签需要的米游币个数
+ ///
+ [JsonPropertyName("coin_cost")]
+ public bool CoinCost { get; set; }
+
+ ///
+ /// 规则
+ ///
+ [JsonPropertyName("rule")]
+ public string Rule { get; set; } = default!;
+}
\ No newline at end of file
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 fe3b13eb..84ef349b 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
@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
+using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Extension;
using Snap.Hutao.Model.Binding;
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
@@ -17,7 +18,7 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
///
/// 游戏记录提供器
///
-[Injection(InjectAs.Transient)]
+[HttpClient(HttpClientConfigration.XRpc)]
internal class GameRecordClient
{
private readonly HttpClient httpClient;
@@ -42,7 +43,7 @@ internal class GameRecordClient
/// 玩家的基础信息
public Task GetPlayerInfoAsync(User user, CancellationToken token = default)
{
- PlayerUid uid = Must.NotNull(user.SelectedUserGameRole!).AsPlayerUid();
+ PlayerUid uid = (PlayerUid)Must.NotNull(user.SelectedUserGameRole!);
return GetPlayerInfoAsync(user, uid, token);
}
@@ -55,12 +56,10 @@ internal class GameRecordClient
/// 玩家的基础信息
public async Task GetPlayerInfoAsync(User user, PlayerUid uid, CancellationToken token = default)
{
- string url = string.Format(ApiEndpoints.GameRecordIndex(uid.Value, uid.Region));
-
Response? resp = await httpClient
.SetUser(user)
- .UsingDynamicSecret2(jsonSerializerOptions, url)
- .GetFromJsonAsync>(url, jsonSerializerOptions, token)
+ .UsingDynamicSecret2(jsonSerializerOptions, ApiEndpoints.GameRecordIndex(uid.Value, uid.Region))
+ .GetFromJsonAsync>(token)
.ConfigureAwait(false);
return resp?.Data;
@@ -75,7 +74,7 @@ internal class GameRecordClient
/// 深渊信息
public Task GetSpiralAbyssAsync(User user, SpiralAbyssSchedule schedule, CancellationToken token = default)
{
- PlayerUid uid = Must.NotNull(user.SelectedUserGameRole!).AsPlayerUid();
+ PlayerUid uid = (PlayerUid)Must.NotNull(user.SelectedUserGameRole!);
return GetSpiralAbyssAsync(user, uid, schedule, token);
}
@@ -89,12 +88,10 @@ internal class GameRecordClient
/// 深渊信息
public async Task GetSpiralAbyssAsync(User user, PlayerUid uid, SpiralAbyssSchedule schedule, CancellationToken token = default)
{
- string url = string.Format(ApiEndpoints.SpiralAbyss, (int)schedule, uid.Value, uid.Region);
-
Response? resp = await httpClient
.SetUser(user)
- .UsingDynamicSecret2(jsonSerializerOptions, url)
- .GetFromJsonAsync>(url, jsonSerializerOptions, token)
+ .UsingDynamicSecret2(jsonSerializerOptions, ApiEndpoints.GameRecordSpiralAbyss(schedule, uid))
+ .GetFromJsonAsync>(token)
.ConfigureAwait(false);
return resp?.Data;
@@ -109,7 +106,7 @@ internal class GameRecordClient
/// 角色列表
public Task> GetCharactersAsync(User user, PlayerInfo playerInfo, CancellationToken token = default)
{
- PlayerUid uid = Must.NotNull(user.SelectedUserGameRole!).AsPlayerUid();
+ PlayerUid uid = (PlayerUid)Must.NotNull(user.SelectedUserGameRole!);
return GetCharactersAsync(user, uid, playerInfo, token);
}
@@ -127,11 +124,13 @@ internal class GameRecordClient
HttpResponseMessage? response = await httpClient
.SetUser(user)
- .UsingDynamicSecret2(jsonSerializerOptions, ApiEndpoints.Character, data)
- .PostAsJsonAsync(ApiEndpoints.Character, data, token)
+ .UsingDynamicSecret2(jsonSerializerOptions, ApiEndpoints.GameRecordCharacter, data)
+ .PostAsJsonAsync(token)
.ConfigureAwait(false);
- Response? resp = await response.Content.ReadFromJsonAsync>(jsonSerializerOptions, token);
+ Response? resp = await response.Content
+ .ReadFromJsonAsync>(jsonSerializerOptions, token)
+ .ConfigureAwait(false);
return EnumerableExtensions.EmptyIfNull(resp?.Data?.Avatars);
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/HttpRequestHeadersExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Web/HttpRequestHeadersExtensions.cs
index 988db68a..38329d90 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/HttpRequestHeadersExtensions.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/HttpRequestHeadersExtensions.cs
@@ -18,7 +18,7 @@ public static class HttpRequestHeadersExtensions
/// 值
public static void Set(this HttpRequestHeaders headers, string name, string? value)
{
- headers.Clear();
+ headers.Remove(name);
headers.Add(name, value);
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HutaoClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HutaoClient.cs
index 976c9387..ee3fae77 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HutaoClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HutaoClient.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction;
+using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Extension;
using Snap.Hutao.Web.Hoyolab;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
@@ -19,7 +20,8 @@ namespace Snap.Hutao.Web.Hutao;
///
/// 胡桃API客户端
///
-[Injection(InjectAs.Transient)]
+// [Injection(InjectAs.Transient)]
+[HttpClient(HttpClientConfigration.Default)]
internal class HutaoClient : ISupportAsyncInitialization
{
private const string AuthHost = "https://auth.snapgenshin.com";
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs b/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs
index 43903f69..b18fb6ce 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs
@@ -9,19 +9,9 @@ namespace Snap.Hutao.Web.Response;
public enum KnownReturnCode
{
///
- /// Json 异常
+ /// 无效请求
///
- JsonParseIssue = -2000000002,
-
- ///
- /// Url为 空
- ///
- UrlIsEmpty = -2000000001,
-
- ///
- /// 内部错误
- ///
- InternalFailure = -2000000000,
+ InvalidRequest = -10001,
///
/// 已经签到过了
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs b/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs
index 4e455988..8bf19529 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs
@@ -25,16 +25,12 @@ public class Response : ISupportValidation
if (!Validate())
{
- Ioc.Default
- .GetRequiredService()
- .Error(ToString());
+ Ioc.Default.GetRequiredService().Error(ToString());
}
if (ReturnCode != 0)
{
- Ioc.Default
- .GetRequiredService()
- .Warning(ToString());
+ Ioc.Default.GetRequiredService().Warning(ToString());
}
}
@@ -71,4 +67,31 @@ public class Response : ISupportValidation
{
return $"状态:{ReturnCode} | 信息:{Message}";
}
+}
+
+///
+/// Mihoyo 标准API响应
+///
+/// 数据类型
+[SuppressMessage("", "SA1402")]
+public class Response : Response
+{
+ ///
+ /// 构造一个新的 Mihoyo 标准API响应
+ ///
+ /// 返回代码
+ /// 消息
+ /// 数据
+ [JsonConstructor]
+ public Response(int returnCode, string message, TData? data)
+ : base(returnCode, message)
+ {
+ Data = data;
+ }
+
+ ///
+ /// 数据
+ ///
+ [JsonPropertyName("data")]
+ public TData? Data { get; set; }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Response/Response{TData}.cs b/src/Snap.Hutao/Snap.Hutao/Web/Response/Response{TData}.cs
deleted file mode 100644
index 34fa102b..00000000
--- a/src/Snap.Hutao/Snap.Hutao/Web/Response/Response{TData}.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) DGP Studio. All rights reserved.
-// Licensed under the MIT license.
-
-using System.Text.Json.Serialization;
-
-namespace Snap.Hutao.Web.Response;
-
-///
-/// Mihoyo 标准API响应
-///
-/// 数据类型
-public class Response : Response
-{
- ///
- /// 构造一个新的 Mihoyo 标准API响应
- ///
- /// 返回代码
- /// 消息
- /// 数据
- [JsonConstructor]
- public Response(int returnCode, string message, TData? data)
- : base(returnCode, message)
- {
- Data = data;
- }
-
- ///
- /// 数据
- ///
- [JsonPropertyName("data")]
- public TData? Data { get; set; }
-}