From 0dd79d4206a5838ee7222608531a5848c29859e5 Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Fri, 25 Nov 2022 14:51:30 +0800 Subject: [PATCH] web bridge check point --- .../Snap.Hutao/Core/Convert/CastTo.cs | 5 + .../Binding/Hutao/ComplexReliquarySet.cs | 5 +- .../Model/Binding/Hutao/ComplexTeamRank.cs | 3 +- .../Snap.Hutao/Model/Binding/Hutao/Team.cs | 5 +- .../Snap.Hutao/Model/Binding/User/User.cs | 2 +- .../Metadata/Reliquary/ReliquaryAffix.cs | 7 + .../Metadata/Reliquary/ReliquaryAffixBase.cs | 3 +- .../Model/Metadata/Reliquary/ReliquarySet.cs | 4 +- .../Snap.Hutao/Model/Primitive/AvatarId.cs | 4 +- .../Primitive/Converter/AvatarIdConverter.cs | 22 -- .../Primitive/Converter/IdentityConverter.cs | 27 +++ .../Primitive/Converter/WeaponIdConverter.cs | 22 -- .../Model/Primitive/EquipAffixId.cs | 65 ++++++ .../Model/Primitive/ExtendedEquipAffixId.cs | 65 ++++++ .../Model/Primitive/ReliquaryAffixId.cs | 65 ++++++ .../Model/Primitive/ReliquaryMainAffixId.cs | 65 ++++++ .../Snap.Hutao/Model/Primitive/WeaponId.cs | 4 +- src/Snap.Hutao/Snap.Hutao/NativeMethods.txt | 1 + .../Factory/SummaryAvatarFactory.cs | 19 +- .../AvatarInfo/Factory/SummaryFactory.cs | 9 +- .../Factory/SummaryFactoryImplementation.cs | 17 +- .../Factory/SummaryReliquaryFactory.cs | 9 +- .../Service/DailyNote/DailyNoteService.cs | 5 +- .../Factory/GachaStatisticsFactory.cs | 9 +- .../Service/GachaLog/GachaLogService.cs | 5 +- .../Snap.Hutao/Service/Hutao/HutaoCache.cs | 23 +- .../Snap.Hutao/Service/InfoBarService.cs | 2 + .../Service/Metadata/IMetadataService.cs | 11 +- .../Metadata/MetadataService.Indexing.cs | 21 +- src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj | 8 +- .../View/Dialog/SignInWebViewDialog.xaml | 13 ++ .../View/Dialog/SignInWebViewDialog.xaml.cs | 68 ++++++ src/Snap.Hutao/Snap.Hutao/View/MainView.xaml | 5 - src/Snap.Hutao/Snap.Hutao/View/UserView.xaml | 7 + .../Snap.Hutao/ViewModel/UserViewModel.cs | 9 + src/Snap.Hutao/Snap.Hutao/Web/ApiEndpoints.cs | 2 +- .../Snap.Hutao/Web/Bridge/BridgeExtension.cs | 107 ++++++++++ .../Web/Bridge/ICoreWebView2Interop.cs | 24 +++ .../Snap.Hutao/Web/Bridge/IMiHoYoJsBridge.cs | 20 ++ .../Snap.Hutao/Web/Bridge/MiHoYoJsBridge.cs | 103 +++++++++ .../Bridge/Model/Event/WebInvokeAttribute.cs | 196 ++++++++++++++++++ .../Snap.Hutao/Web/Bridge/Model/JsParam.cs | 60 ++++++ .../Snap.Hutao/Web/Bridge/Model/JsResult.cs | 37 ++++ .../Web/Bridge/Model/JsonElementExtension.cs | 25 +++ .../DynamicSecret/DynamicSecretHandler.cs | 32 ++- .../Web/Hoyolab/Passport/PassportClient2.cs | 2 +- .../Web/Hutao/Model/ReliquarySet.cs | 4 +- 47 files changed, 1099 insertions(+), 127 deletions(-) delete mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Primitive/Converter/AvatarIdConverter.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Primitive/Converter/IdentityConverter.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Primitive/Converter/WeaponIdConverter.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Primitive/EquipAffixId.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Primitive/ExtendedEquipAffixId.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Primitive/ReliquaryAffixId.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Primitive/ReliquaryMainAffixId.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/View/Dialog/SignInWebViewDialog.xaml create mode 100644 src/Snap.Hutao/Snap.Hutao/View/Dialog/SignInWebViewDialog.xaml.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Bridge/BridgeExtension.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Bridge/ICoreWebView2Interop.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Bridge/IMiHoYoJsBridge.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJsBridge.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/Event/WebInvokeAttribute.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsParam.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsResult.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsonElementExtension.cs diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Convert/CastTo.cs b/src/Snap.Hutao/Snap.Hutao/Core/Convert/CastTo.cs index a6895536..d4d6ccd2 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Convert/CastTo.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Convert/CastTo.cs @@ -30,8 +30,13 @@ public static class CastTo private static Func Get() { + // 参数表达式,表示 传入源类型 ParameterExpression param = Expression.Parameter(typeof(TCachedFrom)); + + // 一元转换 调用 相关类的显式或隐式转换运算符 UnaryExpression convert = Expression.ConvertChecked(param, typeof(TTo)); + + // 生成一个源类型入,目标类型出的 lamdba return Expression.Lambda>(convert, param).Compile(); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexReliquarySet.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexReliquarySet.cs index a65515f7..fca39132 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexReliquarySet.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexReliquarySet.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Snap.Hutao.Model.Metadata.Converter; +using Snap.Hutao.Model.Primitive; using Snap.Hutao.Web.Hutao.Model; using System.Text; @@ -17,7 +18,7 @@ public class ComplexReliquarySet /// /// 圣遗物套装率 /// 圣遗物套装映射 - public ComplexReliquarySet(ItemRate reliquarySetRate, Dictionary idReliquarySetMap) + public ComplexReliquarySet(ItemRate reliquarySetRate, Dictionary idReliquarySetMap) { ReliquarySets sets = reliquarySetRate.Item; @@ -43,7 +44,7 @@ public class ComplexReliquarySet } else { - Name = "无圣遗物"; + Name = "无圣遗物或散件"; } Rate = $"{reliquarySetRate.Rate:P3}"; diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexTeamRank.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexTeamRank.cs index 8d2ce83c..bf7c5953 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexTeamRank.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/ComplexTeamRank.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Snap.Hutao.Model.Metadata.Avatar; +using Snap.Hutao.Model.Primitive; using Snap.Hutao.Web.Hutao.Model; namespace Snap.Hutao.Model.Binding.Hutao; @@ -16,7 +17,7 @@ internal class ComplexTeamRank /// /// 队伍排行 /// 映射 - public ComplexTeamRank(TeamAppearance teamRank, Dictionary idAvatarMap) + public ComplexTeamRank(TeamAppearance teamRank, Dictionary idAvatarMap) { Floor = $"第 {teamRank.Floor} 层"; Up = teamRank.Up.Select(teamRate => new Team(teamRate, idAvatarMap)).ToList(); diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/Team.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/Team.cs index 66f8ef52..37f94a52 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/Team.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/Team.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Snap.Hutao.Model.Metadata.Avatar; +using Snap.Hutao.Model.Primitive; using Snap.Hutao.Web.Hutao.Model; namespace Snap.Hutao.Model.Binding.Hutao; @@ -16,10 +17,10 @@ internal class Team : List /// /// 队伍 /// 映射 - public Team(ItemRate team, Dictionary idAvatarMap) + public Team(ItemRate team, Dictionary idAvatarMap) : base(4) { - IEnumerable ids = team.Item.Split(',').Select(i => int.Parse(i)); + IEnumerable ids = team.Item.Split(',').Select(int.Parse); foreach (int id in ids) { diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/User/User.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/User/User.cs index e3ea7f94..9349c911 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Binding/User/User.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/User/User.cs @@ -181,7 +181,7 @@ public class User : ObservableObject if (cookieToken != null) { - Cookie cookieTokenCookie = Cookie.Parse($"acount_id={Entity.Aid};cookie_token={cookieToken}"); + Cookie cookieTokenCookie = Cookie.Parse($"account_id={Entity.Aid};cookie_token={cookieToken}"); Entity.CookieToken = cookieTokenCookie; } } diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Reliquary/ReliquaryAffix.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Reliquary/ReliquaryAffix.cs index 6474ca8c..48e16e08 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Reliquary/ReliquaryAffix.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Reliquary/ReliquaryAffix.cs @@ -1,6 +1,8 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Model.Primitive; + namespace Snap.Hutao.Model.Metadata.Reliquary; /// @@ -8,6 +10,11 @@ namespace Snap.Hutao.Model.Metadata.Reliquary; /// public class ReliquaryAffix : ReliquaryAffixBase { + /// + /// Id + /// + public new ReliquaryAffixId Id { get; set; } + /// /// 值 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Reliquary/ReliquaryAffixBase.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Reliquary/ReliquaryAffixBase.cs index d37210ce..6d78c46b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Reliquary/ReliquaryAffixBase.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Reliquary/ReliquaryAffixBase.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Snap.Hutao.Model.Intrinsic; +using Snap.Hutao.Model.Primitive; namespace Snap.Hutao.Model.Metadata.Reliquary; @@ -13,7 +14,7 @@ public class ReliquaryAffixBase /// /// Id /// - public int Id { get; set; } + public ReliquaryMainAffixId Id { get; set; } /// /// 战斗属性 diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Reliquary/ReliquarySet.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Reliquary/ReliquarySet.cs index 4cc90bff..88f1b13a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Reliquary/ReliquarySet.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Reliquary/ReliquarySet.cs @@ -1,6 +1,8 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Model.Primitive; + namespace Snap.Hutao.Model.Metadata.Reliquary; /// @@ -16,7 +18,7 @@ public class ReliquarySet /// /// 装备被动Id /// - public int EquipAffixId { get; set; } + public EquipAffixId EquipAffixId { get; set; } /// /// 套装名称 diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Primitive/AvatarId.cs b/src/Snap.Hutao/Snap.Hutao/Model/Primitive/AvatarId.cs index afa1e917..b3029912 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Primitive/AvatarId.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Primitive/AvatarId.cs @@ -6,9 +6,9 @@ using Snap.Hutao.Model.Primitive.Converter; namespace Snap.Hutao.Model.Primitive; /// -/// 角色Id +/// 8位 角色Id /// -[JsonConverter(typeof(AvatarIdConverter))] +[JsonConverter(typeof(IdentityConverter))] public readonly struct AvatarId : IEquatable { /// diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Primitive/Converter/AvatarIdConverter.cs b/src/Snap.Hutao/Snap.Hutao/Model/Primitive/Converter/AvatarIdConverter.cs deleted file mode 100644 index 4d1f9443..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Model/Primitive/Converter/AvatarIdConverter.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -namespace Snap.Hutao.Model.Primitive.Converter; - -/// -/// 角色Id转换器 -/// -internal class AvatarIdConverter : JsonConverter -{ - /// - public override AvatarId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return reader.GetInt32(); - } - - /// - public override void Write(Utf8JsonWriter writer, AvatarId value, JsonSerializerOptions options) - { - writer.WriteNumberValue(value); - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Primitive/Converter/IdentityConverter.cs b/src/Snap.Hutao/Snap.Hutao/Model/Primitive/Converter/IdentityConverter.cs new file mode 100644 index 00000000..7f6e9d36 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Primitive/Converter/IdentityConverter.cs @@ -0,0 +1,27 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core.Convert; + +namespace Snap.Hutao.Model.Primitive.Converter; + +/// +/// Id 转换器 +/// +/// 包装类型 +internal class IdentityConverter : JsonConverter + where TWrapper : struct +{ + /// + public override TWrapper Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return CastTo.From(reader.GetInt32()); + } + + /// + public override void Write(Utf8JsonWriter writer, TWrapper value, JsonSerializerOptions options) + { + + writer.WriteNumberValue(CastTo.From(value)); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Primitive/Converter/WeaponIdConverter.cs b/src/Snap.Hutao/Snap.Hutao/Model/Primitive/Converter/WeaponIdConverter.cs deleted file mode 100644 index e489f3a2..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Model/Primitive/Converter/WeaponIdConverter.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -namespace Snap.Hutao.Model.Primitive.Converter; - -/// -/// 武器Id转换器 -/// -internal class WeaponIdConverter : JsonConverter -{ - /// - public override WeaponId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return reader.GetInt32(); - } - - /// - public override void Write(Utf8JsonWriter writer, WeaponId value, JsonSerializerOptions options) - { - writer.WriteNumberValue(value); - } -} diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Primitive/EquipAffixId.cs b/src/Snap.Hutao/Snap.Hutao/Model/Primitive/EquipAffixId.cs new file mode 100644 index 00000000..82f999d1 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Primitive/EquipAffixId.cs @@ -0,0 +1,65 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Primitive.Converter; + +namespace Snap.Hutao.Model.Primitive; + +/// +/// 6位 装备属性Id +/// +[JsonConverter(typeof(IdentityConverter))] +public readonly struct EquipAffixId : IEquatable +{ + /// + /// 值 + /// + public readonly int Value; + + /// + /// Initializes a new instance of the struct. + /// + /// value + public EquipAffixId(int value) + { + Value = value; + } + + public static implicit operator int(EquipAffixId value) + { + return value.Value; + } + + public static implicit operator EquipAffixId(int value) + { + return new(value); + } + + public static bool operator ==(EquipAffixId left, EquipAffixId right) + { + return left.Value == right.Value; + } + + public static bool operator !=(EquipAffixId left, EquipAffixId right) + { + return !(left == right); + } + + /// + public bool Equals(EquipAffixId other) + { + return Value == other.Value; + } + + /// + public override bool Equals(object? obj) + { + return obj is EquipAffixId other && Equals(other); + } + + /// + public override int GetHashCode() + { + return Value.GetHashCode(); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Primitive/ExtendedEquipAffixId.cs b/src/Snap.Hutao/Snap.Hutao/Model/Primitive/ExtendedEquipAffixId.cs new file mode 100644 index 00000000..d6fe2fe9 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Primitive/ExtendedEquipAffixId.cs @@ -0,0 +1,65 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Primitive.Converter; + +namespace Snap.Hutao.Model.Primitive; + +/// +/// 7位 装备属性Id +/// +[JsonConverter(typeof(IdentityConverter))] +public readonly struct ExtendedEquipAffixId : IEquatable +{ + /// + /// 值 + /// + public readonly int Value; + + /// + /// Initializes a new instance of the struct. + /// + /// value + public ExtendedEquipAffixId(int value) + { + Value = value; + } + + public static implicit operator int(ExtendedEquipAffixId value) + { + return value.Value; + } + + public static implicit operator ExtendedEquipAffixId(int value) + { + return new(value); + } + + public static bool operator ==(ExtendedEquipAffixId left, ExtendedEquipAffixId right) + { + return left.Value == right.Value; + } + + public static bool operator !=(ExtendedEquipAffixId left, ExtendedEquipAffixId right) + { + return !(left == right); + } + + /// + public bool Equals(ExtendedEquipAffixId other) + { + return Value == other.Value; + } + + /// + public override bool Equals(object? obj) + { + return obj is ExtendedEquipAffixId other && Equals(other); + } + + /// + public override int GetHashCode() + { + return Value.GetHashCode(); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Primitive/ReliquaryAffixId.cs b/src/Snap.Hutao/Snap.Hutao/Model/Primitive/ReliquaryAffixId.cs new file mode 100644 index 00000000..f6983743 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Primitive/ReliquaryAffixId.cs @@ -0,0 +1,65 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Primitive.Converter; + +namespace Snap.Hutao.Model.Primitive; + +/// +/// 6位 圣遗物副词条Id +/// +[JsonConverter(typeof(IdentityConverter))] +public readonly struct ReliquaryAffixId : IEquatable +{ + /// + /// 值 + /// + public readonly int Value; + + /// + /// Initializes a new instance of the struct. + /// + /// value + public ReliquaryAffixId(int value) + { + Value = value; + } + + public static implicit operator int(ReliquaryAffixId value) + { + return value.Value; + } + + public static implicit operator ReliquaryAffixId(int value) + { + return new(value); + } + + public static bool operator ==(ReliquaryAffixId left, ReliquaryAffixId right) + { + return left.Value == right.Value; + } + + public static bool operator !=(ReliquaryAffixId left, ReliquaryAffixId right) + { + return !(left == right); + } + + /// + public bool Equals(ReliquaryAffixId other) + { + return Value == other.Value; + } + + /// + public override bool Equals(object? obj) + { + return obj is ReliquaryAffixId other && Equals(other); + } + + /// + public override int GetHashCode() + { + return Value.GetHashCode(); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Primitive/ReliquaryMainAffixId.cs b/src/Snap.Hutao/Snap.Hutao/Model/Primitive/ReliquaryMainAffixId.cs new file mode 100644 index 00000000..a10d900c --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Primitive/ReliquaryMainAffixId.cs @@ -0,0 +1,65 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Primitive.Converter; + +namespace Snap.Hutao.Model.Primitive; + +/// +/// 5位 圣遗物主属性Id +/// +[JsonConverter(typeof(IdentityConverter))] +public readonly struct ReliquaryMainAffixId : IEquatable +{ + /// + /// 值 + /// + public readonly int Value; + + /// + /// Initializes a new instance of the struct. + /// + /// value + public ReliquaryMainAffixId(int value) + { + Value = value; + } + + public static implicit operator int(ReliquaryMainAffixId value) + { + return value.Value; + } + + public static implicit operator ReliquaryMainAffixId(int value) + { + return new(value); + } + + public static bool operator ==(ReliquaryMainAffixId left, ReliquaryMainAffixId right) + { + return left.Value == right.Value; + } + + public static bool operator !=(ReliquaryMainAffixId left, ReliquaryMainAffixId right) + { + return !(left == right); + } + + /// + public bool Equals(ReliquaryMainAffixId other) + { + return Value == other.Value; + } + + /// + public override bool Equals(object? obj) + { + return obj is ReliquaryMainAffixId other && Equals(other); + } + + /// + public override int GetHashCode() + { + return Value.GetHashCode(); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Primitive/WeaponId.cs b/src/Snap.Hutao/Snap.Hutao/Model/Primitive/WeaponId.cs index 731234de..d274ae44 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Primitive/WeaponId.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Primitive/WeaponId.cs @@ -6,9 +6,9 @@ using Snap.Hutao.Model.Primitive.Converter; namespace Snap.Hutao.Model.Primitive; /// -/// 武器Id +/// 5位 武器Id /// -[JsonConverter(typeof(WeaponIdConverter))] +[JsonConverter(typeof(IdentityConverter))] public readonly struct WeaponId : IEquatable { /// diff --git a/src/Snap.Hutao/Snap.Hutao/NativeMethods.txt b/src/Snap.Hutao/Snap.Hutao/NativeMethods.txt index 5014ecc2..face9562 100644 --- a/src/Snap.Hutao/Snap.Hutao/NativeMethods.txt +++ b/src/Snap.Hutao/Snap.Hutao/NativeMethods.txt @@ -4,6 +4,7 @@ WM_NCRBUTTONDOWN WM_NCRBUTTONUP // Type definition +HRESULT MINMAXINFO // Comctl32 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs index 2522e8a6..c2e9c46d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs @@ -6,6 +6,7 @@ using Snap.Hutao.Model; using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Metadata.Converter; using Snap.Hutao.Model.Metadata.Reliquary; +using Snap.Hutao.Model.Primitive; using Snap.Hutao.Web.Enka.Model; using MetadataAvatar = Snap.Hutao.Model.Metadata.Avatar.Avatar; using MetadataReliquary = Snap.Hutao.Model.Metadata.Reliquary.Reliquary; @@ -22,10 +23,10 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory; /// internal class SummaryAvatarFactory { - private readonly Dictionary idAvatarMap; - private readonly Dictionary idRelicMainPropMap; - private readonly Dictionary idReliquaryAffixMap; - private readonly Dictionary idWeaponMap; + private readonly Dictionary idAvatarMap; + private readonly Dictionary idWeaponMap; + private readonly Dictionary idRelicMainPropMap; + private readonly Dictionary idReliquaryAffixMap; private readonly List reliqueryLevels; private readonly List reliquaries; @@ -42,18 +43,18 @@ internal class SummaryAvatarFactory /// 圣遗物 /// 角色信息 public SummaryAvatarFactory( - Dictionary idAvatarMap, - Dictionary idWeaponMap, - Dictionary idRelicMainPropMap, - Dictionary idReliquaryAffixMap, + Dictionary idAvatarMap, + Dictionary idWeaponMap, + Dictionary idRelicMainPropMap, + Dictionary idReliquaryAffixMap, List reliqueryLevels, List reliquaries, ModelAvatarInfo avatarInfo) { this.idAvatarMap = idAvatarMap; + this.idWeaponMap = idWeaponMap; this.idRelicMainPropMap = idRelicMainPropMap; this.idReliquaryAffixMap = idReliquaryAffixMap; - this.idWeaponMap = idWeaponMap; this.reliqueryLevels = reliqueryLevels; this.reliquaries = reliquaries; this.avatarInfo = avatarInfo; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs index a6ab5157..03177b79 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs @@ -4,6 +4,7 @@ using Snap.Hutao.Model.Binding.AvatarProperty; using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Metadata.Reliquary; +using Snap.Hutao.Model.Primitive; using Snap.Hutao.Service.Metadata; using MetadataAvatar = Snap.Hutao.Model.Metadata.Avatar.Avatar; using MetadataReliquary = Snap.Hutao.Model.Metadata.Reliquary.Reliquary; @@ -33,10 +34,10 @@ internal class SummaryFactory : ISummaryFactory /// public async Task CreateAsync(ModelPlayerInfo playerInfo, IEnumerable avatarInfos, CancellationToken token) { - Dictionary idAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false); - Dictionary idWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false); - Dictionary idRelicMainPropMap = await metadataService.GetIdToReliquaryMainPropertyMapAsync(token).ConfigureAwait(false); - Dictionary idReliquaryAffixMap = await metadataService.GetIdReliquaryAffixMapAsync(token).ConfigureAwait(false); + Dictionary idAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false); + Dictionary idWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false); + Dictionary idRelicMainPropMap = await metadataService.GetIdToReliquaryMainPropertyMapAsync(token).ConfigureAwait(false); + Dictionary idReliquaryAffixMap = await metadataService.GetIdReliquaryAffixMapAsync(token).ConfigureAwait(false); List reliqueryLevels = await metadataService.GetReliquaryLevelsAsync(token).ConfigureAwait(false); List reliquaries = await metadataService.GetReliquariesAsync(token).ConfigureAwait(false); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactoryImplementation.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactoryImplementation.cs index 782698b4..dc604b71 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactoryImplementation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactoryImplementation.cs @@ -5,6 +5,7 @@ using Snap.Hutao.Model.Binding.AvatarProperty; using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Metadata; using Snap.Hutao.Model.Metadata.Reliquary; +using Snap.Hutao.Model.Primitive; using MetadataAvatar = Snap.Hutao.Model.Metadata.Avatar.Avatar; using MetadataReliquary = Snap.Hutao.Model.Metadata.Reliquary.Reliquary; using MetadataWeapon = Snap.Hutao.Model.Metadata.Weapon.Weapon; @@ -18,10 +19,10 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory; /// internal class SummaryFactoryImplementation { - private readonly Dictionary idAvatarMap; - private readonly Dictionary idRelicMainPropMap; - private readonly Dictionary idWeaponMap; - private readonly Dictionary idReliquaryAffixMap; + private readonly Dictionary idAvatarMap; + private readonly Dictionary idWeaponMap; + private readonly Dictionary idRelicMainPropMap; + private readonly Dictionary idReliquaryAffixMap; private readonly List reliqueryLevels; private readonly List reliquaries; @@ -35,10 +36,10 @@ internal class SummaryFactoryImplementation /// 圣遗物主属性等级 /// 圣遗物 public SummaryFactoryImplementation( - Dictionary idAvatarMap, - Dictionary idWeaponMap, - Dictionary idRelicMainPropMap, - Dictionary idReliquaryAffixMap, + Dictionary idAvatarMap, + Dictionary idWeaponMap, + Dictionary idRelicMainPropMap, + Dictionary idReliquaryAffixMap, List reliqueryLevels, List reliquaries) { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryReliquaryFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryReliquaryFactory.cs index bfffd5de..0be9dae7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryReliquaryFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryReliquaryFactory.cs @@ -6,6 +6,7 @@ using Snap.Hutao.Model.Binding.AvatarProperty; using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Metadata.Converter; using Snap.Hutao.Model.Metadata.Reliquary; +using Snap.Hutao.Model.Primitive; using Snap.Hutao.Web.Enka.Model; using System.Runtime.InteropServices; using MetadataReliquary = Snap.Hutao.Model.Metadata.Reliquary.Reliquary; @@ -20,8 +21,8 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory; /// internal class SummaryReliquaryFactory { - private readonly Dictionary idReliquaryAffixMap; - private readonly Dictionary idRelicMainPropMap; + private readonly Dictionary idReliquaryAffixMap; + private readonly Dictionary idRelicMainPropMap; private readonly List reliqueryLevels; private readonly List reliquaries; @@ -38,8 +39,8 @@ internal class SummaryReliquaryFactory /// 角色信息 /// 圣遗物 public SummaryReliquaryFactory( - Dictionary idReliquaryAffixMap, - Dictionary idRelicMainPropMap, + Dictionary idReliquaryAffixMap, + Dictionary idRelicMainPropMap, List reliqueryLevels, List reliquaries, ModelAvatarInfo avatarInfo, diff --git a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs index 98f612b7..9de0e456 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs @@ -108,11 +108,8 @@ internal class DailyNoteService : IDailyNoteService, IRecipient e.UserId == userId).UpdateDailyNote(dailyNote); + entries?.Single(e => e.UserId == entry.UserId && e.Uid == entry.Uid).UpdateDailyNote(dailyNote); if (notify) { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs index 8086beed..e1601632 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs @@ -10,6 +10,7 @@ using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Metadata; using Snap.Hutao.Model.Metadata.Avatar; using Snap.Hutao.Model.Metadata.Weapon; +using Snap.Hutao.Model.Primitive; using Snap.Hutao.Service.Metadata; namespace Snap.Hutao.Service.GachaLog.Factory; @@ -37,8 +38,8 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory /// public async Task CreateAsync(IEnumerable items) { - Dictionary idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false); - Dictionary idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false); + Dictionary idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false); + Dictionary idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false); Dictionary nameAvatarMap = await metadataService.GetNameToAvatarMapAsync().ConfigureAwait(false); Dictionary nameWeaponMap = await metadataService.GetNameToWeaponMapAsync().ConfigureAwait(false); @@ -66,8 +67,8 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory private static GachaStatistics CreateCore( IOrderedEnumerable items, List historyWishBuilders, - Dictionary avatarMap, - Dictionary weaponMap, + Dictionary avatarMap, + Dictionary weaponMap, bool isEmptyHistoryWishVisible) { TypedWishSummaryBuilder permanentWishBuilder = new("奔行世间", TypedWishSummaryBuilder.PermanentWish, 90, 10); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs index 8724148d..8d0130df 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs @@ -13,6 +13,7 @@ using Snap.Hutao.Model.Binding.Gacha.Abstraction; using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.InterChange.GachaLog; using Snap.Hutao.Model.Metadata.Abstraction; +using Snap.Hutao.Model.Primitive; using Snap.Hutao.Service.GachaLog.Factory; using Snap.Hutao.Service.Metadata; using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo; @@ -52,8 +53,8 @@ internal class GachaLogService : IGachaLogService, ISupportAsyncInitialization private Dictionary? nameAvatarMap; private Dictionary? nameWeaponMap; - private Dictionary? idAvatarMap; - private Dictionary? idWeaponMap; + private Dictionary? idAvatarMap; + private Dictionary? idWeaponMap; private ObservableCollection? archiveCollection; /// diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs index fea53c96..eb2bd94f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs @@ -5,6 +5,7 @@ using Snap.Hutao.Model.Binding.Hutao; using Snap.Hutao.Model.Metadata; using Snap.Hutao.Model.Metadata.Avatar; using Snap.Hutao.Model.Metadata.Weapon; +using Snap.Hutao.Model.Primitive; using Snap.Hutao.Service.Metadata; using Snap.Hutao.Web.Hutao.Model; @@ -19,7 +20,7 @@ internal class HutaoCache : IHutaoCache private readonly IHutaoService hutaoService; private readonly IMetadataService metadataService; - private Dictionary? idAvatarExtendedMap; + private Dictionary? idAvatarExtendedMap; /// /// 构造一个新的胡桃 API 缓存 @@ -55,7 +56,7 @@ internal class HutaoCache : IHutaoCache { if (await metadataService.InitializeAsync().ConfigureAwait(false)) { - Dictionary idAvatarMap = await GetIdAvatarMapExtendedAsync().ConfigureAwait(false); + Dictionary idAvatarMap = await GetIdAvatarMapExtendedAsync().ConfigureAwait(false); Task avatarAppearanceRankTask = AvatarAppearanceRankAsync(idAvatarMap); Task avatarUsageRank = AvatarUsageRanksAsync(idAvatarMap); @@ -82,9 +83,9 @@ internal class HutaoCache : IHutaoCache { if (await metadataService.InitializeAsync().ConfigureAwait(false)) { - Dictionary idAvatarMap = await GetIdAvatarMapExtendedAsync().ConfigureAwait(false); - Dictionary idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false); - Dictionary idReliquarySetMap = await metadataService.GetEquipAffixIdToReliquarySetMapAsync().ConfigureAwait(false); + Dictionary idAvatarMap = await GetIdAvatarMapExtendedAsync().ConfigureAwait(false); + Dictionary idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false); + Dictionary idReliquarySetMap = await metadataService.GetEquipAffixIdToReliquarySetMapAsync().ConfigureAwait(false); // AvatarCollocation List avatarCollocationsRaw = await hutaoService.GetAvatarCollocationsAsync().ConfigureAwait(false); @@ -105,11 +106,11 @@ internal class HutaoCache : IHutaoCache return false; } - private async ValueTask> GetIdAvatarMapExtendedAsync() + private async ValueTask> GetIdAvatarMapExtendedAsync() { if (idAvatarExtendedMap == null) { - Dictionary idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false); + Dictionary idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false); idAvatarExtendedMap = new(idAvatarMap) { [AvatarIds.PlayerBoy] = new() { Name = "旅行者", Icon = "UI_AvatarIcon_PlayerBoy", Quality = Model.Intrinsic.ItemQuality.QUALITY_ORANGE }, @@ -120,7 +121,7 @@ internal class HutaoCache : IHutaoCache return idAvatarExtendedMap; } - private async Task AvatarAppearanceRankAsync(Dictionary idAvatarMap) + private async Task AvatarAppearanceRankAsync(Dictionary idAvatarMap) { List avatarAppearanceRanksRaw = await hutaoService.GetAvatarAppearanceRanksAsync().ConfigureAwait(false); AvatarAppearanceRanks = avatarAppearanceRanksRaw.OrderByDescending(r => r.Floor).Select(rank => new ComplexAvatarRank @@ -130,7 +131,7 @@ internal class HutaoCache : IHutaoCache }).ToList(); } - private async Task AvatarUsageRanksAsync(Dictionary idAvatarMap) + private async Task AvatarUsageRanksAsync(Dictionary idAvatarMap) { List avatarUsageRanksRaw = await hutaoService.GetAvatarUsageRanksAsync().ConfigureAwait(false); AvatarUsageRanks = avatarUsageRanksRaw.OrderByDescending(r => r.Floor).Select(rank => new ComplexAvatarRank @@ -140,7 +141,7 @@ internal class HutaoCache : IHutaoCache }).ToList(); } - private async Task AvatarConstellationInfosAsync(Dictionary idAvatarMap) + private async Task AvatarConstellationInfosAsync(Dictionary idAvatarMap) { List avatarConstellationInfosRaw = await hutaoService.GetAvatarConstellationInfosAsync().ConfigureAwait(false); AvatarConstellationInfos = avatarConstellationInfosRaw.OrderBy(i => i.HoldingRate).Select(info => @@ -149,7 +150,7 @@ internal class HutaoCache : IHutaoCache }).ToList(); } - private async Task TeamAppearancesAsync(Dictionary idAvatarMap) + private async Task TeamAppearancesAsync(Dictionary idAvatarMap) { List teamAppearancesRaw = await hutaoService.GetTeamAppearancesAsync().ConfigureAwait(false); TeamAppearances = teamAppearancesRaw.OrderByDescending(t => t.Floor).Select(team => new ComplexTeamRank(team, idAvatarMap)).ToList(); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs b/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs index ba04a5e2..58dd7620 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media.Animation; using Snap.Hutao.Service.Abstraction; namespace Snap.Hutao.Service; @@ -117,6 +118,7 @@ internal class InfoBarService : IInfoBarService Title = title, Message = message, IsOpen = true, + Transitions = new() { new AddDeleteThemeTransition() }, }; infoBar.Closed += OnInfoBarClosed; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataService.cs index a8bbb426..b19954a7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataService.cs @@ -7,6 +7,7 @@ using Snap.Hutao.Model.Metadata.Achievement; using Snap.Hutao.Model.Metadata.Avatar; using Snap.Hutao.Model.Metadata.Reliquary; using Snap.Hutao.Model.Metadata.Weapon; +using Snap.Hutao.Model.Primitive; namespace Snap.Hutao.Service.Metadata; @@ -47,7 +48,7 @@ internal interface IMetadataService /// /// 取消令牌 /// 装备被动Id到圣遗物套装的映射 - ValueTask> GetEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default); + ValueTask> GetEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default); /// /// 异步获取卡池配置列表 @@ -61,28 +62,28 @@ internal interface IMetadataService /// /// 取消令牌 /// Id到角色的字典 - ValueTask> GetIdToAvatarMapAsync(CancellationToken token = default); + ValueTask> GetIdToAvatarMapAsync(CancellationToken token = default); /// /// 异步获取ID到圣遗物副词条的字典 /// /// 取消令牌 /// 字典 - ValueTask> GetIdReliquaryAffixMapAsync(CancellationToken token = default); + ValueTask> GetIdReliquaryAffixMapAsync(CancellationToken token = default); /// /// 异步获取圣遗物主词条Id与属性的字典 /// /// 取消令牌 /// 字典 - ValueTask> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default); + ValueTask> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default); /// /// 异步获取ID到武器的字典 /// /// 取消令牌 /// Id到武器的字典 - ValueTask> GetIdToWeaponMapAsync(CancellationToken token = default); + ValueTask> GetIdToWeaponMapAsync(CancellationToken token = default); /// /// 异步获取名称到角色的字典 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Indexing.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Indexing.cs index e7496a77..8c27a5b7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Indexing.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Indexing.cs @@ -5,6 +5,7 @@ using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Metadata.Avatar; using Snap.Hutao.Model.Metadata.Reliquary; using Snap.Hutao.Model.Metadata.Weapon; +using Snap.Hutao.Model.Primitive; namespace Snap.Hutao.Service.Metadata; @@ -14,33 +15,33 @@ namespace Snap.Hutao.Service.Metadata; internal partial class MetadataService { /// - public ValueTask> GetEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default) + public ValueTask> GetEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default) { - return FromCacheAsDictionaryAsync("ReliquarySet", r => r.EquipAffixId, token); + return FromCacheAsDictionaryAsync("ReliquarySet", r => r.EquipAffixId, token); } /// - public ValueTask> GetIdToAvatarMapAsync(CancellationToken token = default) + public ValueTask> GetIdToAvatarMapAsync(CancellationToken token = default) { - return FromCacheAsDictionaryAsync("Avatar", a => a.Id, token); + return FromCacheAsDictionaryAsync("Avatar", a => a.Id, token); } /// - public ValueTask> GetIdReliquaryAffixMapAsync(CancellationToken token = default) + public ValueTask> GetIdReliquaryAffixMapAsync(CancellationToken token = default) { - return FromCacheAsDictionaryAsync("ReliquaryAffix", a => a.Id, token); + return FromCacheAsDictionaryAsync("ReliquaryAffix", a => a.Id, token); } /// - public ValueTask> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default) + public ValueTask> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default) { - return FromCacheAsDictionaryAsync("ReliquaryMainAffix", r => r.Id, r => r.Type, token); + return FromCacheAsDictionaryAsync("ReliquaryMainAffix", r => r.Id, r => r.Type, token); } /// - public ValueTask> GetIdToWeaponMapAsync(CancellationToken token = default) + public ValueTask> GetIdToWeaponMapAsync(CancellationToken token = default) { - return FromCacheAsDictionaryAsync("Weapon", w => w.Id, token); + return FromCacheAsDictionaryAsync("Weapon", w => w.Id, token); } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj index 2190a5b2..4b5c281a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj +++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj @@ -71,6 +71,7 @@ + @@ -145,7 +146,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all @@ -169,6 +170,11 @@ + + + MSBuild:Compile + + MSBuild:Compile diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/SignInWebViewDialog.xaml b/src/Snap.Hutao/Snap.Hutao/View/Dialog/SignInWebViewDialog.xaml new file mode 100644 index 00000000..e642b2ff --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/SignInWebViewDialog.xaml @@ -0,0 +1,13 @@ + + + + + + diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/SignInWebViewDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/SignInWebViewDialog.xaml.cs new file mode 100644 index 00000000..85c71a90 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/SignInWebViewDialog.xaml.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Microsoft.UI.Xaml.Controls; +using Microsoft.Web.WebView2.Core; +using Snap.Hutao.Model.Binding.User; +using Snap.Hutao.Service.Abstraction; +using Snap.Hutao.Service.User; +using Snap.Hutao.Web.Bridge; +using Snap.Hutao.Web.Bridge.Model; +using Snap.Hutao.Web.Bridge.Model.Event; +using Snap.Hutao.Web.Hoyolab.DynamicSecret; +using Windows.UI.Popups; + +namespace Snap.Hutao.View.Dialog; + +/// +/// ǩҳͼԻ +/// +public sealed partial class SignInWebViewDialog : ContentDialog +{ + /// + /// һµǩҳͼԻ + /// + /// + public SignInWebViewDialog(MainWindow window) + { + InitializeComponent(); + XamlRoot = window.Content.XamlRoot; + } + + private void OnGridLoaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) + { + InitializeAsync().SafeForget(); + } + + private async Task InitializeAsync() + { + await WebView.EnsureCoreWebView2Async(); + CoreWebView2 coreWebView2 = WebView.CoreWebView2; + IUserService userService = Ioc.Default.GetRequiredService(); + IInfoBarService infoBarService = Ioc.Default.GetRequiredService(); + ILogger logger = Ioc.Default.GetRequiredService>(); + User? user = userService.Current; + + coreWebView2.SetCookie(user?.CookieToken, user?.Ltoken); + coreWebView2.SetMobileUserAgent(); + coreWebView2.InitializeBridge(logger, false) + .Register(e => Hide()) + .Register(e => infoBarService.Information("޷ʹô˹", "ǰʵ֤")) + .Register(s => s.Callback(result => result.Data["statusBarHeight"] = 0)) + .Register(s => s.Callback(result => + { + result.Data["DS"] = DynamicSecretHandler.GetDynamicSecret(nameof(SaltType.K2), nameof(DynamicSecretVersion.Gen1), includeChars: true); + })) + .Register(s => s.Callback(result => + { + result.Data["id"] = "111"; + result.Data["gender"] = 0; + result.Data["nickname"] = "222"; + result.Data["introduce"] = "333"; + result.Data["avatar_url"] = "https://img-static.mihoyo.com/communityweb/upload/52de23f1b1a060e4ccaa8b24c1305dd9.png"; + })); + + coreWebView2.OpenDevToolsWindow(); + coreWebView2.Navigate("https://webstatic.mihoyo.com/bbs/event/signin-ys/index.html?act_id=e202009291139501"); + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml index ff880288..c8a028bf 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml @@ -133,11 +133,6 @@ - - - - - diff --git a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml index f25cbc25..2f351d56 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml @@ -194,6 +194,13 @@ + + + + (RemoveUserAsync); CopyCookieCommand = new RelayCommand(CopyCookie); + ShowSignInWebViewDialogCommand = asyncRelayCommandFactory.Create(ShowSignInWebViewDialogAsync); } /// @@ -83,6 +84,8 @@ internal class UserViewModel : ObservableObject /// public ICommand LoginMihoyoUserCommand { get; } + public ICommand ShowSignInWebViewDialogCommand { get; } + /// /// 移除用户命令 /// @@ -165,4 +168,10 @@ internal class UserViewModel : ObservableObject infoBarService.Error(e); } } + + private async Task ShowSignInWebViewDialogAsync() + { + MainWindow mainWindow = Ioc.Default.GetRequiredService(); + await new SignInWebViewDialog(mainWindow).ShowAsync(); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/ApiEndpoints.cs b/src/Snap.Hutao/Snap.Hutao/Web/ApiEndpoints.cs index 460f996c..f9e2fb15 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/ApiEndpoints.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/ApiEndpoints.cs @@ -226,7 +226,7 @@ internal static class ApiEndpoints /// /// 获取V2Stoken /// - public const string AccountGetSTokenByOldtoken = $"{PassportApi}/account/ma-cn-session/app/getTokenBySToken"; + public const string AccountGetSTokenByOldToken = $"{PassportApi}/account/ma-cn-session/app/getTokenBySToken"; /// /// 登录 diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/BridgeExtension.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/BridgeExtension.cs new file mode 100644 index 00000000..cca23e81 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/BridgeExtension.cs @@ -0,0 +1,107 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.Web.WebView2.Core; +using Snap.Hutao.Web.Hoyolab; +using WinRT; + +namespace Snap.Hutao.Web.Bridge; + +/// +/// Bridge 拓展 +/// +public static class BridgeExtension +{ + private const string InitializeJsInterfaceScript = """ + let c = {}; + c.postMessage = str => chrome.webview.hostObjects.MiHoYoJsBridge.OnMessage(str); + c.closePage = () => c.postMessage('{"method":"closePage"}'); + window.MiHoYoJSInterface = c; + """; + + private const string HideScrollBarScript = """ + let st = document.createElement('style'); + st.innerHTML = '::-webkit-scrollbar{display:none}'; + document.querySelector('body').appendChild(st); + """; + + /// + /// 设置 移动端UA + /// + /// webview2 + public static void SetMobileUserAgent(this CoreWebView2 webView) + { + webView.Settings.UserAgent = "Mozilla/5.0 (Linux; Android 12) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/106.0.5249.126 Mobile Safari/537.36 miHoYoBBS/2.41.0"; + } + + /// + /// 初始化调用桥 + /// + /// webview2 + /// 日志器 + /// 检查主机 + /// 初始化后的调用桥 + public static MiHoYoJsBridge InitializeBridge(this CoreWebView2 webView, ILogger logger, bool checkHost = true) + { + MiHoYoJsBridge bridge = new(webView, logger); + var result = webView.As().AddHostObjectToScript("MiHoYoJsBridge", bridge); + + webView.DOMContentLoaded += OnDOMContentLoaded; + webView.NavigationStarting += (coreWebView2, args) => OnWebViewNavigationStarting(coreWebView2, args, checkHost); + + return bridge; + } + + /// + /// 设置WebView2的Cookie + /// + /// webview2 + /// CookieToken + /// Ltoken + /// Stoken + /// 链式调用的WebView2 + public static CoreWebView2 SetCookie(this CoreWebView2 webView, Cookie? cookieToken = null, Cookie? ltoken = null, Cookie? stoken = null) + { + CoreWebView2CookieManager cookieManager = webView.CookieManager; + + if (cookieToken != null) + { + cookieManager.AddMihoyoCookie("account_id", cookieToken).AddMihoyoCookie("cookie_token", cookieToken); + } + + if (ltoken != null) + { + cookieManager.AddMihoyoCookie("ltuid", ltoken).AddMihoyoCookie("ltoken", ltoken); + } + + if (stoken != null) + { + cookieManager.AddMihoyoCookie("stuid", stoken).AddMihoyoCookie("stoken", stoken); + } + + return webView; + } + + private static CoreWebView2CookieManager AddMihoyoCookie(this CoreWebView2CookieManager manager, string name, Cookie cookie) + { + manager.AddOrUpdateCookie(manager.CreateCookie(name, cookie[name], ".mihoyo.com", "/")); + return manager; + } + + [SuppressMessage("", "VSTHRD100")] + private static async void OnDOMContentLoaded(CoreWebView2 coreWebView2, CoreWebView2DOMContentLoadedEventArgs args) + { + string result = await coreWebView2.ExecuteScriptAsync(HideScrollBarScript); + _ = result; + } + + [SuppressMessage("", "VSTHRD100")] + private static async void OnWebViewNavigationStarting(CoreWebView2 coreWebView2, CoreWebView2NavigationStartingEventArgs args, bool checkHost) + { + if (!checkHost || new Uri(args.Uri).Host.EndsWith("mihoyo.com")) + { + string result = await coreWebView2.ExecuteScriptAsync(InitializeJsInterfaceScript); + _ = result; + } + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/ICoreWebView2Interop.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/ICoreWebView2Interop.cs new file mode 100644 index 00000000..7df0bb69 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/ICoreWebView2Interop.cs @@ -0,0 +1,24 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using System.Runtime.InteropServices; +using Windows.Win32.Foundation; + +namespace Snap.Hutao.Web.Bridge; + +/// +/// ICoreWebView2Interop +/// +[ComImport] +[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +[Guid("912b34a7-d10b-49c4-af18-7cb7e604e01a")] +public interface ICoreWebView2Interop +{ + /// + /// Add the provided host object to script running in the WebView with the specified name. + /// + /// 名称 + /// 对象 + /// 结果 + HRESULT AddHostObjectToScript([In] string name, [In] ref object obj); +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/IMiHoYoJsBridge.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/IMiHoYoJsBridge.cs new file mode 100644 index 00000000..0c8f2c2a --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/IMiHoYoJsBridge.cs @@ -0,0 +1,20 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using System.Runtime.InteropServices; + +namespace Snap.Hutao.Web.Bridge; + +/// +/// 调用桥暴露的COM接口 +/// +[ComVisible(true)] +[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] +public interface IMiHoYoJsBridge +{ + /// + /// 消息发生时调用 + /// + /// 消息 + void OnMessage(string str); +} diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJsBridge.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJsBridge.cs new file mode 100644 index 00000000..e0a76468 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJsBridge.cs @@ -0,0 +1,103 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.Web.WebView2.Core; +using Snap.Hutao.Web.Bridge.Model; +using Snap.Hutao.Web.Bridge.Model.Event; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace Snap.Hutao.Web.Bridge; + +/// +/// 调用桥 +/// +[ComVisible(true)] +[ClassInterface(ClassInterfaceType.AutoDual)] +public sealed class MiHoYoJsBridge /*: IMiHoYoJsBridge*/ +{ + private readonly CoreWebView2 webView; + private readonly ILogger? logger; + + private readonly Dictionary jsWebInvokeTypeCache = new(); + private readonly Dictionary> callbackHandlers = new(); + + /// + /// 构造一个新的调用桥 + /// + /// webview2 + /// 日志器 + internal MiHoYoJsBridge(CoreWebView2 webView, ILogger? logger = null) + { + this.webView = webView; + this.logger = logger; + } + + /// + /// 消息发生时调用 + /// + /// 消息 + public void OnMessage(string message) + { + logger?.LogInformation("[OnMessage] {message}", message); + + JsParam p = JsonSerializer.Deserialize(message)!; + p.Bridge = this; + + callbackHandlers.GetValueOrDefault(p.Method)?.Invoke(p); + } + + /// + /// 调用JS回调 + /// + /// 回调名称 + /// 传输的数据 + /// 执行结果 + public Task InvokeJsCallbackAsync(string callbackName, string? payload = null) + { + if (string.IsNullOrEmpty(callbackName)) + { + return Task.FromResult(string.Empty); + } + + string dataStr = payload == null ? string.Empty : $", {payload}"; + string js = $"javascript:mhyWebBridge(\"{callbackName}\"{dataStr})"; + logger?.LogInformation("[InvokeJsCallback] {js}", js); + return webView.ExecuteScriptAsync(js).AsTask(); + } + + /// + /// 注册回调 + /// + /// 回调类型 + /// 回调 + /// + public MiHoYoJsBridge Register(Action callback) + where T : notnull + { + callbackHandlers[GetCallbackName()] = callback; + return this; + } + + /// + /// 注册回调 + /// + /// 回调类型 + /// 回调 + /// + public MiHoYoJsBridge Register(Action callback) + where T : notnull + { + callbackHandlers[GetCallbackName()] = p => callback(p, p.Data.As()); + return this; + } + + private string GetCallbackName() + { + Type type = typeof(T); + string invokeName = type.GetCustomAttribute()?.Name + ?? throw new ArgumentException("Type Callback not registered."); + + return jsWebInvokeTypeCache[type.Name] = invokeName; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/Event/WebInvokeAttribute.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/Event/WebInvokeAttribute.cs new file mode 100644 index 00000000..eb7a1d09 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/Event/WebInvokeAttribute.cs @@ -0,0 +1,196 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Web.Bridge.Model.Event; + +/// +/// Web 调用 +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] +public class WebInvokeAttribute : Attribute +{ + /// + /// 构造一个新的Web 调用特性 + /// + /// 函数名称 + public WebInvokeAttribute(string name) + { + Name = name; + } + + /// + /// 调用函数名称 + /// + public string Name { get; init; } +} + +public class ButtonParam +{ + [JsonPropertyName("title")] + public string Title { get; set; } = default!; + + [JsonPropertyName("style")] + public string Style { get; set; } = default!; +} + +public abstract class GenAuthKeyBase +{ + [JsonPropertyName("game_biz")] + public string Biz { get; set; } = default!; + + [JsonPropertyName("auth_appid")] + public string AppId { get; set; } = default!; + + [JsonPropertyName("game_uid")] + public uint Uid { get; set; } + + [JsonPropertyName("region")] + public string Region { get; set; } = default!; +} + +[WebInvoke("closePage")] +public struct JsEventClosePage +{ +} + +[WebInvoke("configure_share")] +public class JsEventConfigureShare +{ + [JsonPropertyName("enable")] + public bool Enable { get; set; } +} + +[WebInvoke("genAppAuthKey")] +public class JsEventGenAppAuthKey + : GenAuthKeyBase +{ +} + +[WebInvoke("genAuthKey")] +public class JsEventGenAuthKey + : GenAuthKeyBase +{ +} + +[WebInvoke("getActionTicket")] +public class JsEventGetActionTicket +{ + [JsonPropertyName("action_type")] + public string ActionType { get; set; } = default!; +} + +[WebInvoke("getCookieToken")] +public class JsEventGetCookieToken +{ + [JsonPropertyName("forceRefresh")] + public bool ForceRefresh { get; set; } +} + +[WebInvoke("getDS")] +public struct JsEventGetDynamicSecretV1 +{ +} + +[WebInvoke("getDS2")] +public class JsEventGetDynamicSecretV2 +{ + [JsonPropertyName("query")] + public Dictionary Query { get; set; } = new(); + + [JsonPropertyName("body")] + public string Body { get; set; } = default!; +} + +[WebInvoke("getNotificationSettings")] +public struct JsEventGetNotificationSettings +{ +} + +[WebInvoke("startRealnameAuth")] +public struct JsEventGetRealNameStatus +{ + // guess +} + +[WebInvoke("getHTTPRequestHeaders")] +public struct JsEventGetRequestHeader +{ +} + +[WebInvoke("getStatusBarHeight")] +public struct JsEventGetStatusBarHeight +{ + // just zero +} + +[WebInvoke("getUserInfo")] +public struct JsEventGetUserInfo +{ +} + +[WebInvoke("getCookieInfo")] +public struct JsEventGetWebLoginInfo +{ +} + +[WebInvoke("openSystemBrowser")] +public class JsEventOpenSystemBrowser +{ + [JsonPropertyName("open_url")] + public string PageUrl { get; set; } = default!; +} + +[WebInvoke("pushPage")] +public class JsEventPushPage +{ + private string pageUrl = default!; + + [JsonPropertyName("page")] + public string PageUrl + { + get => pageUrl; + set => SetPageUrl(value); + } + + private void SetPageUrl(string value) + { + pageUrl = value.StartsWith("mihoyobbs") + ? value.Replace("mihoyobbs://", "https://bbs.mihoyo.com/dby/").Replace("topic", "topicDetail") + : value; + } +} + +[WebInvoke("startRealPersonValidation")] +public struct JsEventRealPersonValidation +{ +} + +[WebInvoke("saveLoginTicket")] +public class JsEventSaveLoginTicket +{ + [JsonPropertyName("login_ticket")] + public string LoginTicket { get; set; } = default!; +} + +[WebInvoke("showAlertDialog")] +public class JsEventShowAlertDialog +{ + [JsonPropertyName("title")] + public string Title { get; set; } = default!; + + [JsonPropertyName("message")] + public string Message { get; set; } = default!; + + [JsonPropertyName("buttons")] + public List Buttons { get; set; } = new(); +} + +[WebInvoke("showToast")] +public class JsEventShowToast +{ + [JsonPropertyName("toast")] + public string Text { get; set; } = default!; + + [JsonPropertyName("type")] + public string Type { get; set; } = default!; +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsParam.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsParam.cs new file mode 100644 index 00000000..6e1b9948 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsParam.cs @@ -0,0 +1,60 @@ +namespace Snap.Hutao.Web.Bridge.Model; + +/// +/// Js 参数 +/// +public class JsParam +{ + /// + /// 方法名称 + /// + [JsonPropertyName("method")] + public string Method { get; set; } = string.Empty; + + /// + /// 数据 + /// + [JsonPropertyName("payload")] + public JsonElement Data { get; set; } + + /// + /// 回调名称 + /// + [JsonPropertyName("callback")] + public string CallbackName { get; set; } = string.Empty; + + /// + /// 对应的调用桥 + /// + internal MiHoYoJsBridge Bridge { get; set; } = null!; + + /// + /// 执行回调 + /// + /// 结果工厂 + public void Callback(Func? resultFactory = null) + { + JsResult? result = resultFactory?.Invoke() ?? new(); + Callback(result?.ToString()); + } + + /// + /// 执行回调 + /// + /// 结果工厂 + public void Callback(Action resultModifier) + { + JsResult result = new(); + resultModifier(result); + Callback(result?.ToString()); + } + + /// + /// 执行回调 + /// + /// 结果 + public void Callback(string? result = null) + { + Bridge.InvokeJsCallbackAsync(CallbackName, result).GetAwaiter().GetResult(); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsResult.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsResult.cs new file mode 100644 index 00000000..9bd26982 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsResult.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Snap.Hutao.Web.Bridge.Model; + +/// +/// Js结果 +/// +public class JsResult +{ + /// + /// 代码 + /// + [JsonPropertyName("retcode")] + public int Code { get; set; } + + /// + /// 消息 + /// + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; + + /// + /// 数据 + /// + [JsonPropertyName("data")] + public Dictionary Data { get; set; } = new(); + + /// + public override string ToString() + { + return JsonSerializer.Serialize(this); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsonElementExtension.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsonElementExtension.cs new file mode 100644 index 00000000..a802c063 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/Model/JsonElementExtension.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Snap.Hutao.Web.Bridge.Model; + +/// +/// JsonElement 拓展 +/// +public static class JsonElementExtension +{ + /// + /// 序列化到对应类型 + /// + /// 对应类型 + /// 元素 + /// 对应类型的实例 + public static T As(this JsonElement jsonElement) + where T : notnull + { + return jsonElement.Deserialize()!; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretHandler.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretHandler.cs index bc69cc3e..1a671bc8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretHandler.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/DynamicSecretHandler.cs @@ -15,7 +15,7 @@ namespace Snap.Hutao.Web.Hoyolab.DynamicSecret; [Injection(InjectAs.Transient)] public class DynamicSecretHandler : DelegatingHandler { - private const string RandomRange = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; + private const string RandomRange = "abcdefghijklmnopqrstuvwxyz1234567890"; // https://github.com/UIGF-org/Hoyolab.Salt private static readonly ImmutableDictionary DynamicSecrets = new Dictionary() @@ -66,6 +66,36 @@ public class DynamicSecretHandler : DelegatingHandler return await base.SendAsync(request, token).ConfigureAwait(false); } + /// + /// 获取DS + /// + /// nameof + /// nameof + /// body + /// query + /// 是否需要字母 + /// DS + public static string GetDynamicSecret(string saltType, string version, string? body = null, string? query = null, bool includeChars = true) + { + string salt = DynamicSecrets[saltType]; + + long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + + string r = includeChars ? GetRandomStringWithChars() : GetRandomStringNoChars(); + + string dsContent = $"salt={salt}&t={t}&r={r}"; + + if (version == nameof(DynamicSecretVersion.Gen2)) + { + string[] queries = Uri.UnescapeDataString(query!).Split('?', 2); + string q = queries.Length == 2 ? string.Join('&', queries[1].Split('&').OrderBy(x => x)) : string.Empty; + + dsContent = $"{dsContent}&b={body}&q={q}"; + } + + return Md5Convert.ToHexString(dsContent).ToLowerInvariant(); + } + private static string GetRandomStringWithChars() { StringBuilder sb = new(6); diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs index 7e651cdf..71a6b1fe 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs @@ -69,7 +69,7 @@ internal class PassportClient2 HttpResponseMessage message = await httpClient .SetHeader("Cookie", stokenV1.ToString()) .UseDynamicSecret(DynamicSecretVersion.Gen2, SaltType.PROD, true) - .PostAsync(ApiEndpoints.AccountGetSTokenByOldtoken, null, token) + .PostAsync(ApiEndpoints.AccountGetSTokenByOldToken, null, token) .ConfigureAwait(false); Response? resp = await message.Content diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/ReliquarySet.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/ReliquarySet.cs index 05ad8134..b2ed9805 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/ReliquarySet.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/ReliquarySet.cs @@ -1,6 +1,8 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Model.Primitive; + namespace Snap.Hutao.Web.Hutao.Model; /// @@ -23,7 +25,7 @@ public class ReliquarySet /// /// Id /// - public int EquipAffixId { get; } + public ExtendedEquipAffixId EquipAffixId { get; } /// /// 个数