diff --git a/src/Snap.Hutao/Snap.Hutao/.filenesting.json b/src/Snap.Hutao/Snap.Hutao/.filenesting.json new file mode 100644 index 00000000..4018919b --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/.filenesting.json @@ -0,0 +1,31 @@ +{ + "help": "https://go.microsoft.com/fwlink/?linkid=866610", + "dependentFileProviders": { + "add": { + "extensionToExtension": { + "add": { + ".json": [ ".txt" ] + } + }, + "pathSegment": { + "add": { + ".*": [ ".cs" ] + } + }, + "fileSuffixToExtension": { + "add": { + "DesignTimeFactory.cs": [".cs"] + } + }, + "fileToFile": { + "add": { + "app.manifest": [ "App.xaml.cs" ], + "Package.appxmanifest": [ "App.xaml.cs" ], + "GlobalUsing.cs": [ "Program.cs" ], + ".filenesting.json": [ "Program.cs" ], + ".editorconfig": [ "Program.cs" ] + } + } + } + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Assets/Logo.ico b/src/Snap.Hutao/Snap.Hutao/Assets/Logo.ico new file mode 100644 index 00000000..0c93b948 Binary files /dev/null and b/src/Snap.Hutao/Snap.Hutao/Assets/Logo.ico differ diff --git a/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs b/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs index e090853d..43d12c36 100644 --- a/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs +++ b/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs @@ -61,6 +61,11 @@ public class AppDbContext : DbContext /// public DbSet GameAccounts { get; set; } = default!; + /// + /// 实时便笺 + /// + public DbSet DailyNotes { get; set; } = default!; + /// /// 构造一个临时的应用程序数据库上下文 /// @@ -76,6 +81,7 @@ public class AppDbContext : DbContext { modelBuilder .ApplyConfiguration(new AvatarInfoConfiguration()) - .ApplyConfiguration(new UserConfiguration()); + .ApplyConfiguration(new UserConfiguration()) + .ApplyConfiguration(new DailyNoteEntryConfiguration()); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs index c8bae443..7f6b3524 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs @@ -15,12 +15,12 @@ namespace Snap.Hutao.Core; /// internal static class CoreEnvironment { - // 计算过程:https://gist.github.com/Lightczx/373c5940b36e24b25362728b52dec4fd + // 计算过程:https://github.com/UIGF-org/Hoyolab.Salt /// /// 动态密钥1的盐 /// - public const string DynamicSecret1Salt = "yUZ3s0Sna1IrSNfk29Vo6vRapdOyqyhB"; + public const string DynamicSecret1Salt = "jEpJb9rRARU2rXDA9qYbZ3selxkuct9a"; /// /// 动态密钥2的盐 @@ -35,7 +35,7 @@ internal static class CoreEnvironment /// /// 米游社 Rpc 版本 /// - public const string HoyolabXrpcVersion = "2.38.1"; + public const string HoyolabXrpcVersion = "2.40.1"; /// /// 标准UA diff --git a/src/Snap.Hutao/Snap.Hutao/Core/TaskSchedulerHelper.cs b/src/Snap.Hutao/Snap.Hutao/Core/TaskSchedulerHelper.cs new file mode 100644 index 00000000..cc906c9c --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/TaskSchedulerHelper.cs @@ -0,0 +1,38 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.Win32.TaskScheduler; +using SchedulerTask = Microsoft.Win32.TaskScheduler.Task; + +namespace Snap.Hutao.Core; + +/// +/// 任务计划器服务 +/// +internal class TaskSchedulerHelper +{ + private const string DailyNoteRefreshTaskName = "SnapHutaoDailyNoteRefreshTask"; + + /// + /// 注册实时便笺刷新任务 + /// + /// 间隔(秒) + public void RegisterForDailyNoteRefresh(int interval) + { + TimeSpan intervalTime = TimeSpan.FromSeconds(interval); + if (TaskService.Instance.GetTask(DailyNoteRefreshTaskName) is SchedulerTask targetTask) + { + TimeTrigger? trigger = targetTask.Definition.Triggers[0] as TimeTrigger; + trigger!.Repetition.Interval = intervalTime; + targetTask.RegisterChanges(); + } + else + { + TaskDefinition task = TaskService.Instance.NewTask(); + task.RegistrationInfo.Description = "胡桃实时便笺刷新任务 | 请勿编辑或删除。"; + task.Triggers.Add(new TimeTrigger() { Repetition = new(intervalTime, TimeSpan.Zero), }); + task.Actions.Add("explorer", "hutao://DailyNote/Refresh"); + TaskService.Instance.RootFolder.RegisterTaskDefinition(DailyNoteRefreshTaskName, task); + } + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Achievement/AchievementTriggerType.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/BackdropType.cs similarity index 50% rename from src/Snap.Hutao/Snap.Hutao/Model/Metadata/Achievement/AchievementTriggerType.cs rename to src/Snap.Hutao/Snap.Hutao/Core/Windowing/BackdropType.cs index ec1d0dcb..b4c51158 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Achievement/AchievementTriggerType.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/BackdropType.cs @@ -1,25 +1,30 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -namespace Snap.Hutao.Model.Metadata.Achievement; +namespace Snap.Hutao.Core.Windowing; /// -/// 成就触发器类型 +/// 背景类型 /// -public enum AchievementTriggerType +public enum BackdropType { /// - /// 任务 + /// 无 /// - Quest = 1, + None = 0, /// - /// 子任务 + /// 亚克力 /// - SubQuest = 2, + Acrylic, /// - /// 日常任务 + /// 云母 /// - DailyTask = 3, + Mica, + + /// + /// 变种云母 + /// + MicaAlt, } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/ExtendedWindow.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/ExtendedWindow.cs index ab63e572..9a87ddd6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/ExtendedWindow.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/ExtendedWindow.cs @@ -1,12 +1,16 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using CommunityToolkit.Mvvm.Messaging; using Microsoft.UI; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; using Snap.Hutao.Core.Logging; using Snap.Hutao.Extension; +using Snap.Hutao.Message; using Snap.Hutao.Win32; +using System.IO; +using Windows.ApplicationModel; using Windows.Graphics; using Windows.UI; using Windows.Win32.Foundation; @@ -15,11 +19,10 @@ using WinRT.Interop; namespace Snap.Hutao.Core.Windowing; /// -/// 窗口管理器 -/// 主要包含了针对窗体的 P/Inoke 逻辑 +/// 扩展窗口 /// /// 窗体类型 -internal sealed class ExtendedWindow +internal sealed class ExtendedWindow : IRecipient where TWindow : Window, IExtendedWindowSource { private readonly HWND handle; @@ -33,8 +36,10 @@ internal sealed class ExtendedWindow private readonly bool useLegacyDragBar; + private SystemBackdrop? systemBackdrop; + /// - /// 构造一个新的窗口状态管理器 + /// 构造一个新的扩展窗口 /// /// 窗口 /// 充当标题栏的元素 @@ -65,6 +70,17 @@ internal sealed class ExtendedWindow return new(window, window.TitleBar); } + /// + public void Receive(BackdropTypeChangedMessage message) + { + if (systemBackdrop != null) + { + systemBackdrop.BackdropType = message.BackdropType; + bool micaApplied = systemBackdrop.TryApply(); + logger.LogInformation(EventIds.BackdropState, "Apply {name} : {result}", nameof(SystemBackdrop), micaApplied ? "succeed" : "failed"); + } + } + private static void UpdateTitleButtonColor(AppWindowTitleBar appTitleBar) { appTitleBar.ButtonBackgroundColor = Colors.Transparent; @@ -102,7 +118,7 @@ internal sealed class ExtendedWindow private void InitializeWindow() { appWindow.Title = "胡桃"; - + appWindow.SetIcon(Path.Combine(Package.Current.InstalledLocation.Path, "Assets/Logos/Logo.ico")); ExtendsContentIntoTitleBar(); Persistence.RecoverOrInit(appWindow, window.PersistSize, window.InitSize); @@ -113,12 +129,14 @@ internal sealed class ExtendedWindow appWindow.Show(true); - bool micaApplied = new SystemBackdrop(window).TryApply(); + systemBackdrop = new(window); + bool micaApplied = systemBackdrop.TryApply(); logger.LogInformation(EventIds.BackdropState, "Apply {name} : {result}", nameof(SystemBackdrop), micaApplied ? "succeed" : "failed"); bool subClassApplied = subclassManager.TrySetWindowSubclass(); logger.LogInformation(EventIds.SubClassing, "Apply {name} : {result}", nameof(WindowSubclassManager), subClassApplied ? "succeed" : "failed"); + Ioc.Default.GetRequiredService().Register(this); window.Closed += OnWindowClosed; } diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/SystemBackdrop.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/SystemBackdrop.cs index 02af6bb5..c21c18ef 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/SystemBackdrop.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/SystemBackdrop.cs @@ -1,9 +1,13 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Microsoft.Extensions.DependencyInjection; using Microsoft.UI.Composition; using Microsoft.UI.Composition.SystemBackdrops; using Microsoft.UI.Xaml; +using Snap.Hutao.Context.Database; +using Snap.Hutao.Core.Database; +using Snap.Hutao.Model.Entity; using System.Runtime.InteropServices; using Windows.System; using WinRT; @@ -18,7 +22,7 @@ public class SystemBackdrop private readonly Window window; private DispatcherQueueHelper? dispatcherQueueHelper; - private MicaController? backdropController; + private ISystemBackdropControllerWithTargets? backdropController; private SystemBackdropConfiguration? configuration; /// @@ -28,38 +32,68 @@ public class SystemBackdrop public SystemBackdrop(Window window) { this.window = window; + using (IServiceScope scope = Ioc.Default.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + SettingEntry entry = appDbContext.Settings.SingleOrAdd(SettingEntry.SystemBackdropType, BackdropType.Mica.ToString()); + BackdropType = Enum.Parse(entry.Value!); + } } + /// + /// 背景类型 + /// + public BackdropType BackdropType { get; set; } + /// /// 尝试设置背景 /// /// 是否设置成功 public bool TryApply() { - if (!MicaController.IsSupported()) + bool isSupport = BackdropType switch + { + BackdropType.Acrylic => DesktopAcrylicController.IsSupported(), + BackdropType.Mica or BackdropType.MicaAlt => MicaController.IsSupported(), + _ => false, + }; + + if (!isSupport) { return false; } else { - dispatcherQueueHelper = new(); - dispatcherQueueHelper.Ensure(); + // Previous one + if (backdropController != null) + { + backdropController.RemoveAllSystemBackdropTargets(); + } + else + { + dispatcherQueueHelper = new(); + dispatcherQueueHelper.Ensure(); + } // Hooking up the policy object - configuration = new(); + configuration = new() + { + IsInputActive = true, // Initial configuration state. + }; + SetConfigurationSourceTheme(configuration); + window.Activated += OnWindowActivated; window.Closed += OnWindowClosed; ((FrameworkElement)window.Content).ActualThemeChanged += OnWindowThemeChanged; - // Initial configuration state. - configuration.IsInputActive = true; - SetConfigurationSourceTheme(configuration); - - backdropController = new() + backdropController = BackdropType switch { - // Mica Alt - Kind = MicaKind.BaseAlt, + BackdropType.Acrylic => new DesktopAcrylicController(), + BackdropType.Mica => new MicaController() { Kind = MicaKind.Base }, + BackdropType.MicaAlt => new MicaController() { Kind = MicaKind.BaseAlt }, + _ => throw Must.NeverHappen(), }; + backdropController.AddSystemBackdropTarget(window.As()); backdropController.SetSystemBackdropConfiguration(configuration); @@ -69,7 +103,7 @@ public class SystemBackdrop private void OnWindowActivated(object sender, WindowActivatedEventArgs args) { - Must.NotNull(configuration!).IsInputActive = args.WindowActivationState != WindowActivationState.Deactivated; + configuration!.IsInputActive = args.WindowActivationState != WindowActivationState.Deactivated; } private void OnWindowClosed(object sender, WindowEventArgs args) diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclassManager.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclassManager.cs index c4c91c02..a53185d7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclassManager.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclassManager.cs @@ -52,9 +52,10 @@ internal class WindowSubclassManager : IDisposable bool titleBarHooked = true; - // only hook up drag bar proc when use legacy Window.ExtendsContentIntoTitleBar + // only hook up drag bar proc when not use legacy Window.ExtendsContentIntoTitleBar if (isLegacyDragBar) { + titleBarHooked = false; hwndDragBar = FindWindowEx(hwnd, default, "DRAG_BAR_WINDOW_CLASS", string.Empty); if (!hwndDragBar.IsNull) @@ -90,6 +91,12 @@ internal class WindowSubclassManager : IDisposable window.ProcessMinMaxInfo((MINMAXINFO*)lParam.Value, scalingFactor); break; } + + case WM_NCRBUTTONDOWN: + case WM_NCRBUTTONUP: + { + return new(0); + } } return DefSubclassProc(hwnd, uMsg, wParam, lParam); diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.Collection.cs b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.Collection.cs new file mode 100644 index 00000000..be1d75c0 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.Collection.cs @@ -0,0 +1,34 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.ObjectModel; + +namespace Snap.Hutao.Extension; + +/// +/// 部分 +/// +public static partial class EnumerableExtension +{ + /// + /// 移除集合中满足条件的项 + /// + /// 集合项类型 + /// 集合 + /// 是否应当移除 + /// 移除的个数 + public static int RemoveWhere(this Collection collection, Func shouldRemovePredicate) + { + int count = 0; + foreach (T item in collection.ToList()) + { + if (shouldRemovePredicate.Invoke(item)) + { + collection.Remove(item); + count++; + } + } + + return count; + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.Dictionary.cs b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.Dictionary.cs new file mode 100644 index 00000000..528121e3 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.Dictionary.cs @@ -0,0 +1,80 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Snap.Hutao.Extension; + +/// +/// 部分 +/// +public static partial class EnumerableExtension +{ + /// + /// 获取值或默认值 + /// + /// 键类型 + /// 值类型 + /// 字典 + /// 键 + /// 默认值 + /// 结果值 + public static TValue? GetValueOrDefault2(this IDictionary dictionary, TKey key, TValue? defaultValue = default) + where TKey : notnull + { + if (dictionary.TryGetValue(key, out TValue? value)) + { + return value; + } + + return defaultValue; + } + + /// + /// 增加计数 + /// + /// 键类型 + /// 字典 + /// 键 + public static void Increase(this Dictionary dict, TKey key) + where TKey : notnull + { + ++CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _); + } + + /// + /// 增加计数 + /// + /// 键类型 + /// 字典 + /// 键 + /// 增加的值 + public static void Increase(this Dictionary dict, TKey key, int value) + where TKey : notnull + { + // ref the value, so that we can manipulate it outside the dict. + ref int current = ref CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _); + current += value; + } + + /// + /// 增加计数 + /// + /// 键类型 + /// 字典 + /// 键 + /// 是否存在键值 + public static bool TryIncrease(this Dictionary dict, TKey key) + where TKey : notnull + { + ref int value = ref CollectionsMarshal.GetValueRefOrNullRef(dict, key); + if (!Unsafe.IsNullRef(ref value)) + { + ++value; + return true; + } + + return false; + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.List.cs b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.List.cs new file mode 100644 index 00000000..a8cfb3c0 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.List.cs @@ -0,0 +1,65 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using System.Runtime.InteropServices; + +namespace Snap.Hutao.Extension; + +/// +/// 部分 +/// +public static partial class EnumerableExtension +{ + /// + public static double AverageNoThrow(this List source) + { + Span span = CollectionsMarshal.AsSpan(source); + + if (span.IsEmpty) + { + return 0; + } + + long sum = 0; + + for (int i = 0; i < span.Length; i++) + { + sum += span[i]; + } + + return (double)sum / span.Length; + } + + /// + /// 如果传入列表不为空则原路返回, + /// 如果传入列表为空返回一个空的列表 + /// + /// 源类型 + /// 源 + /// 源列表或空列表 + public static List EmptyIfNull(this List? source) + { + return source ?? new(); + } + + /// + /// 移除表中首个满足条件的项 + /// + /// 项的类型 + /// 表 + /// 是否应当移除 + /// 是否移除了元素 + public static bool RemoveFirstWhere(this IList list, Func shouldRemovePredicate) + { + for (int i = 0; i < list.Count; i++) + { + if (shouldRemovePredicate.Invoke(list[i])) + { + list.RemoveAt(i); + return true; + } + } + + return false; + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs index b869cf5c..149d911b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs @@ -1,9 +1,6 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - namespace Snap.Hutao.Extension; /// @@ -11,26 +8,6 @@ namespace Snap.Hutao.Extension; /// public static partial class EnumerableExtension { - /// - public static double AverageNoThrow(this List source) - { - Span span = CollectionsMarshal.AsSpan(source); - - if (span.IsEmpty) - { - return 0; - } - - long sum = 0; - - for (int i = 0; i < span.Length; i++) - { - sum += span[i]; - } - - return (double)sum / span.Length; - } - /// /// 计数 /// @@ -63,18 +40,6 @@ public static partial class EnumerableExtension return source ?? Enumerable.Empty(); } - /// - /// 如果传入列表不为空则原路返回, - /// 如果传入列表为空返回一个空的列表 - /// - /// 源类型 - /// 源 - /// 源列表或空列表 - public static List EmptyIfNull(this List? source) - { - return source ?? new(); - } - /// /// 将源转换为仅包含单个元素的枚举 /// @@ -99,94 +64,6 @@ public static partial class EnumerableExtension return source.FirstOrDefault(predicate) ?? source.FirstOrDefault(); } - /// - /// 获取值或默认值 - /// - /// 键类型 - /// 值类型 - /// 字典 - /// 键 - /// 默认值 - /// 结果值 - public static TValue? GetValueOrDefault2(this IDictionary dictionary, TKey key, TValue? defaultValue = default) - where TKey : notnull - { - if (dictionary.TryGetValue(key, out TValue? value)) - { - return value; - } - - return defaultValue; - } - - /// - /// 增加计数 - /// - /// 键类型 - /// 字典 - /// 键 - public static void Increase(this Dictionary dict, TKey key) - where TKey : notnull - { - ++CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _); - } - - /// - /// 增加计数 - /// - /// 键类型 - /// 字典 - /// 键 - /// 增加的值 - public static void Increase(this Dictionary dict, TKey key, int value) - where TKey : notnull - { - // ref the value, so that we can manipulate it outside the dict. - ref int current = ref CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _); - current += value; - } - - /// - /// 增加计数 - /// - /// 键类型 - /// 字典 - /// 键 - /// 是否存在键值 - public static bool TryIncrease(this Dictionary dict, TKey key) - where TKey : notnull - { - ref int value = ref CollectionsMarshal.GetValueRefOrNullRef(dict, key); - if (!Unsafe.IsNullRef(ref value)) - { - ++value; - return true; - } - - return false; - } - - /// - /// 移除表中首个满足条件的项 - /// - /// 项的类型 - /// 表 - /// 是否应当移除 - /// 是否移除了元素 - public static bool RemoveFirstWhere(this IList list, Func shouldRemovePredicate) - { - for (int i = 0; i < list.Count; i++) - { - if (shouldRemovePredicate.Invoke(list[i])) - { - list.RemoveAt(i); - return true; - } - } - - return false; - } - /// public static Dictionary ToDictionaryOverride(this IEnumerable source, Func keySelector) where TKey : notnull diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/StringBuilderExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Extension/StringBuilderExtensions.cs new file mode 100644 index 00000000..11a1e954 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Extension/StringBuilderExtensions.cs @@ -0,0 +1,24 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using System.Text; + +namespace Snap.Hutao.Extension; + +/// +/// 扩展方法 +/// +public static class StringBuilderExtensions +{ + /// + /// 当条件符合时执行 + /// + /// 字符串建造器 + /// 条件 + /// 附加的字符串 + /// 同一个字符串建造器 + public static StringBuilder AppendIf(this StringBuilder sb, bool condition, string? value) + { + return condition ? sb.Append(value) : sb; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs b/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs index 25437c8a..0000ae2a 100644 --- a/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs @@ -35,6 +35,7 @@ public sealed partial class LaunchGameWindow : Window, IDisposable, IExtendedWin scope = scopeFactory.CreateScope(); RootGrid.DataContext = scope.ServiceProvider.GetRequiredService(); + Closed += (s, e) => Dispose(); } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Message/BackdropTypeChangedMessage.cs b/src/Snap.Hutao/Snap.Hutao/Message/BackdropTypeChangedMessage.cs new file mode 100644 index 00000000..2a2085b9 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Message/BackdropTypeChangedMessage.cs @@ -0,0 +1,26 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core.Windowing; + +namespace Snap.Hutao.Message; + +/// +/// 背景类型改变消息 +/// +internal class BackdropTypeChangedMessage +{ + /// + /// 构造一个新的背景类型改变消息 + /// + /// 背景类型 + public BackdropTypeChangedMessage(BackdropType backdropType) + { + BackdropType = backdropType; + } + + /// + /// 背景类型 + /// + public BackdropType BackdropType { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Message/UserChangedMessage.cs b/src/Snap.Hutao/Snap.Hutao/Message/UserChangedMessage.cs index 3bf2443a..57d1165f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Message/UserChangedMessage.cs +++ b/src/Snap.Hutao/Snap.Hutao/Message/UserChangedMessage.cs @@ -1,7 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Snap.Hutao.Model.Binding; +using Snap.Hutao.Model.Binding.User; namespace Snap.Hutao.Message; diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/20221108081525_DailyNoteEntry.Designer.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/20221108081525_DailyNoteEntry.Designer.cs new file mode 100644 index 00000000..43118a2d --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Migrations/20221108081525_DailyNoteEntry.Designer.cs @@ -0,0 +1,282 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Snap.Hutao.Context.Database; + +#nullable disable + +namespace Snap.Hutao.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20221108081525_DailyNoteEntry")] + partial class DailyNoteEntry + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.10"); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ArchiveId") + .HasColumnType("TEXT"); + + b.Property("Current") + .HasColumnType("INTEGER"); + + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Time") + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.HasIndex("ArchiveId"); + + b.ToTable("achievements"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsSelected") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.ToTable("achievement_archives"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Info") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Uid") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.ToTable("avatar_infos"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("DailyNote") + .HasColumnType("TEXT"); + + b.Property("DailyTaskNotify") + .HasColumnType("INTEGER"); + + b.Property("DailyTaskNotifySuppressed") + .HasColumnType("INTEGER"); + + b.Property("ExpeditionNotify") + .HasColumnType("INTEGER"); + + b.Property("ExpeditionNotifySuppressed") + .HasColumnType("INTEGER"); + + b.Property("HomeCoinNotifySuppressed") + .HasColumnType("INTEGER"); + + b.Property("HomeCoinNotifyThreshold") + .HasColumnType("INTEGER"); + + b.Property("ResinNotifySuppressed") + .HasColumnType("INTEGER"); + + b.Property("ResinNotifyThreshold") + .HasColumnType("INTEGER"); + + b.Property("ShowInHomeWidget") + .HasColumnType("INTEGER"); + + b.Property("TransformerNotify") + .HasColumnType("INTEGER"); + + b.Property("TransformerNotifySuppressed") + .HasColumnType("INTEGER"); + + b.Property("Uid") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.HasIndex("UserId"); + + b.ToTable("daily_notes"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsSelected") + .HasColumnType("INTEGER"); + + b.Property("Uid") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.ToTable("gacha_archives"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ArchiveId") + .HasColumnType("TEXT"); + + b.Property("GachaType") + .HasColumnType("INTEGER"); + + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("INTEGER"); + + b.Property("QueryType") + .HasColumnType("INTEGER"); + + b.Property("Time") + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.HasIndex("ArchiveId"); + + b.ToTable("gacha_items"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AttachUid") + .HasColumnType("TEXT"); + + b.Property("MihoyoSDK") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("InnerId"); + + b.ToTable("game_accounts"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b => + { + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Key"); + + b.ToTable("settings"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Cookie") + .HasColumnType("TEXT"); + + b.Property("IsSelected") + .HasColumnType("INTEGER"); + + b.HasKey("InnerId"); + + b.ToTable("users"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b => + { + b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive") + .WithMany() + .HasForeignKey("ArchiveId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Archive"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b => + { + b.HasOne("Snap.Hutao.Model.Entity.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b => + { + b.HasOne("Snap.Hutao.Model.Entity.GachaArchive", "Archive") + .WithMany() + .HasForeignKey("ArchiveId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Archive"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/20221108081525_DailyNoteEntry.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/20221108081525_DailyNoteEntry.cs new file mode 100644 index 00000000..860460b0 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Migrations/20221108081525_DailyNoteEntry.cs @@ -0,0 +1,55 @@ +// +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Snap.Hutao.Migrations +{ + public partial class DailyNoteEntry : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "daily_notes", + columns: table => new + { + InnerId = table.Column(type: "TEXT", nullable: false), + UserId = table.Column(type: "TEXT", nullable: false), + Uid = table.Column(type: "TEXT", nullable: false), + DailyNote = table.Column(type: "TEXT", nullable: true), + ResinNotifyThreshold = table.Column(type: "INTEGER", nullable: false), + ResinNotifySuppressed = table.Column(type: "INTEGER", nullable: false), + HomeCoinNotifyThreshold = table.Column(type: "INTEGER", nullable: false), + HomeCoinNotifySuppressed = table.Column(type: "INTEGER", nullable: false), + TransformerNotify = table.Column(type: "INTEGER", nullable: false), + TransformerNotifySuppressed = table.Column(type: "INTEGER", nullable: false), + DailyTaskNotify = table.Column(type: "INTEGER", nullable: false), + DailyTaskNotifySuppressed = table.Column(type: "INTEGER", nullable: false), + ExpeditionNotify = table.Column(type: "INTEGER", nullable: false), + ExpeditionNotifySuppressed = table.Column(type: "INTEGER", nullable: false), + ShowInHomeWidget = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_daily_notes", x => x.InnerId); + table.ForeignKey( + name: "FK_daily_notes_users_UserId", + column: x => x.UserId, + principalTable: "users", + principalColumn: "InnerId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_daily_notes_UserId", + table: "daily_notes", + column: "UserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "daily_notes"); + } + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs index a20c886d..1f24f21b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs +++ b/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs @@ -82,6 +82,62 @@ namespace Snap.Hutao.Migrations b.ToTable("avatar_infos"); }); + modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("DailyNote") + .HasColumnType("TEXT"); + + b.Property("DailyTaskNotify") + .HasColumnType("INTEGER"); + + b.Property("DailyTaskNotifySuppressed") + .HasColumnType("INTEGER"); + + b.Property("ExpeditionNotify") + .HasColumnType("INTEGER"); + + b.Property("ExpeditionNotifySuppressed") + .HasColumnType("INTEGER"); + + b.Property("HomeCoinNotifySuppressed") + .HasColumnType("INTEGER"); + + b.Property("HomeCoinNotifyThreshold") + .HasColumnType("INTEGER"); + + b.Property("ResinNotifySuppressed") + .HasColumnType("INTEGER"); + + b.Property("ResinNotifyThreshold") + .HasColumnType("INTEGER"); + + b.Property("ShowInHomeWidget") + .HasColumnType("INTEGER"); + + b.Property("TransformerNotify") + .HasColumnType("INTEGER"); + + b.Property("TransformerNotifySuppressed") + .HasColumnType("INTEGER"); + + b.Property("Uid") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.HasIndex("UserId"); + + b.ToTable("daily_notes"); + }); + modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b => { b.Property("InnerId") @@ -197,6 +253,17 @@ namespace Snap.Hutao.Migrations b.Navigation("Archive"); }); + modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b => + { + b.HasOne("Snap.Hutao.Model.Entity.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b => { b.HasOne("Snap.Hutao.Model.Entity.GachaArchive", "Archive") diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/LaunchGame/LaunchScheme.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/LaunchGame/LaunchScheme.cs index 4a29e9b9..e1d8adf9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Binding/LaunchGame/LaunchScheme.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/LaunchGame/LaunchScheme.cs @@ -18,11 +18,13 @@ public class LaunchScheme /// 通道 /// 通道描述字符串 /// 子通道 - public LaunchScheme(string name, string channel, string subChannel) + /// 启动器Id + public LaunchScheme(string name, string channel, string subChannel, string launcherId) { Name = name; Channel = channel; SubChannel = subChannel; + LauncherId = launcherId; } /// @@ -39,4 +41,9 @@ public class LaunchScheme /// 子通道 /// public string SubChannel { get; set; } + + /// + /// 启动器Id + /// + public string LauncherId { get; set; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/User.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/User/User.cs similarity index 99% rename from src/Snap.Hutao/Snap.Hutao/Model/Binding/User.cs rename to src/Snap.Hutao/Snap.Hutao/Model/Binding/User/User.cs index d9423096..4103abf5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Binding/User.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/User/User.cs @@ -8,7 +8,7 @@ using Snap.Hutao.Web.Hoyolab.Bbs.User; using Snap.Hutao.Web.Hoyolab.Takumi.Binding; using EntityUser = Snap.Hutao.Model.Entity.User; -namespace Snap.Hutao.Model.Binding; +namespace Snap.Hutao.Model.Binding.User; /// /// 用于视图绑定的用户 @@ -146,4 +146,4 @@ public class User : ObservableObject return UserInfo != null && UserGameRoles.Any(); } -} \ No newline at end of file +} diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/User/UserAndRole.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/User/UserAndRole.cs new file mode 100644 index 00000000..4b77ce39 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/User/UserAndRole.cs @@ -0,0 +1,34 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Web.Hoyolab.Takumi.Binding; +using EntityUser = Snap.Hutao.Model.Entity.User; + +namespace Snap.Hutao.Model.Binding.User; + +/// +/// 角色与实体用户 +/// +public class UserAndRole +{ + /// + /// 构造一个新的实体用户与角色 + /// + /// 实体用户 + /// 角色 + public UserAndRole(EntityUser user, UserGameRole role) + { + User = user; + Role = role; + } + + /// + /// 实体用户 + /// + public EntityUser User { get; private set; } + + /// + /// 角色 + /// + public UserGameRole Role { get; private set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/Configuration/DailyNoteEntryConfiguration.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Configuration/DailyNoteEntryConfiguration.cs new file mode 100644 index 00000000..c8054ed4 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Configuration/DailyNoteEntryConfiguration.cs @@ -0,0 +1,21 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Snap.Hutao.Model.Entity.Configuration; + +/// +/// 实时便笺入口配置 +/// +internal class DailyNoteEntryConfiguration : IEntityTypeConfiguration +{ + /// + public void Configure(EntityTypeBuilder builder) + { + builder.Property(e => e.DailyNote) + .HasColumnType("TEXT") + .HasConversion>(); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/DailyNoteEntry.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/DailyNoteEntry.cs new file mode 100644 index 00000000..6883985f --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/DailyNoteEntry.cs @@ -0,0 +1,98 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Snap.Hutao.Model.Entity; + +/// +/// 实时便笺入口 +/// +[Table("daily_notes")] +public class DailyNoteEntry +{ + /// + /// 内部Id + /// + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid InnerId { get; set; } + + /// + /// 用户Id + /// + [ForeignKey(nameof(UserId))] + public Guid UserId { get; set; } + + /// + /// 用户 + /// + public User User { get; set; } = default!; + + /// + /// Uid + /// + public string Uid { get; set; } = default!; + + /// + /// Json!!! 实时便笺 + /// + public DailyNote? DailyNote { get; set; } + + /// + /// 树脂提醒阈值 + /// + public int ResinNotifyThreshold { get; set; } + + /// + /// 用于判断树脂是否继续提醒 + /// + public bool ResinNotifySuppressed { get; set; } + + /// + /// 洞天宝钱提醒阈值 + /// + public int HomeCoinNotifyThreshold { get; set; } + + /// + /// 用于判断洞天宝钱是否继续提醒 + /// + public bool HomeCoinNotifySuppressed { get; set; } + + /// + /// 参量质变仪提醒 + /// + public bool TransformerNotify { get; set; } + + /// + /// 用于判断参量质变仪是否继续提醒 + /// + public bool TransformerNotifySuppressed { get; set; } + + /// + /// 每日委托提醒 + /// + public bool DailyTaskNotify { get; set; } + + /// + /// 用于判断每日委托是否继续提醒 + /// + public bool DailyTaskNotifySuppressed { get; set; } + + /// + /// 探索派遣提醒 + /// + public bool ExpeditionNotify { get; set; } + + /// + /// 用于判断探索派遣是否继续提醒 + /// + public bool ExpeditionNotifySuppressed { get; set; } + + /// + /// 是否在主页显示小组件 + /// + public bool ShowInHomeWidget { get; set; } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/GachaItem.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/GachaItem.cs index a495f57f..695a64fd 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/GachaItem.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/GachaItem.cs @@ -26,6 +26,7 @@ public class GachaItem /// /// 存档 /// + [ForeignKey(nameof(ArchiveId))] public GachaArchive Archive { get; set; } = default!; /// diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs index 8a947f70..30f57d73 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs @@ -22,6 +22,11 @@ public class SettingEntry /// public const string IsEmptyHistoryWishVisible = "IsEmptyHistoryWishVisible"; + /// + /// 窗口背景类型 + /// + public const string SystemBackdropType = "SystemBackdropType"; + /// /// 启动游戏 全屏 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Achievement/Achievement.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Achievement/Achievement.cs index 6446faa3..3d8ced19 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Achievement/Achievement.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Achievement/Achievement.cs @@ -43,11 +43,6 @@ public class Achievement /// public int Progress { get; set; } - /// - /// 触发器 - /// - public IEnumerable? Triggers { get; set; } - /// /// 图标 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Achievement/AchievementTrigger.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Achievement/AchievementTrigger.cs deleted file mode 100644 index 5178701f..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Achievement/AchievementTrigger.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -namespace Snap.Hutao.Model.Metadata.Achievement; - -/// -/// 成就触发器 -/// -public class AchievementTrigger -{ - /// - /// 触发器类型 - /// - public AchievementTriggerType Type { get; set; } - - /// - /// Id - /// - public string Id { get; set; } = default!; - - /// - /// 标题 - /// - public string Title { get; set; } = default!; - - /// - /// 描述 - /// - public string Description { get; set; } = default!; -} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs new file mode 100644 index 00000000..b46f505c --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs @@ -0,0 +1,44 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.EntityFrameworkCore; +using Snap.Hutao.Context.Database; +using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord; + +namespace Snap.Hutao.Service.DailyNote; + +/// +/// 实时便笺服务 +/// +[Injection(InjectAs.Singleton)] +internal class DailyNoteService +{ + private readonly AppDbContext appDbContext; + + /// + /// 构造一个新的实时便笺服务 + /// + /// 数据库上下文 + public DailyNoteService(AppDbContext appDbContext) + { + this.appDbContext = appDbContext; + } + + public async ValueTask RefreshDailyNotesAndNotifyAsync() + { + GameRecordClient gameRecordClient = Ioc.Default.GetRequiredService(); + + foreach (Model.Entity.DailyNoteEntry entry in appDbContext.DailyNotes.Include(n => n.User)) + { + entry.DailyNote = await gameRecordClient.GetDialyNoteAsync(entry.User, entry.Uid).ConfigureAwait(false); + } + + await appDbContext.SaveChangesAsync().ConfigureAwait(false); + + } + + private async ValueTask NotifyDailyNoteAsync() + { + + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlStokenProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlStokenProvider.cs index eeb68976..8e960ef8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlStokenProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlStokenProvider.cs @@ -35,7 +35,7 @@ internal class GachaLogUrlStokenProvider : IGachaLogUrlProvider /// public async Task> GetQueryAsync() { - Model.Binding.User? user = userService.Current; + Model.Binding.User.User? user = userService.Current; if (user != null && user.SelectedUserGameRole != null) { if (user.Cookie!.ContainsSToken()) diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs index 5f4b7c5d..41e8f631 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs @@ -168,25 +168,38 @@ internal class GameService : IGameService elements = IniSerializer.Deserialize(readStream).ToList(); } + bool changed = false; + foreach (IniElement element in elements) { if (element is IniParameter parameter) { if (parameter.Key == "channel") { - parameter.Value = scheme.Channel; + if (parameter.Value != scheme.Channel) + { + parameter.Value = scheme.Channel; + changed = true; + } } if (parameter.Key == "sub_channel") { - parameter.Value = scheme.SubChannel; + if (parameter.Value != scheme.SubChannel) + { + parameter.Value = scheme.SubChannel; + changed = true; + } } } } - using (FileStream writeStream = File.Create(configPath)) + if (changed) { - IniSerializer.Serialize(writeStream, elements); + using (FileStream writeStream = File.Create(configPath)) + { + IniSerializer.Serialize(writeStream, elements); + } } } @@ -340,6 +353,7 @@ internal class GameService : IGameService if (isOk) { + await ThreadHelper.SwitchToMainThreadAsync(); gameAccount.UpdateName(name); // sync database diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/ManualGameLocator.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/ManualGameLocator.cs index d75aa184..8c616d37 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/ManualGameLocator.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/ManualGameLocator.cs @@ -44,7 +44,7 @@ internal class ManualGameLocator : IGameLocator { FileOpenPicker picker = pickerFactory.GetFileOpenPicker(); picker.FileTypeFilter.Add(".exe"); - picker.SuggestedStartLocation = PickerLocationId.ComputerFolder; + picker.SuggestedStartLocation = PickerLocationId.Desktop; // System.Runtime.InteropServices.COMException (0x80004005): Error HRESULT E_FAIL has been returned from a call to a COM component. // Not sure what's going on here. diff --git a/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs b/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs index afed7251..e9bd7318 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. using Microsoft.UI.Xaml.Controls; -using Snap.Hutao.Core.Threading; using Snap.Hutao.Service.Abstraction; namespace Snap.Hutao.Service; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs index bd189307..33515ca9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs @@ -1,10 +1,9 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Snap.Hutao.Core.Threading; using Snap.Hutao.Web.Hoyolab; using System.Collections.ObjectModel; -using BindingUser = Snap.Hutao.Model.Binding.User; +using BindingUser = Snap.Hutao.Model.Binding.User.User; namespace Snap.Hutao.Service.User; @@ -18,6 +17,12 @@ public interface IUserService /// BindingUser? Current { get; set; } + /// + /// 异步获取角色与用户集合 + /// + /// 角色与用户集合 + Task> GetRoleCollectionAsync(); + /// /// 初始化用户服务及所有用户 /// 异步获取同步的用户信息集合 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserHelper.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserHelper.cs index 18697503..f8bfac3a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserHelper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserHelper.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. using System.Collections.ObjectModel; -using BindingUser = Snap.Hutao.Model.Binding.User; +using BindingUser = Snap.Hutao.Model.Binding.User.User; namespace Snap.Hutao.Service.User; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs index ec122468..bf53743a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs @@ -5,13 +5,12 @@ using CommunityToolkit.Mvvm.Messaging; using Microsoft.Extensions.DependencyInjection; using Snap.Hutao.Context.Database; using Snap.Hutao.Core.Database; -using Snap.Hutao.Core.Threading; +using Snap.Hutao.Extension; using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab.Bbs.User; -using Snap.Hutao.Web.Hoyolab.Takumi.Auth; using Snap.Hutao.Web.Hoyolab.Takumi.Binding; using System.Collections.ObjectModel; -using BindingUser = Snap.Hutao.Model.Binding.User; +using BindingUser = Snap.Hutao.Model.Binding.User.User; namespace Snap.Hutao.Service.User; @@ -27,6 +26,7 @@ internal class UserService : IUserService private BindingUser? currentUser; private ObservableCollection? userCollection; + private ObservableCollection? roleCollection; /// /// 构造一个新的用户服务 @@ -86,15 +86,17 @@ internal class UserService : IUserService public async Task RemoveUserAsync(BindingUser user) { await Task.Yield(); - Must.NotNull(userCollection!); // Sync cache - userCollection.Remove(user); + userCollection!.Remove(user); + roleCollection!.RemoveWhere(r => r.User.InnerId == user.Entity.InnerId); // Sync database using (IServiceScope scope = scopeFactory.CreateScope()) { AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + + // Note: cascade deleted dailynotes appDbContext.Users.RemoveAndSave(user.Entity); } } @@ -137,6 +139,27 @@ internal class UserService : IUserService return userCollection; } + /// + public async Task> GetRoleCollectionAsync() + { + if (roleCollection == null) + { + List userAndRoles = new(); + ObservableCollection observableUsers = await GetUserCollectionAsync().ConfigureAwait(false); + foreach (BindingUser user in observableUsers.ToList()) + { + foreach (UserGameRole role in user.UserGameRoles) + { + userAndRoles.Add(new(user.Entity, role)); + } + } + + roleCollection = new(userAndRoles); + } + + return roleCollection; + } + /// public async Task> ProcessInputCookieAsync(Cookie cookie) { @@ -148,7 +171,7 @@ internal class UserService : IUserService { // 检查 login ticket 是否存在 // 若存在则尝试升级至 stoken - await TryAddMultiTokenAsync(cookie, uid).ConfigureAwait(false); + await cookie.TryAddMultiTokenAsync(uid).ConfigureAwait(false); // 检查 uid 对应用户是否存在 if (UserHelper.TryGetUserByUid(userCollection, uid, out BindingUser? userWithSameUid)) @@ -180,32 +203,14 @@ internal class UserService : IUserService } else if (cookie.ContainsLTokenAndCookieToken()) { - return await TryCreateUserAndAddAsync(userCollection, cookie).ConfigureAwait(false); + return await TryCreateUserAndAddAsync(cookie).ConfigureAwait(false); } } return new(UserOptionResult.Incomplete, null!); } - private async Task TryAddMultiTokenAsync(Cookie cookie, string uid) - { - if (cookie.TryGetLoginTicket(out string? loginTicket)) - { - // get multitoken - Dictionary multiToken = await Ioc.Default - .GetRequiredService() - .GetMultiTokenByLoginTicketAsync(loginTicket, uid, default) - .ConfigureAwait(false); - - if (multiToken.Count >= 2) - { - cookie.InsertMultiToken(uid, multiToken); - cookie.RemoveLoginTicket(); - } - } - } - - private async Task> TryCreateUserAndAddAsync(ObservableCollection users, Cookie cookie) + private async Task> TryCreateUserAndAddAsync(Cookie cookie) { using (IServiceScope scope = scopeFactory.CreateScope()) { @@ -217,8 +222,19 @@ internal class UserService : IUserService if (newUser != null) { // Sync cache - await ThreadHelper.SwitchToMainThreadAsync(); - users.Add(newUser); + if (userCollection != null) + { + await ThreadHelper.SwitchToMainThreadAsync(); + userCollection!.Add(newUser); + + if (roleCollection != null) + { + foreach (UserGameRole role in newUser.UserGameRoles) + { + roleCollection.Add(new(newUser.Entity, role)); + } + } + } // Sync database appDbContext.Users.AddAndSave(newUser.Entity); diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj index 759205a7..fefee604 100644 --- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj +++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj @@ -30,6 +30,7 @@ True true embedded + Assets\Logo.ico @@ -115,28 +116,31 @@ - + - - - - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml index 0de5595a..b847d54c 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml @@ -148,7 +148,7 @@ BorderThickness="{ThemeResource CardBorderThickness}" BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" Padding="8" - Margin="0,16,0,0" + Margin="0,8,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Center" MinHeight="48"> diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml index 301bd7bf..5051279e 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml @@ -23,145 +23,144 @@ - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Margin="16,16,0,-4"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/DailyNotePage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/DailyNotePage.xaml index 424a6502..e3670c5b 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/DailyNotePage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/DailyNotePage.xaml @@ -4,13 +4,26 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:shc="using:Snap.Hutao.Control" - xmlns:shcm="using:Snap.Hutao.Control.Markup" + xmlns:mxi="using:Microsoft.Xaml.Interactivity" xmlns:sc="using:SettingsUI.Controls" + xmlns:shc="using:Snap.Hutao.Control" + xmlns:shcb="using:Snap.Hutao.Control.Behavior" + xmlns:shcm="using:Snap.Hutao.Control.Markup" xmlns:shv="using:Snap.Hutao.ViewModel" mc:Ignorable="d" d:DataContext="{d:DesignInstance shv:DailyNoteViewModel}" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> + + + + + + + + + @@ -20,11 +33,62 @@ - + + + + + + + + + + + + + + + + + + [Injection(InjectAs.Scoped)] -internal class DailyNoteViewModel : ObservableObject, ISupportCancellation +internal class DailyNoteViewModel : ObservableObject, ISupportCancellation, IDisposable { + private readonly IUserService userService; + private readonly List> refreshTimes = new() { new("4 分钟 | 0.5 树脂", 240), @@ -22,6 +28,23 @@ internal class DailyNoteViewModel : ObservableObject, ISupportCancellation new("60 分钟 | 7.5 树脂", 3600), }; + private bool isReminderNotification; + private NamedValue? selectedRefreshTime; + private ObservableCollection? userAndRoles; + + /// + /// 构造一个新的实时便笺视图模型 + /// + /// 用户服务 + /// 异步命令工厂 + public DailyNoteViewModel(IUserService userService, IAsyncRelayCommandFactory asyncRelayCommandFactory) + { + this.userService = userService; + + OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync); + TrackRoleCommand = asyncRelayCommandFactory.Create(TrackRoleAsync); + } + /// public CancellationToken CancellationToken { get; set; } @@ -29,4 +52,44 @@ internal class DailyNoteViewModel : ObservableObject, ISupportCancellation /// 刷新时间 /// public List> RefreshTimes { get => refreshTimes; } -} + + /// + /// 选中的刷新时间 + /// + public NamedValue? SelectedRefreshTime { get => selectedRefreshTime; set => SetProperty(ref selectedRefreshTime, value); } + + /// + /// 提醒式通知 + /// + public bool IsReminderNotification { get => isReminderNotification; set => SetProperty(ref isReminderNotification, value); } + + /// + /// 用户与角色集合 + /// + public ObservableCollection? UserAndRoles { get => userAndRoles; set => userAndRoles = value; } + + /// + /// 打开界面命令 + /// + public ICommand OpenUICommand { get; } + + /// + /// 跟踪角色命令 + /// + public ICommand TrackRoleCommand { get; } + + public void Dispose() + { + throw new NotImplementedException(); + } + + private async Task OpenUIAsync() + { + UserAndRoles = await userService.GetRoleCollectionAsync().ConfigureAwait(true); + } + + private async Task TrackRoleAsync() + { + + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/ExperimentalFeaturesViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/ExperimentalFeaturesViewModel.cs index 7a66a3f5..f0022951 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/ExperimentalFeaturesViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/ExperimentalFeaturesViewModel.cs @@ -68,7 +68,7 @@ internal class ExperimentalFeaturesViewModel : ObservableObject IUserService userService = Ioc.Default.GetRequiredService(); IInfoBarService infoBarService = Ioc.Default.GetRequiredService(); - if (userService.Current is Model.Binding.User user) + if (userService.Current is Model.Binding.User.User user) { if (user.SelectedUserGameRole == null) { diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs index 7377299f..879dd11c 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs @@ -35,8 +35,8 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation private readonly List knownSchemes = new() { - new LaunchScheme(name: "官方服 | 天空岛", channel: "1", subChannel: "1"), - new LaunchScheme(name: "渠道服 | 世界树", channel: "14", subChannel: "0"), + new LaunchScheme(name: "官方服 | 天空岛", channel: "1", subChannel: "1", launcherId: "18"), + new LaunchScheme(name: "渠道服 | 世界树", channel: "14", subChannel: "0", launcherId: "17"), // new LaunchScheme(name: "国际服 | 暂不支持", channel: "1", subChannel: "0"), }; @@ -231,7 +231,7 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation } catch (UnauthorizedAccessException) { - infoBarService.Warning("切换服务器失败,保存配置文件时发生异常\n请以管理员模式启动胡桃。"); + infoBarService.Warning("读取或保存配置文件时发生异常,请以管理员模式启动胡桃。"); } } @@ -239,7 +239,7 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation { if (!gameService.SetGameAccount(SelectedGameAccount)) { - Ioc.Default.GetRequiredService().Warning("切换账号失败"); + infoBarService.Warning("切换账号失败"); } } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs index b884f214..09a31086 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs @@ -3,10 +3,12 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using CommunityToolkit.Mvvm.Messaging; using Snap.Hutao.Context.Database; using Snap.Hutao.Core.Database; -using Snap.Hutao.Core.Threading; +using Snap.Hutao.Core.Windowing; using Snap.Hutao.Factory.Abstraction; +using Snap.Hutao.Model; using Snap.Hutao.Model.Entity; using Snap.Hutao.Service.Game; using Snap.Hutao.Service.Game.Locator; @@ -22,9 +24,17 @@ internal class SettingViewModel : ObservableObject private readonly AppDbContext appDbContext; private readonly IGameService gameService; private readonly SettingEntry isEmptyHistoryWishVisibleEntry; + private readonly SettingEntry selectedBackdropTypeEntry; + private readonly List> backdropTypes = new() + { + new("亚克力", BackdropType.Acrylic), + new("云母", BackdropType.Mica), + new("变种云母", BackdropType.MicaAlt), + }; private bool isEmptyHistoryWishVisible; private string gamePath; + private NamedValue selectedBackdropType; /// /// 构造一个新的测试视图模型 @@ -40,10 +50,16 @@ internal class SettingViewModel : ObservableObject Experimental = experimental; - isEmptyHistoryWishVisibleEntry = appDbContext.Settings - .SingleOrAdd(e => e.Key == SettingEntry.IsEmptyHistoryWishVisible, () => new(SettingEntry.IsEmptyHistoryWishVisible, true.ToString()), out _); + isEmptyHistoryWishVisibleEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.IsEmptyHistoryWishVisible, true.ToString()); IsEmptyHistoryWishVisible = bool.Parse(isEmptyHistoryWishVisibleEntry.Value!); + selectedBackdropTypeEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.SystemBackdropType, BackdropType.Mica.ToString()); + BackdropType type = Enum.Parse(selectedBackdropTypeEntry.Value!); + + // prevent unnecessary backdrop setting. + selectedBackdropType = backdropTypes.Single(t => t.Value == type); + OnPropertyChanged(nameof(SelectedBackdropType)); + GamePath = gameService.GetGamePathSkipLocator(); SetGamePathCommand = asyncRelayCommandFactory.Create(SetGamePathAsync); @@ -67,9 +83,11 @@ internal class SettingViewModel : ObservableObject get => isEmptyHistoryWishVisible; set { - SetProperty(ref isEmptyHistoryWishVisible, value); - isEmptyHistoryWishVisibleEntry.Value = value.ToString(); - appDbContext.Settings.UpdateAndSave(isEmptyHistoryWishVisibleEntry); + if (SetProperty(ref isEmptyHistoryWishVisible, value)) + { + isEmptyHistoryWishVisibleEntry.Value = value.ToString(); + appDbContext.Settings.UpdateAndSave(isEmptyHistoryWishVisibleEntry); + } } } @@ -83,6 +101,29 @@ internal class SettingViewModel : ObservableObject set => SetProperty(ref gamePath, value); } + /// + /// 背景类型 + /// + public List> BackdropTypes { get => backdropTypes; } + + /// + /// 选中的背景类型 + /// + public NamedValue SelectedBackdropType + { + get => selectedBackdropType; + [MemberNotNull(nameof(selectedBackdropType))] + set + { + if (SetProperty(ref selectedBackdropType, value)) + { + selectedBackdropTypeEntry.Value = value.Value.ToString(); + appDbContext.Settings.UpdateAndSave(selectedBackdropTypeEntry); + Ioc.Default.GetRequiredService().Send(new Message.BackdropTypeChangedMessage(value.Value)); + } + } + } + /// /// 实验性功能 /// diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs index 7e77f7e4..82ba9748 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs @@ -6,7 +6,7 @@ using CommunityToolkit.Mvvm.Input; using Snap.Hutao.Core.IO.DataTransfer; using Snap.Hutao.Core.Threading; using Snap.Hutao.Factory.Abstraction; -using Snap.Hutao.Model.Binding; +using Snap.Hutao.Model.Binding.User; using Snap.Hutao.Service.Abstraction; using Snap.Hutao.Service.Navigation; using Snap.Hutao.Service.User; diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ApiEndpoints.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ApiEndpoints.cs index 150f320f..ec6bb1cc 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ApiEndpoints.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ApiEndpoints.cs @@ -43,6 +43,17 @@ internal static class ApiEndpoints /// public const string GameRecordCharacter = $"{ApiTakumiRecordApi}/character"; + /// + /// 游戏记录实时便笺 + /// + /// uid + /// 服务器区域 + /// 游戏记录实时便笺字符串 + public static string GameRecordDailyNote(string uid, string server) + { + return $"{ApiTakumiRecordApi}/dailyNote?server={server}&role_id={uid}"; + } + /// /// 游戏记录主页 /// @@ -121,12 +132,13 @@ internal static class ApiEndpoints /// /// 启动器资源 /// + /// 启动器Id /// 通道 /// 子通道 /// 启动器资源字符串 - public static string SdkStaticLauncherResource(string channel, string subChannel) + public static string SdkStaticLauncherResource(string launcherId, string channel, string subChannel) { - return $"{SdkStaticLauncherApi}/resource?key=eYd89JmJ&launcher_id=18&channel_id={channel}&sub_channel_id={subChannel}"; + return $"{SdkStaticLauncherApi}/resource?key=eYd89JmJ&launcher_id={launcherId}&channel_id={channel}&sub_channel_id={subChannel}"; } // https://sdk-static.mihoyo.com/hk4e_cn/mdk/launcher/api/content?filter_adv=true&key=eYd89JmJ&language=zh-cn&launcher_id=18 diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient.cs index 237b893f..06d74223 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient.cs @@ -36,7 +36,7 @@ internal class UserClient /// 用户 /// 取消令牌 /// 详细信息 - public async Task GetUserFullInfoAsync(Model.Binding.User user, CancellationToken token = default) + public async Task GetUserFullInfoAsync(Model.Binding.User.User user, CancellationToken token = default) { Response? resp = await httpClient .SetUser(user) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.Constant.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.Constant.cs new file mode 100644 index 00000000..e75309c5 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.Constant.cs @@ -0,0 +1,24 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Web.Hoyolab; + +/// +/// 键部分 +/// +[SuppressMessage("", "SA1310")] +[SuppressMessage("", "SA1600")] +public partial class Cookie +{ + public const string COOKIE_TOKEN = "cookie_token"; + public const string ACCOUNT_ID = "account_id"; + + public const string LOGIN_TICKET = "login_ticket"; + public const string LOGIN_UID = "login_uid"; + + public const string LTOKEN = "ltoken"; + public const string LTUID = "ltuid"; + + public const string STOKEN = "stoken"; + public const string STUID = "stuid"; +} diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs index b98f3f92..800a1b83 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs @@ -3,6 +3,7 @@ using Microsoft.Web.WebView2.Core; using Snap.Hutao.Extension; +using Snap.Hutao.Web.Hoyolab.Takumi.Auth; namespace Snap.Hutao.Web.Hoyolab; @@ -90,20 +91,6 @@ public partial class Cookie return inner.ContainsKey(STOKEN); } - /// - /// 插入Stoken - /// - /// uid - /// tokens - public void InsertMultiToken(string uid, Dictionary multiToken) - { - inner[STUID] = uid; - inner[STOKEN] = multiToken[STOKEN]; - - inner[LTUID] = uid; - inner[LTOKEN] = multiToken[LTOKEN]; - } - /// /// 插入 Stoken /// @@ -115,15 +102,6 @@ public partial class Cookie inner[STOKEN] = cookie.inner[STOKEN]; } - /// - /// 移除 LoginTicket - /// - public void RemoveLoginTicket() - { - inner.Remove(LOGIN_TICKET); - inner.Remove(LOGIN_UID); - } - /// /// 移除无效的键 /// @@ -186,6 +164,34 @@ public partial class Cookie } } + /// + /// 异步尝试添加MultiToken + /// + /// uid + /// 任务 + public async Task TryAddMultiTokenAsync(string uid) + { + if (TryGetLoginTicket(out string? loginTicket)) + { + // get multitoken + Dictionary multiToken = await Ioc.Default + .GetRequiredService() + .GetMultiTokenByLoginTicketAsync(loginTicket, uid, default) + .ConfigureAwait(false); + + if (multiToken.Count >= 2) + { + inner[STUID] = uid; + inner[STOKEN] = multiToken[STOKEN]; + inner[LTUID] = uid; + inner[LTOKEN] = multiToken[LTOKEN]; + + inner.Remove(LOGIN_TICKET); + inner.Remove(LOGIN_UID); + } + } + } + /// /// 转换为Cookie的字符串表示 /// @@ -194,24 +200,4 @@ public partial class Cookie { return string.Join(';', inner.Select(kvp => $"{kvp.Key}={kvp.Value}")); } -} - -/// -/// 键部分 -/// -[SuppressMessage("", "SA1310")] -[SuppressMessage("", "SA1600")] -public partial class Cookie -{ - public const string COOKIE_TOKEN = "cookie_token"; - public const string ACCOUNT_ID = "account_id"; - - public const string LOGIN_TICKET = "login_ticket"; - public const string LOGIN_UID = "login_uid"; - - public const string LTOKEN = "ltoken"; - public const string LTUID = "ltuid"; - - public const string STOKEN = "stoken"; - public const string STUID = "stuid"; } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs index a06b6775..7c12df53 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. using Snap.Hutao.Core.Logging; -using Snap.Hutao.Model.Binding; +using Snap.Hutao.Model.Binding.User; using Snap.Hutao.Web.Request; using System.Net.Http; using System.Net.Http.Json; @@ -64,7 +64,7 @@ internal static class HttpClientExtensions /// 设置用户的Cookie /// /// http客户端 - /// 用户 + /// 绑定用户 /// 客户端 internal static HttpClient SetUser(this HttpClient httpClient, User user) { @@ -72,6 +72,18 @@ internal static class HttpClientExtensions return httpClient; } + /// + /// 设置用户的Cookie + /// + /// http客户端 + /// 实体用户 + /// 客户端 + internal static HttpClient SetUser(this HttpClient httpClient, Model.Entity.User user) + { + httpClient.DefaultRequestHeaders.Set("Cookie", user.Cookie!.ToString()); + return httpClient; + } + /// /// 设置Referer /// diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs index e8159f3a..ba3a52a2 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs @@ -41,6 +41,11 @@ public struct PlayerUid get => region ??= EvaluateRegion(Value[0]); } + public static implicit operator PlayerUid(string source) + { + return new(source); + } + /// public override string ToString() { diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/SdkStatic/Hk4e/Launcher/ResourceClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/SdkStatic/Hk4e/Launcher/ResourceClient.cs index 3c42c98e..5c9c3e4d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/SdkStatic/Hk4e/Launcher/ResourceClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/SdkStatic/Hk4e/Launcher/ResourceClient.cs @@ -39,8 +39,9 @@ internal class ResourceClient /// 游戏资源 public async Task GetResourceAsync(LaunchScheme scheme, CancellationToken token = default) { + string url = ApiEndpoints.SdkStaticLauncherResource(scheme.LauncherId, scheme.Channel, scheme.SubChannel); Response? response = await httpClient - .TryCatchGetFromJsonAsync>(ApiEndpoints.SdkStaticLauncherResource(scheme.Channel, scheme.SubChannel), options, logger, token) + .TryCatchGetFromJsonAsync>(url, options, logger, token) .ConfigureAwait(false); return response?.Data; diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient.cs index 8c2c8674..5f4aef23 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient.cs @@ -3,7 +3,7 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.Extension; -using Snap.Hutao.Model.Binding; +using Snap.Hutao.Model.Binding.User; using Snap.Hutao.Web.Response; using System.Net.Http; diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient2.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient2.cs index e61d2c2f..22d73332 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient2.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/BindingClient2.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; -using Snap.Hutao.Model.Binding; +using Snap.Hutao.Model.Binding.User; using Snap.Hutao.Web.Hoyolab.DynamicSecret; using Snap.Hutao.Web.Response; using System.Net.Http; diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRole.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRole.cs index ef5b6e8c..5984d023 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRole.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRole.cs @@ -64,7 +64,7 @@ public class UserGameRole get => $"{RegionName} | Lv.{Level}"; } - public static explicit operator PlayerUid(UserGameRole userGameRole) + public static implicit operator PlayerUid(UserGameRole userGameRole) { return new PlayerUid(userGameRole.GameUid, userGameRole.Region); } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/DailyNote.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/DailyNote.cs new file mode 100644 index 00000000..6c1fa885 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/DailyNote.cs @@ -0,0 +1,171 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote; + +/// +/// 实时便笺 +/// +public class DailyNote +{ + /// + /// 当前树脂 + /// + [JsonPropertyName("current_resin")] + public int CurrentResin { get; set; } + + /// + /// 最大树脂 + /// + [JsonPropertyName("max_resin")] + public int MaxResin { get; set; } + + /// + /// 树脂恢复时间 类型的秒数 + /// + [JsonPropertyName("resin_recovery_time")] + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] + public int ResinRecoveryTime { get; set; } + + /// + /// 格式化的树脂恢复时间 + /// + public string ResinRecoveryTargetTime + { + get + { + DateTime tt = DateTime.Now.AddSeconds(ResinRecoveryTime); + int totalDays = (tt - DateTime.Today).Days; + string day = totalDays switch + { + 0 => "今天", + 1 => "明天", + 2 => "后天", + _ => $"{totalDays}天", + }; + return $"{day} {tt:HH:mm}"; + } + } + + /// + /// 委托完成数 + /// + [JsonPropertyName("finished_task_num")] + public int FinishedTaskNum { get; set; } + + /// + /// 委托总数 + /// + [JsonPropertyName("total_task_num")] + public int TotalTaskNum { get; set; } + + /// + /// 4次委托额外奖励是否领取 + /// + [JsonPropertyName("is_extra_task_reward_received")] + public bool IsExtraTaskRewardReceived { get; set; } + + /// + /// 每日委托奖励字符串 + /// + public string ExtraTaskRewardDescription + { + get + { + return IsExtraTaskRewardReceived + ? "已领取「每日委托」奖励" + : FinishedTaskNum == TotalTaskNum + ? "「每日委托」奖励待领取" + : "今日完成委托次数不足"; + } + } + + /// + /// 剩余周本折扣次数 + /// + [JsonPropertyName("remain_resin_discount_num")] + public int RemainResinDiscountNum { get; set; } + + /// + /// 周本树脂减免使用次数 + /// + public int ResinDiscountUsedNum + { + get => ResinDiscountNumLimit - RemainResinDiscountNum; + } + + /// + /// 周本折扣总次数 + /// + [JsonPropertyName("resin_discount_num_limit")] + public int ResinDiscountNumLimit { get; set; } + + /// + /// 当前派遣数 + /// + [JsonPropertyName("current_expedition_num")] + public int CurrentExpeditionNum { get; set; } + + /// + /// 最大派遣数 + /// + [JsonPropertyName("max_expedition_num")] + public int MaxExpeditionNum { get; set; } + + /// + /// 派遣 + /// + [JsonPropertyName("expeditions")] + public List Expeditions { get; set; } = default!; + + /// + /// 当前洞天宝钱 + /// + [JsonPropertyName("current_home_coin")] + public int CurrentHomeCoin { get; set; } + + /// + /// 最大洞天宝钱 + /// + [JsonPropertyName("max_home_coin")] + public int MaxHomeCoin { get; set; } + + /// + /// 洞天宝钱恢复时间 类型的秒数 + /// + [JsonPropertyName("home_coin_recovery_time")] + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] + public int HomeCoinRecoveryTime { get; set; } + + /// + /// 格式化的洞天宝钱恢复时间 + /// + public string HomeCoinRecoveryTargetTimeFormatted + { + get + { + DateTime reach = DateTime.Now.AddSeconds(HomeCoinRecoveryTime); + int totalDays = (reach - DateTime.Today).Days; + string day = totalDays switch + { + 0 => "今天", + 1 => "明天", + 2 => "后天", + _ => $"{totalDays}天", + }; + return $"{day} {reach:HH:mm}"; + } + } + + /// + /// 日历链接 + /// + [JsonPropertyName("calendar_url")] + public string CalendarUrl { get; set; } = default!; + + /// + /// 参量质变仪 + /// + [JsonPropertyName("transformer")] + public Transformer Transformer { get; set; } = default!; +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/Expedition.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/Expedition.cs new file mode 100644 index 00000000..f09cf96c --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/Expedition.cs @@ -0,0 +1,47 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote; + +/// +/// 探索派遣 +/// +public class Expedition +{ + /// + /// 图标 + /// + [JsonPropertyName("avatar_side_icon")] + public Uri AvatarSideIcon { get; set; } = default!; + + /// + /// 状态 Ongoing:派遣中 Finished:已完成 + /// + [JsonPropertyName("status")] + [JsonConverter(typeof(JsonStringEnumConverter))] + public ExpeditionStatus Status { get; set; } + + /// + /// 剩余时间 + /// + [JsonPropertyName("remained_time")] + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] + public int RemainedTime { get; set; } + + /// + /// 格式化的剩余时间 + /// + public string RemainedTimeFormatted + { + get + { + if (Status == ExpeditionStatus.Finished) + { + return "已完成"; + } + + TimeSpan ts = new(0, 0, RemainedTime); + return ts.Hours > 0 ? $"{ts.Hours}时" : $"{ts.Minutes}分"; + } + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/ExpeditionStatus.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/ExpeditionStatus.cs new file mode 100644 index 00000000..2cf50657 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/ExpeditionStatus.cs @@ -0,0 +1,20 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote; + +/// +/// 探索派遣状态 +/// +public enum ExpeditionStatus +{ + /// + /// 进行中 + /// + Ongoing, + + /// + /// 完成 + /// + Finished, +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/RecoveryTime.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/RecoveryTime.cs new file mode 100644 index 00000000..ad4141ab --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/RecoveryTime.cs @@ -0,0 +1,77 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Extension; +using System.Text; + +namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote; + +/// +/// 参量质变仪恢复时间包装 +/// 已准备完成 $后可再次使用 +/// 冷却中 可使用 +/// +public class RecoveryTime +{ + /// + /// 日 + /// + [JsonPropertyName("Day")] + public int Day { get; set; } + + /// + /// 时 + /// + [JsonPropertyName("Hour")] + public int Hour { get; set; } + + /// + /// 分 + /// + [JsonPropertyName("Minute")] + public int Minute { get; set; } + + /// + /// 秒 + /// + [JsonPropertyName("Second")] + public int Second { get; set; } + + /// + /// 是否已经到达 + /// + [JsonPropertyName("reached")] + public bool Reached { get; set; } + + /// + /// 获取格式化的剩余时间 + /// + public string TimeFormatted + { + get + { + if (Reached) + { + return "已准备完成"; + } + else + { + return new StringBuilder() + .AppendIf(Day > 0, $"{Day}天") + .AppendIf(Hour > 0, $"{Hour}时") + .AppendIf(Minute > 0, $"{Minute}分") + .AppendIf(Second > 0, $"{Second}秒") + .Append(" 后可再次使用") + .ToString(); + } + } + } + + /// + /// 获取格式化的状态 + /// + public string ReachedFormatted + { + get => Reached ? "可使用" : "冷却中"; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/Transformer.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/Transformer.cs new file mode 100644 index 00000000..d924c32f --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/DailyNote/Transformer.cs @@ -0,0 +1,40 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote; + +/// +/// 参量质变仪 +/// +public class Transformer +{ + /// + /// 是否拥有该道具 + /// + [JsonPropertyName("obtained")] + public bool Obtained { get; set; } + + /// + /// 恢复时间包装 + /// + [JsonPropertyName("recovery_time")] + public RecoveryTime? RecoveryTime { get; set; } + + /// + /// Wiki链接 + /// + [JsonPropertyName("wiki")] + public Uri Wiki { get; set; } = default!; + + /// + /// 是否提醒 + /// + [JsonPropertyName("noticed")] + public bool Noticed { get; set; } + + /// + /// 上个任务的Id + /// + [JsonPropertyName("latest_job_id")] + public string LastJobId { get; set; } = default!; +} diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs index 4b879eb4..90bdcd29 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs @@ -3,7 +3,7 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.Extension; -using Snap.Hutao.Model.Binding; +using Snap.Hutao.Model.Binding.User; using Snap.Hutao.Web.Hoyolab.DynamicSecret; using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar; using Snap.Hutao.Web.Response; @@ -34,6 +34,42 @@ internal class GameRecordClient this.logger = logger; } + /// + /// 异步获取实时便笺 + /// + /// 用户 + /// 查询uid + /// 取消令牌 + /// 实时便笺 + public async Task GetDialyNoteAsync(User user, PlayerUid uid, CancellationToken token = default) + { + Response? resp = await httpClient + .SetUser(user) + .UsingDynamicSecret(options, ApiEndpoints.GameRecordDailyNote(uid.Value, uid.Region)) + .GetFromJsonAsync>(token) + .ConfigureAwait(false); + + return resp?.Data; + } + + /// + /// 异步获取实时便笺 + /// + /// 用户 + /// 查询uid + /// 取消令牌 + /// 实时便笺 + public async Task GetDialyNoteAsync(Model.Entity.User user, PlayerUid uid, CancellationToken token = default) + { + Response? resp = await httpClient + .SetUser(user) + .UsingDynamicSecret(options, ApiEndpoints.GameRecordDailyNote(uid.Value, uid.Region)) + .GetFromJsonAsync>(token) + .ConfigureAwait(false); + + return resp?.Data; + } + /// /// 获取玩家基础信息 /// @@ -68,7 +104,7 @@ internal class GameRecordClient /// 获取玩家深渊信息 /// /// 用户 - /// 1:当期,2:上期 + /// 期 /// 取消令牌 /// 深渊信息 public Task GetSpiralAbyssAsync(User user, SpiralAbyssSchedule schedule, CancellationToken token = default) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs index 4699d5e8..b4a9169d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs @@ -3,6 +3,7 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.Extension; +using Snap.Hutao.Model.Binding.User; using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord; using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar; @@ -172,7 +173,7 @@ internal class HomaClient /// 用户 /// 取消令牌 /// 玩家记录 - public async Task GetPlayerRecordAsync(Snap.Hutao.Model.Binding.User user, CancellationToken token = default) + public async Task GetPlayerRecordAsync(User user, CancellationToken token = default) { PlayerInfo? playerInfo = await gameRecordClient .GetPlayerInfoAsync(user, token) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs b/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs index 260f78a3..4d1a0b0e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs @@ -41,7 +41,7 @@ public enum KnownReturnCode : int /// /// 访问过于频繁 /// - VIsitTooFrequently = -110, + VisitTooFrequently = -110, /// /// 应用Id错误 diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs b/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs index 06b1c138..8d44268b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs @@ -89,14 +89,4 @@ public class Response : Response { return ReturnCode == 0; } - - /// - public override int GetHashCode() - { - int j = ReturnCode.GetHashCode(); - int k = Message == null ? 0 : Message.GetHashCode(); - int i = Data == null ? 0 : Data.GetHashCode(); - - return (((j * 31) + k) * 31) + i; - } } \ No newline at end of file