Update sign logic

This commit is contained in:
DismissedLight
2022-08-21 23:18:42 +08:00
parent 039b9d8fdc
commit 8972914a72
31 changed files with 515 additions and 221 deletions

View File

@@ -71,6 +71,7 @@ public partial class App : Application
if (firstInstance.IsCurrent) if (firstInstance.IsCurrent)
{ {
// manually invoke the
Activation.Activate(firstInstance, activatedEventArgs); Activation.Activate(firstInstance, activatedEventArgs);
firstInstance.Activated += Activation.Activate; firstInstance.Activated += Activation.Activate;

View File

@@ -39,7 +39,7 @@ public class CachedImage : ImageEx
// check token state to determine whether the operation should be canceled. // check token state to determine whether the operation should be canceled.
Must.ThrowOnCanceled(token, "Image source has changed."); Must.ThrowOnCanceled(token, "Image source has changed.");
// BitmapImage initialize with a uri will increase image quality. // BitmapImage initialize with a uri will increase image quality and loading speed.
return new BitmapImage(new(file.Path)); return new BitmapImage(new(file.Path));
} }
catch (COMException ex) when (ex.Is(COMError.WINCODEC_ERR_COMPONENTNOTFOUND)) catch (COMException ex) when (ex.Is(COMError.WINCODEC_ERR_COMPONENTNOTFOUND))

View File

@@ -9,6 +9,7 @@ using Microsoft.UI.Xaml.Documents;
using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media;
using Snap.Hutao.Core; using Snap.Hutao.Core;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Windows.UI; using Windows.UI;
@@ -22,6 +23,8 @@ public class DescriptionTextBlock : ContentControl
private static readonly DependencyProperty DescriptionProperty = private static readonly DependencyProperty DescriptionProperty =
Property<DescriptionTextBlock>.Depend(nameof(Description), string.Empty, OnDescriptionChanged); Property<DescriptionTextBlock>.Depend(nameof(Description), string.Empty, OnDescriptionChanged);
private static readonly Regex ColorRegex = new(@"<color=([^>]+)>([^<]+)</color>", RegexOptions.Compiled);
/// <summary> /// <summary>
/// 构造一个新的呈现描述文本的文本块 /// 构造一个新的呈现描述文本的文本块
/// </summary> /// </summary>
@@ -46,17 +49,17 @@ public class DescriptionTextBlock : ContentControl
text.Inlines.Clear(); text.Inlines.Clear();
string[] lines = ((string)e.NewValue).Split('\n'); string[] lines = ((string)e.NewValue).Split('\n');
foreach (string line in lines) foreach (string line in lines)
{ {
MatchCollection matches = Regex.Matches(line, @"<color=([^>]+)>([^<]+)</color>");
string left, right = line; string left, right = line;
foreach (Match match in matches) foreach (Match match in ColorRegex.Matches(line))
{ {
string matched = match.Groups[0].Value; string fullMatch = match.Groups[0].Value;
int matchPosition = right.IndexOf(matched); int matchPosition = right.IndexOf(fullMatch);
left = right[..matchPosition]; left = right[..matchPosition];
right = right[(matchPosition + matched.Length)..]; right = right[(matchPosition + fullMatch.Length)..];
if (!string.IsNullOrWhiteSpace(left)) if (!string.IsNullOrWhiteSpace(left))
{ {
@@ -66,7 +69,7 @@ public class DescriptionTextBlock : ContentControl
string hexColor = match.Groups[1].Value; string hexColor = match.Groups[1].Value;
string content = match.Groups[2].Value; string content = match.Groups[2].Value;
text.Inlines.Add(new Run { Text = content, Foreground = GetSolidColorBrush(hexColor[..7]) }); text.Inlines.Add(new Run { Text = content, Foreground = new SolidColorBrush(new HexColor(hexColor[1..])) });
} }
if (!string.IsNullOrWhiteSpace(right)) if (!string.IsNullOrWhiteSpace(right))
@@ -91,12 +94,33 @@ public class DescriptionTextBlock : ContentControl
} }
} }
private static SolidColorBrush GetSolidColorBrush(string hex) [StructLayout(LayoutKind.Explicit)]
private struct HexColor
{ {
hex = hex.Replace("#", string.Empty); [FieldOffset(3)]
byte r = (byte)Convert.ToUInt32(hex.Substring(0, 2), 16); public byte R;
byte g = (byte)Convert.ToUInt32(hex.Substring(2, 2), 16); [FieldOffset(2)]
byte b = (byte)Convert.ToUInt32(hex.Substring(4, 2), 16); public byte G;
return new SolidColorBrush(Color.FromArgb(255, r, g, b)); [FieldOffset(1)]
public byte B;
[FieldOffset(0)]
public byte A;
[FieldOffset(0)]
private readonly uint data;
public HexColor(string hex)
{
R = 0;
G = 0;
B = 0;
A = 0;
data = Convert.ToUInt32(hex, 16);
}
public static implicit operator Color(HexColor hexColor)
{
return Color.FromArgb(hexColor.A, hexColor.R, hexColor.G, hexColor.B);
}
} }
} }

View File

@@ -246,6 +246,7 @@ public abstract class CacheBase<T>
{ {
try try
{ {
logger.LogInformation(EventIds.CacheRemoveFile, "Removing file {file}", file);
await file.DeleteAsync().AsTask().ConfigureAwait(false); await file.DeleteAsync().AsTask().ConfigureAwait(false);
} }
catch catch

View File

@@ -11,10 +11,14 @@ namespace Snap.Hutao.Core;
/// </summary> /// </summary>
internal static class CoreEnvironment internal static class CoreEnvironment
{ {
// Used DS1 History
// 2.34.1 9nQiU3AV0rJSIBWgdynfoGMGKaklfbM7
// 2.35.2 N50pqm7FSy2AkFz2B3TqtuZMJ5TOl3Ep
/// <summary> /// <summary>
/// 动态密钥1的盐 /// 动态密钥1的盐
/// </summary> /// </summary>
public const string DynamicSecret1Salt = "9nQiU3AV0rJSIBWgdynfoGMGKaklfbM7"; public const string DynamicSecret1Salt = "N50pqm7FSy2AkFz2B3TqtuZMJ5TOl3Ep";
/// <summary> /// <summary>
/// 动态密钥2的盐 /// 动态密钥2的盐
@@ -25,7 +29,12 @@ internal static class CoreEnvironment
/// <summary> /// <summary>
/// 米游社请求UA /// 米游社请求UA
/// </summary> /// </summary>
public const string HoyolabUA = $"miHoYoBBS/2.34.1"; public const string HoyolabUA = $"miHoYoBBS/{HoyolabXrpcVersion}";
/// <summary>
/// 米游社 Rpc 版本
/// </summary>
public const string HoyolabXrpcVersion = "2.35.2";
/// <summary> /// <summary>
/// 标准UA /// 标准UA

View File

@@ -36,7 +36,7 @@ internal static partial class IocHttpClientConfiguration
{ {
client.Timeout = Timeout.InfiniteTimeSpan; client.Timeout = Timeout.InfiniteTimeSpan;
client.DefaultRequestHeaders.UserAgent.ParseAdd(CoreEnvironment.HoyolabUA); client.DefaultRequestHeaders.UserAgent.ParseAdd(CoreEnvironment.HoyolabUA);
client.DefaultRequestHeaders.Add("x-rpc-app_version", "2.34.1"); client.DefaultRequestHeaders.Add("x-rpc-app_version", CoreEnvironment.HoyolabXrpcVersion);
client.DefaultRequestHeaders.Add("x-rpc-client_type", "5"); client.DefaultRequestHeaders.Add("x-rpc-client_type", "5");
client.DefaultRequestHeaders.Add("x-rpc-device_id", CoreEnvironment.HoyolabDeviceId); client.DefaultRequestHeaders.Add("x-rpc-device_id", CoreEnvironment.HoyolabDeviceId);
} }

View File

@@ -10,7 +10,6 @@ namespace Snap.Hutao.Core.Json.Converter;
/// <summary> /// <summary>
/// Json字典转换器 /// Json字典转换器
/// </summary> /// </summary>
/// <typeparam name="TKeyConverter">键的类型</typeparam>
public class StringEnumKeyDictionaryConverter : JsonConverterFactory public class StringEnumKeyDictionaryConverter : JsonConverterFactory
{ {
/// <inheritdoc/> /// <inheritdoc/>

View File

@@ -13,8 +13,7 @@ namespace Snap.Hutao.Core.LifeCycle;
/// </summary> /// </summary>
internal static class Activation internal static class Activation
{ {
private static volatile bool isActivating = false; private static readonly SemaphoreSlim ActivateSemaphore = new(1);
private static object activationLock = new();
/// <summary> /// <summary>
/// 响应激活事件 /// 响应激活事件
@@ -33,28 +32,13 @@ internal static class Activation
/// <returns>任务</returns> /// <returns>任务</returns>
private static async Task HandleActivationAsync(AppActivationArguments args) private static async Task HandleActivationAsync(AppActivationArguments args)
{ {
if (isActivating) if (ActivateSemaphore.CurrentCount > 0)
{ {
lock (activationLock) using (await ActivateSemaphore.EnterAsync().ConfigureAwait(false))
{ {
if (isActivating) await HandleActivationCoreAsync(args).ConfigureAwait(false);
{
return;
}
} }
} }
lock (activationLock)
{
isActivating = true;
}
await HandleActivationCoreAsync(args).ConfigureAwait(false);
lock (activationLock)
{
isActivating = false;
}
} }
private static async Task HandleActivationCoreAsync(AppActivationArguments args) private static async Task HandleActivationCoreAsync(AppActivationArguments args)

View File

@@ -54,18 +54,19 @@ internal sealed partial class DatebaseLogger : ILogger
return; return;
} }
LogEntry entry = new()
{
Category = name,
LogLevel = logLevel,
EventId = eventId.Id,
Message = message,
Exception = exception?.ToString(),
};
// DbContext is not a thread safe class, so we have to lock the wirte procedure // DbContext is not a thread safe class, so we have to lock the wirte procedure
lock (logDbContextLock) lock (logDbContextLock)
{ {
logDbContext.Logs.Add(new LogEntry logDbContext.Logs.Add(entry);
{
Category = name,
LogLevel = logLevel,
EventId = eventId.Id,
Message = message,
Exception = exception?.ToString(),
});
logDbContext.SaveChanges(); logDbContext.SaveChanges();
} }
} }

View File

@@ -10,14 +10,14 @@ using System.Linq;
namespace Snap.Hutao.Core.Logging; namespace Snap.Hutao.Core.Logging;
/// <summary> /// <summary>
/// The provider for the <see cref="DebugLogger"/>. /// The provider for the <see cref="DatebaseLogger"/>.
/// </summary> /// </summary>
[ProviderAlias("Database")] [ProviderAlias("Database")]
public sealed class DatebaseLoggerProvider : ILoggerProvider public sealed class DatebaseLoggerProvider : ILoggerProvider
{ {
private static readonly object LogDbContextLock = new(); private static readonly object LogDbContextLock = new();
// the provider is created per logger, we don't want to create to much // the provider is created per logger, we don't want to create too much
private static volatile LogDbContext? logDbContext; private static volatile LogDbContext? logDbContext;
private static LogDbContext LogDbContext private static LogDbContext LogDbContext

View File

@@ -6,9 +6,10 @@ namespace Snap.Hutao.Core.Logging;
/// <summary> /// <summary>
/// 事件Id定义 /// 事件Id定义
/// </summary> /// </summary>
[SuppressMessage("", "SA1124")]
internal static class EventIds internal static class EventIds
{ {
// 异 #region
/// <summary> /// <summary>
/// 未经处理的异常 /// 未经处理的异常
@@ -44,8 +45,9 @@ internal static class EventIds
/// Xaml绑定错误 /// Xaml绑定错误
/// </summary> /// </summary>
public static readonly EventId UnobservedTaskException = 100006; public static readonly EventId UnobservedTaskException = 100006;
#endregion
// 服 #region
/// <summary> /// <summary>
/// 导航历史 /// 导航历史
@@ -73,11 +75,17 @@ internal static class EventIds
public static readonly EventId FileCaching = 100120; public static readonly EventId FileCaching = 100120;
/// <summary> /// <summary>
/// 文件缓存 /// 删除缓存文件
/// </summary>
public static readonly EventId CacheRemoveFile = 100121;
/// <summary>
/// 成就
/// </summary> /// </summary>
public static readonly EventId Achievement = 100130; public static readonly EventId Achievement = 100130;
#endregion
// 杂 #region
/// <summary> /// <summary>
/// 杂项Log /// 杂项Log
@@ -93,4 +101,5 @@ internal static class EventIds
/// 子类控制 /// 子类控制
/// </summary> /// </summary>
public static readonly EventId SubClassing = 200002; public static readonly EventId SubClassing = 200002;
#endregion
} }

View File

@@ -28,10 +28,12 @@ internal static class LocalSetting
/// <param name="key">键</param> /// <param name="key">键</param>
/// <param name="defaultValue">默认值</param> /// <param name="defaultValue">默认值</param>
/// <returns>获取的值</returns> /// <returns>获取的值</returns>
public static T? Get<T>(string key, T? defaultValue = default) [return:MaybeNull]
public static T Get<T>(string key, [AllowNull] T defaultValue = default)
{ {
if (Container.Values.TryGetValue(key, out object? value)) if (Container.Values.TryGetValue(key, out object? value))
{ {
// unbox the value
return value is null ? defaultValue : (T)value; return value is null ? defaultValue : (T)value;
} }
else else
@@ -48,9 +50,8 @@ internal static class LocalSetting
/// <param name="key">键</param> /// <param name="key">键</param>
/// <param name="value">值</param> /// <param name="value">值</param>
/// <returns>设置的值</returns> /// <returns>设置的值</returns>
public static T Set<T>(string key, T value) public static object? Set<T>(string key, T value)
{ {
Container.Values[key] = value; return Container.Values[key] = value;
return value;
} }
} }

View File

@@ -0,0 +1,35 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Threading.Abstraction;
/// <summary>
/// 表示一个可等待对象,如果一个方法返回此类型的实例,则此方法可以使用 <see langword="await"/> 异步等待。
/// </summary>
/// <typeparam name="TAwaiter">用于给 await 确定返回时机的 IAwaiter 的实例。</typeparam>
public interface IAwaitable<out TAwaiter>
where TAwaiter : IAwaiter
{
/// <summary>
/// 获取一个可用于 await 关键字异步等待的异步等待对象。
/// 此方法会被编译器自动调用。
/// </summary>
/// <returns>等待器</returns>
TAwaiter GetAwaiter();
}
/// <summary>
/// 表示一个包含返回值的可等待对象,如果一个方法返回此类型的实例,则此方法可以使用 <see langword="await"/> 异步等待返回值。
/// </summary>
/// <typeparam name="TAwaiter">用于给 await 确定返回时机的 <see cref="IAwaiter{TResult}"/> 的实例。</typeparam>
/// <typeparam name="TResult">异步返回的返回值类型。</typeparam>
public interface IAwaitable<out TAwaiter, out TResult>
where TAwaiter : IAwaiter<TResult>
{
/// <summary>
/// 获取一个可用于 await 关键字异步等待的异步等待对象。
/// 此方法会被编译器自动调用。
/// </summary>
/// <returns>等待器</returns>
TAwaiter GetAwaiter();
}

View File

@@ -0,0 +1,42 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Core.Threading.Abstraction;
/// <summary>
/// 用于给 await 确定异步返回的时机。
/// </summary>
public interface IAwaiter : INotifyCompletion
{
/// <summary>
/// 获取一个状态,该状态表示正在异步等待的操作已经完成(成功完成或发生了异常);此状态会被编译器自动调用。
/// 在实现中,为了达到各种效果,可以灵活应用其值:可以始终为 true或者始终为 false。
/// </summary>
bool IsCompleted { get; }
/// <summary>
/// 此方法会被编译器在 await 结束时自动调用以获取返回状态(包括异常)。
/// </summary>
void GetResult();
}
/// <summary>
/// 用于给 await 确定异步返回的时机,并获取到返回值。
/// </summary>
/// <typeparam name="TResult">异步返回的返回值类型。</typeparam>
public interface IAwaiter<out TResult> : INotifyCompletion
{
/// <summary>
/// 获取一个状态,该状态表示正在异步等待的操作已经完成(成功完成或发生了异常);此状态会被编译器自动调用。
/// 在实现中,为了达到各种效果,可以灵活应用其值:可以始终为 true或者始终为 false。
/// </summary>
bool IsCompleted { get; }
/// <summary>
/// 获取此异步等待操作的返回值,此方法会被编译器在 await 结束时自动调用以获取返回值(包括异常)。
/// </summary>
/// <returns>异步操作的返回值。</returns>
TResult GetResult();
}

View File

@@ -0,0 +1,23 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Core.Threading.Abstraction;
/// <summary>
/// 当执行关键代码(此代码中的错误可能给应用程序中的其他状态造成负面影响)时,
/// 用于给 await 确定异步返回的时机。
/// </summary>
public interface ICriticalAwaiter : IAwaiter, ICriticalNotifyCompletion
{
}
/// <summary>
/// 当执行关键代码(此代码中的错误可能给应用程序中的其他状态造成负面影响)时,
/// 用于给 await 确定异步返回的时机,并获取到返回值。
/// </summary>
/// <typeparam name="TResult">异步返回的返回值类型。</typeparam>
public interface ICriticalAwaiter<out TResult> : IAwaiter<TResult>, ICriticalNotifyCompletion
{
}

View File

@@ -21,13 +21,11 @@ internal class ConcurrentCancellationTokenSource<TItem>
/// <returns>取消令牌</returns> /// <returns>取消令牌</returns>
public CancellationToken Register(TItem item) public CancellationToken Register(TItem item)
{ {
if (waitingItems.TryRemove(item, out CancellationTokenSource? prevSource)) if (waitingItems.TryRemove(item, out CancellationTokenSource? previousSource))
{ {
prevSource.Cancel(); previousSource.Cancel();
} }
CancellationTokenSource current = waitingItems.GetOrAdd(item, new CancellationTokenSource()); return waitingItems.GetOrAdd(item, new CancellationTokenSource()).Token;
return current.Token;
} }
} }

View File

@@ -0,0 +1,49 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Dispatching;
using Snap.Hutao.Core.Threading.Abstraction;
namespace Snap.Hutao.Core.Threading;
/// <summary>
/// 调度器队列切换操作
/// </summary>
public struct DispatherQueueSwitchOperation : IAwaitable<DispatherQueueSwitchOperation>, IAwaiter
{
private readonly DispatcherQueue dispatherQueue;
/// <summary>
/// 构造一个新的同步上下文等待器
/// </summary>
/// <param name="dispatherQueue">同步上下文</param>
public DispatherQueueSwitchOperation(DispatcherQueue dispatherQueue)
{
this.dispatherQueue = dispatherQueue;
}
/// <summary>
/// 是否完成
/// </summary>
public bool IsCompleted => dispatherQueue.HasThreadAccess;
/// <inheritdoc/>
public void OnCompleted(Action continuation)
{
dispatherQueue.TryEnqueue(() => { continuation(); });
}
/// <inheritdoc/>
public void GetResult()
{
}
/// <summary>
/// 获取等待器
/// </summary>
/// <returns>等待器</returns>
public DispatherQueueSwitchOperation GetAwaiter()
{
return this;
}
}

View File

@@ -1,24 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Threading;
/// <summary>
/// 构造复杂的结果
/// </summary>
public static class Results
{
/// <summary>
/// 根据条件构造结果
/// </summary>
/// <typeparam name="T">结果的类型</typeparam>
/// <param name="condition">条件</param>
/// <param name="trueValue">条件符合时的值</param>
/// <param name="falseValue">条件不符合时的值</param>
/// <returns>结果</returns>
public static Result<bool, T> Condition<T>(bool condition, T trueValue, T falseValue)
where T : notnull
{
return new(condition, condition ? trueValue : falseValue);
}
}

View File

@@ -1,30 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Threading;
/// <summary>
/// 同步上下文等待体
/// </summary>
public struct SynchronizationContextAwaitable
{
private readonly SynchronizationContext context;
/// <summary>
/// 构造一个新的同步上下文等待体
/// </summary>
/// <param name="context">同步上下文</param>
public SynchronizationContextAwaitable(SynchronizationContext context)
{
this.context = context;
}
/// <summary>
/// 获取等待器
/// </summary>
/// <returns>等待器</returns>
public SynchronizationContextAwaiter GetAwaiter()
{
return new SynchronizationContextAwaiter(context);
}
}

View File

@@ -1,43 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Core.Threading;
/// <summary>
/// 同步上下文等待器
/// </summary>
public struct SynchronizationContextAwaiter : INotifyCompletion
{
private static readonly SendOrPostCallback PostCallback = state => ((Action)state!)();
private readonly SynchronizationContext context;
/// <summary>
/// 构造一个新的同步上下文等待器
/// </summary>
/// <param name="context">同步上下文</param>
public SynchronizationContextAwaiter(SynchronizationContext context)
{
this.context = context;
}
/// <summary>
/// 是否完成
/// </summary>
public bool IsCompleted => context == SynchronizationContext.Current;
/// <summary>
/// 完成操作
/// </summary>
/// <param name="continuation">后续操作</param>
[SuppressMessage("", "VSTHRD001")]
public void OnCompleted(Action continuation) => context.Post(PostCallback, continuation);
/// <summary>
/// 获取执行结果
/// </summary>
public void GetResult()
{
}
}

View File

@@ -60,7 +60,9 @@ internal static class CompositionExtensions
/// <param name="compositor">合成器</param> /// <param name="compositor">合成器</param>
/// <param name="source">源</param> /// <param name="source">源</param>
/// <returns>合成效果画刷</returns> /// <returns>合成效果画刷</returns>
public static CompositionEffectBrush CompositeGrayScaleEffectBrush(this Compositor compositor, CompositionBrush source) public static CompositionEffectBrush CompositeGrayScaleEffectBrush(
this Compositor compositor,
CompositionBrush source)
{ {
GrayscaleEffect effect = new() GrayscaleEffect effect = new()
{ {

View File

@@ -15,23 +15,14 @@ namespace Snap.Hutao;
public static class Program public static class Program
{ {
private static volatile DispatcherQueue? dispatcherQueue; private static volatile DispatcherQueue? dispatcherQueue;
private static volatile SynchronizationContext? context;
/// <summary>
/// 主线程调度器队列
/// </summary>
public static DispatcherQueue UIDispatcherQueue
{
get => Must.NotNull(dispatcherQueue!);
}
/// <summary> /// <summary>
/// 异步切换到主线程 /// 异步切换到主线程
/// </summary> /// </summary>
/// <returns>等待体</returns> /// <returns>等待体</returns>
public static SynchronizationContextAwaitable SwitchToMainThreadAsync() public static DispatherQueueSwitchOperation SwitchToMainThreadAsync()
{ {
return new SynchronizationContextAwaitable(context!); return new(dispatcherQueue!);
} }
[DllImport("Microsoft.ui.xaml.dll")] [DllImport("Microsoft.ui.xaml.dll")]
@@ -47,7 +38,7 @@ public static class Program
Application.Start(p => Application.Start(p =>
{ {
dispatcherQueue = DispatcherQueue.GetForCurrentThread(); dispatcherQueue = DispatcherQueue.GetForCurrentThread();
context = new DispatcherQueueSynchronizationContext(dispatcherQueue); DispatcherQueueSynchronizationContext context = new(dispatcherQueue);
SynchronizationContext.SetSynchronizationContext(context); SynchronizationContext.SetSynchronizationContext(context);
_ = new App(); _ = new App();
}); });

View File

@@ -0,0 +1,17 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Sign;
/// <summary>
/// 签到服务
/// </summary>
public interface ISignService
{
/// <summary>
/// 异步全部签到
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>任务</returns>
Task<SignResult> SignForAllAsync(CancellationToken token);
}

View File

@@ -0,0 +1,37 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Sign;
/// <summary>
/// 签到操作结果
/// </summary>
public struct SignResult
{
/// <summary>
/// 构造一个新的签到操作结果
/// </summary>
/// <param name="totalCount">总次数</param>
/// <param name="retryCount">重试次数</param>
public SignResult(int totalCount, int retryCount, TimeSpan time)
{
TotalCount = totalCount;
RetryCount = retryCount;
Time = time;
}
/// <summary>
/// 总次数
/// </summary>
public int TotalCount { get; set; }
/// <summary>
/// 重试次数
/// </summary>
public int RetryCount { get; set; }
/// <summary>
/// 用时
/// </summary>
public TimeSpan Time { get; set; }
}

View File

@@ -0,0 +1,117 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Diagnostics;
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 System.Collections.Generic;
namespace Snap.Hutao.Service.Sign;
/// <summary>
/// 签到服务
/// </summary>
[Injection(InjectAs.Transient, typeof(ISignService))]
internal class SignService : ISignService
{
private readonly IUserService userService;
private readonly IInfoBarService infoBarService;
private readonly SignClient signClient;
/// <summary>
/// 构造一个新的签到服务
/// </summary>
/// <param name="userService">用户服务</param>
/// <param name="infoBarService">信息条服务</param>
/// <param name="signClient">签到客户端</param>
public SignService(IUserService userService, IInfoBarService infoBarService, SignClient signClient)
{
this.userService = userService;
this.infoBarService = infoBarService;
this.signClient = signClient;
}
/// <inheritdoc/>
public async Task<SignResult> SignForAllAsync(CancellationToken token)
{
IEnumerable<Model.Binding.User>? users = await userService
.GetUserCollectionAsync()
.ConfigureAwait(false);
Queue<UserRole> userRolesQueue = GetSignQueue(users);
int totalCount = 0;
int retryCount = 0;
ValueStopwatch stopwatch = ValueStopwatch.StartNew();
while (userRolesQueue.TryDequeue(out UserRole current))
{
totalCount++;
Response<SignInResult>? resp = await signClient
.SignAsync(current, token)
.ConfigureAwait(false);
Must.NotNull(resp!);
if (resp.Data != null)
{
Must.Argument(resp.ReturnCode == 0, "返回代码应为 0");
// Geetest applied
if (resp.Data.Success != 0)
{
userRolesQueue.Enqueue(current);
retryCount++;
}
else
{
infoBarService.Information($"[{current.Role}] 签到成功");
}
}
else
{
switch ((KnownReturnCode)resp.ReturnCode)
{
case KnownReturnCode.OK:
infoBarService.Information($"[{current.Role}] 签到成功");
break;
case KnownReturnCode.NotLoggedIn:
case KnownReturnCode.AlreadySignedIn:
infoBarService.Information($"[{current.Role}] {resp.Message}");
break;
case KnownReturnCode.InvalidRequest:
infoBarService.Information("米游社SALT过期请更新胡桃");
break;
default:
throw Must.NeverHappen();
}
}
if (userRolesQueue.Count > 0)
{
int seconds = Random.Shared.Next(5, 15);
await Task
.Delay(TimeSpan.FromSeconds(seconds), token)
.ConfigureAwait(false);
}
}
return new(totalCount, retryCount, stopwatch.GetElapsedTime());
}
private static Queue<UserRole> GetSignQueue(IEnumerable<Model.Binding.User> users)
{
Queue<UserRole> queue = new();
foreach (Model.Binding.User user in users)
{
foreach (UserGameRole role in user.UserGameRoles)
{
queue.Enqueue(new(user, role));
}
}
return queue;
}
}

View File

@@ -4,6 +4,7 @@
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Core; using Snap.Hutao.Core;
using Snap.Hutao.Core.Logging; using Snap.Hutao.Core.Logging;
using Snap.Hutao.Extension;
using Snap.Hutao.Service.Abstraction; using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Navigation; using Snap.Hutao.Service.Navigation;
using Snap.Hutao.View.Page; using Snap.Hutao.View.Page;
@@ -42,11 +43,13 @@ public sealed partial class MainView : UserControl
private void OnUISettingsColorValuesChanged(UISettings sender, object args) private void OnUISettingsColorValuesChanged(UISettings sender, object args)
{ {
Program.UIDispatcherQueue.TryEnqueue(UpdateTheme); UpdateThemeAsync().SafeForget();
} }
private void UpdateTheme() private async Task UpdateThemeAsync()
{ {
await Program.SwitchToMainThreadAsync();
if (!ThemeHelper.Equals(App.Current.RequestedTheme, RequestedTheme)) if (!ThemeHelper.Equals(App.Current.RequestedTheme, RequestedTheme))
{ {
ILogger<MainView> logger = Ioc.Default.GetRequiredService<ILogger<MainView>>(); ILogger<MainView> logger = Ioc.Default.GetRequiredService<ILogger<MainView>>();

View File

@@ -42,26 +42,23 @@
ItemsSource="{Binding Archives,Mode=OneWay}" ItemsSource="{Binding Archives,Mode=OneWay}"
SelectedItem="{Binding SelectedArchive,Mode=TwoWay}"/> SelectedItem="{Binding SelectedArchive,Mode=TwoWay}"/>
</AppBarElementContainer> </AppBarElementContainer>
<CommandBar.SecondaryCommands> <AppBarButton
<AppBarButton Icon="Add"
Icon="Add" Label="创建新存档"
Label="创建新存档" Command="{Binding AddArchiveCommand}"/>
Command="{Binding AddArchiveCommand}"/> <AppBarButton
<AppBarButton Icon="Delete"
Icon="Delete" Label="删除当前存档"
Label="删除当前存档" Command="{Binding RemoveArchiveCommand}"/>
Command="{Binding RemoveArchiveCommand}"/> <AppBarSeparator/>
<AppBarSeparator/> <AppBarButton
<AppBarButton Icon="Paste"
Icon="Paste" Label="从剪贴板导入"
Label="从剪贴板导入" Command="{Binding ImportUIAFFromClipboardCommand}"/>
Command="{Binding ImportUIAFFromClipboardCommand}"/> <AppBarButton
<AppBarButton Icon="OpenFile"
Icon="OpenFile" Label="从UIAF文件导入"
Label="从UIAF文件导入" Command="{Binding ImportUIAFFromFileCommand}"/>
Command="{Binding ImportUIAFFromFileCommand}"/>
</CommandBar.SecondaryCommands>
</CommandBar> </CommandBar>
<SplitView <SplitView
Grid.Row="1" Grid.Row="1"

View File

@@ -5,9 +5,8 @@ using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Context.FileSystem.Location; using Snap.Hutao.Context.FileSystem.Location;
using Snap.Hutao.Factory.Abstraction; using Snap.Hutao.Factory.Abstraction;
using Snap.Hutao.Service.Abstraction; using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding; using Snap.Hutao.Service.Sign;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward; using System.Text;
using Snap.Hutao.Web.Response;
using Windows.System; using Windows.System;
namespace Snap.Hutao.ViewModel; namespace Snap.Hutao.ViewModel;
@@ -19,8 +18,7 @@ namespace Snap.Hutao.ViewModel;
internal class ExperimentalFeaturesViewModel : ObservableObject internal class ExperimentalFeaturesViewModel : ObservableObject
{ {
private readonly IFileSystemLocation hutaoLocation; private readonly IFileSystemLocation hutaoLocation;
private readonly IUserService userService; private readonly ISignService signService;
private readonly SignClient signClient;
private readonly IInfoBarService infoBarService; private readonly IInfoBarService infoBarService;
/// <summary> /// <summary>
@@ -28,19 +26,16 @@ internal class ExperimentalFeaturesViewModel : ObservableObject
/// </summary> /// </summary>
/// <param name="asyncRelayCommandFactory">异步命令工厂</param> /// <param name="asyncRelayCommandFactory">异步命令工厂</param>
/// <param name="hutaoLocation">数据文件夹</param> /// <param name="hutaoLocation">数据文件夹</param>
/// <param name="userService">用户服务</param> /// <param name="signService">签到客户端</param>
/// <param name="signClient">签到客户端</param> /// <param name="infoBarService">信息条服务</param>
/// <param name="infoBarService">信息栏服务</param>
public ExperimentalFeaturesViewModel( public ExperimentalFeaturesViewModel(
IAsyncRelayCommandFactory asyncRelayCommandFactory, IAsyncRelayCommandFactory asyncRelayCommandFactory,
HutaoLocation hutaoLocation, HutaoLocation hutaoLocation,
IUserService userService, ISignService signService,
SignClient signClient,
IInfoBarService infoBarService) IInfoBarService infoBarService)
{ {
this.hutaoLocation = hutaoLocation; this.hutaoLocation = hutaoLocation;
this.userService = userService; this.signService = signService;
this.signClient = signClient;
this.infoBarService = infoBarService; this.infoBarService = infoBarService;
OpenCacheFolderCommand = asyncRelayCommandFactory.Create(OpenCacheFolderAsync); OpenCacheFolderCommand = asyncRelayCommandFactory.Create(OpenCacheFolderAsync);
@@ -73,20 +68,15 @@ internal class ExperimentalFeaturesViewModel : ObservableObject
return Launcher.LaunchFolderPathAsync(hutaoLocation.GetPath()).AsTask(); return Launcher.LaunchFolderPathAsync(hutaoLocation.GetPath()).AsTask();
} }
private async Task SignAllUserGameRolesAsync() private async Task SignAllUserGameRolesAsync(CancellationToken token)
{ {
foreach (Model.Binding.User user in await userService.GetUserCollectionAsync()) SignResult result = await signService.SignForAllAsync(token);
{
foreach (UserGameRole role in user.UserGameRoles)
{
Response<SignInResult>? result = await signClient.SignAsync(user, role);
if (result != null)
{
infoBarService.Information(result.Message);
}
await Task.Delay(TimeSpan.FromSeconds(15)); StringBuilder stringBuilder = new StringBuilder()
} .Append($"签到完成 - 用时: {result.Time.TotalSeconds:F2} 秒\r\n")
} .Append($"请求: {result.TotalCount} 次\r\n")
.Append($"补签: {result.RetryCount} 次");
infoBarService.Information(stringBuilder.ToString());
} }
} }

View File

@@ -83,6 +83,17 @@ internal class SignClient
return resp?.Data; return resp?.Data;
} }
/// <summary>
/// 补签
/// </summary>
/// <param name="userRole">用户角色</param>
/// <param name="token">取消令牌</param>
/// <returns>签到结果</returns>
public Task<Response<SignInResult>?> ReSignAsync(UserRole userRole, CancellationToken token = default)
{
return ReSignAsync(userRole.User, userRole.Role, token);
}
/// <summary> /// <summary>
/// 补签 /// 补签
/// </summary> /// </summary>
@@ -106,13 +117,24 @@ internal class SignClient
return resp; return resp;
} }
/// <summary>
/// 签到
/// </summary>
/// <param name="userRole">用户角色</param>
/// <param name="token">取消令牌</param>
/// <returns>签到结果</returns>
public Task<Response<SignInResult>?> SignAsync(UserRole userRole, CancellationToken token = default)
{
return SignAsync(userRole.User, userRole.Role, token);
}
/// <summary> /// <summary>
/// 签到 /// 签到
/// </summary> /// </summary>
/// <param name="user">用户</param> /// <param name="user">用户</param>
/// <param name="role">角色</param> /// <param name="role">角色</param>
/// <param name="token">取消令牌</param> /// <param name="token">取消令牌</param>
/// <returns>签到消息</returns> /// <returns>签到结果</returns>
public async Task<Response<SignInResult>?> SignAsync(User user, UserGameRole role, CancellationToken token = default) public async Task<Response<SignInResult>?> SignAsync(User user, UserGameRole role, CancellationToken token = default)
{ {
HttpResponseMessage response = await httpClient HttpResponseMessage response = await httpClient
@@ -126,4 +148,4 @@ internal class SignClient
return resp; return resp;
} }
} }

View File

@@ -0,0 +1,34 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Binding;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
/// <summary>
/// 将用户与角色捆绑在一起
/// </summary>
public struct UserRole
{
/// <summary>
/// 构造一个新的用户角色
/// </summary>
/// <param name="user">用户</param>
/// <param name="role">角色</param>
public UserRole(User user, UserGameRole role)
{
User = user;
Role = role;
}
/// <summary>
/// 用户
/// </summary>
public User User { get; set; }
/// <summary>
/// 角色
/// </summary>
public UserGameRole Role { get; set; }
}

View File

@@ -6,7 +6,7 @@ namespace Snap.Hutao.Web.Response;
/// <summary> /// <summary>
/// 已知的返回代码 /// 已知的返回代码
/// </summary> /// </summary>
public enum KnownReturnCode public enum KnownReturnCode : int
{ {
/// <summary> /// <summary>
/// 无效请求 /// 无效请求
@@ -18,6 +18,11 @@ public enum KnownReturnCode
/// </summary> /// </summary>
AlreadySignedIn = -5003, AlreadySignedIn = -5003,
/// <summary>
/// 尚未登录
/// </summary>
NotLoggedIn = -100,
/// <summary> /// <summary>
/// 验证密钥过期 /// 验证密钥过期
/// </summary> /// </summary>