mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf5fcb70f8 | ||
|
|
fda642b72f | ||
|
|
fa650a95c5 | ||
|
|
02fae69d1e | ||
|
|
76800de6ee | ||
|
|
72b660119f |
39
.github/workflows/PublishDistribution.yml
vendored
Normal file
39
.github/workflows/PublishDistribution.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: PublishDistribution
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
Publish:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Download Publish.zip
|
||||
- name: Download Release
|
||||
uses: robinraju/release-downloader@v1.5
|
||||
with:
|
||||
repository: "DGP-Studio/Snap.Hutao"
|
||||
latest: true
|
||||
fileName: "*.zip"
|
||||
out-file-path: ./release-download
|
||||
|
||||
# Upload to OD21 (Testing)
|
||||
- name: Upload OD21
|
||||
env:
|
||||
RCCONF: ${{ secrets.RCCONF }}
|
||||
run: |
|
||||
curl https://rclone.org/install.sh | sudo bash
|
||||
mkdir -p ~/.config/rclone/
|
||||
cat << EOF > ~/.config/rclone/rclone.conf
|
||||
$RCCONF
|
||||
EOF
|
||||
|
||||
rclone copy ./release-download/* dgpODCN:/snaphutao/Releases/
|
||||
@@ -9,6 +9,7 @@ using Snap.Hutao.Core.LifeCycle;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Service.AppCenter;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using System.Diagnostics;
|
||||
using Windows.Storage;
|
||||
@@ -27,13 +28,14 @@ public partial class App : Application
|
||||
/// Initializes the singleton application object.
|
||||
/// </summary>
|
||||
/// <param name="logger">日志器</param>
|
||||
public App(ILogger<App> logger)
|
||||
/// <param name="appCenter">App Center</param>
|
||||
public App(ILogger<App> logger, AppCenter appCenter)
|
||||
{
|
||||
// load app resource
|
||||
InitializeComponent();
|
||||
this.logger = logger;
|
||||
|
||||
_ = new ExceptionRecorder(this, logger);
|
||||
_ = new ExceptionRecorder(this, logger, appCenter);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -57,6 +59,8 @@ public partial class App : Application
|
||||
.ImplictAs<IMetadataInitializer>()?
|
||||
.InitializeInternalAsync()
|
||||
.SafeForget(logger);
|
||||
|
||||
Ioc.Default.GetRequiredService<AppCenter>().Initialize();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ internal class I18NExtension : MarkupExtension
|
||||
static I18NExtension()
|
||||
{
|
||||
string currentName = CultureInfo.CurrentUICulture.Name;
|
||||
Type? languageType = ((IDictionary<string, Type>)TranslationMap).GetValueOrDefault2(currentName, typeof(LanguagezhCN));
|
||||
Type? languageType = TranslationMap.GetValueOrDefault2(currentName, typeof(LanguagezhCN));
|
||||
Translation = (ITranslation)Activator.CreateInstance(languageType!)!;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Markup;
|
||||
|
||||
namespace Snap.Hutao.Control.Markup;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
namespace Snap.Hutao.Control.Panel;
|
||||
|
||||
@@ -28,8 +27,8 @@ public sealed partial class PanelSelector : UserControl
|
||||
/// </summary>
|
||||
public string Current
|
||||
{
|
||||
get { return (string)GetValue(CurrentProperty); }
|
||||
set { SetValue(CurrentProperty, value); }
|
||||
get => (string)GetValue(CurrentProperty);
|
||||
set => SetValue(CurrentProperty, value);
|
||||
}
|
||||
|
||||
private void SplitButtonLoaded(object sender, RoutedEventArgs e)
|
||||
|
||||
@@ -11,6 +11,9 @@ namespace Snap.Hutao.Control;
|
||||
/// <summary>
|
||||
/// 表示支持取消加载的异步页面
|
||||
/// 在被导航到其他页面前触发取消异步通知
|
||||
/// <para/>
|
||||
/// InitializeWith{T}();
|
||||
/// InitializeComponent();
|
||||
/// </summary>
|
||||
public class ScopedPage : Page
|
||||
{
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Win32;
|
||||
using Snap.Hutao.Extension;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using Windows.ApplicationModel;
|
||||
|
||||
@@ -12,6 +15,9 @@ namespace Snap.Hutao.Core;
|
||||
/// </summary>
|
||||
internal static class CoreEnvironment
|
||||
{
|
||||
private const string CryptographyKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\";
|
||||
private const string MachineGuidValue = "MachineGuid";
|
||||
|
||||
// 计算过程:https://gist.github.com/Lightczx/373c5940b36e24b25362728b52dec4fd
|
||||
|
||||
/// <summary>
|
||||
@@ -49,6 +55,11 @@ internal static class CoreEnvironment
|
||||
/// </summary>
|
||||
public static readonly string HoyolabDeviceId;
|
||||
|
||||
/// <summary>
|
||||
/// AppCenter 设备Id
|
||||
/// </summary>
|
||||
public static readonly string AppCenterDeviceId;
|
||||
|
||||
/// <summary>
|
||||
/// 默认的Json序列化选项
|
||||
/// </summary>
|
||||
@@ -67,5 +78,15 @@ internal static class CoreEnvironment
|
||||
|
||||
// simply assign a random guid
|
||||
HoyolabDeviceId = Guid.NewGuid().ToString();
|
||||
AppCenterDeviceId = GetUniqueUserID();
|
||||
}
|
||||
|
||||
private static string GetUniqueUserID()
|
||||
{
|
||||
string userName = Environment.UserName;
|
||||
object? machineGuid = Registry.GetValue(CryptographyKey, MachineGuidValue, userName);
|
||||
byte[] bytes = Encoding.UTF8.GetBytes($"{userName}{machineGuid}");
|
||||
byte[] hash = MD5.Create().ComputeHash(bytes);
|
||||
return System.Convert.ToHexString(hash);
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ internal static partial class IocHttpClientConfiguration
|
||||
{
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd(CoreEnvironment.HoyolabUA);
|
||||
client.DefaultRequestHeaders.Accept.ParseAdd("application/json");
|
||||
client.DefaultRequestHeaders.Add("x-rpc-app_version", CoreEnvironment.HoyolabXrpcVersion);
|
||||
client.DefaultRequestHeaders.Add("x-rpc-client_type", "5");
|
||||
client.DefaultRequestHeaders.Add("x-rpc-device_id", CoreEnvironment.HoyolabDeviceId);
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.Diagnostics;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using Snap.Hutao.Service.AppCenter;
|
||||
|
||||
namespace Snap.Hutao.Core.Exception;
|
||||
|
||||
@@ -15,15 +13,18 @@ namespace Snap.Hutao.Core.Exception;
|
||||
internal class ExceptionRecorder
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private readonly AppCenter appCenter;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的异常记录器
|
||||
/// </summary>
|
||||
/// <param name="application">应用程序</param>
|
||||
/// <param name="logger">日志器</param>
|
||||
public ExceptionRecorder(Application application, ILogger logger)
|
||||
/// <param name="appCenter">App Center</param>
|
||||
public ExceptionRecorder(Application application, ILogger logger, AppCenter appCenter)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.appCenter = appCenter;
|
||||
|
||||
application.UnhandledException += OnAppUnhandledException;
|
||||
application.DebugSettings.BindingFailed += OnXamlBindingFailed;
|
||||
@@ -31,9 +32,7 @@ internal class ExceptionRecorder
|
||||
|
||||
private void OnAppUnhandledException(object? sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
// string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
|
||||
// string fileName = $"ex-{DateTimeOffset.Now:yyyyMMddHHmmssffff}.txt";
|
||||
// File.WriteAllText(Path.Combine(path, fileName), $"{e.Exception}\r\n{e.Exception.StackTrace}");
|
||||
appCenter.TrackCrash(e.Exception);
|
||||
logger.LogError(EventIds.UnhandledException, e.Exception, "未经处理的异常");
|
||||
|
||||
foreach (ILoggerProvider provider in Ioc.Default.GetRequiredService<IEnumerable<ILoggerProvider>>())
|
||||
|
||||
30
src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniComment.cs
Normal file
30
src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniComment.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Ini;
|
||||
|
||||
/// <summary>
|
||||
/// Ini 注释
|
||||
/// </summary>
|
||||
internal class IniComment : IniElement
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的 Ini 注释
|
||||
/// </summary>
|
||||
/// <param name="comment">注释</param>
|
||||
public IniComment(string comment)
|
||||
{
|
||||
Comment = comment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注释
|
||||
/// </summary>
|
||||
public string Comment { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $";{Comment}";
|
||||
}
|
||||
}
|
||||
16
src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniElement.cs
Normal file
16
src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniElement.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Ini;
|
||||
|
||||
/// <summary>
|
||||
/// Ini 元素
|
||||
/// </summary>
|
||||
internal abstract class IniElement
|
||||
{
|
||||
/// <summary>
|
||||
/// 将当前元素转换到等价的字符串表示
|
||||
/// </summary>
|
||||
/// <returns>字符串</returns>
|
||||
public new abstract string ToString();
|
||||
}
|
||||
37
src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniParameter.cs
Normal file
37
src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniParameter.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Ini;
|
||||
|
||||
/// <summary>
|
||||
/// Ini 参数
|
||||
/// </summary>
|
||||
internal class IniParameter : IniElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Ini 参数
|
||||
/// </summary>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">值</param>
|
||||
public IniParameter(string key, string value)
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 键
|
||||
/// </summary>
|
||||
public string Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 值
|
||||
/// </summary>
|
||||
public string Value { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Key}={Value}";
|
||||
}
|
||||
}
|
||||
31
src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniSection.cs
Normal file
31
src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniSection.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Ini;
|
||||
|
||||
/// <summary>
|
||||
/// Ini 节
|
||||
/// </summary>
|
||||
internal class IniSection : IniElement
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的Ini 节
|
||||
/// </summary>
|
||||
/// <param name="name">名称</param>
|
||||
/// <param name="elements">元素</param>
|
||||
public IniSection(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[{Name}]";
|
||||
}
|
||||
}
|
||||
47
src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniSerializer.cs
Normal file
47
src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniSerializer.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Ini;
|
||||
|
||||
/// <summary>
|
||||
/// Ini 序列化器
|
||||
/// </summary>
|
||||
internal static class IniSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步反序列化
|
||||
/// </summary>
|
||||
/// <param name="fileStream">文件流</param>
|
||||
/// <returns>Ini 元素集合</returns>
|
||||
public static IEnumerable<IniElement> Deserialize(FileStream fileStream)
|
||||
{
|
||||
using (TextReader reader = new StreamReader(fileStream))
|
||||
{
|
||||
while (reader.ReadLine() is string line)
|
||||
{
|
||||
if (line.Length > 0)
|
||||
{
|
||||
if (line[0] == '[')
|
||||
{
|
||||
yield return new IniSection(line[1..^1]);
|
||||
}
|
||||
|
||||
if (line[0] == ';')
|
||||
{
|
||||
yield return new IniComment(line[1..]);
|
||||
}
|
||||
|
||||
if (line.IndexOf('=') > 0)
|
||||
{
|
||||
string[] parameters = line.Split('=', 2);
|
||||
yield return new IniParameter(parameters[0], parameters[1]);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Service.AppCenter;
|
||||
|
||||
namespace Snap.Hutao.Factory;
|
||||
|
||||
@@ -11,15 +12,18 @@ namespace Snap.Hutao.Factory;
|
||||
[Injection(InjectAs.Transient, typeof(IAsyncRelayCommandFactory))]
|
||||
internal class AsyncRelayCommandFactory : IAsyncRelayCommandFactory
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private readonly ILogger<AsyncRelayCommandFactory> logger;
|
||||
private readonly AppCenter appCenter;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的异步命令工厂
|
||||
/// </summary>
|
||||
/// <param name="logger">日志器</param>
|
||||
public AsyncRelayCommandFactory(ILogger<AsyncRelayCommandFactory> logger)
|
||||
/// <param name="appCenter">App Center</param>
|
||||
public AsyncRelayCommandFactory(ILogger<AsyncRelayCommandFactory> logger, AppCenter appCenter)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.appCenter = appCenter;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -94,6 +98,7 @@ internal class AsyncRelayCommandFactory : IAsyncRelayCommandFactory
|
||||
{
|
||||
Exception baseException = exception.GetBaseException();
|
||||
logger.LogError(EventIds.AsyncCommandException, baseException, "{name} Exception", nameof(AsyncRelayCommand));
|
||||
appCenter.TrackError(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,19 @@ public class Reliquary : EquipBase
|
||||
/// <summary>
|
||||
/// 副属性列表
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public List<ReliquarySubProperty> SubProperties { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 初始词条
|
||||
/// </summary>
|
||||
public List<ReliquarySubProperty> PrimarySubProperties { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 强化词条
|
||||
/// </summary>
|
||||
public List<ReliquarySubProperty> SecondarySubProperties { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 评分
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using Snap.Hutao.Model.Metadata.Converter;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.Hutao;
|
||||
|
||||
/// <summary>
|
||||
/// 角色
|
||||
/// </summary>
|
||||
internal class ComplexAvatar
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个胡桃数据库角色
|
||||
/// </summary>
|
||||
/// <param name="avatar">元数据角色</param>
|
||||
/// <param name="rate">率</param>
|
||||
public ComplexAvatar(Avatar avatar, double rate)
|
||||
{
|
||||
Name = avatar.Name;
|
||||
Icon = AvatarIconConverter.IconNameToUri(avatar.Icon);
|
||||
Quality = avatar.Quality;
|
||||
Rate = $"{rate:P3}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
public Uri Icon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 星级
|
||||
/// </summary>
|
||||
public ItemQuality Quality { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 比率
|
||||
/// </summary>
|
||||
public string Rate { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using Snap.Hutao.Model.Metadata.Converter;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.Hutao;
|
||||
|
||||
/// <summary>
|
||||
/// 角色搭配
|
||||
/// </summary>
|
||||
internal class ComplexAvatarCollocation : ComplexAvatar
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的角色搭配
|
||||
/// </summary>
|
||||
/// <param name="avatar">角色</param>
|
||||
/// <param name="rate">比率</param>
|
||||
public ComplexAvatarCollocation(Avatar avatar)
|
||||
: base(avatar, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色
|
||||
/// </summary>
|
||||
public List<ComplexAvatar> Avatars { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 武器
|
||||
/// </summary>
|
||||
public List<ComplexWeapon> Weapons { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 圣遗物套装
|
||||
/// </summary>
|
||||
public List<ComplexReliquarySet> ReliquarySets { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.Hutao;
|
||||
|
||||
/// <summary>
|
||||
/// 角色命座信息
|
||||
/// </summary>
|
||||
internal class ComplexAvatarConstellationInfo : ComplexAvatar
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的角色命座信息
|
||||
/// </summary>
|
||||
/// <param name="avatar">角色</param>
|
||||
/// <param name="rate">持有率</param>
|
||||
/// <param name="rates">命座比率</param>
|
||||
public ComplexAvatarConstellationInfo(Avatar avatar, double rate, IEnumerable<double> rates)
|
||||
: base(avatar, rate)
|
||||
{
|
||||
Rates = rates.Select(r => $"{r:P3}").ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 命座比率
|
||||
/// </summary>
|
||||
public List<string> Rates { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.Hutao;
|
||||
|
||||
/// <summary>
|
||||
/// 角色榜
|
||||
/// </summary>
|
||||
internal class ComplexAvatarRank
|
||||
{
|
||||
/// <summary>
|
||||
/// 层数
|
||||
/// </summary>
|
||||
public string Floor { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 排行信息
|
||||
/// </summary>
|
||||
public List<ComplexAvatar> Avatars { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Metadata.Converter;
|
||||
using Snap.Hutao.Web.Hutao.Model;
|
||||
using System.Text;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.Hutao;
|
||||
|
||||
/// <summary>
|
||||
/// 圣遗物套装
|
||||
/// </summary>
|
||||
internal class ComplexReliquarySet
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的胡桃数据库圣遗物套装
|
||||
/// </summary>
|
||||
/// <param name="reliquarySetRate">圣遗物套装率</param>
|
||||
/// <param name="idReliquarySetMap">圣遗物套装映射</param>
|
||||
public ComplexReliquarySet(ItemRate<ReliquarySets, double> reliquarySetRate, Dictionary<int, Metadata.Reliquary.ReliquarySet> idReliquarySetMap)
|
||||
{
|
||||
ReliquarySets sets = reliquarySetRate.Item;
|
||||
|
||||
if (sets.Count >= 1)
|
||||
{
|
||||
StringBuilder setStringBuilder = new();
|
||||
List<Uri> icons = new();
|
||||
foreach (ReliquarySet set in sets)
|
||||
{
|
||||
Metadata.Reliquary.ReliquarySet metaSet = idReliquarySetMap[set.EquipAffixId / 10];
|
||||
|
||||
if (setStringBuilder.Length != 0)
|
||||
{
|
||||
setStringBuilder.Append(Environment.NewLine);
|
||||
}
|
||||
|
||||
setStringBuilder.Append(set.Count).Append('×').Append(metaSet.Name);
|
||||
icons.Add(RelicIconConverter.IconNameToUri(metaSet.Icon));
|
||||
}
|
||||
|
||||
Name = setStringBuilder.ToString();
|
||||
Icons = icons;
|
||||
}
|
||||
else
|
||||
{
|
||||
Name = "无圣遗物";
|
||||
}
|
||||
|
||||
Rate = $"{reliquarySetRate.Rate:P3}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
public List<Uri> Icons { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 比率
|
||||
/// </summary>
|
||||
public string Rate { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.Hutao;
|
||||
|
||||
/// <summary>
|
||||
/// 队伍排行
|
||||
/// </summary>
|
||||
internal class ComplexTeamRank
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的队伍排行
|
||||
/// </summary>
|
||||
/// <param name="teamRank">队伍排行</param>
|
||||
/// <param name="idAvatarMap">映射</param>
|
||||
public ComplexTeamRank(TeamAppearance teamRank, Dictionary<int, Avatar> idAvatarMap)
|
||||
{
|
||||
Floor = $"第 {teamRank.Floor} 层";
|
||||
Up = teamRank.Up.Select(teamRate => new Team(teamRate, idAvatarMap)).ToList();
|
||||
Down = teamRank.Down.Select(teamRate => new Team(teamRate, idAvatarMap)).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 层数
|
||||
/// </summary>
|
||||
public string Floor { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 上半阵容
|
||||
/// </summary>
|
||||
public List<Team> Up { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 下半阵容
|
||||
/// </summary>
|
||||
public List<Team> Down { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata.Converter;
|
||||
using Snap.Hutao.Model.Metadata.Weapon;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.Hutao;
|
||||
|
||||
/// <summary>
|
||||
/// 胡桃数据库武器
|
||||
/// </summary>
|
||||
internal class ComplexWeapon
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个胡桃数据库武器
|
||||
/// </summary>
|
||||
/// <param name="weapon">元数据武器</param>
|
||||
/// <param name="rate">率</param>
|
||||
public ComplexWeapon(Weapon weapon, double rate)
|
||||
{
|
||||
Name = weapon.Name;
|
||||
Icon = EquipIconConverter.IconNameToUri(weapon.Icon);
|
||||
Quality = weapon.Quality;
|
||||
Rate = $"{rate:P3}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
public Uri Icon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 星级
|
||||
/// </summary>
|
||||
public ItemQuality Quality { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 比率
|
||||
/// </summary>
|
||||
public string Rate { get; set; } = default!;
|
||||
}
|
||||
36
src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/Team.cs
Normal file
36
src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/Team.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.Hutao;
|
||||
|
||||
/// <summary>
|
||||
/// 队伍
|
||||
/// </summary>
|
||||
internal class Team : List<ComplexAvatar>
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的队伍
|
||||
/// </summary>
|
||||
/// <param name="team">队伍</param>
|
||||
/// <param name="idAvatarMap">映射</param>
|
||||
public Team(ItemRate<string, int> team, Dictionary<int, Avatar> idAvatarMap)
|
||||
: base(4)
|
||||
{
|
||||
IEnumerable<int> ids = team.Item.Split(',').Select(i => int.Parse(i));
|
||||
|
||||
foreach (int id in ids)
|
||||
{
|
||||
Add(new(idAvatarMap[id], 0));
|
||||
}
|
||||
|
||||
Rate = $"上场 {team.Rate} 次";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上场次数
|
||||
/// </summary>
|
||||
public string Rate { get; set; }
|
||||
}
|
||||
@@ -27,11 +27,13 @@ public class UIGFInfo
|
||||
/// 导出的时间戳
|
||||
/// </summary>
|
||||
[JsonPropertyName("export_timestamp")]
|
||||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
|
||||
public long? ExportTimestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 导出时间
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public DateTimeOffset ExportDateTime
|
||||
{
|
||||
get => DateTimeOffsetExtension.FromUnixTime(ExportTimestamp, DateTimeOffset.MinValue);
|
||||
|
||||
@@ -11,10 +11,13 @@ public static class AvatarIds
|
||||
{
|
||||
public const int Ayaka = 10000002;
|
||||
public const int Qin = 10000003;
|
||||
|
||||
public const int Lisa = 10000006;
|
||||
|
||||
public const int Barbara = 10000014;
|
||||
public const int Kaeya = 10000015;
|
||||
public const int Diluc = 10000016;
|
||||
|
||||
public const int Razor = 10000020;
|
||||
public const int Ambor = 10000021;
|
||||
public const int Venti = 10000022;
|
||||
@@ -23,6 +26,7 @@ public static class AvatarIds
|
||||
public const int Xingqiu = 10000025;
|
||||
public const int Xiao = 10000026;
|
||||
public const int Ningguang = 10000027;
|
||||
|
||||
public const int Klee = 10000029;
|
||||
public const int Zhongli = 10000030;
|
||||
public const int Fischl = 10000031;
|
||||
@@ -34,6 +38,7 @@ public static class AvatarIds
|
||||
public const int Ganyu = 10000037;
|
||||
public const int Albedo = 10000038;
|
||||
public const int Diona = 10000039;
|
||||
|
||||
public const int Mona = 10000041;
|
||||
public const int Keqing = 10000042;
|
||||
public const int Sucrose = 10000043;
|
||||
@@ -54,6 +59,7 @@ public static class AvatarIds
|
||||
public const int Yae = 10000058;
|
||||
public const int Heizou = 10000059;
|
||||
public const int Yelan = 10000060;
|
||||
|
||||
public const int Aloy = 10000062;
|
||||
public const int Shenhe = 10000063;
|
||||
public const int Yunjin = 10000064;
|
||||
@@ -62,6 +68,9 @@ public static class AvatarIds
|
||||
public const int Collei = 10000067;
|
||||
public const int Dori = 10000068;
|
||||
public const int Tighnari = 10000069;
|
||||
public const int Nilou = 10000070;
|
||||
public const int Cyno = 10000071;
|
||||
public const int Candace = 10000072;
|
||||
public const int Nahida = 10000073;
|
||||
public const int Layla = 10000074;
|
||||
}
|
||||
@@ -11,15 +11,30 @@ public class ReliquarySet
|
||||
/// <summary>
|
||||
/// 套装Id
|
||||
/// </summary>
|
||||
public int SetId { get; set; } = default!;
|
||||
public int SetId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 装备被动Id
|
||||
/// </summary>
|
||||
public int EquipAffixId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 套装名称
|
||||
/// </summary>
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 套装图标
|
||||
/// </summary>
|
||||
public string Icon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 需要的数量
|
||||
/// </summary>
|
||||
public IEnumerable<int> NeedNumber { get; set; } = default!;
|
||||
public List<int> NeedNumber { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 描述
|
||||
/// </summary>
|
||||
public IEnumerable<string> Descriptions { get; set; } = default!;
|
||||
public List<string> Descriptions { get; set; } = default!;
|
||||
}
|
||||
@@ -61,7 +61,10 @@ public class Weapon : IStatisticsItemSource, ISummaryItemSource, INameQuality
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public ItemQuality Quality => RankLevel;
|
||||
public ItemQuality Quality
|
||||
{
|
||||
get => RankLevel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为基础物品
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity
|
||||
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
|
||||
Publisher="CN=DGP Studio"
|
||||
Version="1.1.9.0" />
|
||||
Version="1.1.13.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>胡桃</DisplayName>
|
||||
|
||||
@@ -20,6 +20,7 @@ public static partial class Program
|
||||
/// <summary>
|
||||
/// 主线程队列
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[SuppressMessage("", "SA1401")]
|
||||
internal static volatile DispatcherQueue? DispatcherQueue;
|
||||
|
||||
BIN
src/Snap.Hutao/Snap.Hutao/Resource/Icon/UI_ChapterIcon_Hutao.png
Normal file
BIN
src/Snap.Hutao/Snap.Hutao/Resource/Icon/UI_ChapterIcon_Hutao.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
namespace Snap.Hutao.Service.Abstraction;
|
||||
|
||||
/// <summary>
|
||||
/// 胡桃 API 服务
|
||||
/// </summary>
|
||||
internal interface IHutaoService
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步获取角色上场率
|
||||
/// </summary>
|
||||
/// <returns>角色上场率</returns>
|
||||
ValueTask<List<AvatarAppearanceRank>> GetAvatarAppearanceRanksAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取角色搭配
|
||||
/// </summary>
|
||||
/// <returns>角色搭配</returns>
|
||||
ValueTask<List<AvatarCollocation>> GetAvatarCollocationsAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取角色持有率信息
|
||||
/// </summary>
|
||||
/// <returns>角色持有率信息</returns>
|
||||
ValueTask<List<AvatarConstellationInfo>> GetAvatarConstellationInfosAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取角色使用率
|
||||
/// </summary>
|
||||
/// <returns>角色使用率</returns>
|
||||
ValueTask<List<AvatarUsageRank>> GetAvatarUsageRanksAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取统计数据
|
||||
/// </summary>
|
||||
/// <returns>统计数据</returns>
|
||||
ValueTask<Overview?> GetOverviewAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取队伍上场
|
||||
/// </summary>
|
||||
/// <returns>队伍上场</returns>
|
||||
ValueTask<List<TeamAppearance>> GetTeamAppearancesAsync();
|
||||
}
|
||||
95
src/Snap.Hutao/Snap.Hutao/Service/AppCenter/AppCenter.cs
Normal file
95
src/Snap.Hutao/Snap.Hutao/Service/AppCenter/AppCenter.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Service.AppCenter.Model;
|
||||
using Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
[Injection(InjectAs.Singleton)]
|
||||
public sealed class AppCenter : IDisposable
|
||||
{
|
||||
private const string AppSecret = "de5bfc48-17fc-47ee-8e7e-dee7dc59d554";
|
||||
private const string API = "https://in.appcenter.ms/logs?api-version=1.0.0";
|
||||
|
||||
private readonly TaskCompletionSource uploadTaskCompletionSource = new();
|
||||
private readonly CancellationTokenSource uploadTaskCancllationTokenSource = new();
|
||||
private readonly HttpClient httpClient;
|
||||
private readonly List<Log> queue;
|
||||
private readonly Device deviceInfo;
|
||||
private readonly JsonSerializerOptions options;
|
||||
|
||||
private Guid sessionID;
|
||||
|
||||
public AppCenter()
|
||||
{
|
||||
options = new(CoreEnvironment.JsonOptions);
|
||||
options.Converters.Add(new LogConverter());
|
||||
|
||||
httpClient = new() { DefaultRequestHeaders = { { "Install-ID", CoreEnvironment.AppCenterDeviceId }, { "App-Secret", AppSecret } } };
|
||||
queue = new List<Log>();
|
||||
deviceInfo = new Device();
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (!uploadTaskCancllationTokenSource.Token.IsCancellationRequested)
|
||||
{
|
||||
await UploadAsync().ConfigureAwait(false);
|
||||
await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
uploadTaskCompletionSource.TrySetResult();
|
||||
}).SafeForget();
|
||||
}
|
||||
|
||||
public async Task UploadAsync()
|
||||
{
|
||||
if (queue.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string? uploadStatus = null;
|
||||
do
|
||||
{
|
||||
queue.ForEach(log => log.Status = LogStatus.Uploading);
|
||||
LogContainer container = new(queue);
|
||||
|
||||
LogUploadResult? response = await httpClient
|
||||
.TryCatchPostAsJsonAsync<LogContainer, LogUploadResult>(API, container, options)
|
||||
.ConfigureAwait(false);
|
||||
uploadStatus = response?.Status;
|
||||
}
|
||||
while (uploadStatus != "Success");
|
||||
|
||||
queue.RemoveAll(log => log.Status == LogStatus.Uploading);
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
sessionID = Guid.NewGuid();
|
||||
queue.Add(new StartServiceLog("Analytics", "Crashes").Initialize(sessionID, deviceInfo));
|
||||
queue.Add(new StartSessionLog().Initialize(sessionID, deviceInfo).Initialize(sessionID, deviceInfo));
|
||||
}
|
||||
|
||||
public void TrackCrash(Exception exception, bool isFatal = true)
|
||||
{
|
||||
queue.Add(new ManagedErrorLog(exception, isFatal).Initialize(sessionID, deviceInfo));
|
||||
}
|
||||
|
||||
public void TrackError(Exception exception)
|
||||
{
|
||||
queue.Add(new HandledErrorLog(exception).Initialize(sessionID, deviceInfo));
|
||||
}
|
||||
|
||||
[SuppressMessage("", "VSTHRD002")]
|
||||
public void Dispose()
|
||||
{
|
||||
uploadTaskCancllationTokenSource.Cancel();
|
||||
uploadTaskCompletionSource.Task.GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
64
src/Snap.Hutao/Snap.Hutao/Service/AppCenter/DeviceHelper.cs
Normal file
64
src/Snap.Hutao/Snap.Hutao/Service/AppCenter/DeviceHelper.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.Win32;
|
||||
using Windows.Graphics;
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter;
|
||||
|
||||
/// <summary>
|
||||
/// 设备帮助类
|
||||
/// </summary>
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public static class DeviceHelper
|
||||
{
|
||||
private static readonly RegistryKey? BiosKey = Registry.LocalMachine.OpenSubKey("HARDWARE\\DESCRIPTION\\System\\BIOS");
|
||||
private static readonly RegistryKey? GeoKey = Registry.CurrentUser.OpenSubKey("Control Panel\\International\\Geo");
|
||||
private static readonly RegistryKey? CurrentVersionKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion");
|
||||
|
||||
public static string? GetOem()
|
||||
{
|
||||
string? oem = BiosKey?.GetValue("SystemManufacturer") as string;
|
||||
return oem == "System manufacturer" ? null : oem;
|
||||
}
|
||||
|
||||
public static string? GetModel()
|
||||
{
|
||||
string? model = BiosKey?.GetValue("SystemProductName") as string;
|
||||
return model == "System Product Name" ? null : model;
|
||||
}
|
||||
|
||||
public static string GetScreenSize()
|
||||
{
|
||||
RectInt32 screen = DisplayArea.Primary.OuterBounds;
|
||||
return $"{screen.Width}x{screen.Height}";
|
||||
}
|
||||
|
||||
public static string? GetCountry()
|
||||
{
|
||||
return GeoKey?.GetValue("Name") as string;
|
||||
}
|
||||
|
||||
public static string GetSystemVersion()
|
||||
{
|
||||
object? majorVersion = CurrentVersionKey?.GetValue("CurrentMajorVersionNumber");
|
||||
if (majorVersion != null)
|
||||
{
|
||||
object? minorVersion = CurrentVersionKey?.GetValue("CurrentMinorVersionNumber", "0");
|
||||
object? buildNumber = CurrentVersionKey?.GetValue("CurrentBuildNumber", "0");
|
||||
return $"{majorVersion}.{minorVersion}.{buildNumber}";
|
||||
}
|
||||
else
|
||||
{
|
||||
object? version = CurrentVersionKey?.GetValue("CurrentVersion", "0.0");
|
||||
object? buildNumber = CurrentVersionKey?.GetValue("CurrentBuild", "0");
|
||||
return $"{version}.{buildNumber}";
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetSystemBuild()
|
||||
{
|
||||
return (int)(CurrentVersionKey?.GetValue("UBR") ?? 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public class AppCenterException
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; } = "UnknownType";
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public string? Message { get; set; }
|
||||
|
||||
[JsonPropertyName("stackTrace")]
|
||||
public string? StackTrace { get; set; }
|
||||
|
||||
[JsonPropertyName("innerExceptions")]
|
||||
public List<AppCenterException>? InnerExceptions { get; set; }
|
||||
}
|
||||
53
src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Device.cs
Normal file
53
src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Device.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public class Device
|
||||
{
|
||||
[JsonPropertyName("sdkName")]
|
||||
public string SdkName { get; set; } = "appcenter.winui";
|
||||
|
||||
[JsonPropertyName("sdkVersion")]
|
||||
public string SdkVersion { get; set; } = "4.5.0";
|
||||
|
||||
[JsonPropertyName("osName")]
|
||||
public string OsName { get; set; } = "WINDOWS";
|
||||
|
||||
[JsonPropertyName("osVersion")]
|
||||
public string OsVersion { get; set; } = DeviceHelper.GetSystemVersion();
|
||||
|
||||
[JsonPropertyName("osBuild")]
|
||||
public string OsBuild { get; set; } = $"{DeviceHelper.GetSystemVersion()}.{DeviceHelper.GetSystemBuild()}";
|
||||
|
||||
[JsonPropertyName("model")]
|
||||
public string? Model { get; set; } = DeviceHelper.GetModel();
|
||||
|
||||
[JsonPropertyName("oemName")]
|
||||
public string? OemName { get; set; } = DeviceHelper.GetOem();
|
||||
|
||||
[JsonPropertyName("screenSize")]
|
||||
public string ScreenSize { get; set; } = DeviceHelper.GetScreenSize();
|
||||
|
||||
[JsonPropertyName("carrierCountry")]
|
||||
public string Country { get; set; } = DeviceHelper.GetCountry() ?? "CN";
|
||||
|
||||
[JsonPropertyName("locale")]
|
||||
public string Locale { get; set; } = CultureInfo.CurrentCulture.Name;
|
||||
|
||||
[JsonPropertyName("timeZoneOffset")]
|
||||
public int TimeZoneOffset { get; set; } = (int)TimeZoneInfo.Local.BaseUtcOffset.TotalMinutes;
|
||||
|
||||
[JsonPropertyName("appVersion")]
|
||||
public string AppVersion { get; set; } = CoreEnvironment.Version.ToString();
|
||||
|
||||
[JsonPropertyName("appBuild")]
|
||||
public string AppBuild { get; set; } = CoreEnvironment.Version.ToString();
|
||||
|
||||
[JsonPropertyName("appNamespace")]
|
||||
public string AppNamespace { get; set; } = typeof(App).Namespace ?? string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public class EventLog : PropertiesLog
|
||||
{
|
||||
public EventLog(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public override string Type { get => "event"; }
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public class HandledErrorLog : PropertiesLog
|
||||
{
|
||||
public HandledErrorLog(Exception exception)
|
||||
{
|
||||
Id = Guid.NewGuid();
|
||||
Exception = LogHelper.Create(exception);
|
||||
}
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public Guid? Id { get; set; }
|
||||
|
||||
[JsonPropertyName("exception")]
|
||||
public AppCenterException Exception { get; set; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public override string Type { get => "handledError"; }
|
||||
}
|
||||
23
src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/Log.cs
Normal file
23
src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/Log.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public abstract class Log
|
||||
{
|
||||
[JsonIgnore]
|
||||
public LogStatus Status { get; set; } = LogStatus.Pending;
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public abstract string Type { get; }
|
||||
|
||||
[JsonPropertyName("sid")]
|
||||
public Guid Session { get; set; }
|
||||
|
||||
[JsonPropertyName("timestamp")]
|
||||
public string Timestamp { get; set; } = DateTime.UtcNow.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ssZ");
|
||||
|
||||
[JsonPropertyName("device")]
|
||||
public Device Device { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public class LogContainer
|
||||
{
|
||||
public LogContainer(IEnumerable<Log> logs)
|
||||
{
|
||||
Logs = logs;
|
||||
}
|
||||
|
||||
[JsonPropertyName("logs")]
|
||||
public IEnumerable<Log> Logs { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
/// <summary>
|
||||
/// 日志转换器
|
||||
/// </summary>
|
||||
public class LogConverter : JsonConverter<Log>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Log? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
throw Must.NeverHappen();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(Utf8JsonWriter writer, Log value, JsonSerializerOptions options)
|
||||
{
|
||||
JsonSerializer.Serialize(writer, value, value.GetType(), options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public static class LogHelper
|
||||
{
|
||||
public static T Initialize<T>(this T log, Guid sid, Device device)
|
||||
where T : Log
|
||||
{
|
||||
log.Session = sid;
|
||||
log.Device = device;
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
public static AppCenterException Create(Exception exception)
|
||||
{
|
||||
AppCenterException current = new()
|
||||
{
|
||||
Type = exception.GetType().ToString(),
|
||||
Message = exception.Message,
|
||||
StackTrace = exception.ToString(),
|
||||
};
|
||||
|
||||
if (exception is AggregateException aggregateException)
|
||||
{
|
||||
if (aggregateException.InnerExceptions.Count != 0)
|
||||
{
|
||||
current.InnerExceptions = new();
|
||||
foreach (var innerException in aggregateException.InnerExceptions)
|
||||
{
|
||||
current.InnerExceptions.Add(Create(innerException));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (exception.InnerException != null)
|
||||
{
|
||||
current.InnerExceptions ??= new();
|
||||
current.InnerExceptions.Add(Create(exception.InnerException));
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
[SuppressMessage("", "SA1602")]
|
||||
public enum LogStatus
|
||||
{
|
||||
Pending,
|
||||
Uploading,
|
||||
Uploaded,
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public class ManagedErrorLog : Log
|
||||
{
|
||||
public ManagedErrorLog(Exception exception, bool fatal = true)
|
||||
{
|
||||
var p = Process.GetCurrentProcess();
|
||||
Id = Guid.NewGuid();
|
||||
Fatal = fatal;
|
||||
UserId = CoreEnvironment.AppCenterDeviceId;
|
||||
ProcessId = p.Id;
|
||||
Exception = LogHelper.Create(exception);
|
||||
ProcessName = p.ProcessName;
|
||||
Architecture = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE");
|
||||
AppLaunchTimestamp = p.StartTime.ToUniversalTime();
|
||||
}
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
[JsonPropertyName("userId")]
|
||||
public string? UserId { get; set; }
|
||||
|
||||
[JsonPropertyName("processId")]
|
||||
public int ProcessId { get; set; }
|
||||
|
||||
[JsonPropertyName("processName")]
|
||||
public string ProcessName { get; set; }
|
||||
|
||||
[JsonPropertyName("fatal")]
|
||||
public bool Fatal { get; set; }
|
||||
|
||||
[JsonPropertyName("appLaunchTimestamp")]
|
||||
public DateTime? AppLaunchTimestamp { get; set; }
|
||||
|
||||
[JsonPropertyName("architecture")]
|
||||
public string? Architecture { get; set; }
|
||||
|
||||
[JsonPropertyName("exception")]
|
||||
public AppCenterException Exception { get; set; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public override string Type { get => "managedError"; }
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public class PageLog : PropertiesLog
|
||||
{
|
||||
public PageLog(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public override string Type { get => "page"; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public abstract class PropertiesLog : Log
|
||||
{
|
||||
[JsonPropertyName("properties")]
|
||||
public IDictionary<string, string> Properties { get; set; } = new Dictionary<string, string>();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public class StartServiceLog : Log
|
||||
{
|
||||
public StartServiceLog(params string[] services)
|
||||
{
|
||||
Services = services;
|
||||
}
|
||||
|
||||
[JsonPropertyName("services")]
|
||||
public string[] Services { get; set; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public override string Type { get => "startService"; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public class StartSessionLog : Log
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public override string Type { get => "startSession"; }
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public class LogUploadResult
|
||||
{
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("validDiagnosticsIds")]
|
||||
public List<Guid> ValidDiagnosticsIds { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("throttledDiagnosticsIds")]
|
||||
public List<Guid> ThrottledDiagnosticsIds { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("correlationId")]
|
||||
public Guid CorrelationId { get; set; }
|
||||
}
|
||||
@@ -11,8 +11,14 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory;
|
||||
/// </summary>
|
||||
internal static partial class ReliquaryWeightConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认
|
||||
/// </summary>
|
||||
public static readonly AffixWeight Default = new(0, 100, 75, 0, 100, 100, 0, 55, 0) { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 100 } };
|
||||
|
||||
/// <summary>
|
||||
/// 词条权重
|
||||
/// https://docs.qq.com/sheet/DUG52SFJlTUN3cmNL?tab=BB08J2
|
||||
/// </summary>
|
||||
public static readonly List<AffixWeight> AffixWeights = new()
|
||||
{
|
||||
@@ -73,6 +79,8 @@ internal static partial class ReliquaryWeightConfiguration
|
||||
new(AvatarIds.Collei, 0, 75, 0, 100, 100, 0, 55, 0) { { FightProperty.FIGHT_PROP_GRASS_ADD_HURT, 100 } },
|
||||
new(AvatarIds.Dori, 100, 75, 0, 100, 100, 0, 55, 0) { { FightProperty.FIGHT_PROP_ELEC_ADD_HURT, 100 } },
|
||||
new(AvatarIds.Tighnari, 0, 75, 0, 100, 100, 0, 55, 0) { { FightProperty.FIGHT_PROP_GRASS_ADD_HURT, 100 } },
|
||||
new(AvatarIds.Nilou, 100, 75, 0, 100, 100, 0, 55, 0, "直伤流") { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 100 } },
|
||||
new(AvatarIds.Nilou, 100, 75, 0, 100, 100, 0, 55, 0, "反应流") { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 100 } },
|
||||
new(AvatarIds.Cyno, 0, 75, 0, 100, 100, 75, 55, 0) { { FightProperty.FIGHT_PROP_ELEC_ADD_HURT, 100 } },
|
||||
new(AvatarIds.Candace, 100, 75, 0, 100, 100, 0, 55, 0) { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 100 } },
|
||||
};
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata.Converter;
|
||||
using Snap.Hutao.Model.Metadata.Reliquary;
|
||||
|
||||
@@ -7,6 +7,7 @@ using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata.Converter;
|
||||
using Snap.Hutao.Model.Metadata.Reliquary;
|
||||
using Snap.Hutao.Web.Enka.Model;
|
||||
using System.Runtime.InteropServices;
|
||||
using MetadataReliquary = Snap.Hutao.Model.Metadata.Reliquary.Reliquary;
|
||||
using MetadataReliquaryAffix = Snap.Hutao.Model.Metadata.Reliquary.ReliquaryAffix;
|
||||
using ModelAvatarInfo = Snap.Hutao.Web.Enka.Model.AvatarInfo;
|
||||
@@ -61,6 +62,12 @@ internal class SummaryReliquaryFactory
|
||||
{
|
||||
MetadataReliquary reliquary = reliquaries.Single(r => r.Ids.Contains(equip.ItemId));
|
||||
List<ReliquarySubProperty> subProperty = equip.Reliquary!.AppendPropIdList.Select(id => CreateSubProperty(id)).ToList();
|
||||
|
||||
int affixCount = GetAffixCount(reliquary);
|
||||
Span<ReliquarySubProperty> span = CollectionsMarshal.AsSpan(subProperty);
|
||||
List<ReliquarySubProperty> primary = new(span[..^affixCount].ToArray());
|
||||
List<ReliquarySubProperty> secondary = new(span[^affixCount..].ToArray());
|
||||
|
||||
ReliquaryLevel relicLevel = reliqueryLevels.Single(r => r.Level == equip.Reliquary!.Level && r.Quality == reliquary.RankLevel);
|
||||
FightProperty property = idRelicMainPropMap[equip.Reliquary.MainPropId];
|
||||
|
||||
@@ -77,21 +84,55 @@ internal class SummaryReliquaryFactory
|
||||
MainProperty = new(property.GetDescription(), PropertyInfoDescriptor.FormatValue(property, relicLevel.Properties[property])),
|
||||
|
||||
// Reliquary
|
||||
SubProperties = subProperty,
|
||||
// SubProperties = subProperty,
|
||||
PrimarySubProperties = primary,
|
||||
SecondarySubProperties = secondary,
|
||||
Score = ScoreReliquary(property, reliquary, relicLevel, subProperty),
|
||||
};
|
||||
}
|
||||
|
||||
private int GetAffixCount(MetadataReliquary reliquary)
|
||||
{
|
||||
return (reliquary.RankLevel, equip.Reliquary!.Level) switch
|
||||
{
|
||||
(ItemQuality.QUALITY_ORANGE, > 20) => 5,
|
||||
(ItemQuality.QUALITY_ORANGE, > 16) => 4,
|
||||
(ItemQuality.QUALITY_ORANGE, > 12) => 3,
|
||||
(ItemQuality.QUALITY_ORANGE, > 8) => 2,
|
||||
(ItemQuality.QUALITY_ORANGE, > 4) => 1,
|
||||
(ItemQuality.QUALITY_ORANGE, _) => 0,
|
||||
|
||||
(ItemQuality.QUALITY_PURPLE, > 16) => 4,
|
||||
(ItemQuality.QUALITY_PURPLE, > 12) => 3,
|
||||
(ItemQuality.QUALITY_PURPLE, > 8) => 2,
|
||||
(ItemQuality.QUALITY_PURPLE, > 4) => 1,
|
||||
(ItemQuality.QUALITY_PURPLE, _) => 0,
|
||||
|
||||
(ItemQuality.QUALITY_BLUE, > 12) => 3,
|
||||
(ItemQuality.QUALITY_BLUE, > 8) => 2,
|
||||
(ItemQuality.QUALITY_BLUE, > 4) => 1,
|
||||
(ItemQuality.QUALITY_BLUE, _) => 0,
|
||||
|
||||
(ItemQuality.QUALITY_GREEN, > 4) => 1,
|
||||
(ItemQuality.QUALITY_GREEN, _) => 0,
|
||||
|
||||
(ItemQuality.QUALITY_WHITE, > 4) => 1,
|
||||
(ItemQuality.QUALITY_WHITE, _) => 0,
|
||||
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
||||
private double ScoreReliquary(FightProperty property, MetadataReliquary reliquary, ReliquaryLevel relicLevel, List<ReliquarySubProperty> subProperties)
|
||||
{
|
||||
// 沙 杯 头
|
||||
if (equip.Flat.EquipType is EquipType.EQUIP_SHOES or EquipType.EQUIP_RING or EquipType.EQUIP_DRESS)
|
||||
{
|
||||
AffixWeight weightConfig = GetAffixWeightForAvatarId(avatarInfo.AvatarId);
|
||||
AffixWeight weightConfig = GetAffixWeightForAvatarId();
|
||||
ReliquaryLevel maxRelicLevel = reliqueryLevels.Where(r => r.Quality == reliquary.RankLevel).MaxBy(r => r.Level)!;
|
||||
|
||||
double percent = relicLevel.Properties[property] / maxRelicLevel.Properties[property];
|
||||
double baseScore = 8 * percent * weightConfig[property];
|
||||
double baseScore = 8 * percent * weightConfig.GetValueOrDefault(property, 0);
|
||||
|
||||
double score = subProperties.Sum(p => p.Score);
|
||||
return ((score + baseScore) / 1700) * 66;
|
||||
@@ -103,9 +144,9 @@ internal class SummaryReliquaryFactory
|
||||
}
|
||||
}
|
||||
|
||||
private AffixWeight GetAffixWeightForAvatarId(int avatarId)
|
||||
private AffixWeight GetAffixWeightForAvatarId()
|
||||
{
|
||||
return ReliquaryWeightConfiguration.AffixWeights.First(w => w.AvatarId == avatarId);
|
||||
return ReliquaryWeightConfiguration.AffixWeights.FirstOrDefault(w => w.AvatarId == avatarInfo.AvatarId, ReliquaryWeightConfiguration.Default);
|
||||
}
|
||||
|
||||
private ReliquarySubProperty CreateSubProperty(int appendPropId)
|
||||
@@ -121,7 +162,7 @@ internal class SummaryReliquaryFactory
|
||||
{
|
||||
MetadataReliquaryAffix affix = idReliquaryAffixMap[appendId];
|
||||
|
||||
AffixWeight weightConfig = GetAffixWeightForAvatarId(avatarInfo.AvatarId);
|
||||
AffixWeight weightConfig = GetAffixWeightForAvatarId();
|
||||
double weight = weightConfig.GetValueOrDefault(affix.Type, 0) / 100D;
|
||||
|
||||
// 小字词条,转换到等效百分比计算
|
||||
|
||||
@@ -106,7 +106,7 @@ internal class GachaLogService : IGachaLogService, ISupportAsyncInitialization
|
||||
{
|
||||
Verify.Operation(IsInitialized, "祈愿记录服务未能正常初始化");
|
||||
|
||||
var list = appDbContext.GachaItems
|
||||
List<UIGFItem> list = appDbContext.GachaItems
|
||||
.Where(i => i.ArchiveId == archive.InnerId)
|
||||
.AsEnumerable()
|
||||
.Select(i => i.ToUIGFItem(GetNameQualityByItemId(i.ItemId)))
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.View.Dialog;
|
||||
|
||||
namespace Snap.Hutao.Service.GachaLog;
|
||||
|
||||
@@ -15,8 +16,26 @@ internal class GachaLogUrlManualInputProvider : IGachaLogUrlProvider
|
||||
public string Name { get => nameof(GachaLogUrlManualInputProvider); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<ValueResult<bool, string>> GetQueryAsync()
|
||||
public async Task<ValueResult<bool, string>> GetQueryAsync()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
ValueResult<bool, string> result = await new GachaLogUrlDialog(mainWindow).GetInputUrlAsync().ConfigureAwait(false);
|
||||
|
||||
if (result.IsOk)
|
||||
{
|
||||
if (result.Value.Contains("&auth_appid=webview_gacha"))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new(false, "提供的Url无效");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new(false, null!);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,9 +36,9 @@ internal class GachaLogUrlStokenProvider : IGachaLogUrlProvider
|
||||
public async Task<ValueResult<bool, string>> GetQueryAsync()
|
||||
{
|
||||
Model.Binding.User? user = userService.Current;
|
||||
if (user != null)
|
||||
if (user != null && user.SelectedUserGameRole != null)
|
||||
{
|
||||
if (user.Cookie!.ContainsSToken() && user.SelectedUserGameRole != null)
|
||||
if (user.Cookie!.ContainsSToken())
|
||||
{
|
||||
PlayerUid uid = (PlayerUid)user.SelectedUserGameRole;
|
||||
GenAuthKeyData data = GenAuthKeyData.CreateForWebViewGacha(uid);
|
||||
@@ -48,9 +48,19 @@ internal class GachaLogUrlStokenProvider : IGachaLogUrlProvider
|
||||
{
|
||||
return new(true, GachaLogConfigration.AsQuery(data, authkey));
|
||||
}
|
||||
else
|
||||
{
|
||||
return new(false, "请求验证密钥失败");
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
return new(false, "当前用户的Cookie不包含 Stoken");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new(false, "尚未选择要刷新的用户以及角色");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,17 @@ internal class GachaLogUrlWebCacheProvider : IGachaLogUrlProvider
|
||||
string folder = Path.GetDirectoryName(path) ?? string.Empty;
|
||||
string cacheFile = Path.Combine(folder, @"YuanShen_Data\webCaches\Cache\Cache_Data\data_2");
|
||||
|
||||
using (TemporaryFile tempFile = TemporaryFile.CreateFromFileCopy(cacheFile))
|
||||
TemporaryFile tempFile;
|
||||
try
|
||||
{
|
||||
tempFile = TemporaryFile.CreateFromFileCopy(cacheFile);
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
return new(false, $"找不到原神内置浏览器缓存路径:\n{cacheFile}");
|
||||
}
|
||||
|
||||
using (tempFile)
|
||||
{
|
||||
using (FileStream fileStream = new(tempFile.Path, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
@@ -74,7 +84,7 @@ internal class GachaLogUrlWebCacheProvider : IGachaLogUrlProvider
|
||||
}
|
||||
else
|
||||
{
|
||||
return new(false, null!);
|
||||
return new(false, $"未正确提供原神路径,或当前设置的路径不正确");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Win32;
|
||||
using Snap.Hutao.Core.IO.Ini;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Snap.Hutao.Service.Game.Locator;
|
||||
|
||||
@@ -18,8 +21,30 @@ internal class RegistryLauncherLocator : IGameLocator
|
||||
/// <inheritdoc/>
|
||||
public Task<ValueResult<bool, string>> LocateGamePathAsync()
|
||||
{
|
||||
// TODO: fix folder moved issue
|
||||
return Task.FromResult(LocateInternal("InstallPath", "\\Genshin Impact Game\\YuanShen.exe"));
|
||||
ValueResult<bool, string> result = LocateInternal("DisplayIcon");
|
||||
|
||||
if (result.IsOk == false)
|
||||
{
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
string path = result.Value;
|
||||
string configPath = Path.Combine(path, "config.ini");
|
||||
string? escapedPath = null;
|
||||
using (FileStream stream = File.OpenRead(configPath))
|
||||
{
|
||||
IEnumerable<IniElement> elements = IniSerializer.Deserialize(stream);
|
||||
escapedPath = elements.OfType<IniParameter>().FirstOrDefault(p => p.Key == "game_install_path")?.Value;
|
||||
}
|
||||
|
||||
if (escapedPath != null)
|
||||
{
|
||||
return Task.FromResult<ValueResult<bool, string>>(new(true, Unescape(escapedPath)));
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult<ValueResult<bool, string>>(new(false, null!));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -28,18 +53,13 @@ internal class RegistryLauncherLocator : IGameLocator
|
||||
return Task.FromResult(LocateInternal("DisplayIcon"));
|
||||
}
|
||||
|
||||
private static ValueResult<bool, string> LocateInternal(string key, string? append = null)
|
||||
private static ValueResult<bool, string> LocateInternal(string key)
|
||||
{
|
||||
RegistryKey? uninstallKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\原神");
|
||||
if (uninstallKey != null)
|
||||
{
|
||||
if (uninstallKey.GetValue(key) is string path)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(append))
|
||||
{
|
||||
path += append;
|
||||
}
|
||||
|
||||
return new(true, path);
|
||||
}
|
||||
else
|
||||
@@ -52,4 +72,18 @@ internal class RegistryLauncherLocator : IGameLocator
|
||||
return new(false, null!);
|
||||
}
|
||||
}
|
||||
|
||||
private static string Unescape(string str)
|
||||
{
|
||||
string? hex4Result = Regex.Replace(str, @"\\x([0-9a-f]{4})", @"\u$1");
|
||||
|
||||
// 不包含中文
|
||||
if (!hex4Result.Contains(@"\u"))
|
||||
{
|
||||
// fix path with \
|
||||
hex4Result = hex4Result.Replace(@"\", @"\\");
|
||||
}
|
||||
|
||||
return Regex.Unescape(hex4Result);
|
||||
}
|
||||
}
|
||||
78
src/Snap.Hutao/Snap.Hutao/Service/HutaoService.cs
Normal file
78
src/Snap.Hutao/Snap.Hutao/Service/HutaoService.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Web.Hutao;
|
||||
using Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
namespace Snap.Hutao.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 胡桃 API 服务
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Transient, typeof(IHutaoService))]
|
||||
internal class HutaoService : IHutaoService
|
||||
{
|
||||
private readonly HomaClient homaClient;
|
||||
private readonly IMemoryCache memoryCache;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的胡桃 API 服务
|
||||
/// </summary>
|
||||
/// <param name="homaClient">胡桃 API 客户端</param>
|
||||
/// <param name="memoryCache">内存缓存</param>
|
||||
public HutaoService(HomaClient homaClient, IMemoryCache memoryCache)
|
||||
{
|
||||
this.homaClient = homaClient;
|
||||
this.memoryCache = memoryCache;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Overview?> GetOverviewAsync()
|
||||
{
|
||||
return FromCacheOrWebAsync(nameof(Overview), homaClient.GetOverviewAsync);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<AvatarAppearanceRank>> GetAvatarAppearanceRanksAsync()
|
||||
{
|
||||
return FromCacheOrWebAsync(nameof(AvatarAppearanceRank), homaClient.GetAvatarAttendanceRatesAsync);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<AvatarUsageRank>> GetAvatarUsageRanksAsync()
|
||||
{
|
||||
return FromCacheOrWebAsync(nameof(AvatarUsageRank), homaClient.GetAvatarUtilizationRatesAsync);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<AvatarConstellationInfo>> GetAvatarConstellationInfosAsync()
|
||||
{
|
||||
return FromCacheOrWebAsync(nameof(AvatarConstellationInfo), homaClient.GetAvatarHoldingRatesAsync);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<AvatarCollocation>> GetAvatarCollocationsAsync()
|
||||
{
|
||||
return FromCacheOrWebAsync(nameof(AvatarCollocation), homaClient.GetAvatarCollocationsAsync);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<TeamAppearance>> GetTeamAppearancesAsync()
|
||||
{
|
||||
return FromCacheOrWebAsync(nameof(TeamAppearance), homaClient.GetTeamCombinationsAsync);
|
||||
}
|
||||
|
||||
private async ValueTask<T> FromCacheOrWebAsync<T>(string typeName, Func<CancellationToken, Task<T>> taskFunc)
|
||||
{
|
||||
string key = $"{nameof(HutaoService)}.Cache.{typeName}";
|
||||
if (memoryCache.TryGetValue(key, out object? cache))
|
||||
{
|
||||
return (T)cache;
|
||||
}
|
||||
|
||||
T web = await taskFunc(default).ConfigureAwait(false);
|
||||
return memoryCache.Set(key, web, TimeSpan.FromMinutes(30));
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,13 @@ internal interface IMetadataService
|
||||
/// <returns>角色列表</returns>
|
||||
ValueTask<List<Avatar>> GetAvatarsAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取装备被动Id到圣遗物套装的映射
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>装备被动Id到圣遗物套装的映射</returns>
|
||||
ValueTask<Dictionary<int, ReliquarySet>> GetEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取卡池配置列表
|
||||
/// </summary>
|
||||
@@ -126,4 +133,11 @@ internal interface IMetadataService
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>武器列表</returns>
|
||||
ValueTask<List<Weapon>> GetWeaponsAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取圣遗物套装
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>圣遗物套装列表</returns>
|
||||
ValueTask<List<ReliquarySet>> GetReliquarySetsAsync(CancellationToken token = default);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata;
|
||||
using Snap.Hutao.Model.Metadata.Achievement;
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
@@ -39,42 +38,6 @@ internal partial class MetadataService
|
||||
return FromCacheOrFileAsync<List<GachaEvent>>("GachaEvent", token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<int, Avatar>> GetIdToAvatarMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<int, Avatar>("Avatar", a => a.Id, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<int, ReliquaryAffix>> GetIdReliquaryAffixMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<int, ReliquaryAffix>("ReliquaryAffix", a => a.Id, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<int, FightProperty>> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<int, FightProperty, ReliquaryAffixBase>("ReliquaryMainAffix", r => r.Id, r => r.Type, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<int, Weapon>> GetIdToWeaponMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<int, Weapon>("Weapon", w => w.Id, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<string, Avatar>> GetNameToAvatarMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<string, Avatar>("Avatar", a => a.Name, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<string, Weapon>> GetNameToWeaponMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<string, Weapon>("Weapon", w => w.Name, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<Reliquary>> GetReliquariesAsync(CancellationToken token = default)
|
||||
{
|
||||
@@ -99,6 +62,12 @@ internal partial class MetadataService
|
||||
return FromCacheOrFileAsync<List<ReliquaryAffixBase>>("ReliquaryMainAffix", token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<ReliquarySet>> GetReliquarySetsAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheOrFileAsync<List<ReliquarySet>>("ReliquarySet", token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<Weapon>> GetWeaponsAsync(CancellationToken token = default)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using Snap.Hutao.Model.Metadata.Reliquary;
|
||||
using Snap.Hutao.Model.Metadata.Weapon;
|
||||
|
||||
namespace Snap.Hutao.Service.Metadata;
|
||||
|
||||
/// <summary>
|
||||
/// 索引部分
|
||||
/// </summary>
|
||||
internal partial class MetadataService
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<int, ReliquarySet>> GetEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<int, ReliquarySet>("ReliquarySet", r => r.EquipAffixId, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<int, Avatar>> GetIdToAvatarMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<int, Avatar>("Avatar", a => a.Id, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<int, ReliquaryAffix>> GetIdReliquaryAffixMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<int, ReliquaryAffix>("ReliquaryAffix", a => a.Id, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<int, FightProperty>> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<int, FightProperty, ReliquaryAffixBase>("ReliquaryMainAffix", r => r.Id, r => r.Type, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<int, Weapon>> GetIdToWeaponMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<int, Weapon>("Weapon", w => w.Id, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<string, Avatar>> GetNameToAvatarMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<string, Avatar>("Avatar", a => a.Name, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<string, Weapon>> GetNameToWeaponMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<string, Weapon>("Weapon", w => w.Name, token);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Snap.Hutao.Context.Database;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
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;
|
||||
|
||||
|
||||
@@ -137,6 +137,7 @@ internal class UserService : IUserService
|
||||
/// <inheritdoc/>
|
||||
public async Task<ValueResult<UserOptionResult, string>> ProcessInputCookieAsync(Cookie cookie)
|
||||
{
|
||||
cookie.Trim();
|
||||
Must.NotNull(userCollection!);
|
||||
|
||||
// 检查 uid 是否存在
|
||||
@@ -154,12 +155,18 @@ internal class UserService : IUserService
|
||||
{
|
||||
// insert stoken directly
|
||||
userWithSameUid.Cookie.InsertSToken(uid, cookie);
|
||||
appDbContext.Users.Update(userWithSameUid.Entity);
|
||||
appDbContext.SaveChanges();
|
||||
|
||||
return new(UserOptionResult.Upgraded, uid);
|
||||
}
|
||||
|
||||
if (cookie.ContainsLTokenAndCookieToken())
|
||||
{
|
||||
UpdateUserCookie(cookie, userWithSameUid);
|
||||
userWithSameUid.Cookie = cookie;
|
||||
appDbContext.Users.Update(userWithSameUid.Entity);
|
||||
appDbContext.SaveChanges();
|
||||
|
||||
return new(UserOptionResult.Updated, uid);
|
||||
}
|
||||
}
|
||||
@@ -189,17 +196,8 @@ internal class UserService : IUserService
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateUserCookie(Cookie cookie, BindingUser user)
|
||||
{
|
||||
user.Cookie = cookie;
|
||||
|
||||
appDbContext.Users.Update(user.Entity);
|
||||
appDbContext.SaveChanges();
|
||||
}
|
||||
|
||||
private async Task<ValueResult<UserOptionResult, string>> TryCreateUserAndAddAsync(ObservableCollection<BindingUser> users, Cookie cookie)
|
||||
{
|
||||
cookie.Trim();
|
||||
BindingUser? newUser = await BindingUser.CreateAsync(cookie, userClient, bindingClient).ConfigureAwait(false);
|
||||
if (newUser != null)
|
||||
{
|
||||
|
||||
@@ -29,6 +29,12 @@
|
||||
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;DISABLE_XAML_GENERATED_BINDING_DEBUG_OUTPUT</DefineConstants>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Control\Panel\PanelSelector.xaml" />
|
||||
@@ -38,6 +44,7 @@
|
||||
<None Remove="Resource\Icon\UI_BagTabIcon_Weapon.png" />
|
||||
<None Remove="Resource\Icon\UI_BtnIcon_ActivityEntry.png" />
|
||||
<None Remove="Resource\Icon\UI_BtnIcon_Gacha.png" />
|
||||
<None Remove="Resource\Icon\UI_ChapterIcon_Hutao.png" />
|
||||
<None Remove="Resource\Icon\UI_Icon_Achievement.png" />
|
||||
<None Remove="Resource\Icon\UI_Icon_BoostUp.png" />
|
||||
<None Remove="Resource\Icon\UI_Icon_Locked.png" />
|
||||
@@ -54,6 +61,7 @@
|
||||
<None Remove="View\Dialog\AvatarInfoQueryDialog.xaml" />
|
||||
<None Remove="View\Dialog\GachaLogImportDialog.xaml" />
|
||||
<None Remove="View\Dialog\GachaLogRefreshProgressDialog.xaml" />
|
||||
<None Remove="View\Dialog\GachaLogUrlDialog.xaml" />
|
||||
<None Remove="View\Dialog\UserAutoCookieDialog.xaml" />
|
||||
<None Remove="View\Dialog\UserDialog.xaml" />
|
||||
<None Remove="View\MainView.xaml" />
|
||||
@@ -62,6 +70,7 @@
|
||||
<None Remove="View\Page\AnnouncementPage.xaml" />
|
||||
<None Remove="View\Page\AvatarPropertyPage.xaml" />
|
||||
<None Remove="View\Page\GachaLogPage.xaml" />
|
||||
<None Remove="View\Page\HutaoDatabasePage.xaml" />
|
||||
<None Remove="View\Page\SettingPage.xaml" />
|
||||
<None Remove="View\Page\WikiAvatarPage.xaml" />
|
||||
<None Remove="View\TitleView.xaml" />
|
||||
@@ -87,6 +96,7 @@
|
||||
<Content Include="Resource\Icon\UI_BagTabIcon_Weapon.png" />
|
||||
<Content Include="Resource\Icon\UI_BtnIcon_ActivityEntry.png" />
|
||||
<Content Include="Resource\Icon\UI_BtnIcon_Gacha.png" />
|
||||
<Content Include="Resource\Icon\UI_ChapterIcon_Hutao.png" />
|
||||
<Content Include="Resource\Icon\UI_Icon_Achievement.png" />
|
||||
<Content Include="Resource\Icon\UI_Icon_BoostUp.png" />
|
||||
<Content Include="Resource\Icon\UI_Icon_Locked.png" />
|
||||
@@ -99,10 +109,9 @@
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.UI.Behaviors" Version="7.1.2" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2" />
|
||||
<!-- Prevent NewtownSoft.Json -->
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.9" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.10" />
|
||||
<!-- The PrivateAssets & IncludeAssets of Microsoft.EntityFrameworkCore.Tools should be remove to prevent multiple deps files-->
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.9" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" />
|
||||
@@ -139,6 +148,16 @@
|
||||
<ItemGroup>
|
||||
<None Include="..\.editorconfig" Link=".editorconfig" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Page\HutaoDatabasePage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Dialog\GachaLogUrlDialog.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Dialog\AvatarInfoQueryDialog.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
||||
@@ -5,7 +5,6 @@ using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Model.Metadata;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Snap.Hutao.View.Control;
|
||||
|
||||
|
||||
19
src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogUrlDialog.xaml
Normal file
19
src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogUrlDialog.xaml
Normal file
@@ -0,0 +1,19 @@
|
||||
<ContentDialog
|
||||
x:Class="Snap.Hutao.View.Dialog.GachaLogUrlDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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"
|
||||
mc:Ignorable="d"
|
||||
Title="手动输入祈愿记录Url"
|
||||
DefaultButton="Primary"
|
||||
PrimaryButtonText="确认"
|
||||
CloseButtonText="取消"
|
||||
Style="{StaticResource DefaultContentDialogStyle}">
|
||||
|
||||
<Grid>
|
||||
<TextBox
|
||||
x:Name="InputText"
|
||||
PlaceholderText="请输入Url"/>
|
||||
</Grid>
|
||||
</ContentDialog>
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
|
||||
namespace Snap.Hutao.View.Dialog;
|
||||
|
||||
/// <summary>
|
||||
/// 祈愿记录Url对话框
|
||||
/// </summary>
|
||||
public sealed partial class GachaLogUrlDialog : ContentDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化一个新的祈愿记录Url对话框
|
||||
/// </summary>
|
||||
/// <param name="window">窗体</param>
|
||||
public GachaLogUrlDialog(Window window)
|
||||
{
|
||||
InitializeComponent();
|
||||
XamlRoot = window.Content.XamlRoot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取输入的Url
|
||||
/// </summary>
|
||||
/// <returns>输入的结果</returns>
|
||||
public async Task<ValueResult<bool, string>> GetInputUrlAsync()
|
||||
{
|
||||
ContentDialogResult result = await ShowAsync();
|
||||
string url = InputText.Text;
|
||||
|
||||
return new(result == ContentDialogResult.Primary, url);
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ public sealed partial class UserAutoCookieDialog : ContentDialog
|
||||
|
||||
CoreWebView2CookieManager manager = WebView.CoreWebView2.CookieManager;
|
||||
IReadOnlyList<CoreWebView2Cookie> cookies = await manager.GetCookiesAsync("https://user.mihoyo.com");
|
||||
foreach (var item in cookies)
|
||||
foreach (CoreWebView2Cookie item in cookies)
|
||||
{
|
||||
manager.DeleteCookie(item);
|
||||
}
|
||||
|
||||
@@ -43,6 +43,11 @@
|
||||
shvh:NavHelper.NavigateTo="shvp:AvatarPropertyPage"
|
||||
Icon="{shcm:BitmapIcon Source=ms-appx:///Resource/Icon/UI_Icon_BoostUp.png}"/>
|
||||
|
||||
<NavigationViewItem
|
||||
Content="深渊统计"
|
||||
shvh:NavHelper.NavigateTo="shvp:HutaoDatabasePage"
|
||||
Icon="{shcm:BitmapIcon Source=ms-appx:///Resource/Icon/UI_ChapterIcon_Hutao.png}"/>
|
||||
|
||||
<NavigationViewItemHeader Content="WIKI"/>
|
||||
|
||||
<NavigationViewItem
|
||||
|
||||
@@ -8,7 +8,6 @@ using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using Snap.Hutao.View.Page;
|
||||
using Windows.UI.ViewManagement;
|
||||
|
||||
namespace Snap.Hutao.View;
|
||||
|
||||
|
||||
@@ -520,25 +520,28 @@
|
||||
Width="80"
|
||||
Height="80"
|
||||
Source="{Binding Icon}"/>
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ItemsControl
|
||||
MinWidth="156"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Stretch"
|
||||
Grid.Row="2"
|
||||
ItemsSource="{Binding SubProperties}">
|
||||
ItemsSource="{Binding PrimarySubProperties}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<cwucont:UniformGrid
|
||||
Columns="2"
|
||||
Columns="1"
|
||||
Rows="5"
|
||||
ColumnSpacing="16"
|
||||
Orientation="Vertical"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Padding="2" MinWidth="152" Opacity="{Binding Opacity}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Padding="2" Opacity="{Binding Opacity}">
|
||||
<TextBlock
|
||||
Text="{Binding Name}"/>
|
||||
<TextBlock
|
||||
@@ -549,6 +552,34 @@
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<ItemsControl
|
||||
MinWidth="156"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Stretch"
|
||||
ItemsSource="{Binding SecondarySubProperties}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<cwucont:UniformGrid
|
||||
Columns="1"
|
||||
Rows="5"
|
||||
Orientation="Vertical"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Padding="2" Opacity="{Binding Opacity}">
|
||||
<TextBlock
|
||||
Text="{Binding Name}"/>
|
||||
<TextBlock
|
||||
Text="{Binding Value}"
|
||||
HorizontalAlignment="Right"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
|
||||
|
||||
<Rectangle Height="1" Grid.Row="3" Margin="0,6" Opacity="0.8">
|
||||
<Rectangle.Fill>
|
||||
<SolidColorBrush Color="{Binding Quality,Converter={StaticResource QualityColorConverter}}"/>
|
||||
|
||||
@@ -72,6 +72,8 @@
|
||||
Command="{Binding ImportFromUIGFJsonCommand}"/>
|
||||
<MenuFlyoutItem
|
||||
Text="从 UIGF Excel 文件导入"
|
||||
IsEnabled="False"
|
||||
Visibility="Collapsed"
|
||||
Command="{Binding ImportFromUIGFExcelCommand}"/>
|
||||
</MenuFlyout>
|
||||
</AppBarButton.Flyout>
|
||||
@@ -84,6 +86,8 @@
|
||||
Command="{Binding ExportToUIGFJsonCommand}"/>
|
||||
<MenuFlyoutItem
|
||||
Text="导出到 UIGF Excel 文件"
|
||||
IsEnabled="False"
|
||||
Visibility="Collapsed"
|
||||
Command="{Binding ExportToUIGFExcelCommand}"/>
|
||||
</MenuFlyout>
|
||||
</AppBarButton.Flyout>
|
||||
|
||||
309
src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml
Normal file
309
src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml
Normal file
@@ -0,0 +1,309 @@
|
||||
<shc:ScopedPage
|
||||
x:Class="Snap.Hutao.View.Page.HutaoDatabasePage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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:cwuc="using:CommunityToolkit.WinUI.UI.Controls"
|
||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:shc="using:Snap.Hutao.Control"
|
||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||
xmlns:shvc="using:Snap.Hutao.View.Control"
|
||||
xmlns:shv="using:Snap.Hutao.ViewModel"
|
||||
mc:Ignorable="d"
|
||||
d:DataContext="{d:DesignInstance shv:HutaoDatabaseViewModel}"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<shc:ScopedPage.Resources>
|
||||
<Thickness x:Key="PivotHeaderItemMargin">8,0,8,0</Thickness>
|
||||
<Thickness x:Key="PivotItemMargin">0</Thickness>
|
||||
</shc:ScopedPage.Resources>
|
||||
|
||||
<Grid>
|
||||
<Pivot>
|
||||
<PivotItem Header="角色使用">
|
||||
<Pivot ItemsSource="{Binding AvatarUsageRanks}">
|
||||
<Pivot.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Floor}"/>
|
||||
</DataTemplate>
|
||||
</Pivot.HeaderTemplate>
|
||||
<Pivot.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ScrollViewer>
|
||||
<GridView
|
||||
SelectionMode="None"
|
||||
ItemsSource="{Binding Avatars}"
|
||||
Margin="12,12,0,0">
|
||||
<GridView.ItemContainerStyle>
|
||||
<Style TargetType="GridViewItem" BasedOn="{StaticResource DefaultGridViewItemStyle}">
|
||||
<Setter Property="Margin" Value="0,0,12,12"/>
|
||||
</Style>
|
||||
</GridView.ItemContainerStyle>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Background="{StaticResource CardBackgroundFillColorDefault}">
|
||||
<StackPanel>
|
||||
<shvc:ItemIcon
|
||||
Icon="{Binding Icon}"
|
||||
Quality="{Binding Quality}"/>
|
||||
<TextBlock
|
||||
Margin="0,0,0,2"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding Rate}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</GridView>
|
||||
</ScrollViewer>
|
||||
</DataTemplate>
|
||||
</Pivot.ItemTemplate>
|
||||
</Pivot>
|
||||
</PivotItem>
|
||||
<PivotItem Header="角色出场">
|
||||
<Pivot ItemsSource="{Binding AvatarAppearanceRanks}">
|
||||
<Pivot.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Floor}"/>
|
||||
</DataTemplate>
|
||||
</Pivot.HeaderTemplate>
|
||||
<Pivot.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ScrollViewer>
|
||||
<GridView
|
||||
SelectionMode="None"
|
||||
ItemsSource="{Binding Avatars}"
|
||||
Margin="12,12,0,0">
|
||||
<GridView.ItemContainerStyle>
|
||||
<Style TargetType="GridViewItem" BasedOn="{StaticResource DefaultGridViewItemStyle}">
|
||||
<Setter Property="Margin" Value="0,0,12,12"/>
|
||||
</Style>
|
||||
</GridView.ItemContainerStyle>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Background="{StaticResource CardBackgroundFillColorDefault}">
|
||||
<StackPanel>
|
||||
<shvc:ItemIcon
|
||||
Icon="{Binding Icon}"
|
||||
Quality="{Binding Quality}"/>
|
||||
<TextBlock
|
||||
Margin="0,0,0,2"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding Rate}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</GridView>
|
||||
</ScrollViewer>
|
||||
</DataTemplate>
|
||||
</Pivot.ItemTemplate>
|
||||
</Pivot>
|
||||
</PivotItem>
|
||||
<PivotItem Header="角色持有">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Margin="12,0,12,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="48"/>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="角色" Margin="6" HorizontalAlignment="Center" Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
<TextBlock Grid.Column="1" Text="持有" Margin="6" HorizontalAlignment="Center" Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
<TextBlock Grid.Column="2" Text="0 命" Margin="6" HorizontalAlignment="Center" Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
<TextBlock Grid.Column="3" Text="1 命" Margin="6" HorizontalAlignment="Center" Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
<TextBlock Grid.Column="4" Text="2 命" Margin="6" HorizontalAlignment="Center" Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
<TextBlock Grid.Column="5" Text="3 命" Margin="6" HorizontalAlignment="Center" Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
<TextBlock Grid.Column="6" Text="4 命" Margin="6" HorizontalAlignment="Center" Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
<TextBlock Grid.Column="7" Text="5 命" Margin="6" HorizontalAlignment="Center" Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
<TextBlock Grid.Column="8" Text="6 命" Margin="6" HorizontalAlignment="Center" Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
</Grid>
|
||||
<ScrollViewer Grid.Row="1">
|
||||
<ItemsControl
|
||||
HorizontalContentAlignment="Stretch"
|
||||
ItemsSource="{Binding AvatarConstellationInfos}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<ItemsStackPanel/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
Background="{StaticResource CardBackgroundFillColorDefault}"
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
Margin="12,0,12,12">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<shvc:ItemIcon
|
||||
Height="48"
|
||||
Width="48"
|
||||
Icon="{Binding Icon}"
|
||||
Quality="{Binding Quality}"/>
|
||||
|
||||
<Grid Grid.Column="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="1*"/>
|
||||
<ColumnDefinition Width="7*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Rate}"/>
|
||||
<ItemsControl Grid.Column="1" ItemsSource="{Binding Rates}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<cwuc:UniformGrid Columns="7"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding}"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</PivotItem>
|
||||
<PivotItem Header="队伍出场">
|
||||
<Pivot ItemsSource="{Binding TeamAppearances}">
|
||||
<Pivot.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Floor}"/>
|
||||
</DataTemplate>
|
||||
</Pivot.HeaderTemplate>
|
||||
<Pivot.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ScrollViewer Grid.Column="0">
|
||||
<ItemsControl ItemsSource="{Binding Up}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<ItemsStackPanel/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
Margin="12,12,12,0"
|
||||
Background="{StaticResource CardBackgroundFillColorDefault}">
|
||||
<Grid Margin="6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition Width="120"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ItemsControl ItemsSource="{Binding}" HorizontalAlignment="Left">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<cwuc:UniformGrid Columns="4" ColumnSpacing="6"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<shvc:ItemIcon
|
||||
Width="48"
|
||||
Height="48"
|
||||
Icon="{Binding Icon}"
|
||||
Quality="{Binding Quality}"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Rate}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
||||
<ScrollViewer Grid.Column="1">
|
||||
<ItemsControl ItemsSource="{Binding Down}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<ItemsStackPanel/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
Margin="12,12,12,0"
|
||||
Background="{StaticResource CardBackgroundFillColorDefault}">
|
||||
<Grid Margin="6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition Width="120"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ItemsControl ItemsSource="{Binding}" HorizontalAlignment="Left">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<cwuc:UniformGrid Columns="4" ColumnSpacing="6"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<shvc:ItemIcon
|
||||
Width="48"
|
||||
Height="48"
|
||||
Icon="{Binding Icon}"
|
||||
Quality="{Binding Quality}"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Rate}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</Pivot.ItemTemplate>
|
||||
</Pivot>
|
||||
</PivotItem>
|
||||
</Pivot>
|
||||
</Grid>
|
||||
</shc:ScopedPage>
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Control;
|
||||
using Snap.Hutao.ViewModel;
|
||||
|
||||
namespace Snap.Hutao.View.Page;
|
||||
|
||||
/// <summary>
|
||||
/// 胡桃数据库页面
|
||||
/// </summary>
|
||||
public sealed partial class HutaoDatabasePage : ScopedPage
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的胡桃数据库页面
|
||||
/// </summary>
|
||||
public HutaoDatabasePage()
|
||||
{
|
||||
InitializeWith<HutaoDatabaseViewModel>();
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -95,6 +95,15 @@
|
||||
<Button Content="打开" Command="{Binding Experimental.OpenCacheFolderCommand}"/>
|
||||
</sc:Setting.ActionContent>
|
||||
</sc:Setting>
|
||||
|
||||
<sc:Setting
|
||||
Icon=""
|
||||
Header="上传深渊数据"
|
||||
Description="将当前账号的深渊数据上传到胡桃数据库">
|
||||
<sc:Setting.ActionContent>
|
||||
<Button Content="上传" Command="{Binding Experimental.UploadSpiralAbyssRecordCommand}"/>
|
||||
</sc:Setting.ActionContent>
|
||||
</sc:Setting>
|
||||
</sc:SettingsGroup>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Context.FileSystem.Location;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.User;
|
||||
using Snap.Hutao.Web.Hutao;
|
||||
using Snap.Hutao.Web.Hutao.Model.Post;
|
||||
using Windows.Storage;
|
||||
using Windows.System;
|
||||
|
||||
@@ -22,8 +26,6 @@ internal class ExperimentalFeaturesViewModel : ObservableObject
|
||||
/// </summary>
|
||||
/// <param name="asyncRelayCommandFactory">异步命令工厂</param>
|
||||
/// <param name="hutaoLocation">数据文件夹</param>
|
||||
/// <param name="signService">签到客户端</param>
|
||||
/// <param name="infoBarService">信息条服务</param>
|
||||
public ExperimentalFeaturesViewModel(
|
||||
IAsyncRelayCommandFactory asyncRelayCommandFactory,
|
||||
HutaoLocation hutaoLocation)
|
||||
@@ -32,6 +34,7 @@ internal class ExperimentalFeaturesViewModel : ObservableObject
|
||||
|
||||
OpenCacheFolderCommand = asyncRelayCommandFactory.Create(OpenCacheFolderAsync);
|
||||
OpenDataFolderCommand = asyncRelayCommandFactory.Create(OpenDataFolderAsync);
|
||||
UploadSpiralAbyssRecordCommand = asyncRelayCommandFactory.Create(UploadSpiralAbyssRecordAsync);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -44,6 +47,11 @@ internal class ExperimentalFeaturesViewModel : ObservableObject
|
||||
/// </summary>
|
||||
public ICommand OpenDataFolderCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 上传深渊记录命令
|
||||
/// </summary>
|
||||
public ICommand UploadSpiralAbyssRecordCommand { get; }
|
||||
|
||||
private Task OpenCacheFolderAsync()
|
||||
{
|
||||
return Launcher.LaunchFolderAsync(ApplicationData.Current.TemporaryFolder).AsTask();
|
||||
@@ -53,4 +61,22 @@ internal class ExperimentalFeaturesViewModel : ObservableObject
|
||||
{
|
||||
return Launcher.LaunchFolderPathAsync(hutaoLocation.GetPath()).AsTask();
|
||||
}
|
||||
|
||||
private async Task UploadSpiralAbyssRecordAsync()
|
||||
{
|
||||
HomaClient homaClient = Ioc.Default.GetRequiredService<HomaClient>();
|
||||
IUserService userService = Ioc.Default.GetRequiredService<IUserService>();
|
||||
|
||||
if (userService.Current is Model.Binding.User user)
|
||||
{
|
||||
SimpleRecord record = await homaClient.GetPlayerRecordAsync(user).ConfigureAwait(false);
|
||||
Web.Response.Response<string>? response = await homaClient.UploadRecordAsync(record).ConfigureAwait(false);
|
||||
|
||||
if (response != null && response.IsOk())
|
||||
{
|
||||
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
||||
infoBarService.Success(response.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,6 +233,13 @@ internal class GachaLogViewModel : ObservableObject, ISupportCancellation
|
||||
dialog.DefaultButton = ContentDialogButton.Primary;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (query is string message)
|
||||
{
|
||||
infoBarService.Warning(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
151
src/Snap.Hutao/Snap.Hutao/ViewModel/HutaoDatabaseViewModel.cs
Normal file
151
src/Snap.Hutao/Snap.Hutao/ViewModel/HutaoDatabaseViewModel.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Control;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Model.Binding.Hutao;
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using Snap.Hutao.Model.Metadata.Weapon;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
namespace Snap.Hutao.ViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// 胡桃数据库视图模型
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Transient)]
|
||||
internal class HutaoDatabaseViewModel : ObservableObject, ISupportCancellation
|
||||
{
|
||||
private readonly IHutaoService hutaoService;
|
||||
private readonly IMetadataService metadataService;
|
||||
|
||||
private List<ComplexAvatarRank>? avatarUsageRanks;
|
||||
private List<ComplexAvatarRank>? avatarAppearanceRanks;
|
||||
private List<ComplexAvatarConstellationInfo>? avatarConstellationInfos;
|
||||
private List<ComplexTeamRank>? teamAppearances;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的胡桃数据库视图模型
|
||||
/// </summary>
|
||||
/// <param name="hutaoService">胡桃服务</param>
|
||||
/// <param name="metadataService">元数据服务</param>
|
||||
/// <param name="asyncRelayCommandFactory">异步命令工厂</param>
|
||||
public HutaoDatabaseViewModel(IHutaoService hutaoService, IMetadataService metadataService, IAsyncRelayCommandFactory asyncRelayCommandFactory)
|
||||
{
|
||||
this.hutaoService = hutaoService;
|
||||
this.metadataService = metadataService;
|
||||
|
||||
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CancellationToken CancellationToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色使用率
|
||||
/// </summary>
|
||||
public List<ComplexAvatarRank>? AvatarUsageRanks { get => avatarUsageRanks; set => SetProperty(ref avatarUsageRanks, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 角色上场率
|
||||
/// </summary>
|
||||
public List<ComplexAvatarRank>? AvatarAppearanceRanks { get => avatarAppearanceRanks; set => SetProperty(ref avatarAppearanceRanks, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 角色命座信息
|
||||
/// </summary>
|
||||
public List<ComplexAvatarConstellationInfo>? AvatarConstellationInfos { get => avatarConstellationInfos; set => avatarConstellationInfos = value; }
|
||||
|
||||
/// <summary>
|
||||
/// 队伍出场
|
||||
/// </summary>
|
||||
public List<ComplexTeamRank>? TeamAppearances { get => teamAppearances; set => SetProperty(ref teamAppearances, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 打开界面命令
|
||||
/// </summary>
|
||||
public ICommand OpenUICommand { get; }
|
||||
|
||||
private async Task OpenUIAsync()
|
||||
{
|
||||
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||
{
|
||||
Dictionary<int, Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
|
||||
idAvatarMap = new(idAvatarMap)
|
||||
{
|
||||
[10000005] = new() { Name = "旅行者", Icon = "UI_AvatarIcon_PlayerBoy", Quality = Model.Intrinsic.ItemQuality.QUALITY_ORANGE },
|
||||
[10000007] = new() { Name = "旅行者", Icon = "UI_AvatarIcon_PlayerGirl", Quality = Model.Intrinsic.ItemQuality.QUALITY_ORANGE },
|
||||
};
|
||||
|
||||
Dictionary<int, Weapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false);
|
||||
Dictionary<int, Model.Metadata.Reliquary.ReliquarySet> idReliquarySetMap = await metadataService.GetEquipAffixIdToReliquarySetMapAsync().ConfigureAwait(false);
|
||||
|
||||
List<ComplexAvatarRank> avatarAppearanceRanksLocal = default!;
|
||||
List<ComplexAvatarRank> avatarUsageRanksLocal = default!;
|
||||
List<ComplexAvatarConstellationInfo> avatarConstellationInfosLocal = default!;
|
||||
List<ComplexTeamRank> teamAppearancesLocal = default!;
|
||||
|
||||
Task avatarAppearanceRankTask = Task.Run(async () =>
|
||||
{
|
||||
// AvatarAppearanceRank
|
||||
List<AvatarAppearanceRank> avatarAppearanceRanksRaw = await hutaoService.GetAvatarAppearanceRanksAsync().ConfigureAwait(false);
|
||||
avatarAppearanceRanksLocal = avatarAppearanceRanksRaw.OrderByDescending(r => r.Floor).Select(rank => new ComplexAvatarRank
|
||||
{
|
||||
Floor = $"第 {rank.Floor} 层",
|
||||
Avatars = rank.Ranks.OrderByDescending(r => r.Rate).Select(rank => new ComplexAvatar(idAvatarMap[rank.Item], rank.Rate)).ToList(),
|
||||
}).ToList();
|
||||
});
|
||||
|
||||
Task avatarUsageRank = Task.Run(async () =>
|
||||
{
|
||||
// AvatarUsageRank
|
||||
List<AvatarUsageRank> avatarUsageRanksRaw = await hutaoService.GetAvatarUsageRanksAsync().ConfigureAwait(false);
|
||||
avatarUsageRanksLocal = avatarUsageRanksRaw.OrderByDescending(r => r.Floor).Select(rank => new ComplexAvatarRank
|
||||
{
|
||||
Floor = $"第 {rank.Floor} 层",
|
||||
Avatars = rank.Ranks.OrderByDescending(r => r.Rate).Select(rank => new ComplexAvatar(idAvatarMap[rank.Item], rank.Rate)).ToList(),
|
||||
}).ToList();
|
||||
});
|
||||
|
||||
Task avatarConstellationInfoTask = Task.Run(async () =>
|
||||
{
|
||||
// AvatarConstellationInfo
|
||||
List<AvatarConstellationInfo> avatarConstellationInfosRaw = await hutaoService.GetAvatarConstellationInfosAsync().ConfigureAwait(false);
|
||||
avatarConstellationInfosLocal = avatarConstellationInfosRaw.OrderBy(i => i.HoldingRate).Select(info =>
|
||||
{
|
||||
return new ComplexAvatarConstellationInfo(idAvatarMap[info.AvatarId], info.HoldingRate, info.Constellations.Select(x => x.Rate));
|
||||
}).ToList();
|
||||
});
|
||||
|
||||
Task teamAppearanceTask = Task.Run(async () =>
|
||||
{
|
||||
List<TeamAppearance> teamAppearancesRaw = await hutaoService.GetTeamAppearancesAsync().ConfigureAwait(false);
|
||||
teamAppearancesLocal = teamAppearancesRaw.OrderByDescending(t => t.Floor).Select(team => new ComplexTeamRank(team, idAvatarMap)).ToList();
|
||||
});
|
||||
|
||||
await Task.WhenAll(avatarAppearanceRankTask, avatarUsageRank, avatarConstellationInfoTask, teamAppearanceTask).ConfigureAwait(false);
|
||||
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
AvatarAppearanceRanks = avatarAppearanceRanksLocal;
|
||||
AvatarUsageRanks = avatarUsageRanksLocal;
|
||||
AvatarConstellationInfos = avatarConstellationInfosLocal;
|
||||
TeamAppearances = teamAppearancesLocal;
|
||||
|
||||
//// AvatarCollocation
|
||||
//List<AvatarCollocation> avatarCollocationsRaw = await hutaoService.GetAvatarCollocationsAsync().ConfigureAwait(false);
|
||||
//List<ComplexAvatarCollocation> avatarCollocationsLocal = avatarCollocationsRaw.Select(co =>
|
||||
//{
|
||||
// return new ComplexAvatarCollocation(idAvatarMap[co.AvatarId])
|
||||
// {
|
||||
// Avatars = co.Avatars.Select(a => new ComplexAvatar(idAvatarMap[a.Item], a.Rate)).ToList(),
|
||||
// Weapons = co.Weapons.Select(w => new ComplexWeapon(idWeaponMap[w.Item], w.Rate)).ToList(),
|
||||
// ReliquarySets = co.Reliquaries.Select(r => new ComplexReliquarySet(r, idReliquarySetMap)).ToList(),
|
||||
// };
|
||||
//}).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ internal static class ApiEndpoints
|
||||
/// <returns>游戏记录主页字符串</returns>
|
||||
public static string GameRecordIndex(string uid, string server)
|
||||
{
|
||||
return $"{ApiTakumiRecordApi}/index?role_id={uid}&server={server}";
|
||||
return $"{ApiTakumiRecordApi}/index?server={server}&role_id={uid}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -38,7 +38,7 @@ public partial class Cookie
|
||||
public static Cookie Parse(string cookieString)
|
||||
{
|
||||
SortedDictionary<string, string> cookieMap = new();
|
||||
|
||||
cookieString = cookieString.Replace(" ", string.Empty);
|
||||
string[] values = cookieString.TrimEnd(';').Split(';');
|
||||
foreach (string[] parts in values.Select(c => c.Split('=', 2)))
|
||||
{
|
||||
|
||||
@@ -29,7 +29,8 @@ internal abstract class DynamicSecretProvider2 : Md5Convert
|
||||
string b = postBody is null ? string.Empty : JsonSerializer.Serialize(postBody, options);
|
||||
|
||||
// query
|
||||
string q = string.Join('&', new UriBuilder(queryUrl).Query.Split('&').OrderBy(x => x));
|
||||
string[] queries = queryUrl.Split('?', 2);
|
||||
string q = queries.Length == 2 ? string.Join('&', queries[1].Split('&').OrderBy(x => x)) : string.Empty;
|
||||
|
||||
// check
|
||||
string check = ToHexString($"salt={Core.CoreEnvironment.DynamicSecret2Salt}&t={t}&r={r}&b={b}&q={q}").ToLowerInvariant();
|
||||
|
||||
@@ -24,7 +24,7 @@ public class GachaLogItem
|
||||
/// </summary>
|
||||
[ExcelColumn(Name = "gacha_type")]
|
||||
[JsonPropertyName("gacha_type")]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
[JsonConverter(typeof(EnumStringValueConverter<GachaConfigType>))]
|
||||
public GachaConfigType GachaType { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -45,6 +45,21 @@ internal static class HttpClientExtensions
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="HttpClientJsonExtensions.PostAsJsonAsync{TValue}(HttpClient, string?, TValue, JsonSerializerOptions?, CancellationToken)"/>
|
||||
internal static async Task<TResult?> TryCatchPostAsJsonAsync<TValue, TResult>(this HttpClient httpClient, string requestUri, TValue value, JsonSerializerOptions options, CancellationToken token = default)
|
||||
where TResult : class
|
||||
{
|
||||
try
|
||||
{
|
||||
HttpResponseMessage message = await httpClient.PostAsJsonAsync(requestUri, value, options, token).ConfigureAwait(false);
|
||||
return await message.Content.ReadFromJsonAsync<TResult>(options, token).ConfigureAwait(false);
|
||||
}
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置用户的Cookie
|
||||
/// </summary>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.Net.Http;
|
||||
|
||||
@@ -18,11 +18,11 @@ public class Offering
|
||||
/// 等级
|
||||
/// </summary>
|
||||
[JsonPropertyName("level")]
|
||||
public string Level { get; set; } = default!;
|
||||
public int Level { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
[JsonPropertyName("icon")]
|
||||
public string Icon { get; set; } = default!;
|
||||
public Uri Icon { get; set; } = default!;
|
||||
}
|
||||
@@ -24,7 +24,7 @@ public class Floor
|
||||
/// 是否解锁
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_unlock")]
|
||||
public string IsUnlock { get; set; } = default!;
|
||||
public bool IsUnlock { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 结束时间
|
||||
|
||||
@@ -25,7 +25,7 @@ public class WorldExploration
|
||||
/// 图标
|
||||
/// </summary>
|
||||
[JsonPropertyName("icon")]
|
||||
public string Icon { get; set; } = default!;
|
||||
public Uri Icon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
@@ -39,7 +39,8 @@ public class WorldExploration
|
||||
/// Reputation
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; } = default!;
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public WorldExplorationType Type { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 供奉进度
|
||||
@@ -63,7 +64,7 @@ public class WorldExploration
|
||||
/// 地图链接
|
||||
/// </summary>
|
||||
[JsonPropertyName("map_url")]
|
||||
public string MapUrl { get; set; } = default!;
|
||||
public Uri MapUrl { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 攻略链接 无攻略时为 <see cref="string.Empty"/>
|
||||
@@ -75,41 +76,25 @@ public class WorldExploration
|
||||
/// 背景图片
|
||||
/// </summary>
|
||||
[JsonPropertyName("background_image")]
|
||||
public string BackgroundImage { get; set; } = default!;
|
||||
public Uri BackgroundImage { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 反色图标
|
||||
/// </summary>
|
||||
[JsonPropertyName("inner_icon")]
|
||||
public string InnerIcon { get; set; } = default!;
|
||||
public Uri InnerIcon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 背景图片
|
||||
/// </summary>
|
||||
[JsonPropertyName("cover")]
|
||||
public string Cover { get; set; } = default!;
|
||||
public Uri Cover { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 百分比*100进度
|
||||
/// </summary>
|
||||
public double ExplorationPercentageBy10
|
||||
{
|
||||
get => ExplorationPercentage / 10.0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 类型名称转换器
|
||||
/// </summary>
|
||||
public string TypeFormatted
|
||||
{
|
||||
get => IsReputation ? "声望等级" : "供奉等级";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指示当前是否为声望
|
||||
/// </summary>
|
||||
public bool IsReputation
|
||||
{
|
||||
get => Type == "Reputation";
|
||||
get => ExplorationPercentage / 1000.0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
|
||||
|
||||
/// <summary>
|
||||
/// 世界探索类型
|
||||
/// </summary>
|
||||
public enum WorldExplorationType
|
||||
{
|
||||
/// <summary>
|
||||
/// 声望
|
||||
/// </summary>
|
||||
Reputation,
|
||||
|
||||
/// <summary>
|
||||
/// 供奉
|
||||
/// </summary>
|
||||
Offering,
|
||||
}
|
||||
@@ -20,13 +20,14 @@ namespace Snap.Hutao.Web.Hutao;
|
||||
/// </summary>
|
||||
// [Injection(InjectAs.Transient)]
|
||||
[HttpClient(HttpClientConfigration.Default)]
|
||||
internal class HutaoClient
|
||||
internal class HomaClient
|
||||
{
|
||||
private const string HutaoAPI = "https://hutao-api.snapgenshin.com";
|
||||
private const string HutaoAPI = "https://homa.snapgenshin.com";
|
||||
|
||||
private readonly HttpClient httpClient;
|
||||
private readonly GameRecordClient gameRecordClient;
|
||||
private readonly JsonSerializerOptions options;
|
||||
private readonly ILogger<HomaClient> logger;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的胡桃API客户端
|
||||
@@ -34,11 +35,13 @@ internal class HutaoClient
|
||||
/// <param name="httpClient">http客户端</param>
|
||||
/// <param name="gameRecordClient">游戏记录客户端</param>
|
||||
/// <param name="options">json序列化选项</param>
|
||||
public HutaoClient(HttpClient httpClient, GameRecordClient gameRecordClient, JsonSerializerOptions options)
|
||||
/// <param name="logger">日志器</param>
|
||||
public HomaClient(HttpClient httpClient, GameRecordClient gameRecordClient, JsonSerializerOptions options, ILogger<HomaClient> logger)
|
||||
{
|
||||
this.httpClient = httpClient;
|
||||
this.gameRecordClient = gameRecordClient;
|
||||
this.options = options;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -94,10 +97,10 @@ internal class HutaoClient
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色出场率</returns>
|
||||
public async Task<IEnumerable<AvatarAppearanceRank>> GetAvatarAttendanceRatesAsync(CancellationToken token = default)
|
||||
public async Task<List<AvatarAppearanceRank>> GetAvatarAttendanceRatesAsync(CancellationToken token = default)
|
||||
{
|
||||
Response<IEnumerable<AvatarAppearanceRank>>? resp = await httpClient
|
||||
.GetFromJsonAsync<Response<IEnumerable<AvatarAppearanceRank>>>($"{HutaoAPI}/Statistics/Avatar/AttendanceRate", token)
|
||||
Response<List<AvatarAppearanceRank>>? resp = await httpClient
|
||||
.GetFromJsonAsync<Response<List<AvatarAppearanceRank>>>($"{HutaoAPI}/Statistics/Avatar/AttendanceRate", token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return EnumerableExtension.EmptyIfNull(resp?.Data);
|
||||
@@ -105,14 +108,14 @@ internal class HutaoClient
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取角色使用率
|
||||
/// GET /Statistics2/AvatarParticipation
|
||||
/// GET /Statistics/Avatar/UtilizationRate
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色出场率</returns>
|
||||
public async Task<IEnumerable<AvatarUsageRank>> GetAvatarParticipations2Async(CancellationToken token = default)
|
||||
public async Task<List<AvatarUsageRank>> GetAvatarUtilizationRatesAsync(CancellationToken token = default)
|
||||
{
|
||||
Response<IEnumerable<AvatarUsageRank>>? resp = await httpClient
|
||||
.GetFromJsonAsync<Response<IEnumerable<AvatarUsageRank>>>($"{HutaoAPI}/Statistics/Avatar/HoldingRate", token)
|
||||
Response<List<AvatarUsageRank>>? resp = await httpClient
|
||||
.GetFromJsonAsync<Response<List<AvatarUsageRank>>>($"{HutaoAPI}/Statistics/Avatar/UtilizationRate", token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return EnumerableExtension.EmptyIfNull(resp?.Data);
|
||||
@@ -120,14 +123,14 @@ internal class HutaoClient
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取角色/武器/圣遗物搭配
|
||||
/// GET /Statistics/AvatarReliquaryUsage
|
||||
/// GET /Statistics/Avatar/AvatarCollocation
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色/武器/圣遗物搭配</returns>
|
||||
public async Task<IEnumerable<AvatarCollocation>> GetAvatarCollocationsAsync(CancellationToken token = default)
|
||||
public async Task<List<AvatarCollocation>> GetAvatarCollocationsAsync(CancellationToken token = default)
|
||||
{
|
||||
Response<IEnumerable<AvatarCollocation>>? resp = await httpClient
|
||||
.GetFromJsonAsync<Response<IEnumerable<AvatarCollocation>>>($"{HutaoAPI}/Statistics/Avatar/AvatarCollocation", token)
|
||||
Response<List<AvatarCollocation>>? resp = await httpClient
|
||||
.GetFromJsonAsync<Response<List<AvatarCollocation>>>($"{HutaoAPI}/Statistics/Avatar/AvatarCollocation", token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return EnumerableExtension.EmptyIfNull(resp?.Data);
|
||||
@@ -135,14 +138,14 @@ internal class HutaoClient
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取角色命座信息
|
||||
/// GET /Statistics/Constellation
|
||||
/// GET /Statistics/Avatar/HoldingRate
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色图片列表</returns>
|
||||
public async Task<IEnumerable<AvatarConstellationInfo>> GetAvatarConstellationInfosAsync(CancellationToken token = default)
|
||||
public async Task<List<AvatarConstellationInfo>> GetAvatarHoldingRatesAsync(CancellationToken token = default)
|
||||
{
|
||||
Response<IEnumerable<AvatarConstellationInfo>>? resp = await httpClient
|
||||
.GetFromJsonAsync<Response<IEnumerable<AvatarConstellationInfo>>>($"{HutaoAPI}/Statistics/Constellation", token)
|
||||
Response<List<AvatarConstellationInfo>>? resp = await httpClient
|
||||
.GetFromJsonAsync<Response<List<AvatarConstellationInfo>>>($"{HutaoAPI}/Statistics/Avatar/HoldingRate", token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return EnumerableExtension.EmptyIfNull(resp?.Data);
|
||||
@@ -150,14 +153,14 @@ internal class HutaoClient
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取队伍出场次数
|
||||
/// GET /Statistics/TeamCombination
|
||||
/// GET /Team/Combination
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>队伍出场列表</returns>
|
||||
public async Task<IEnumerable<TeamAppearance>> GetTeamCombinationsAsync(CancellationToken token = default)
|
||||
public async Task<List<TeamAppearance>> GetTeamCombinationsAsync(CancellationToken token = default)
|
||||
{
|
||||
Response<IEnumerable<TeamAppearance>>? resp = await httpClient
|
||||
.GetFromJsonAsync<Response<IEnumerable<TeamAppearance>>>($"{HutaoAPI}/Team/Combination", token)
|
||||
Response<List<TeamAppearance>>? resp = await httpClient
|
||||
.GetFromJsonAsync<Response<List<TeamAppearance>>>($"{HutaoAPI}/Statistics/Team/Combination", token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return EnumerableExtension.EmptyIfNull(resp?.Data);
|
||||
@@ -195,14 +198,8 @@ internal class HutaoClient
|
||||
/// <param name="playerRecord">玩家记录</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>响应</returns>
|
||||
public async Task<Response<string>?> UploadRecordAsync(SimpleRecord playerRecord, CancellationToken token = default)
|
||||
public Task<Response<string>?> UploadRecordAsync(SimpleRecord playerRecord, CancellationToken token = default)
|
||||
{
|
||||
HttpResponseMessage response = await httpClient
|
||||
.PostAsJsonAsync($"{HutaoAPI}/Record/Upload", playerRecord, options, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return await response.Content
|
||||
.ReadFromJsonAsync<Response<string>>(options, token)
|
||||
.ConfigureAwait(false);
|
||||
return httpClient.TryCatchPostAsJsonAsync<SimpleRecord, Response<string>>($"{HutaoAPI}/Record/Upload", playerRecord, options, logger, token);
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,14 @@ namespace Snap.Hutao.Web.Hutao.Model.Converter;
|
||||
/// </summary>
|
||||
internal class ReliquarySetsConverter : JsonConverter<ReliquarySets>
|
||||
{
|
||||
private const char Separator = ',';
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ReliquarySets? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.GetString() is string source)
|
||||
{
|
||||
string[] sets = source.Split(';');
|
||||
string[] sets = source.Split(Separator, StringSplitOptions.RemoveEmptyEntries);
|
||||
return new(sets.Select(set => new ReliquarySet(set)));
|
||||
}
|
||||
else
|
||||
@@ -25,6 +27,6 @@ internal class ReliquarySetsConverter : JsonConverter<ReliquarySets>
|
||||
/// <inheritdoc/>
|
||||
public override void Write(Utf8JsonWriter writer, ReliquarySets value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(string.Join(';', value));
|
||||
writer.WriteStringValue(string.Join(Separator, value));
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ public class SimpleAvatar
|
||||
AvatarId = character.Id;
|
||||
WeaponId = character.Weapon.Id;
|
||||
ReliquarySetIds = character.Reliquaries.Select(r => r.ReliquarySet.Id);
|
||||
ActivedConstellationNumber = character.ActivedConstellationNum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -16,14 +16,14 @@ public class ReliquarySet
|
||||
{
|
||||
string[]? deconstructed = set.Split('-');
|
||||
|
||||
Id = int.Parse(deconstructed[0]);
|
||||
EquipAffixId = int.Parse(deconstructed[0]);
|
||||
Count = int.Parse(deconstructed[1]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
public int Id { get; }
|
||||
public int EquipAffixId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 个数
|
||||
@@ -33,6 +33,6 @@ public class ReliquarySet
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Id}-{Count}";
|
||||
return $"{EquipAffixId}-{Count}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,17 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 队伍出场次数
|
||||
/// </summary>
|
||||
public class TeamAppearance
|
||||
{
|
||||
/// <summary>
|
||||
/// 层
|
||||
/// </summary>
|
||||
public int Floor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 上半
|
||||
/// </summary>
|
||||
|
||||
@@ -40,16 +40,6 @@ public class Response : ISupportValidation
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 响应是否正常
|
||||
/// </summary>
|
||||
/// <param name="response">响应</param>
|
||||
/// <returns>是否Ok</returns>
|
||||
public static bool IsOk(Response? response)
|
||||
{
|
||||
return response is not null && response.ReturnCode == 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Validate()
|
||||
{
|
||||
@@ -89,6 +79,17 @@ public class Response<TData> : Response
|
||||
[JsonPropertyName("data")]
|
||||
public TData? Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 响应是否正常
|
||||
/// </summary>
|
||||
/// <param name="response">响应</param>
|
||||
/// <returns>是否Ok</returns>
|
||||
[MemberNotNullWhen(true, nameof(Data))]
|
||||
public bool IsOk()
|
||||
{
|
||||
return ReturnCode == 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ using Windows.Win32.System.Diagnostics.ToolHelp;
|
||||
namespace Snap.Hutao.Win32;
|
||||
|
||||
/// <summary>
|
||||
/// 内存拓展 for <see cref="Memory{T}"/> <see cref="Span{T}"/>
|
||||
/// 内存拓展 for <see cref="Memory{T}"/> and <see cref="Span{T}"/>
|
||||
/// </summary>
|
||||
internal static class MemoryExtensions
|
||||
{
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Windows.Graphics;
|
||||
using Windows.Win32.System.Diagnostics.ToolHelp;
|
||||
|
||||
namespace Snap.Hutao.Win32;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user