mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
add hutao api client impl
This commit is contained in:
@@ -91,6 +91,9 @@ dotnet_diagnostic.SA1623.severity = none
|
||||
# SA1636: File header copyright text should match
|
||||
dotnet_diagnostic.SA1636.severity = none
|
||||
|
||||
# SA1414: Tuple types in signatures should have element names
|
||||
dotnet_diagnostic.SA1414.severity = none
|
||||
|
||||
[*.vb]
|
||||
#### 命名样式 ####
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ public partial class App : Application
|
||||
{
|
||||
IServiceProvider services = new ServiceCollection()
|
||||
.AddLogging(builder => builder.AddDebug())
|
||||
.AddDatebase()
|
||||
.AddHttpClients()
|
||||
.AddDefaultJsonSerializerOptions()
|
||||
.AddInjections(typeof(App))
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
|
||||
namespace Snap.Hutao.Context.Database;
|
||||
|
||||
@@ -18,4 +19,9 @@ internal class AppDbContext : DbContext
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置项
|
||||
/// </summary>
|
||||
public DbSet<SettingEntry> Settings { get; set; } = default!;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using CommunityToolkit.WinUI.UI;
|
||||
using CommunityToolkit.WinUI.UI.Animations;
|
||||
using Microsoft.UI.Composition;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Snap.Hutao.Control.Animation;
|
||||
|
||||
/// <summary>
|
||||
/// 图片放大动画
|
||||
/// </summary>
|
||||
internal class ImageZoomInAnimation : ImplicitAnimation<string, Vector3>
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的图片放大动画
|
||||
/// </summary>
|
||||
public ImageZoomInAnimation()
|
||||
{
|
||||
EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseOut;
|
||||
EasingType = CommunityToolkit.WinUI.UI.Animations.EasingType.Circle;
|
||||
To = "1.1";
|
||||
Duration = TimeSpan.FromSeconds(0.5);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string ExplicitTarget => nameof(Visual.Scale);
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override (Vector3?, Vector3?) GetParsedValues()
|
||||
{
|
||||
return (To?.ToVector3(), From?.ToVector3());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using CommunityToolkit.WinUI.UI;
|
||||
using CommunityToolkit.WinUI.UI.Animations;
|
||||
using Microsoft.UI.Composition;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Snap.Hutao.Control.Animation;
|
||||
|
||||
/// <summary>
|
||||
/// 图片缩小动画
|
||||
/// </summary>
|
||||
internal class ImageZoomOutAnimation : ImplicitAnimation<string, Vector3>
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的图片缩小动画
|
||||
/// </summary>
|
||||
public ImageZoomOutAnimation()
|
||||
{
|
||||
EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseOut;
|
||||
EasingType = CommunityToolkit.WinUI.UI.Animations.EasingType.Circle;
|
||||
To = "1";
|
||||
Duration = TimeSpan.FromSeconds(0.5);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string ExplicitTarget => nameof(Visual.Scale);
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override (Vector3?, Vector3?) GetParsedValues()
|
||||
{
|
||||
return (To?.ToVector3(), From?.ToVector3());
|
||||
}
|
||||
}
|
||||
@@ -40,16 +40,22 @@ internal class AutoHeightBehavior : BehaviorBase<FrameworkElement>
|
||||
protected override void OnAssociatedObjectLoaded()
|
||||
{
|
||||
AssociatedObject.SizeChanged += OnSizeChanged;
|
||||
UpdateElementHeight();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnAssociatedObjectUnloaded()
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
AssociatedObject.SizeChanged -= OnSizeChanged;
|
||||
}
|
||||
|
||||
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
AssociatedObject.Height = (double)((FrameworkElement)sender).ActualWidth * (TargetHeight / TargetWidth);
|
||||
UpdateElementHeight();
|
||||
}
|
||||
|
||||
private void UpdateElementHeight()
|
||||
{
|
||||
AssociatedObject.Height = (double)AssociatedObject.ActualWidth * (TargetHeight / TargetWidth);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Abstraction;
|
||||
|
||||
/// <summary>
|
||||
/// 可异步初始化
|
||||
/// </summary>
|
||||
internal interface IAsyncInitializable
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否已经初始化完成
|
||||
/// </summary>
|
||||
public bool IsInitialized { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 异步初始化
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>初始化任务</returns>
|
||||
Task<bool> InitializeAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -41,4 +41,4 @@ public static class Browser
|
||||
failAction?.Invoke(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
25
src/Snap.Hutao/Snap.Hutao/Core/Converting/Md5Convert.cs
Normal file
25
src/Snap.Hutao/Snap.Hutao/Core/Converting/Md5Convert.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Snap.Hutao.Core.Converting;
|
||||
|
||||
/// <summary>
|
||||
/// 支持Md5转换
|
||||
/// </summary>
|
||||
internal abstract class Md5Convert
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取字符串的MD5计算结果
|
||||
/// </summary>
|
||||
/// <param name="source">源字符串</param>
|
||||
/// <returns>计算的结果</returns>
|
||||
public static string ToHexString(string source)
|
||||
{
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(source);
|
||||
byte[] hash = MD5.HashData(bytes);
|
||||
return Convert.ToHexString(hash);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,8 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Win32;
|
||||
using Snap.Hutao.Core.Converting;
|
||||
using Snap.Hutao.Extension;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Windows.ApplicationModel;
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
@@ -41,8 +40,6 @@ internal static class CoreEnvironment
|
||||
{
|
||||
string userName = Environment.UserName;
|
||||
object? machineGuid = Registry.GetValue(CryptographyKey, MachineGuidValue, userName);
|
||||
byte[] bytes = Encoding.UTF8.GetBytes($"{userName}{machineGuid}");
|
||||
byte[] hash = MD5.HashData(bytes);
|
||||
return Convert.ToHexString(hash);
|
||||
return Md5Convert.ToHexString($"{userName}{machineGuid}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Http Json 处理
|
||||
/// </summary>
|
||||
public class HttpJson
|
||||
{
|
||||
private readonly Json json;
|
||||
private readonly HttpClient httpClient;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化一个新的 Http Json 处理 实例
|
||||
/// </summary>
|
||||
/// <param name="json">Json 处理器</param>
|
||||
/// <param name="httpClient">http 客户端</param>
|
||||
public HttpJson(Json json, HttpClient httpClient)
|
||||
{
|
||||
this.json = json;
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从网站上下载json并转换为对象
|
||||
/// </summary>
|
||||
/// <typeparam name="T">对象的类型</typeparam>
|
||||
/// <param name="url">链接</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>Json字符串中的反序列化对象, 如果反序列化失败会抛出异常</returns>
|
||||
public async Task<T?> FromWebsiteAsync<T>(string url, CancellationToken cancellationToken = default)
|
||||
{
|
||||
string response = await httpClient.GetStringAsync(url, cancellationToken);
|
||||
return json.ToObject<T>(response);
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Json操作
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Transient)]
|
||||
public class Json
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
|
||||
private readonly JsonSerializerSettings jsonSerializerSettings = new()
|
||||
{
|
||||
DateFormatString = "yyyy'-'MM'-'dd' 'HH':'mm':'ss.FFFFFFFK",
|
||||
Formatting = Formatting.Indented,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 初始化一个新的 Json操作 实例
|
||||
/// </summary>
|
||||
/// <param name="logger">日志器</param>
|
||||
public Json(ILogger<Json> logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将JSON反序列化为指定的.NET类型
|
||||
/// </summary>
|
||||
/// <typeparam name="T">要反序列化的对象的类型</typeparam>
|
||||
/// <param name="value">要反序列化的JSON</param>
|
||||
/// <returns>Json字符串中的反序列化对象, 如果反序列化失败会抛出异常</returns>
|
||||
public T? ToObject<T>(string value)
|
||||
{
|
||||
try
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError("反序列化Json时遇到问题:{ex}", ex);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将JSON反序列化为指定的.NET类型
|
||||
/// 若为null则返回一个新建的实例
|
||||
/// </summary>
|
||||
/// <typeparam name="T">指定的类型</typeparam>
|
||||
/// <param name="value">字符串</param>
|
||||
/// <returns>Json字符串中的反序列化对象, 如果反序列化失败会抛出异常</returns>
|
||||
public T ToObjectOrNew<T>(string value)
|
||||
where T : new()
|
||||
{
|
||||
return ToObject<T>(value) ?? new T();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定的对象序列化为JSON字符串
|
||||
/// </summary>
|
||||
/// <param name="value">要序列化的对象</param>
|
||||
/// <returns>对象的JSON字符串表示形式</returns>
|
||||
public string Stringify(object? value)
|
||||
{
|
||||
return JsonConvert.SerializeObject(value, jsonSerializerSettings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 <see cref="FileMode.Open"/>, <see cref="FileAccess.Read"/> 和 <see cref="FileShare.Read"/> 从文件中读取后转化为实体类
|
||||
/// </summary>
|
||||
/// <typeparam name="T">要反序列化的对象的类型</typeparam>
|
||||
/// <param name="fileName">存放JSON数据的文件路径</param>
|
||||
/// <returns>JSON字符串中的反序列化对象, 如果反序列化失败则抛出异常,若文件不存在则返回 <see langword="null"/></returns>
|
||||
public T? FromFile<T>(string fileName)
|
||||
{
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
// FileShare.Read is important to read some file
|
||||
using (StreamReader sr = new(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)))
|
||||
{
|
||||
return ToObject<T>(sr.ReadToEnd());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 <see cref="FileMode.Open"/>, <see cref="FileAccess.Read"/> 和 <see cref="FileShare.Read"/> 从文件中读取后转化为实体类
|
||||
/// 若为null则返回一个新建的实例
|
||||
/// </summary>
|
||||
/// <typeparam name="T">要反序列化的对象的类型</typeparam>
|
||||
/// <param name="fileName">存放JSON数据的文件路径</param>
|
||||
/// <returns>JSON字符串中的反序列化对象</returns>
|
||||
public T FromFileOrNew<T>(string fileName)
|
||||
where T : new()
|
||||
{
|
||||
return FromFile<T>(fileName) ?? new T();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从文件中读取后转化为实体类
|
||||
/// </summary>
|
||||
/// <typeparam name="T">要反序列化的对象的类型</typeparam>
|
||||
/// <param name="file">存放JSON数据的文件</param>
|
||||
/// <returns>JSON字符串中的反序列化对象</returns>
|
||||
public T? FromFile<T>(FileInfo file)
|
||||
{
|
||||
using (StreamReader sr = file.OpenText())
|
||||
{
|
||||
return ToObject<T>(sr.ReadToEnd());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将对象保存到文件
|
||||
/// </summary>
|
||||
/// <param name="fileName">文件名称</param>
|
||||
/// <param name="value">对象</param>
|
||||
public void ToFile(string fileName, object? value)
|
||||
{
|
||||
File.WriteAllText(fileName, Stringify(value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Core.Json.Converter;
|
||||
|
||||
/// <summary>
|
||||
/// 逗号分隔列表转换器
|
||||
/// </summary>
|
||||
internal class SeparatorCommaInt32EnumerableConverter : JsonConverter<IEnumerable<int>>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override IEnumerable<int> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
string? team = reader.GetString();
|
||||
IEnumerable<int>? ids = team?.Split(',').Select(x => int.Parse(x));
|
||||
return ids ?? Enumerable.Empty<int>();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(Utf8JsonWriter writer, IEnumerable<int> value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(string.Join(',', value));
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 简单的实现了 <see cref="INotifyPropertyChanged"/> 接口
|
||||
/// </summary>
|
||||
public class Observable : INotifyPropertyChanged
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 设置字段的值
|
||||
/// </summary>
|
||||
/// <typeparam name="T">字段类型</typeparam>
|
||||
/// <param name="storage">现有值</param>
|
||||
/// <param name="value">新的值</param>
|
||||
/// <param name="propertyName">属性名称</param>
|
||||
protected void Set<T>([NotNullIfNotNull("value")] ref T storage, T value, [CallerMemberName] string propertyName = default!)
|
||||
{
|
||||
if (Equals(storage, value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
storage = value;
|
||||
OnPropertyChanged(propertyName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发 <see cref="PropertyChanged"/>
|
||||
/// </summary>
|
||||
/// <param name="propertyName">属性名称</param>
|
||||
protected void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ internal static class LocalSetting
|
||||
}
|
||||
else
|
||||
{
|
||||
Set(key, defaultValue);
|
||||
SetValueType(key, defaultValue);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Snap.Hutao.Core.Validation;
|
||||
public static class Must
|
||||
{
|
||||
/// <summary>
|
||||
/// Throws an exception if the specified parameter's value is null.
|
||||
/// Throws an <see cref="ArgumentNullException"/> if the specified parameter's value is null.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the parameter.</typeparam>
|
||||
/// <param name="value">The value of the argument.</param>
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Snap.Hutao.Core;
|
||||
/// 检测 WebView2运行时 是否存在
|
||||
/// 不再使用注册表检查方式
|
||||
/// </summary>
|
||||
internal class WebView2Helper
|
||||
internal static class WebView2Helper
|
||||
{
|
||||
private static bool hasEverDetected = false;
|
||||
private static bool isSupported = false;
|
||||
|
||||
69
src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtensions.cs
Normal file
69
src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtensions.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IEnumerable{T}"/> 扩展
|
||||
/// </summary>
|
||||
public static class EnumerableExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 将二维可枚举对象一维化
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">源类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
/// <returns>扁平的对象</returns>
|
||||
public static IEnumerable<TSource> Flatten<TSource>(this IEnumerable<IEnumerable<TSource>> source)
|
||||
{
|
||||
return source.SelectMany(x => x);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计数
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">源类型</typeparam>
|
||||
/// <typeparam name="TKey">计数的键类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
/// <param name="keySelector">键选择器</param>
|
||||
/// <returns>计数表</returns>
|
||||
public static IEnumerable<KeyValuePair<TKey, int>> CountBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource,TKey> keySelector)
|
||||
where TKey : notnull, IEquatable<TKey>
|
||||
{
|
||||
CounterInt32<TKey> counter = new();
|
||||
foreach (TSource item in source)
|
||||
{
|
||||
counter.Increase(keySelector(item));
|
||||
}
|
||||
|
||||
return counter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个对 <see cref="TItem"/> 类型的计数器
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">待计数的类型</typeparam>
|
||||
private class CounterInt32<TItem> : Dictionary<TItem, int>
|
||||
where TItem : notnull, IEquatable<TItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// 增加计数器
|
||||
/// </summary>
|
||||
/// <param name="item">物品</param>
|
||||
public void Increase(TItem? item)
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
if (!ContainsKey(item))
|
||||
{
|
||||
this[item] = 0;
|
||||
}
|
||||
|
||||
this[item] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
global using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
global using Microsoft;
|
||||
global using Microsoft.Extensions.Logging;
|
||||
global using Snap.Hutao.Core.DependencyInjection;
|
||||
global using Snap.Hutao.Core.DependencyInjection.Annotation;
|
||||
|
||||
@@ -76,11 +76,7 @@ internal static class IocConfiguration
|
||||
|
||||
bool shouldMigrate = false;
|
||||
|
||||
if (!myDocument.FileExists(dbFile))
|
||||
{
|
||||
shouldMigrate = true;
|
||||
}
|
||||
else
|
||||
if (myDocument.FileExists(dbFile))
|
||||
{
|
||||
string? versionString = LocalSetting.Get<string>(SettingKeys.LastAppVersion);
|
||||
if (Version.TryParse(versionString, out Version? lastVersion))
|
||||
@@ -91,6 +87,10 @@ internal static class IocConfiguration
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldMigrate = true;
|
||||
}
|
||||
|
||||
if (shouldMigrate)
|
||||
{
|
||||
@@ -104,6 +104,6 @@ internal static class IocConfiguration
|
||||
LocalSetting.Set(SettingKeys.LastAppVersion, CoreEnvironment.Version.ToString());
|
||||
|
||||
return services
|
||||
.AddPooledDbContextFactory<AppDbContext>(builder => builder.UseSqlite(sqlConnectionString));
|
||||
.AddDbContextPool<AppDbContext>(builder => builder.UseSqlite(sqlConnectionString));
|
||||
}
|
||||
}
|
||||
36
src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs
Normal file
36
src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Snap.Hutao.Model.Entity;
|
||||
|
||||
/// <summary>
|
||||
/// 设置入口
|
||||
/// </summary>
|
||||
[Table("settings")]
|
||||
public class SettingEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的设置入口
|
||||
/// </summary>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">值</param>
|
||||
public SettingEntry(string key, string? value)
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 键
|
||||
/// </summary>
|
||||
[Key]
|
||||
public string Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 值
|
||||
/// </summary>
|
||||
public string? Value { get; set; }
|
||||
}
|
||||
19
src/Snap.Hutao/Snap.Hutao/Model/Entity/User.cs
Normal file
19
src/Snap.Hutao/Snap.Hutao/Model/Entity/User.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Snap.Hutao.Model.Entity;
|
||||
|
||||
/// <summary>
|
||||
/// 用户
|
||||
/// </summary>
|
||||
[Table("users")]
|
||||
public class User
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户的Cookie
|
||||
/// </summary>
|
||||
public string? Cookie { get; set; }
|
||||
}
|
||||
@@ -62,4 +62,4 @@ public interface INavigationService
|
||||
/// <param name="pageType">同步的页面类型</param>
|
||||
/// <returns>是否同步成功</returns>
|
||||
bool SyncSelectedNavigationViewItemWith(Type pageType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Entity;
|
||||
|
||||
namespace Snap.Hutao.Service.Abstraction;
|
||||
|
||||
/// <summary>
|
||||
/// 用户服务
|
||||
/// </summary>
|
||||
public interface IUserService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前用户信息
|
||||
/// </summary>
|
||||
User CurrentUser { get; }
|
||||
}
|
||||
@@ -28,7 +28,7 @@ internal class AnnouncementService : IAnnouncementService
|
||||
/// <inheritdoc/>
|
||||
public async Task<AnnouncementWrapper> GetAnnouncementsAsync(ICommand openAnnouncementUICommand, CancellationToken cancellationToken = default)
|
||||
{
|
||||
AnnouncementWrapper? wrapper = await announcementProvider.GetAnnouncementWrapperAsync(cancellationToken);
|
||||
AnnouncementWrapper? wrapper = await announcementProvider.GetAnnouncementsAsync(cancellationToken);
|
||||
List<AnnouncementContent> contents = await announcementProvider.GetAnnouncementContentsAsync(cancellationToken);
|
||||
|
||||
Dictionary<int, string?> contentMap = contents
|
||||
@@ -42,12 +42,6 @@ internal class AnnouncementService : IAnnouncementService
|
||||
// 将公告内容联入公告列表
|
||||
JoinAnnouncements(openAnnouncementUICommand, contentMap, announcementListWrappers);
|
||||
|
||||
// we only cares about activities
|
||||
if (announcementListWrappers[0].List is List<Announcement> activities)
|
||||
{
|
||||
AdjustActivitiesTime(ref activities);
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
@@ -64,7 +58,6 @@ internal class AnnouncementService : IAnnouncementService
|
||||
{
|
||||
listWrapper.List?.ForEach(item =>
|
||||
{
|
||||
// fix key issue
|
||||
if (contentMap.TryGetValue(item.AnnId, out string? rawContent))
|
||||
{
|
||||
// remove <t/> tag
|
||||
@@ -76,26 +69,4 @@ internal class AnnouncementService : IAnnouncementService
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void AdjustActivitiesTime(ref List<Announcement> activities)
|
||||
{
|
||||
// Match yyyy/MM/dd HH:mm:ss time format
|
||||
Regex dateTimeRegex = new(@"(\d+\/\d+\/\d+\s\d+:\d+:\d+)", RegexOptions.IgnoreCase | RegexOptions.Multiline);
|
||||
activities.ForEach(item =>
|
||||
{
|
||||
Match matched = dateTimeRegex.Match(item.Content ?? string.Empty);
|
||||
if (matched.Success && DateTime.TryParse(matched.Value, out DateTime time))
|
||||
{
|
||||
if (time > item.StartTime && time < item.EndTime)
|
||||
{
|
||||
item.StartTime = time;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
activities = activities
|
||||
.OrderBy(i => i.StartTime)
|
||||
.ThenBy(i => i.EndTime)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
|
||||
<NeutralLanguage>zh-CN</NeutralLanguage>
|
||||
<DefaultLanguage>zh-cn</DefaultLanguage>
|
||||
|
||||
<DefaultLanguage>zh-CN</DefaultLanguage>
|
||||
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
|
||||
<AppxPackageSigningEnabled>True</AppxPackageSigningEnabled>
|
||||
<PackageCertificateThumbprint>F8C2255969BEA4A681CED102771BF807856AEC02</PackageCertificateThumbprint>
|
||||
@@ -53,15 +52,11 @@
|
||||
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2" />
|
||||
<PackageReference Include="Microsoft.AppCenter.Analytics" Version="4.5.1" />
|
||||
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.5.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="17.1.46" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.1.46">
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="17.2.32" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.2.32">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -26,7 +26,6 @@ public sealed partial class MainView : UserControl
|
||||
|
||||
navigationService = Ioc.Default.GetRequiredService<INavigationService>();
|
||||
navigationService.Initialize(NavView, ContentFrame);
|
||||
|
||||
navigationService.Navigate<Page.AnnouncementPage>();
|
||||
navigationService.Navigate<Page.AnnouncementPage>(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||
xmlns:shcc="using:Snap.Hutao.Control.Cancellable"
|
||||
xmlns:shvc="using:Snap.Hutao.View.Converter"
|
||||
xmlns:shvc="using:Snap.Hutao.View.Converter" xmlns:shca="using:Snap.Hutao.Control.Animation"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
@@ -82,18 +82,10 @@
|
||||
</Border.Background>
|
||||
<cwua:Explicit.Animations>
|
||||
<cwua:AnimationSet x:Name="ImageZoomInAnimation">
|
||||
<cwua:ScaleAnimation
|
||||
EasingMode="EaseOut"
|
||||
EasingType="Circle"
|
||||
To="1.1"
|
||||
Duration="0:0:0.5"/>
|
||||
<shca:ImageZoomInAnimation/>
|
||||
</cwua:AnimationSet>
|
||||
<cwua:AnimationSet x:Name="ImageZoomOutAnimation">
|
||||
<cwua:ScaleAnimation
|
||||
EasingMode="EaseOut"
|
||||
EasingType="Circle"
|
||||
To="1"
|
||||
Duration="0:0:0.5"/>
|
||||
<shca:ImageZoomOutAnimation/>
|
||||
</cwua:AnimationSet>
|
||||
</cwua:Explicit.Animations>
|
||||
</Border>
|
||||
@@ -105,12 +97,6 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Bottom"
|
||||
Visibility="{Binding ShouldShowTimeDescription,Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<!--<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
|
||||
<GradientStop Color="#00000000"/>
|
||||
<GradientStop Offset="1" Color="#A0000000"/>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>-->
|
||||
<ProgressBar
|
||||
MinHeight="2"
|
||||
Value="{Binding TimePercent,Mode=OneWay}"
|
||||
|
||||
@@ -11,13 +11,34 @@ internal static class ApiEndpoints
|
||||
/// <summary>
|
||||
/// 公告列表
|
||||
/// </summary>
|
||||
public static readonly string AnnList = $"{Hk4eApi}/common/hk4e_cn/announcement/api/getAnnList?{AnnouncementQuery}";
|
||||
public const string AnnList = $"{Hk4eApi}/common/hk4e_cn/announcement/api/getAnnList?{AnnouncementQuery}";
|
||||
|
||||
/// <summary>
|
||||
/// 公告内容
|
||||
/// </summary>
|
||||
public static readonly string AnnContent = $"{Hk4eApi}/common/hk4e_cn/announcement/api/getAnnContent?{AnnouncementQuery}";
|
||||
public const string AnnContent = $"{Hk4eApi}/common/hk4e_cn/announcement/api/getAnnContent?{AnnouncementQuery}";
|
||||
|
||||
/// <summary>
|
||||
/// 游戏记录主页
|
||||
/// </summary>
|
||||
public const string GameRecordIndex = $"{ApiTakumiRecordApi}/index?role_id={{0}}&server={{1}}";
|
||||
|
||||
/// <summary>
|
||||
/// 深渊信息
|
||||
/// </summary>
|
||||
public const string SpiralAbyss = $"{ApiTakumiRecordApi}/spiralAbyss?schedule_type={{0}}&role_id={{1}}&server={{2}}";
|
||||
|
||||
/// <summary>
|
||||
/// 角色信息
|
||||
/// </summary>
|
||||
public const string Character = $"{ApiTakumiRecordApi}/character";
|
||||
|
||||
public const string UserGameRoles = $"{ApiTakumi}/binding/api/getUserGameRolesByCookie?game_biz=hk4e_cn";
|
||||
|
||||
private const string ApiTakumi = "https://api-takumi.mihoyo.com";
|
||||
private const string ApiTakumiRecord = "https://api-takumi-record.mihoyo.com";
|
||||
private const string ApiTakumiRecordApi = $"{ApiTakumiRecord}/game_record/app/genshin/api";
|
||||
private const string Hk4eApi = "https://hk4e-api.mihoyo.com";
|
||||
|
||||
private const string AnnouncementQuery = "game=hk4e&game_biz=hk4e_cn&lang=zh-cn&bundle_id=hk4e_cn&platform=pc®ion=cn_gf01&level=55&uid=100000000";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Converting;
|
||||
using Snap.Hutao.Core.Json;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.Linq;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.DynamicSecret;
|
||||
|
||||
/// <summary>
|
||||
/// 为MiHoYo接口请求器 <see cref="Requester"/> 提供2代动态密钥
|
||||
/// </summary>
|
||||
internal abstract class DynamicSecretProvider2 : Md5Convert
|
||||
{
|
||||
/// <summary>
|
||||
/// 似乎已经与版本号无关,自2.11.1以来未曾改变salt
|
||||
/// </summary>
|
||||
public const string AppVersion = "2.16.1";
|
||||
|
||||
/// <summary>
|
||||
/// 米游社的盐
|
||||
/// 计算过程:https://gist.github.com/Lightczx/373c5940b36e24b25362728b52dec4fd
|
||||
/// </summary>
|
||||
private static readonly string APISalt = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs";
|
||||
private static readonly Random Random = new();
|
||||
|
||||
/// <summary>
|
||||
/// 创建动态密钥
|
||||
/// </summary>
|
||||
/// <param name="json">json格式化器</param>
|
||||
/// <param name="queryUrl">查询url</param>
|
||||
/// <param name="postBody">请求体</param>
|
||||
/// <returns>密钥</returns>
|
||||
public static string Create(Json json, string queryUrl, object? postBody = null)
|
||||
{
|
||||
// unix timestamp
|
||||
long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
|
||||
// random
|
||||
string r = GetRandomString();
|
||||
|
||||
// body
|
||||
string b = postBody is null ? string.Empty : json.Stringify(postBody);
|
||||
|
||||
// query
|
||||
string q = string.Empty;
|
||||
string? query = new UriBuilder(queryUrl).Query;
|
||||
if (!string.IsNullOrEmpty(query))
|
||||
{
|
||||
q = string.Join("&", query.Split('&').OrderBy(x => x));
|
||||
}
|
||||
|
||||
// check
|
||||
string check = ToHexString($"salt={APISalt}&t={t}&r={r}&b={b}&q={q}");
|
||||
|
||||
return $"{t},{r},{check}";
|
||||
}
|
||||
|
||||
private static string GetRandomString()
|
||||
{
|
||||
// 原汁原味
|
||||
// https://github.com/Azure99/GenshinPlayerQuery/issues/20#issuecomment-913641512
|
||||
// int rand = (int)((Random.Next() / (int.MaxValue + 3D) * 100000D) + 100000D) % 1000000;
|
||||
// if (rand < 100001)
|
||||
// {
|
||||
// rand += 542367;
|
||||
// }
|
||||
int rand = Random.Next(100000, 200000);
|
||||
if (rand == 100000)
|
||||
{
|
||||
rand = 642367;
|
||||
}
|
||||
|
||||
return rand.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Web.Request;
|
||||
using Snap.Hutao.Web.Response;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.DynamicSecret;
|
||||
|
||||
/// <summary>
|
||||
/// 动态密钥扩展
|
||||
/// </summary>
|
||||
internal static class RequesterExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用二代动态密钥执行 GET 操作
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">返回的类类型</typeparam>
|
||||
/// <param name="requester">请求器</param>
|
||||
/// <param name="url">地址</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>响应</returns>
|
||||
public static async Task<Response<TResult>?> GetUsingDS2Async<TResult>(
|
||||
this Requester requester,
|
||||
string url,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
requester.Headers["DS"] = DynamicSecretProvider2.Create(requester.Json, url);
|
||||
|
||||
return await requester
|
||||
.GetAsync<TResult>(url, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用二代动态密钥执行 POST 操作
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">返回的类类型</typeparam>
|
||||
/// <param name="requester">请求器</param>
|
||||
/// <param name="url">地址</param>
|
||||
/// <param name="data">post数据</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>响应</returns>
|
||||
public static async Task<Response<TResult>?> PostUsingDS2Async<TResult>(
|
||||
this Requester requester,
|
||||
string url,
|
||||
object data,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
requester.Headers["DS"] = DynamicSecretProvider2.Create(requester.Json, url, data);
|
||||
|
||||
return await requester
|
||||
.PostAsync<TResult>(url, data, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -100,19 +100,19 @@ public class Announcement : AnnouncementContent
|
||||
/// 类型标签
|
||||
/// </summary>
|
||||
[JsonPropertyName("type_label")]
|
||||
public string? TypeLabel { get; set; }
|
||||
public string TypeLabel { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 标签文本
|
||||
/// </summary>
|
||||
[JsonPropertyName("tag_label")]
|
||||
public string? TagLabel { get; set; }
|
||||
public string TagLabel { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 标签图标
|
||||
/// </summary>
|
||||
[JsonPropertyName("tag_icon")]
|
||||
public string? TagIcon { get; set; }
|
||||
public string TagIcon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 登录提醒
|
||||
@@ -156,13 +156,13 @@ public class Announcement : AnnouncementContent
|
||||
/// 标签开始时间
|
||||
/// </summary>
|
||||
[JsonPropertyName("tag_start_time")]
|
||||
public string? TagStartTime { get; set; }
|
||||
public string TagStartTime { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 标签结束时间
|
||||
/// </summary>
|
||||
[JsonPropertyName("tag_end_time")]
|
||||
public string? TagEndTime { get; set; }
|
||||
public string TagEndTime { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 提醒版本
|
||||
|
||||
@@ -20,30 +20,30 @@ public class AnnouncementContent
|
||||
/// 公告标题
|
||||
/// </summary>
|
||||
[JsonPropertyName("title")]
|
||||
public string? Title { get; set; }
|
||||
public string Title { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 副标题
|
||||
/// </summary>
|
||||
[JsonPropertyName("subtitle")]
|
||||
public string? Subtitle { get; set; }
|
||||
public string Subtitle { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 横幅Url
|
||||
/// </summary>
|
||||
[JsonPropertyName("banner")]
|
||||
public string? Banner { get; set; }
|
||||
public string Banner { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 内容字符串
|
||||
/// 可能包含了一些html格式
|
||||
/// </summary>
|
||||
[JsonPropertyName("content")]
|
||||
public string? Content { get; set; }
|
||||
public string Content { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 语言
|
||||
/// </summary>
|
||||
[JsonPropertyName("lang")]
|
||||
public string? Lang { get; set; }
|
||||
public string Lang { get; set; } = default!;
|
||||
}
|
||||
|
||||
@@ -21,5 +21,5 @@ public class AnnouncementListWrapper : ListWrapper<Announcement>
|
||||
/// 类型标签
|
||||
/// </summary>
|
||||
[JsonPropertyName("type_label")]
|
||||
public string? TypeLabel { get; set; }
|
||||
public string TypeLabel { get; set; } = default!;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ internal class AnnouncementProvider
|
||||
/// 构造一个新的公告提供器
|
||||
/// </summary>
|
||||
/// <param name="requester">请求器</param>
|
||||
/// <param name="gZipRequester">GZip 请求器</param>
|
||||
public AnnouncementProvider(Requester requester)
|
||||
{
|
||||
this.requester = requester;
|
||||
@@ -31,12 +30,13 @@ internal class AnnouncementProvider
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>公告列表</returns>
|
||||
public async Task<AnnouncementWrapper?> GetAnnouncementWrapperAsync(CancellationToken cancellationToken = default)
|
||||
public async Task<AnnouncementWrapper?> GetAnnouncementsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
Response<AnnouncementWrapper>? resp = await requester
|
||||
.Reset()
|
||||
.GetAsync<AnnouncementWrapper>(ApiEndpoints.AnnList, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data;
|
||||
}
|
||||
|
||||
@@ -51,9 +51,10 @@ internal class AnnouncementProvider
|
||||
|
||||
Response<ListWrapper<AnnouncementContent>>? resp = await requester
|
||||
.Reset()
|
||||
.AddHeader("Accept", RequestOptions.Json)
|
||||
.AddHeader(RequestHeaders.Accept, RequestOptions.Json)
|
||||
.GetAsync<ListWrapper<AnnouncementContent>>(ApiEndpoints.AnnContent, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data?.List ?? new();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,11 @@ public class AnnouncementType
|
||||
/// 名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string? Name { get; set; }
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 国际化名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("mi18n_name")]
|
||||
public string? MI18NName { get; set; }
|
||||
public string MI18NName { get; set; } = default!;
|
||||
}
|
||||
@@ -22,7 +22,7 @@ public class AnnouncementWrapper : ListWrapper<AnnouncementListWrapper>
|
||||
/// 类型列表
|
||||
/// </summary>
|
||||
[JsonPropertyName("type_list")]
|
||||
public List<AnnouncementType>? TypeList { get; set; }
|
||||
public List<AnnouncementType> TypeList { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 提醒
|
||||
@@ -46,5 +46,5 @@ public class AnnouncementWrapper : ListWrapper<AnnouncementListWrapper>
|
||||
/// 时间戳
|
||||
/// </summary>
|
||||
[JsonPropertyName("t")]
|
||||
public string? TimeStamp { get; set; }
|
||||
public string TimeStamp { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
|
||||
/// <summary>
|
||||
/// 用户游戏角色
|
||||
/// </summary>
|
||||
public record UserGameRole
|
||||
{
|
||||
/// <summary>
|
||||
/// hk4e_cn for Genshin Impact
|
||||
/// </summary>
|
||||
[JsonPropertyName("game_biz")]
|
||||
public string GameBiz { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 服务器
|
||||
/// </summary>
|
||||
[JsonPropertyName("region")]
|
||||
public string Region { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 游戏Uid
|
||||
/// </summary>
|
||||
[JsonPropertyName("game_uid")]
|
||||
public string GameUid { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 昵称
|
||||
/// </summary>
|
||||
[JsonPropertyName("nickname")]
|
||||
public string Nickname { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 等级
|
||||
/// </summary>
|
||||
[JsonPropertyName("level")]
|
||||
public int Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否选中
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_chosen")]
|
||||
public bool IsChosen { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 地区名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("region_name")]
|
||||
public string RegionName { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 是否为官服
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_official")]
|
||||
public string IsOfficial { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 转化为 <see cref="PlayerUid"/>
|
||||
/// </summary>
|
||||
/// <returns>一个等价的 <see cref="PlayerUid"/> 实例</returns>
|
||||
public PlayerUid AsPlayerUid()
|
||||
{
|
||||
return new PlayerUid(GameUid, Region);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Nickname} | {RegionName} | Lv.{Level}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Web.Request;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
|
||||
/// <summary>
|
||||
/// 用户游戏角色提供器
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Transient)]
|
||||
internal class UserGameRoleProvider
|
||||
{
|
||||
private readonly IUserService userService;
|
||||
private readonly Requester requester;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的用户游戏角色提供器
|
||||
/// </summary>
|
||||
/// <param name="userService">用户服务</param>
|
||||
/// <param name="requester">请求器</param>
|
||||
public UserGameRoleProvider(IUserService userService, Requester requester)
|
||||
{
|
||||
this.userService = userService;
|
||||
this.requester = requester;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户角色信息
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>用户角色信息</returns>
|
||||
public async Task<List<UserGameRole>> GetUserGameRolesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
Response<ListWrapper<UserGameRole>>? resp = await requester
|
||||
.Reset()
|
||||
.SetAcceptJson()
|
||||
.SetCommonUA()
|
||||
.SetRequestWithHyperion()
|
||||
.SetUser(userService.CurrentUser)
|
||||
.GetAsync<ListWrapper<UserGameRole>>(ApiEndpoints.UserGameRoles, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
return resp?.Data?.List ?? new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
|
||||
|
||||
/// <summary>
|
||||
/// 角色的基础信息
|
||||
/// </summary>
|
||||
public class Avatar
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图片Url
|
||||
/// </summary>
|
||||
[JsonPropertyName("image")]
|
||||
public string Image { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 元素英文名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("element")]
|
||||
public string Element { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 好感度
|
||||
/// </summary>
|
||||
[JsonPropertyName("fetter")]
|
||||
public int Fetter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 等级
|
||||
/// </summary>
|
||||
[JsonPropertyName("level")]
|
||||
public int Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 稀有度
|
||||
/// </summary>
|
||||
[JsonPropertyName("rarity")]
|
||||
public Rarity Rarity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 激活的命座数
|
||||
/// </summary>
|
||||
[JsonPropertyName("actived_constellation_num")]
|
||||
public int ActivedConstellationNum { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
|
||||
|
||||
/// <summary>
|
||||
/// 角色详细详细
|
||||
/// </summary>
|
||||
public class Character : Avatar
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色图片
|
||||
/// </summary>
|
||||
[Obsolete("we don't want to use this ugly pic here")]
|
||||
[JsonPropertyName("image")]
|
||||
public new string Image { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
[JsonPropertyName("icon")]
|
||||
public string Icon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 武器
|
||||
/// </summary>
|
||||
[JsonPropertyName("weapon")]
|
||||
public Weapon Weapon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 圣遗物
|
||||
/// </summary>
|
||||
[JsonPropertyName("reliquaries")]
|
||||
public List<Reliquary> Reliquaries { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 命座
|
||||
/// </summary>
|
||||
[JsonPropertyName("constellations")]
|
||||
public List<Constellation> Constellations { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 时装
|
||||
/// </summary>
|
||||
[JsonPropertyName("costumes")]
|
||||
public List<Costume> Costumes { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
|
||||
|
||||
/// <summary>
|
||||
/// 包装详细角色信息列表
|
||||
/// </summary>
|
||||
public class CharacterWrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色列表
|
||||
/// </summary>
|
||||
[JsonPropertyName("avatars")]
|
||||
public List<Character> Avatars { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
|
||||
|
||||
/// <summary>
|
||||
/// 命座
|
||||
/// </summary>
|
||||
public class Constellation
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
[JsonPropertyName("icon")]
|
||||
public string Icon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 效果描述
|
||||
/// </summary>
|
||||
[JsonPropertyName("effect")]
|
||||
public string Effect { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 是否激活
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_actived")]
|
||||
public bool IsActived { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 位置
|
||||
/// </summary>
|
||||
[JsonPropertyName("pos")]
|
||||
public int Position { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
|
||||
|
||||
/// <summary>
|
||||
/// 角色装扮
|
||||
/// </summary>
|
||||
public class Costume
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
[Obsolete("不应使用此处的图标")]
|
||||
[JsonPropertyName("icon")]
|
||||
public string Icon { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
|
||||
|
||||
/// <summary>
|
||||
/// 稀有度
|
||||
/// </summary>
|
||||
public enum Rarity
|
||||
{
|
||||
/// <summary>
|
||||
/// 一星
|
||||
/// </summary>
|
||||
Gray = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 二星
|
||||
/// </summary>
|
||||
Green = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 三星
|
||||
/// </summary>
|
||||
Blue = 3,
|
||||
|
||||
/// <summary>
|
||||
/// 四星
|
||||
/// </summary>
|
||||
Purple = 4,
|
||||
|
||||
/// <summary>
|
||||
/// 五星
|
||||
/// </summary>
|
||||
Orange = 5,
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
|
||||
|
||||
/// <summary>
|
||||
/// 圣遗物
|
||||
/// </summary>
|
||||
public class Reliquary
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
[JsonPropertyName("icon")]
|
||||
public string Icon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 部位
|
||||
/// </summary>
|
||||
[JsonPropertyName("pos")]
|
||||
public ReliquaryPosition Position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 稀有度
|
||||
/// </summary>
|
||||
[JsonPropertyName("rarity")]
|
||||
public Rarity Rarity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 等级
|
||||
/// </summary>
|
||||
[JsonPropertyName("level")]
|
||||
public int Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 圣遗物套装
|
||||
/// </summary>
|
||||
[JsonPropertyName("set")]
|
||||
public ReliquarySet ReliquarySet { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 部位名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("pos_name")]
|
||||
public string PositionName { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
|
||||
|
||||
/// <summary>
|
||||
/// 圣遗物套装效果
|
||||
/// </summary>
|
||||
public class ReliquaryAffix
|
||||
{
|
||||
/// <summary>
|
||||
/// 激活个数
|
||||
/// </summary>
|
||||
[JsonPropertyName("activation_number")]
|
||||
public int ActivationNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 效果描述
|
||||
/// </summary>
|
||||
[JsonPropertyName("effect")]
|
||||
public string? Effect { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
|
||||
|
||||
/// <summary>
|
||||
/// 圣遗物部位
|
||||
/// </summary>
|
||||
public enum ReliquaryPosition
|
||||
{
|
||||
/// <summary>
|
||||
/// 生之花
|
||||
/// </summary>
|
||||
FlowerOfLife = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 死之羽
|
||||
/// </summary>
|
||||
PlumeOfDeath = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 时之沙
|
||||
/// </summary>
|
||||
SandOfEon = 3,
|
||||
|
||||
/// <summary>
|
||||
/// 空之杯
|
||||
/// </summary>
|
||||
GobletOfEonothem = 4,
|
||||
|
||||
/// <summary>
|
||||
/// 理之冠
|
||||
/// </summary>
|
||||
CircletOfLogos = 5,
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
|
||||
|
||||
/// <summary>
|
||||
/// 圣遗物套装信息
|
||||
/// </summary>
|
||||
public class ReliquarySet
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 套装名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 套装效果
|
||||
/// </summary>
|
||||
[JsonPropertyName("affixes")]
|
||||
public List<ReliquaryAffix> Affixes { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
|
||||
|
||||
/// <summary>
|
||||
/// 武器信息
|
||||
/// </summary>
|
||||
public class Weapon
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")] public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")] public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
[JsonPropertyName("icon")] public string Icon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 类型
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")] public WeaponType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 稀有度
|
||||
/// </summary>
|
||||
[JsonPropertyName("rarity")] public Rarity Rarity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 等级
|
||||
/// </summary>
|
||||
[JsonPropertyName("level")] public int Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 突破等级
|
||||
/// </summary>
|
||||
[JsonPropertyName("promote_level")] public int PromoteLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 类型名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("type_name")] public string TypeName { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 武器介绍
|
||||
/// </summary>
|
||||
[JsonPropertyName("desc")] public string Description { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 精炼等级
|
||||
/// </summary>
|
||||
[JsonPropertyName("affix_level")] public int AffixLevel { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
|
||||
|
||||
/// <summary>
|
||||
/// 武器类型
|
||||
/// </summary>
|
||||
public enum WeaponType
|
||||
{
|
||||
/// <summary>
|
||||
/// 单手剑
|
||||
/// </summary>
|
||||
Sword = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 法器
|
||||
/// </summary>
|
||||
Catalyst = 10,
|
||||
|
||||
/// <summary>
|
||||
/// 双手剑
|
||||
/// </summary>
|
||||
Claymore = 11,
|
||||
|
||||
/// <summary>
|
||||
/// 弓
|
||||
/// </summary>
|
||||
Bow = 12,
|
||||
|
||||
/// <summary>
|
||||
/// 长柄武器
|
||||
/// </summary>
|
||||
Polearm = 13,
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
|
||||
using Snap.Hutao.Web.Request;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
|
||||
|
||||
/// <summary>
|
||||
/// 游戏记录提供器
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Transient)]
|
||||
internal class GameRecordProvider
|
||||
{
|
||||
private readonly IUserService userService;
|
||||
private readonly Requester requester;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的游戏记录提供器
|
||||
/// </summary>
|
||||
/// <param name="userService">用户服务</param>
|
||||
/// <param name="requester">请求器</param>
|
||||
public GameRecordProvider(IUserService userService, Requester requester)
|
||||
{
|
||||
this.userService = userService;
|
||||
this.requester = requester;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取玩家基础信息
|
||||
/// </summary>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>玩家的基础信息</returns>
|
||||
public async Task<PlayerInfo?> GetPlayerInfoAsync(PlayerUid uid, CancellationToken token)
|
||||
{
|
||||
string url = string.Format(ApiEndpoints.GameRecordIndex, uid.Value, uid.Region);
|
||||
|
||||
Response<PlayerInfo>? resp = await PrepareRequester()
|
||||
.GetUsingDS2Async<PlayerInfo>(url, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取玩家深渊信息
|
||||
/// </summary>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="schedule">1:当期,2:上期</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>深渊信息</returns>
|
||||
public async Task<SpiralAbyss.SpiralAbyss?> GetSpiralAbyssAsync(PlayerUid uid, SpiralAbyssSchedule schedule, CancellationToken token = default)
|
||||
{
|
||||
string url = string.Format(ApiEndpoints.SpiralAbyss, (int)schedule, uid.Value, uid.Region);
|
||||
|
||||
Response<SpiralAbyss.SpiralAbyss>? resp = await PrepareRequester()
|
||||
.GetUsingDS2Async<SpiralAbyss.SpiralAbyss>(url, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取玩家角色详细信息
|
||||
/// </summary>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="playerInfo">玩家的基础信息</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>角色列表</returns>
|
||||
public async Task<List<Character>> GetCharactersAsync(PlayerUid uid, PlayerInfo playerInfo, CancellationToken cancellationToken = default)
|
||||
{
|
||||
CharacterData data = new(uid, playerInfo.Avatars.Select(x => x.Id));
|
||||
|
||||
Response<CharacterWrapper>? resp = await PrepareRequester()
|
||||
.PostUsingDS2Async<CharacterWrapper>(ApiEndpoints.Character, data, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data?.Avatars ?? new();
|
||||
}
|
||||
|
||||
private Requester PrepareRequester()
|
||||
{
|
||||
return requester
|
||||
.Reset()
|
||||
.SetAcceptJson()
|
||||
.AddHeader(RequestHeaders.AppVersion, DynamicSecretProvider2.AppVersion)
|
||||
.SetCommonUA()
|
||||
.AddHeader(RequestHeaders.ClientType, RequestOptions.DefaultClientType)
|
||||
.SetRequestWithHyperion()
|
||||
.SetUser(userService.CurrentUser);
|
||||
}
|
||||
|
||||
private class CharacterData
|
||||
{
|
||||
public CharacterData(PlayerUid uid, IEnumerable<int> characterIds)
|
||||
{
|
||||
CharacterIds = characterIds;
|
||||
Uid = uid.Value;
|
||||
Server = uid.Region;
|
||||
}
|
||||
|
||||
[JsonPropertyName("character_ids")]
|
||||
public IEnumerable<int> CharacterIds { get; }
|
||||
|
||||
[JsonPropertyName("role_id")]
|
||||
public string Uid { get; }
|
||||
|
||||
[JsonPropertyName("server")]
|
||||
public string Server { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
|
||||
|
||||
/// <summary>
|
||||
/// 家园信息
|
||||
/// </summary>
|
||||
public class Home
|
||||
{
|
||||
/// <summary>
|
||||
/// 等级
|
||||
/// </summary>
|
||||
[JsonPropertyName("level")]
|
||||
public int Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 历史访客数
|
||||
/// </summary>
|
||||
[JsonPropertyName("visit_num")]
|
||||
public int VisitNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最高洞天仙力
|
||||
/// </summary>
|
||||
[JsonPropertyName("comfort_num")]
|
||||
public int ComfortNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得摆设数
|
||||
/// </summary>
|
||||
[JsonPropertyName("item_num")]
|
||||
public int ItemNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 洞天名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
[JsonPropertyName("icon")]
|
||||
public string Icon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 洞天等级名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("comfort_level_name")]
|
||||
public string ComfortLevelName { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 洞天等级图标
|
||||
/// </summary>
|
||||
[JsonPropertyName("comfort_level_icon")]
|
||||
public string ComfortLevelIcon { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
|
||||
|
||||
/// <summary>
|
||||
/// 供奉信息
|
||||
/// </summary>
|
||||
public class Offering
|
||||
{
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 等级
|
||||
/// </summary>
|
||||
[JsonPropertyName("level")]
|
||||
public string Level { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
[JsonPropertyName("icon")]
|
||||
public string Icon { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Web.Request;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
|
||||
|
||||
/// <summary>
|
||||
/// 玩家信息
|
||||
/// </summary>
|
||||
public class PlayerInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 持有的角色的信息
|
||||
/// </summary>
|
||||
[JsonPropertyName("avatars")]
|
||||
public List<Avatar.Avatar> Avatars { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 玩家的基本信息
|
||||
/// </summary>
|
||||
[JsonPropertyName("stats")]
|
||||
public PlayerStats PlayerStat { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 世界探索
|
||||
/// </summary>
|
||||
[JsonPropertyName("world_explorations")]
|
||||
public List<WorldExploration> WorldExplorations { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 洞天
|
||||
/// </summary>
|
||||
[JsonPropertyName("homes")]
|
||||
public List<Home> Homes { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
|
||||
|
||||
/// <summary>
|
||||
/// 玩家统计数据
|
||||
/// </summary>
|
||||
public class PlayerStats
|
||||
{
|
||||
/// <summary>
|
||||
/// 活跃天数
|
||||
/// </summary>
|
||||
[JsonPropertyName("active_day_number")]
|
||||
public int ActiveDayNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 成就完成数
|
||||
/// </summary>
|
||||
[JsonPropertyName("achievement_number")]
|
||||
public int AchievementNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 风神瞳个数
|
||||
/// </summary>
|
||||
[JsonPropertyName("anemoculus_number")]
|
||||
public int AnemoculusNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 岩神瞳个数
|
||||
/// </summary>
|
||||
[JsonPropertyName("geoculus_number")]
|
||||
public int GeoculusNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 雷神瞳个数
|
||||
/// </summary>
|
||||
[JsonPropertyName("electroculus_number")]
|
||||
public int ElectroculusNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色个数
|
||||
/// </summary>
|
||||
[JsonPropertyName("avatar_number")]
|
||||
public int AvatarNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 传送锚点个数
|
||||
/// </summary>
|
||||
[JsonPropertyName("way_point_number")]
|
||||
public int WayPointNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 秘境个数
|
||||
/// </summary>
|
||||
[JsonPropertyName("domain_number")]
|
||||
public int DomainNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 深渊层数
|
||||
/// </summary>
|
||||
[JsonPropertyName("spiral_abyss")]
|
||||
public string SpiralAbyss { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 华丽宝箱个数
|
||||
/// </summary>
|
||||
[JsonPropertyName("luxurious_chest_number")]
|
||||
public int LuxuriousChestNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 珍贵宝箱个数
|
||||
/// </summary>
|
||||
[JsonPropertyName("precious_chest_number")]
|
||||
public int PreciousChestNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 精致宝箱个数
|
||||
/// </summary>
|
||||
[JsonPropertyName("exquisite_chest_number")]
|
||||
public int ExquisiteChestNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 普通宝箱个数
|
||||
/// </summary>
|
||||
[JsonPropertyName("common_chest_number")]
|
||||
public int CommonChestNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 奇馈宝箱
|
||||
/// </summary>
|
||||
[JsonPropertyName("magic_chest_number")]
|
||||
public int MagicChestNumber { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.SpiralAbyss;
|
||||
|
||||
/// <summary>
|
||||
/// 仅包含头像的角色信息
|
||||
/// </summary>
|
||||
public class Avatar
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
[JsonPropertyName("icon")]
|
||||
public string Icon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 等级
|
||||
/// </summary>
|
||||
[JsonPropertyName("level")]
|
||||
public int Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 稀有度
|
||||
/// </summary>
|
||||
[JsonPropertyName("rarity")]
|
||||
public int Rarity { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.SpiralAbyss;
|
||||
|
||||
/// <summary>
|
||||
/// 表示一次战斗
|
||||
/// </summary>
|
||||
public class Battle
|
||||
{
|
||||
/// <summary>
|
||||
/// 索引
|
||||
/// </summary>
|
||||
[JsonPropertyName("index")]
|
||||
public int Index { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 时间戳
|
||||
/// </summary>
|
||||
[JsonPropertyName("timestamp")]
|
||||
public string Timestamp { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 参战角色
|
||||
/// </summary>
|
||||
[JsonPropertyName("avatars")]
|
||||
public List<Avatar> Avatars { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 时间
|
||||
/// </summary>
|
||||
public DateTime Time
|
||||
{
|
||||
get
|
||||
{
|
||||
return DateTimeOffset.FromUnixTimeSeconds(int.Parse(Timestamp)).LocalDateTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.SpiralAbyss;
|
||||
|
||||
/// <summary>
|
||||
/// 层
|
||||
/// </summary>
|
||||
public class Floor
|
||||
{
|
||||
/// <summary>
|
||||
/// 层号
|
||||
/// </summary>
|
||||
[JsonPropertyName("index")]
|
||||
public int Index { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
[JsonPropertyName("icon")]
|
||||
public string Icon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 是否解锁
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_unlock")]
|
||||
public string IsUnlock { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 结束时间
|
||||
/// </summary>
|
||||
[JsonPropertyName("settle_time")]
|
||||
public string SettleTime { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 星数
|
||||
/// </summary>
|
||||
[JsonPropertyName("star")]
|
||||
public int Star { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最大星数
|
||||
/// </summary>
|
||||
[JsonPropertyName("max_star")]
|
||||
public int MaxStar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 层信息
|
||||
/// </summary>
|
||||
[JsonPropertyName("levels")]
|
||||
public List<Level> Levels { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.SpiralAbyss;
|
||||
|
||||
/// <summary>
|
||||
/// 间
|
||||
/// </summary>
|
||||
public class Level
|
||||
{
|
||||
/// <summary>
|
||||
/// 索引
|
||||
/// </summary>
|
||||
[JsonPropertyName("index")]
|
||||
public int Index { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 星数
|
||||
/// </summary>
|
||||
[JsonPropertyName("star")]
|
||||
public int Star { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最大星数
|
||||
/// </summary>
|
||||
[JsonPropertyName("max_star")]
|
||||
public int MaxStar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 上下半
|
||||
/// </summary>
|
||||
[JsonPropertyName("battles")]
|
||||
public List<Battle> Battles { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.SpiralAbyss;
|
||||
|
||||
/// <summary>
|
||||
/// 角色数值排行信息
|
||||
/// </summary>
|
||||
public class Rank
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色Id
|
||||
/// </summary>
|
||||
[JsonPropertyName("avatar_id")]
|
||||
public int AvatarId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色图标
|
||||
/// </summary>
|
||||
[JsonPropertyName("avatar_icon")]
|
||||
public string AvatarIcon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 值
|
||||
/// </summary>
|
||||
[JsonPropertyName("value")]
|
||||
public int Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 稀有度
|
||||
/// </summary>
|
||||
[JsonPropertyName("rarity")]
|
||||
public int Rarity { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.SpiralAbyss;
|
||||
|
||||
/// <summary>
|
||||
/// 深境螺旋信息
|
||||
/// </summary>
|
||||
public class SpiralAbyss
|
||||
{
|
||||
/// <summary>
|
||||
/// 计划Id
|
||||
/// </summary>
|
||||
[JsonPropertyName("schedule_id")]
|
||||
public int ScheduleId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 开始时间
|
||||
/// </summary>
|
||||
[JsonPropertyName("start_time")]
|
||||
public string StartTime { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 结束时间
|
||||
/// </summary>
|
||||
[JsonPropertyName("end_time")]
|
||||
public string EndTime { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 战斗次数
|
||||
/// </summary>
|
||||
[JsonPropertyName("total_battle_times")]
|
||||
public int TotalBattleTimes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 胜利次数
|
||||
/// </summary>
|
||||
[JsonPropertyName("total_win_times")]
|
||||
public int TotalWinTimes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最深抵达
|
||||
/// </summary>
|
||||
[JsonPropertyName("max_floor")]
|
||||
public string MaxFloor { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 出战次数
|
||||
/// </summary>
|
||||
[JsonPropertyName("reveal_rank")]
|
||||
public List<Rank> RevealRank { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 击破次数
|
||||
/// </summary>
|
||||
[JsonPropertyName("defeat_rank")]
|
||||
public List<Rank> DefeatRank { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 最强一击
|
||||
/// </summary>
|
||||
[JsonPropertyName("damage_rank")]
|
||||
public List<Rank> DamageRank { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 承受伤害
|
||||
/// </summary>
|
||||
[JsonPropertyName("take_damage_rank")]
|
||||
public List<Rank> TakeDamageRank { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 元素战技
|
||||
/// </summary>
|
||||
[JsonPropertyName("normal_skill_rank")]
|
||||
public List<Rank> NormalSkillRank { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 元素爆发
|
||||
/// </summary>
|
||||
[JsonPropertyName("energy_skill_rank")]
|
||||
public List<Rank> EnergySkillRank { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 层信息
|
||||
/// </summary>
|
||||
[JsonPropertyName("floors")]
|
||||
public List<Floor> Floors { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 共获得渊星
|
||||
/// </summary>
|
||||
[JsonPropertyName("total_star")]
|
||||
public int TotalStar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否解锁
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_unlock")]
|
||||
public bool IsUnlock { get; set; }
|
||||
}
|
||||
@@ -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 SpiralAbyssSchedule
|
||||
{
|
||||
/// <summary>
|
||||
/// 当期
|
||||
/// </summary>
|
||||
Current = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 上期
|
||||
/// </summary>
|
||||
Last = 2,
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
|
||||
|
||||
/// <summary>
|
||||
/// 世界探索
|
||||
/// </summary>
|
||||
public class WorldExploration
|
||||
{
|
||||
/// <summary>
|
||||
/// 等级
|
||||
/// </summary>
|
||||
[JsonPropertyName("level")]
|
||||
public int Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 探索度
|
||||
/// Maxmium is 1000
|
||||
/// </summary>
|
||||
[JsonPropertyName("exploration_percentage")]
|
||||
public int ExplorationPercentage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
[JsonPropertyName("icon")]
|
||||
public string Icon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 类型
|
||||
/// Offering
|
||||
/// Reputation
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 供奉进度
|
||||
/// </summary>
|
||||
[JsonPropertyName("offerings")]
|
||||
public List<Offering> Offerings { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// ID
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 父ID 当无链接的父对象时为0
|
||||
/// </summary>
|
||||
[JsonPropertyName("parent_id")]
|
||||
public int ParentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 地图链接
|
||||
/// </summary>
|
||||
[JsonPropertyName("map_url")]
|
||||
public string MapUrl { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 攻略链接 无攻略时为 <see cref="string.Empty"/>
|
||||
/// </summary>
|
||||
[JsonPropertyName("strategy_url")]
|
||||
public string StrategyUrl { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 背景图片
|
||||
/// </summary>
|
||||
[JsonPropertyName("background_image")]
|
||||
public string BackgroundImage { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 反色图标
|
||||
/// </summary>
|
||||
[JsonPropertyName("inner_icon")]
|
||||
public string InnerIcon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 背景图片
|
||||
/// </summary>
|
||||
[JsonPropertyName("cover")]
|
||||
public string 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";
|
||||
}
|
||||
}
|
||||
59
src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/PlayerUid.cs
Normal file
59
src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/PlayerUid.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi;
|
||||
|
||||
/// <summary>
|
||||
/// 玩家 Uid
|
||||
/// </summary>
|
||||
public struct PlayerUid
|
||||
{
|
||||
private string? region = null;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的玩家 Uid 结构
|
||||
/// </summary>
|
||||
/// <param name="value">uid</param>
|
||||
/// <param name="region">服务器,当提供该参数时会无条件信任</param>
|
||||
public PlayerUid(string value, string? region = default)
|
||||
{
|
||||
Requires.Argument(value.Length == 9, nameof(value), "uid应为9位数字");
|
||||
Value = value;
|
||||
|
||||
if (region != null)
|
||||
{
|
||||
this.region = region;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UID 的实际值
|
||||
/// </summary>
|
||||
public string Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 地区代码
|
||||
/// </summary>
|
||||
public string Region
|
||||
{
|
||||
get
|
||||
{
|
||||
region ??= EvaluateRegion(Value[0]);
|
||||
return region;
|
||||
}
|
||||
}
|
||||
|
||||
private static string EvaluateRegion(char first)
|
||||
{
|
||||
return first switch
|
||||
{
|
||||
>= '1' and <= '4' => "cn_gf01", // 国服
|
||||
'5' => "cn_qd01", // 渠道
|
||||
'6' => "os_usa", // 美服
|
||||
'7' => "os_euro", // 欧服
|
||||
'8' => "os_asia", // 亚服
|
||||
'9' => "os_cht", // 台服
|
||||
_ => Must.NeverHappen<string>(),
|
||||
};
|
||||
}
|
||||
}
|
||||
341
src/Snap.Hutao/Snap.Hutao/Web/Hutao/HutaoAPIProvider.cs
Normal file
341
src/Snap.Hutao/Snap.Hutao/Web/Hutao/HutaoAPIProvider.cs
Normal file
@@ -0,0 +1,341 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.SpiralAbyss;
|
||||
using Snap.Hutao.Web.Hutao.Model;
|
||||
using Snap.Hutao.Web.Hutao.Model.Post;
|
||||
using Snap.Hutao.Web.Request;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao;
|
||||
|
||||
/// <summary>
|
||||
/// 胡桃API提供器
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Transient)]
|
||||
internal class HutaoAPIProvider : IAsyncInitializable
|
||||
{
|
||||
private const string AuthAPIHost = "https://auth.snapgenshin.com";
|
||||
private const string HutaoAPI = "https://hutao-api.snapgenshin.com";
|
||||
private const string PostContentType = "text/json";
|
||||
|
||||
private readonly Requester requester;
|
||||
private readonly AuthRequester authRequester;
|
||||
private readonly GameRecordProvider gameRecordProvider;
|
||||
private readonly UserGameRoleProvider userGameRoleProvider;
|
||||
|
||||
private bool isInitialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的胡桃API提供器
|
||||
/// </summary>
|
||||
/// <param name="requester">请求器</param>
|
||||
/// <param name="authRequester">支持验证的请求器</param>
|
||||
/// <param name="gameRecordProvider">游戏记录提供器</param>
|
||||
/// <param name="userGameRoleProvider">用户游戏角色提供器</param>
|
||||
public HutaoAPIProvider(
|
||||
Requester requester,
|
||||
AuthRequester authRequester,
|
||||
GameRecordProvider gameRecordProvider,
|
||||
UserGameRoleProvider userGameRoleProvider)
|
||||
{
|
||||
this.requester = requester;
|
||||
this.authRequester = authRequester;
|
||||
this.gameRecordProvider = gameRecordProvider;
|
||||
this.userGameRoleProvider = userGameRoleProvider;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsInitialized { get => isInitialized; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<bool> InitializeAsync(CancellationToken token = default)
|
||||
{
|
||||
Auth auth = new(
|
||||
"08d9e212-0cb3-4d71-8ed7-003606da7b20",
|
||||
"7ueWgZGn53dDhrm8L5ZRw+YWfOeSWtgQmJWquRgaygw=");
|
||||
|
||||
Response<Token>? resp = await requester
|
||||
.Reset()
|
||||
.PostAsync<Token>($"{AuthAPIHost}/Auth/Login", auth, PostContentType, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
authRequester.AuthToken = Must.NotNull(resp?.Data?.AccessToken!);
|
||||
isInitialized = true;
|
||||
|
||||
return isInitialized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查对应的uid当前是否上传了数据
|
||||
/// </summary>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>当前是否上传了数据</returns>
|
||||
public async Task<bool> CheckPeriodRecordUploadedAsync(string uid, CancellationToken token = default)
|
||||
{
|
||||
Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
|
||||
|
||||
Response<UploadStatus>? resp = await authRequester
|
||||
.GetAsync<UploadStatus>($"{HutaoAPI}/Record/CheckRecord/{uid}", token)
|
||||
.ConfigureAwait(false);
|
||||
return resp?.Data is not null && resp.Data.PeriodUploaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取总览数据
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>总览信息</returns>
|
||||
public async Task<Overview?> GetOverviewAsync(CancellationToken token = default)
|
||||
{
|
||||
Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
|
||||
|
||||
Response<Overview>? resp = await authRequester
|
||||
.GetAsync<Overview>($"{HutaoAPI}/Statistics/Overview", token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取角色出场率
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色出场率</returns>
|
||||
public async Task<IEnumerable<AvatarParticipation>> GetAvatarParticipationsAsync(CancellationToken token = default)
|
||||
{
|
||||
Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
|
||||
|
||||
Response<IEnumerable<AvatarParticipation>>? resp = await authRequester
|
||||
.GetAsync<IEnumerable<AvatarParticipation>>($"{HutaoAPI}/Statistics/AvatarParticipation", token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data ?? Enumerable.Empty<AvatarParticipation>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取角色圣遗物搭配
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色圣遗物搭配</returns>
|
||||
public async Task<IEnumerable<AvatarReliquaryUsage>> GetAvatarReliquaryUsagesAsync(CancellationToken token = default)
|
||||
{
|
||||
Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
|
||||
|
||||
Response<IEnumerable<AvatarReliquaryUsage>>? resp = await authRequester
|
||||
.GetAsync<IEnumerable<AvatarReliquaryUsage>>($"{HutaoAPI}/Statistics/AvatarReliquaryUsage", token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data ?? Enumerable.Empty<AvatarReliquaryUsage>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取角色搭配数据
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色搭配数据</returns>
|
||||
public async Task<IEnumerable<TeamCollocation>> GetTeamCollocationsAsync(CancellationToken token = default)
|
||||
{
|
||||
Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
|
||||
|
||||
Response<IEnumerable<TeamCollocation>>? resp = await authRequester
|
||||
.GetAsync<IEnumerable<TeamCollocation>>($"{HutaoAPI}/Statistics/TeamCollocation", token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data ?? Enumerable.Empty<TeamCollocation>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取角色武器搭配数据
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色武器搭配数据</returns>
|
||||
public async Task<IEnumerable<AvatarWeaponUsage>> GetAvatarWeaponUsagesAsync(CancellationToken token = default)
|
||||
{
|
||||
Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
|
||||
|
||||
Response<IEnumerable<AvatarWeaponUsage>>? resp = await authRequester
|
||||
.GetAsync<IEnumerable<AvatarWeaponUsage>>($"{HutaoAPI}/Statistics/AvatarWeaponUsage", token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data ?? Enumerable.Empty<AvatarWeaponUsage>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取角色命座信息
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色图片列表</returns>
|
||||
public async Task<IEnumerable<AvatarConstellationNum>> GetAvatarConstellationsAsync(CancellationToken token = default)
|
||||
{
|
||||
Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
|
||||
|
||||
Response<IEnumerable<AvatarConstellationNum>>? resp = await authRequester
|
||||
.GetAsync<IEnumerable<AvatarConstellationNum>>($"{HutaoAPI}/Statistics/Constellation", token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data ?? Enumerable.Empty<AvatarConstellationNum>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取队伍出场次数
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>队伍出场列表</returns>
|
||||
public async Task<IEnumerable<TeamCombination>> GetTeamCombinationsAsync(CancellationToken token = default)
|
||||
{
|
||||
Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
|
||||
|
||||
Response<IEnumerable<TeamCombination>>? resp = await authRequester
|
||||
.GetAsync<IEnumerable<TeamCombination>>($"{HutaoAPI}/Statistics/TeamCombination", token)
|
||||
.ConfigureAwait(false);
|
||||
return resp?.Data ?? Enumerable.Empty<TeamCombination>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取角色图片列表
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色图片列表</returns>
|
||||
public async Task<IEnumerable<Item>> GetAvatarMapAsync(CancellationToken token = default)
|
||||
{
|
||||
Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
|
||||
|
||||
Response<IEnumerable<Item>>? resp = await authRequester
|
||||
.GetAsync<IEnumerable<Item>>($"{HutaoAPI}/GenshinItem/Avatars", token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data ?? Enumerable.Empty<Item>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取武器图片列表
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>武器图片列表</returns>
|
||||
public async Task<IEnumerable<Item>> GetWeaponMapAsync(CancellationToken token = default)
|
||||
{
|
||||
Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
|
||||
|
||||
Response<IEnumerable<Item>>? resp = await authRequester
|
||||
.GetAsync<IEnumerable<Item>>($"{HutaoAPI}/GenshinItem/Weapons", token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data ?? Enumerable.Empty<Item>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取圣遗物图片列表
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>圣遗物图片列表</returns>
|
||||
public async Task<IEnumerable<Item>> GetReliquaryMapAsync(CancellationToken token = default)
|
||||
{
|
||||
Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
|
||||
|
||||
Response<IEnumerable<Item>>? resp = await authRequester
|
||||
.GetAsync<IEnumerable<Item>>($"{HutaoAPI}/GenshinItem/Reliquaries", token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data ?? Enumerable.Empty<Item>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取所有记录并上传到数据库
|
||||
/// </summary>
|
||||
/// <param name="confirmAsyncFunc">异步确认委托</param>
|
||||
/// <param name="resultAsyncFunc">结果确认委托</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>任务</returns>
|
||||
public async Task GetAllRecordsAndUploadAsync(Func<PlayerRecord, Task<bool>> confirmAsyncFunc, Func<Response.Response, Task> resultAsyncFunc, CancellationToken token = default)
|
||||
{
|
||||
List<UserGameRole> userGameRoles = await userGameRoleProvider
|
||||
.GetUserGameRolesAsync(token);
|
||||
|
||||
foreach (UserGameRole role in userGameRoles)
|
||||
{
|
||||
PlayerInfo? playerInfo = await gameRecordProvider
|
||||
.GetPlayerInfoAsync(role.AsPlayerUid(), token);
|
||||
Must.NotNull(playerInfo!);
|
||||
|
||||
List<Character> characters = await gameRecordProvider
|
||||
.GetCharactersAsync(role.AsPlayerUid(), playerInfo, token);
|
||||
|
||||
SpiralAbyss? spiralAbyssInfo = await gameRecordProvider
|
||||
.GetSpiralAbyssAsync(role.AsPlayerUid(), SpiralAbyssSchedule.Current, token);
|
||||
Must.NotNull(spiralAbyssInfo!);
|
||||
|
||||
PlayerRecord playerRecord = PlayerRecord.Create(role.GameUid, characters, spiralAbyssInfo);
|
||||
if (await confirmAsyncFunc(playerRecord))
|
||||
{
|
||||
Response<string>? resp = null;
|
||||
if (Response.Response.IsOk(await UploadItemsAsync(characters, token)))
|
||||
{
|
||||
resp = await authRequester
|
||||
.PostAsync<string>($"{HutaoAPI}/Record/Upload", playerRecord, PostContentType, token);
|
||||
}
|
||||
|
||||
await resultAsyncFunc(resp ?? Response.Response.CreateForException($"{role.GameUid}-记录提交失败。"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步上传物品所有物品
|
||||
/// </summary>
|
||||
/// <param name="characters">角色详细信息</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>响应</returns>
|
||||
private async Task<Response<string>?> UploadItemsAsync(List<Character> characters, CancellationToken token = default)
|
||||
{
|
||||
Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
|
||||
|
||||
IEnumerable<Item> avatars = characters
|
||||
.Select(avatar => new Item(avatar.Id, avatar.Name, avatar.Icon))
|
||||
.DistinctBy(item => item.Id);
|
||||
|
||||
IEnumerable<Item> weapons = characters
|
||||
.Select(avatar => avatar.Weapon)
|
||||
.Select(weapon => new Item(weapon.Id, weapon.Name, weapon.Icon))
|
||||
.DistinctBy(item => item.Id);
|
||||
|
||||
IEnumerable<Item> reliquaries = characters
|
||||
.Select(avatars => avatars.Reliquaries)
|
||||
.Flatten()
|
||||
.Where(relic => relic.Position == ReliquaryPosition.FlowerOfLife)
|
||||
.DistinctBy(relic => relic.Id)
|
||||
.Select(relic => new Item(relic.ReliquarySet.Id, relic.ReliquarySet.Name, relic.Icon));
|
||||
|
||||
GenshinItemWrapper? data = new(avatars, weapons, reliquaries);
|
||||
|
||||
return await authRequester
|
||||
.PostAsync<string>($"{HutaoAPI}/GenshinItem/Upload", data, PostContentType, token)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private class Auth
|
||||
{
|
||||
public Auth(string appid, string secret)
|
||||
{
|
||||
Appid = appid;
|
||||
Secret = secret;
|
||||
}
|
||||
|
||||
public string Appid { get; }
|
||||
|
||||
public string Secret { get; }
|
||||
}
|
||||
|
||||
private class Token
|
||||
{
|
||||
public string AccessToken { get; } = default!;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 命座比例
|
||||
/// </summary>
|
||||
public class AvatarConstellationNum
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色ID
|
||||
/// </summary>
|
||||
public int Avatar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 持有率
|
||||
/// </summary>
|
||||
public decimal HoldingRate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 各命座比率
|
||||
/// </summary>
|
||||
public IEnumerable<Rate<int>> Rate { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 出场数据
|
||||
/// </summary>
|
||||
public class AvatarParticipation
|
||||
{
|
||||
/// <summary>
|
||||
/// 层
|
||||
/// </summary>
|
||||
public int Floor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色比率
|
||||
/// </summary>
|
||||
public IEnumerable<Rate<int>> AvatarUsage { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 圣遗物配置数据
|
||||
/// </summary>
|
||||
public class AvatarReliquaryUsage
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色Id
|
||||
/// </summary>
|
||||
public int Avatar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 圣遗物比率
|
||||
/// </summary>
|
||||
public IEnumerable<Rate<ReliquarySets>> ReliquaryUsage { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 武器使用数据
|
||||
/// </summary>
|
||||
public class AvatarWeaponUsage
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色Id
|
||||
/// </summary>
|
||||
public int Avatar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 武器比率
|
||||
/// </summary>
|
||||
public IEnumerable<Rate<int>> Weapons { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model.Converter;
|
||||
|
||||
/// <summary>
|
||||
/// 圣遗物套装转换器
|
||||
/// </summary>
|
||||
internal class ReliquarySetsConverter : JsonConverter<ReliquarySets>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override ReliquarySets? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
string? source = reader.GetString();
|
||||
|
||||
if (source != null)
|
||||
{
|
||||
string[] sets = source.Split(';');
|
||||
return new(sets.Select(set => new ReliquarySet(set)));
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(Utf8JsonWriter writer, ReliquarySets value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(string.Join(';', value));
|
||||
}
|
||||
}
|
||||
41
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Item.cs
Normal file
41
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Item.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 胡桃数据库物品
|
||||
/// </summary>
|
||||
public class Item
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的胡桃数据库物品
|
||||
/// </summary>
|
||||
/// <param name="id">物品Id</param>
|
||||
/// <param name="name">名称</param>
|
||||
/// <param name="url">链接</param>
|
||||
[JsonConstructor]
|
||||
public Item(int id, string name, string url)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
Url = url;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 物品Id
|
||||
/// </summary>
|
||||
public int Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 链接
|
||||
/// </summary>
|
||||
public string Url { get; }
|
||||
}
|
||||
20
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/LevelInfo.cs
Normal file
20
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/LevelInfo.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 层
|
||||
/// </summary>
|
||||
public class LevelInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 间
|
||||
/// </summary>
|
||||
public int Floor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 上下半 0,1
|
||||
/// </summary>
|
||||
public int Index { get; set; }
|
||||
}
|
||||
25
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Overview.cs
Normal file
25
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Overview.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 统计数据
|
||||
/// </summary>
|
||||
public class Overview
|
||||
{
|
||||
/// <summary>
|
||||
/// 所有用户数量
|
||||
/// </summary>
|
||||
public int TotalPlayerCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当期提交深渊数据用户数量
|
||||
/// </summary>
|
||||
public int CollectedPlayerCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当期满星用户数量
|
||||
/// </summary>
|
||||
public int FullStarPlayerCount { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model.Post;
|
||||
|
||||
/// <summary>
|
||||
/// 角色圣遗物套装
|
||||
/// </summary>
|
||||
public class AvatarReliquarySet
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的角色圣遗物套装
|
||||
/// </summary>
|
||||
/// <param name="id">套装Id</param>
|
||||
/// <param name="count">个数</param>
|
||||
public AvatarReliquarySet(int id, int count)
|
||||
{
|
||||
Id = id;
|
||||
Count = count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的角色圣遗物套装
|
||||
/// </summary>
|
||||
/// <param name="kvp">键值对</param>
|
||||
public AvatarReliquarySet(KeyValuePair<int, int> kvp)
|
||||
: this(kvp.Key, kvp.Value)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 套装Id
|
||||
/// </summary>
|
||||
public int Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 个数
|
||||
/// </summary>
|
||||
public int Count { get; }
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model.Post;
|
||||
|
||||
/// <summary>
|
||||
/// 角色武器
|
||||
/// </summary>
|
||||
public class AvatarWeapon
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的角色武器
|
||||
/// </summary>
|
||||
/// <param name="id">武器Id</param>
|
||||
/// <param name="level">武器等级</param>
|
||||
/// <param name="affixLevel">精炼等级</param>
|
||||
public AvatarWeapon(int id, int level, int affixLevel)
|
||||
{
|
||||
Id = id;
|
||||
Level = level;
|
||||
AffixLevel = affixLevel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 武器等级
|
||||
/// </summary>
|
||||
public int Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 武器等级
|
||||
/// </summary>
|
||||
public int Level { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 精炼
|
||||
/// </summary>
|
||||
public int AffixLevel { get; }
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model.Post;
|
||||
|
||||
/// <summary>
|
||||
/// 原神物品包装器
|
||||
/// </summary>
|
||||
public class GenshinItemWrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的原神物品包装器
|
||||
/// </summary>
|
||||
/// <param name="avatars">角色</param>
|
||||
/// <param name="weapons">武器</param>
|
||||
/// <param name="reliquaries">圣遗物</param>
|
||||
public GenshinItemWrapper(IEnumerable<Item> avatars, IEnumerable<Item> weapons, IEnumerable<Item> reliquaries)
|
||||
{
|
||||
Avatars = avatars;
|
||||
Weapons = weapons;
|
||||
Reliquaries = reliquaries;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色列表
|
||||
/// </summary>
|
||||
public IEnumerable<Item> Avatars { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 武器列表
|
||||
/// </summary>
|
||||
public IEnumerable<Item> Weapons { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 圣遗物列表
|
||||
/// </summary>
|
||||
public IEnumerable<Item> Reliquaries { get; }
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.SpiralAbyss;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model.Post;
|
||||
|
||||
/// <summary>
|
||||
/// 带有层信息的间
|
||||
/// </summary>
|
||||
internal class IndexedLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的带有层信息的间
|
||||
/// </summary>
|
||||
/// <param name="floorIndex">层号</param>
|
||||
/// <param name="level">间信息</param>
|
||||
public IndexedLevel(int floorIndex, Level level)
|
||||
{
|
||||
FloorIndex = floorIndex;
|
||||
Level = level;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 层号
|
||||
/// </summary>
|
||||
public int FloorIndex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 层信息
|
||||
/// </summary>
|
||||
public Level Level { get; }
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model.Post;
|
||||
|
||||
/// <summary>
|
||||
/// 玩家角色
|
||||
/// </summary>
|
||||
public class PlayerAvatar
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的玩家角色
|
||||
/// </summary>
|
||||
/// <param name="avatar">角色</param>
|
||||
internal PlayerAvatar(Character avatar)
|
||||
{
|
||||
Id = avatar.Id;
|
||||
Level = avatar.Level;
|
||||
ActivedConstellationNum = avatar.ActivedConstellationNum;
|
||||
Weapon = new(avatar.Weapon.Id, avatar.Weapon.Level, avatar.Weapon.AffixLevel);
|
||||
ReliquarySets = avatar.Reliquaries
|
||||
.CountBy(relic => relic.ReliquarySet.Id)
|
||||
.Select(kvp => new AvatarReliquarySet(kvp))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色Id
|
||||
/// </summary>
|
||||
public int Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色等级
|
||||
/// </summary>
|
||||
public int Level { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 命座
|
||||
/// </summary>
|
||||
public int ActivedConstellationNum { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 武器
|
||||
/// </summary>
|
||||
public AvatarWeapon Weapon { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 圣遗物套装
|
||||
/// </summary>
|
||||
public List<AvatarReliquarySet> ReliquarySets { get; }
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.SpiralAbyss;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model.Post;
|
||||
|
||||
/// <summary>
|
||||
/// 玩家记录
|
||||
/// 使用 <see cref="Create(string, List{Character}, SpiralAbyss)"/> 来构建一个实例
|
||||
/// </summary>
|
||||
public class PlayerRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的玩家记录
|
||||
/// </summary>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="playerAvatars">玩家角色</param>
|
||||
/// <param name="playerSpiralAbyssesLevels">玩家深渊信息</param>
|
||||
private PlayerRecord(string uid, IEnumerable<PlayerAvatar> playerAvatars, IEnumerable<PlayerSpiralAbyssLevel> playerSpiralAbyssesLevels)
|
||||
{
|
||||
Uid = uid;
|
||||
PlayerAvatars = playerAvatars;
|
||||
PlayerSpiralAbyssesLevels = playerSpiralAbyssesLevels;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// uid
|
||||
/// </summary>
|
||||
public string Uid { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 玩家角色
|
||||
/// </summary>
|
||||
public IEnumerable<PlayerAvatar> PlayerAvatars { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 玩家深渊信息
|
||||
/// </summary>
|
||||
public IEnumerable<PlayerSpiralAbyssLevel> PlayerSpiralAbyssesLevels { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 建造玩家记录
|
||||
/// </summary>
|
||||
/// <param name="uid">玩家的uid</param>
|
||||
/// <param name="detailAvatars">角色详情信息</param>
|
||||
/// <param name="spiralAbyss">深渊信息</param>
|
||||
/// <returns>玩家记录</returns>
|
||||
internal static PlayerRecord Create(string uid, List<Character> detailAvatars, SpiralAbyss spiralAbyss)
|
||||
{
|
||||
IEnumerable<PlayerAvatar> playerAvatars = detailAvatars
|
||||
.Select(avatar => new PlayerAvatar(avatar));
|
||||
|
||||
IEnumerable<PlayerSpiralAbyssLevel> playerSpiralAbyssLevels = spiralAbyss.Floors
|
||||
.SelectMany(f => f.Levels, (f, level) => new IndexedLevel(f.Index, level))
|
||||
.Select(indexedLevel => new PlayerSpiralAbyssLevel(indexedLevel));
|
||||
|
||||
PlayerRecord playerRecord = new(uid, playerAvatars, playerSpiralAbyssLevels);
|
||||
return playerRecord;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.SpiralAbyss;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model.Post;
|
||||
|
||||
/// <summary>
|
||||
/// 玩家深渊某间的战斗信息
|
||||
/// </summary>
|
||||
public class PlayerSpiralAbyssBattle
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的战斗信息
|
||||
/// </summary>
|
||||
/// <param name="battle">战斗</param>
|
||||
internal PlayerSpiralAbyssBattle(Battle battle)
|
||||
{
|
||||
BattleIndex = battle.Index;
|
||||
AvatarIds = battle.Avatars.Select(a => a.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 战斗上下半间 0,1
|
||||
/// </summary>
|
||||
public int BattleIndex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色Id列表
|
||||
/// </summary>
|
||||
public IEnumerable<int> AvatarIds { get; }
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model.Post;
|
||||
|
||||
/// <summary>
|
||||
/// 玩家深渊战斗间信息
|
||||
/// </summary>
|
||||
public class PlayerSpiralAbyssLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的玩家深渊战斗间信息
|
||||
/// </summary>
|
||||
/// <param name="indexedLevel">楼层</param>
|
||||
internal PlayerSpiralAbyssLevel(IndexedLevel indexedLevel)
|
||||
{
|
||||
FloorIndex = indexedLevel.FloorIndex;
|
||||
LevelIndex = indexedLevel.Level.Index;
|
||||
Star = indexedLevel.Level.Star;
|
||||
Battles = indexedLevel.Level.Battles
|
||||
.Select(battle => new PlayerSpiralAbyssBattle(battle));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 层号
|
||||
/// </summary>
|
||||
public int FloorIndex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 间号
|
||||
/// </summary>
|
||||
public int LevelIndex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 星数
|
||||
/// </summary>
|
||||
public int Star { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 战斗列表 分上下半间
|
||||
/// </summary>
|
||||
public IEnumerable<PlayerSpiralAbyssBattle> Battles { get; }
|
||||
}
|
||||
21
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Rate{T}.cs
Normal file
21
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Rate{T}.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 比率
|
||||
/// </summary>
|
||||
/// <typeparam name="T"><see cref="Id"/> 的类型</typeparam>
|
||||
public class Rate<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示唯一标识符的实例
|
||||
/// </summary>
|
||||
public T Id { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 比率
|
||||
/// </summary>
|
||||
public decimal Value { get; set; }
|
||||
}
|
||||
38
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/ReliquarySet.cs
Normal file
38
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/ReliquarySet.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 圣遗物套装
|
||||
/// </summary>
|
||||
public class ReliquarySet
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的圣遗物套装
|
||||
/// </summary>
|
||||
/// <param name="set">简单套装字符串</param>
|
||||
public ReliquarySet(string set)
|
||||
{
|
||||
string[]? deconstructed = set.Split('-');
|
||||
|
||||
Id = int.Parse(deconstructed[0]);
|
||||
Count = int.Parse(deconstructed[1]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
public int Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 个数
|
||||
/// </summary>
|
||||
public int Count { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Id}-{Count}";
|
||||
}
|
||||
}
|
||||
24
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/ReliquarySets.cs
Normal file
24
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/ReliquarySets.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Web.Hutao.Model.Converter;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 包装圣遗物套装
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(ReliquarySetsConverter))]
|
||||
public class ReliquarySets : List<ReliquarySet>
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的圣遗物包装器
|
||||
/// </summary>
|
||||
/// <param name="sets">圣遗物套装</param>
|
||||
public ReliquarySets(IEnumerable<ReliquarySet> sets)
|
||||
: base(sets)
|
||||
{
|
||||
}
|
||||
}
|
||||
26
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Team.cs
Normal file
26
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/Team.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Json.Converter;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 队伍
|
||||
/// </summary>
|
||||
public class Team
|
||||
{
|
||||
/// <summary>
|
||||
/// 上半
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(SeparatorCommaInt32EnumerableConverter))]
|
||||
public IEnumerable<int> UpHalf { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 下半
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(SeparatorCommaInt32EnumerableConverter))]
|
||||
public IEnumerable<int> DownHalf { get; set; } = null!;
|
||||
}
|
||||
22
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/TeamCollocation.cs
Normal file
22
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/TeamCollocation.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 组队数据
|
||||
/// </summary>
|
||||
public class TeamCollocation
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色Id
|
||||
/// </summary>
|
||||
public int Avatar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色搭配比率
|
||||
/// </summary>
|
||||
public IEnumerable<Rate<int>> Collocations { get; set; } = default!;
|
||||
}
|
||||
22
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/TeamCombination.cs
Normal file
22
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/TeamCombination.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 队伍上场率
|
||||
/// </summary>
|
||||
public record TeamCombination
|
||||
{
|
||||
/// <summary>
|
||||
/// 层
|
||||
/// </summary>
|
||||
public LevelInfo Level { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 队伍
|
||||
/// </summary>
|
||||
public IEnumerable<Rate<Team>> Teams { get; set; } = null!;
|
||||
}
|
||||
15
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/UploadStatus.cs
Normal file
15
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/UploadStatus.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 上传信息
|
||||
/// </summary>
|
||||
public class UploadStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// 本期是否已经上传
|
||||
/// </summary>
|
||||
public bool PeriodUploaded { get; set; }
|
||||
}
|
||||
41
src/Snap.Hutao/Snap.Hutao/Web/Request/RequestHeaders.cs
Normal file
41
src/Snap.Hutao/Snap.Hutao/Web/Request/RequestHeaders.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Request
|
||||
{
|
||||
/// <summary>
|
||||
/// 请求头的键
|
||||
/// </summary>
|
||||
public class RequestHeaders
|
||||
{
|
||||
/// <summary>
|
||||
/// Accept
|
||||
/// </summary>
|
||||
public const string Accept = "Accept";
|
||||
|
||||
/// <summary>
|
||||
/// Cookie
|
||||
/// </summary>
|
||||
public const string Cookie = "Cookie";
|
||||
|
||||
/// <summary>
|
||||
/// x-rpc-app_version
|
||||
/// </summary>
|
||||
public const string AppVersion = "x-rpc-app_version";
|
||||
|
||||
/// <summary>
|
||||
/// User-Agent
|
||||
/// </summary>
|
||||
public const string UserAgent = "User-Agent";
|
||||
|
||||
/// <summary>
|
||||
/// x-rpc-client_type
|
||||
/// </summary>
|
||||
public const string ClientType = "x-rpc-client_type";
|
||||
|
||||
/// <summary>
|
||||
/// X-Requested-With
|
||||
/// </summary>
|
||||
public const string RequestWith = "X-Requested-With";
|
||||
}
|
||||
}
|
||||
@@ -8,22 +8,10 @@ namespace Snap.Hutao.Web.Request
|
||||
/// </summary>
|
||||
public class RequestOptions : Dictionary<string, string>
|
||||
{
|
||||
/// <summary>
|
||||
/// 不再使用
|
||||
/// </summary>
|
||||
[Obsolete("不再使用")]
|
||||
public const string CommonUA2101 = @"Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBS/2.10.1";
|
||||
|
||||
/// <summary>
|
||||
/// 不再使用
|
||||
/// </summary>
|
||||
[Obsolete("不再使用")]
|
||||
public const string CommonUA2111 = @"Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBS/2.11.1";
|
||||
|
||||
/// <summary>
|
||||
/// 支持更新的DS2算法
|
||||
/// </summary>
|
||||
public const string CommonUA2161 = @"Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBS/2.16.1";
|
||||
public const string CommonUA = @"Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBS/2.16.1";
|
||||
|
||||
/// <summary>
|
||||
/// 应用程序/Json
|
||||
@@ -35,6 +23,11 @@ namespace Snap.Hutao.Web.Request
|
||||
/// </summary>
|
||||
public const string Hyperion = "com.mihoyo.hyperion";
|
||||
|
||||
/// <summary>
|
||||
/// 默认的客户端类型
|
||||
/// </summary>
|
||||
public const string DefaultClientType = "5";
|
||||
|
||||
/// <summary>
|
||||
/// 设备Id
|
||||
/// </summary>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Json;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.Net.Http;
|
||||
@@ -20,6 +21,8 @@ public class Requester
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly ILogger<Requester> logger;
|
||||
|
||||
private bool isRequesting = false;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的 <see cref="Requester"/> 对象
|
||||
/// </summary>
|
||||
@@ -40,6 +43,11 @@ public class Requester
|
||||
/// </summary>
|
||||
public RequestOptions Headers { get; set; } = new RequestOptions();
|
||||
|
||||
/// <summary>
|
||||
/// 此请求器使用的Json解析器
|
||||
/// </summary>
|
||||
public Json Json { get => json; }
|
||||
|
||||
/// <summary>
|
||||
/// 内部使用的 <see cref="System.Net.Http.HttpClient"/>
|
||||
/// </summary>
|
||||
@@ -52,7 +60,7 @@ public class Requester
|
||||
/// <param name="url">地址</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>响应</returns>
|
||||
public async Task<Response<TResult>?> GetAsync<TResult>(string? url, CancellationToken cancellationToken = default)
|
||||
public async Task<Response<TResult>?> GetAsync<TResult>(string url, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (url is null)
|
||||
{
|
||||
@@ -73,14 +81,14 @@ public class Requester
|
||||
/// <param name="data">要发送的.NET(匿名)对象</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>响应</returns>
|
||||
public async Task<Response<TResult>?> PostAsync<TResult>(string? url, object data, CancellationToken cancellationToken = default)
|
||||
public async Task<Response<TResult>?> PostAsync<TResult>(string url, object data, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (url is null)
|
||||
{
|
||||
return Response<TResult>.CreateForEmptyUrl();
|
||||
}
|
||||
|
||||
string dataString = json.Stringify(data);
|
||||
string dataString = Json.Stringify(data);
|
||||
HttpContent content = new StringContent(dataString);
|
||||
|
||||
Task<HttpResponseMessage> PostMethod(HttpClient client, CancellationToken token) => client.PostAsync(url, content, token);
|
||||
@@ -98,14 +106,14 @@ public class Requester
|
||||
/// <param name="contentType">内容类型</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>响应</returns>
|
||||
public async Task<Response<TResult>?> PostAsync<TResult>(string? url, object data, string contentType, CancellationToken cancellationToken = default)
|
||||
public async Task<Response<TResult>?> PostAsync<TResult>(string url, object data, string contentType, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (url is null)
|
||||
{
|
||||
return Response<TResult>.CreateForEmptyUrl();
|
||||
}
|
||||
|
||||
string dataString = json.Stringify(data);
|
||||
string dataString = Json.Stringify(data);
|
||||
HttpContent content = new StringContent(dataString, Encoding.UTF8, contentType);
|
||||
|
||||
Task<HttpResponseMessage> PostMethod(HttpClient client, CancellationToken token) => client.PostAsync(url, content, token);
|
||||
@@ -121,6 +129,7 @@ public class Requester
|
||||
/// <returns>链式调用需要的实例</returns>
|
||||
public Requester Reset()
|
||||
{
|
||||
Verify.Operation(!isRequesting, "无法在请求发生时重置请求器");
|
||||
Headers.Clear();
|
||||
return this;
|
||||
}
|
||||
@@ -133,10 +142,48 @@ public class Requester
|
||||
/// <returns>链式调用需要的实例</returns>
|
||||
public Requester AddHeader(string key, string value)
|
||||
{
|
||||
Verify.Operation(!isRequesting, "无法在请求发生时修改请求头");
|
||||
Headers.Add(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 接受Json
|
||||
/// </summary>
|
||||
/// <returns>链式调用需要的实例</returns>
|
||||
public Requester SetAcceptJson()
|
||||
{
|
||||
return AddHeader(RequestHeaders.Accept, RequestOptions.Json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置常规UA
|
||||
/// </summary>
|
||||
/// <returns>链式调用需要的实例</returns>
|
||||
public Requester SetCommonUA()
|
||||
{
|
||||
return AddHeader(RequestHeaders.UserAgent, RequestOptions.CommonUA);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置为由米游社请求
|
||||
/// </summary>
|
||||
/// <returns>链式调用需要的实例</returns>
|
||||
public Requester SetRequestWithHyperion()
|
||||
{
|
||||
return AddHeader(RequestHeaders.RequestWith, RequestOptions.Hyperion);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加Cookie请求头
|
||||
/// </summary>
|
||||
/// <param name="user">用户</param>
|
||||
/// <returns>链式调用需要的实例</returns>
|
||||
public Requester SetUser(User user)
|
||||
{
|
||||
return AddHeader(RequestHeaders.Cookie, user.Cookie ?? string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在请求前准备 <see cref="System.Net.Http.HttpClient"/>
|
||||
/// </summary>
|
||||
@@ -151,14 +198,15 @@ public class Requester
|
||||
}
|
||||
|
||||
private async Task<Response<TResult>?> RequestAsync<TResult>(
|
||||
Func<HttpClient, CancellationToken, Task<HttpResponseMessage>> requestFunc,
|
||||
Func<HttpClient, CancellationToken, Task<HttpResponseMessage>> requestAsyncFunc,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
isRequesting = true;
|
||||
PrepareHttpClient();
|
||||
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response = await requestFunc
|
||||
HttpResponseMessage response = await requestAsyncFunc
|
||||
.Invoke(HttpClient, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
@@ -166,12 +214,15 @@ public class Requester
|
||||
.ReadAsStringAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
Response<TResult>? resp = json.ToObject<Response<TResult>>(contentString);
|
||||
if (resp?.ToString() is string representable)
|
||||
Response<TResult>? resp = Json.ToObject<Response<TResult>>(contentString);
|
||||
|
||||
if (resp is null)
|
||||
{
|
||||
infoBarService.Information(representable);
|
||||
return Response<TResult>.CreateForJsonException(contentString);
|
||||
}
|
||||
|
||||
ValidateResponse(resp);
|
||||
|
||||
return resp;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -181,7 +232,17 @@ public class Requester
|
||||
}
|
||||
finally
|
||||
{
|
||||
isRequesting = false;
|
||||
logger.LogInformation("Request Completed");
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateResponse<TResult>(Response<TResult> resp)
|
||||
{
|
||||
// 未知的返回代码
|
||||
if (!Enum.IsDefined(typeof(KnownReturnCode), resp.ReturnCode))
|
||||
{
|
||||
infoBarService.Information(resp.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,11 @@ namespace Snap.Hutao.Web.Response;
|
||||
/// </summary>
|
||||
public enum KnownReturnCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Url为 空
|
||||
/// </summary>
|
||||
JsonParseIssue = -2000000002,
|
||||
|
||||
/// <summary>
|
||||
/// Url为 空
|
||||
/// </summary>
|
||||
|
||||
@@ -16,5 +16,5 @@ public class ListWrapper<T>
|
||||
/// 列表
|
||||
/// </summary>
|
||||
[JsonPropertyName("list")]
|
||||
public List<T>? List { get; set; }
|
||||
public List<T> List { get; set; } = default!;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ public class Response
|
||||
/// 消息
|
||||
/// </summary>
|
||||
[JsonPropertyName("message")]
|
||||
public string? Message { get; set; }
|
||||
public string Message { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 响应是否正常
|
||||
|
||||
@@ -31,6 +31,20 @@ public class Response<TData> : Response
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个失败的响应
|
||||
/// </summary>
|
||||
/// <param name="message">消息</param>
|
||||
/// <returns>响应</returns>
|
||||
public static Response<TData> CreateForJsonException(string message)
|
||||
{
|
||||
return new Response<TData>()
|
||||
{
|
||||
ReturnCode = (int)KnownReturnCode.InternalFailure,
|
||||
Message = message,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个空Url的响应
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user