mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Update sign logic
This commit is contained in:
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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/>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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();
|
||||||
});
|
});
|
||||||
|
|||||||
17
src/Snap.Hutao/Snap.Hutao/Service/Sign/ISignService.cs
Normal file
17
src/Snap.Hutao/Snap.Hutao/Service/Sign/ISignService.cs
Normal 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);
|
||||||
|
}
|
||||||
37
src/Snap.Hutao/Snap.Hutao/Service/Sign/SignResult.cs
Normal file
37
src/Snap.Hutao/Snap.Hutao/Service/Sign/SignResult.cs
Normal 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; }
|
||||||
|
}
|
||||||
117
src/Snap.Hutao/Snap.Hutao/Service/Sign/SignService.cs
Normal file
117
src/Snap.Hutao/Snap.Hutao/Service/Sign/SignService.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>>();
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user