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)
{
// manually invoke the
Activation.Activate(firstInstance, activatedEventArgs);
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.
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));
}
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 Snap.Hutao.Core;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Windows.UI;
@@ -22,6 +23,8 @@ public class DescriptionTextBlock : ContentControl
private static readonly DependencyProperty DescriptionProperty =
Property<DescriptionTextBlock>.Depend(nameof(Description), string.Empty, OnDescriptionChanged);
private static readonly Regex ColorRegex = new(@"<color=([^>]+)>([^<]+)</color>", RegexOptions.Compiled);
/// <summary>
/// 构造一个新的呈现描述文本的文本块
/// </summary>
@@ -46,17 +49,17 @@ public class DescriptionTextBlock : ContentControl
text.Inlines.Clear();
string[] lines = ((string)e.NewValue).Split('\n');
foreach (string line in lines)
{
MatchCollection matches = Regex.Matches(line, @"<color=([^>]+)>([^<]+)</color>");
string left, right = line;
foreach (Match match in matches)
foreach (Match match in ColorRegex.Matches(line))
{
string matched = match.Groups[0].Value;
int matchPosition = right.IndexOf(matched);
string fullMatch = match.Groups[0].Value;
int matchPosition = right.IndexOf(fullMatch);
left = right[..matchPosition];
right = right[(matchPosition + matched.Length)..];
right = right[(matchPosition + fullMatch.Length)..];
if (!string.IsNullOrWhiteSpace(left))
{
@@ -66,7 +69,7 @@ public class DescriptionTextBlock : ContentControl
string hexColor = match.Groups[1].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))
@@ -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);
byte r = (byte)Convert.ToUInt32(hex.Substring(0, 2), 16);
byte g = (byte)Convert.ToUInt32(hex.Substring(2, 2), 16);
byte b = (byte)Convert.ToUInt32(hex.Substring(4, 2), 16);
return new SolidColorBrush(Color.FromArgb(255, r, g, b));
[FieldOffset(3)]
public byte R;
[FieldOffset(2)]
public byte G;
[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
{
logger.LogInformation(EventIds.CacheRemoveFile, "Removing file {file}", file);
await file.DeleteAsync().AsTask().ConfigureAwait(false);
}
catch

View File

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

View File

@@ -36,7 +36,7 @@ internal static partial class IocHttpClientConfiguration
{
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-app_version", CoreEnvironment.HoyolabXrpcVersion);
client.DefaultRequestHeaders.Add("x-rpc-client_type", "5");
client.DefaultRequestHeaders.Add("x-rpc-device_id", CoreEnvironment.HoyolabDeviceId);
}

View File

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

View File

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

View File

@@ -54,18 +54,19 @@ 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
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
lock (logDbContextLock)
{
logDbContext.Logs.Add(entry);
logDbContext.SaveChanges();
}
}

View File

@@ -10,14 +10,14 @@ using System.Linq;
namespace Snap.Hutao.Core.Logging;
/// <summary>
/// The provider for the <see cref="DebugLogger"/>.
/// The provider for the <see cref="DatebaseLogger"/>.
/// </summary>
[ProviderAlias("Database")]
public sealed class DatebaseLoggerProvider : ILoggerProvider
{
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 LogDbContext LogDbContext

View File

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

View File

@@ -28,10 +28,12 @@ internal static class LocalSetting
/// <param name="key">键</param>
/// <param name="defaultValue">默认值</param>
/// <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))
{
// unbox the value
return value is null ? defaultValue : (T)value;
}
else
@@ -48,9 +50,8 @@ internal static class LocalSetting
/// <param name="key">键</param>
/// <param name="value">值</param>
/// <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 value;
return Container.Values[key] = 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>
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 current.Token;
return waitingItems.GetOrAdd(item, new CancellationTokenSource()).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="source">源</param>
/// <returns>合成效果画刷</returns>
public static CompositionEffectBrush CompositeGrayScaleEffectBrush(this Compositor compositor, CompositionBrush source)
public static CompositionEffectBrush CompositeGrayScaleEffectBrush(
this Compositor compositor,
CompositionBrush source)
{
GrayscaleEffect effect = new()
{

View File

@@ -15,23 +15,14 @@ namespace Snap.Hutao;
public static class Program
{
private static volatile DispatcherQueue? dispatcherQueue;
private static volatile SynchronizationContext? context;
/// <summary>
/// 主线程调度器队列
/// </summary>
public static DispatcherQueue UIDispatcherQueue
{
get => Must.NotNull(dispatcherQueue!);
}
/// <summary>
/// 异步切换到主线程
/// </summary>
/// <returns>等待体</returns>
public static SynchronizationContextAwaitable SwitchToMainThreadAsync()
public static DispatherQueueSwitchOperation SwitchToMainThreadAsync()
{
return new SynchronizationContextAwaitable(context!);
return new(dispatcherQueue!);
}
[DllImport("Microsoft.ui.xaml.dll")]
@@ -47,7 +38,7 @@ public static class Program
Application.Start(p =>
{
dispatcherQueue = DispatcherQueue.GetForCurrentThread();
context = new DispatcherQueueSynchronizationContext(dispatcherQueue);
DispatcherQueueSynchronizationContext context = new(dispatcherQueue);
SynchronizationContext.SetSynchronizationContext(context);
_ = 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 Snap.Hutao.Core;
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Extension;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Navigation;
using Snap.Hutao.View.Page;
@@ -42,11 +43,13 @@ public sealed partial class MainView : UserControl
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))
{
ILogger<MainView> logger = Ioc.Default.GetRequiredService<ILogger<MainView>>();

View File

@@ -42,7 +42,6 @@
ItemsSource="{Binding Archives,Mode=OneWay}"
SelectedItem="{Binding SelectedArchive,Mode=TwoWay}"/>
</AppBarElementContainer>
<CommandBar.SecondaryCommands>
<AppBarButton
Icon="Add"
Label="创建新存档"
@@ -60,8 +59,6 @@
Icon="OpenFile"
Label="从UIAF文件导入"
Command="{Binding ImportUIAFFromFileCommand}"/>
</CommandBar.SecondaryCommands>
</CommandBar>
<SplitView
Grid.Row="1"

View File

@@ -5,9 +5,8 @@ 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 Snap.Hutao.Service.Sign;
using System.Text;
using Windows.System;
namespace Snap.Hutao.ViewModel;
@@ -19,8 +18,7 @@ namespace Snap.Hutao.ViewModel;
internal class ExperimentalFeaturesViewModel : ObservableObject
{
private readonly IFileSystemLocation hutaoLocation;
private readonly IUserService userService;
private readonly SignClient signClient;
private readonly ISignService signService;
private readonly IInfoBarService infoBarService;
/// <summary>
@@ -28,19 +26,16 @@ internal class ExperimentalFeaturesViewModel : ObservableObject
/// </summary>
/// <param name="asyncRelayCommandFactory">异步命令工厂</param>
/// <param name="hutaoLocation">数据文件夹</param>
/// <param name="userService">用户服务</param>
/// <param name="signClient">签到客户端</param>
/// <param name="infoBarService">信息栏服务</param>
/// <param name="signService">签到客户端</param>
/// <param name="infoBarService">信息条服务</param>
public ExperimentalFeaturesViewModel(
IAsyncRelayCommandFactory asyncRelayCommandFactory,
HutaoLocation hutaoLocation,
IUserService userService,
SignClient signClient,
ISignService signService,
IInfoBarService infoBarService)
{
this.hutaoLocation = hutaoLocation;
this.userService = userService;
this.signClient = signClient;
this.signService = signService;
this.infoBarService = infoBarService;
OpenCacheFolderCommand = asyncRelayCommandFactory.Create(OpenCacheFolderAsync);
@@ -73,20 +68,15 @@ internal class ExperimentalFeaturesViewModel : ObservableObject
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())
{
foreach (UserGameRole role in user.UserGameRoles)
{
Response<SignInResult>? result = await signClient.SignAsync(user, role);
if (result != null)
{
infoBarService.Information(result.Message);
}
SignResult result = await signService.SignForAllAsync(token);
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;
}
/// <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>
@@ -106,13 +117,24 @@ internal class SignClient
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>
/// <param name="user">用户</param>
/// <param name="role">角色</param>
/// <param name="token">取消令牌</param>
/// <returns>签到消息</returns>
/// <returns>签到结果</returns>
public async Task<Response<SignInResult>?> SignAsync(User user, UserGameRole role, CancellationToken token = default)
{
HttpResponseMessage response = await httpClient

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>
public enum KnownReturnCode
public enum KnownReturnCode : int
{
/// <summary>
/// 无效请求
@@ -18,6 +18,11 @@ public enum KnownReturnCode
/// </summary>
AlreadySignedIn = -5003,
/// <summary>
/// 尚未登录
/// </summary>
NotLoggedIn = -100,
/// <summary>
/// 验证密钥过期
/// </summary>