Compare commits

..

7 Commits

Author SHA1 Message Date
qhy040404
a845dff6ee Revert "temporary fix qr login"
This reverts commit d4bd610fe2.
2024-04-14 13:55:16 +08:00
DismissedLight
ee99d0b665 refactor static resource 2024-04-12 21:40:00 +08:00
Lightczx
72b62aa9c6 ServerGarbageCollection 2024-04-12 17:30:10 +08:00
Lightczx
6b031e1866 update static resource setting 2024-04-12 16:52:54 +08:00
Lightczx
59c03c7f3b update WAS to 1.5.2 2024-04-12 11:57:49 +08:00
Lightczx
c03a96b44f refine guide ui 2024-04-11 16:14:04 +08:00
DismissedLight
d5a97903d3 Merge pull request #1545 from DGP-Studio/feat/version1.10guide 2024-04-11 15:57:44 +08:00
17 changed files with 215 additions and 80 deletions

View File

@@ -5,6 +5,9 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Core.IO;
using Snap.Hutao.Core.IO.Hashing;
using Snap.Hutao.Core.Logging;
using Snap.Hutao.ViewModel.Guide;
using Snap.Hutao.Web.Request.Builder;
using Snap.Hutao.Web.Request.Builder.Abstraction;
using System.Collections.Concurrent;
using System.Collections.Frozen;
using System.IO;
@@ -36,6 +39,7 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
private readonly ConcurrentDictionary<string, Task> concurrentTasks = new();
private readonly IHttpClientFactory httpClientFactory;
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly IServiceProvider serviceProvider;
private readonly ILogger<ImageCache> logger;
@@ -169,38 +173,49 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
HttpClient httpClient = httpClientFactory.CreateClient(nameof(ImageCache));
while (retryCount < 3)
{
using (HttpResponseMessage message = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
{
if (message.RequestMessage is { RequestUri: { } target } && target != uri)
{
logger.LogDebug("The Request '{Source}' has been redirected to '{Target}'", uri, target);
}
HttpRequestMessageBuilder requestMessageBuilder = httpRequestMessageBuilderFactory
.Create()
.SetRequestUri(uri)
if (message.IsSuccessStatusCode)
// These headers are only available for our own api
.SetStaticResourceControlHeadersIf(uri.Host.Contains("api.snapgenshin.com", StringComparison.OrdinalIgnoreCase))
.Get();
using (HttpRequestMessage requestMessage = requestMessageBuilder.HttpRequestMessage)
{
using (HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
{
using (Stream httpStream = await message.Content.ReadAsStreamAsync().ConfigureAwait(false))
if (responseMessage.RequestMessage is { RequestUri: { } target } && target != uri)
{
using (FileStream fileStream = File.Create(baseFile))
logger.LogDebug("The Request '{Source}' has been redirected to '{Target}'", uri, target);
}
if (responseMessage.IsSuccessStatusCode)
{
using (Stream httpStream = await responseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
await httpStream.CopyToAsync(fileStream).ConfigureAwait(false);
return;
using (FileStream fileStream = File.Create(baseFile))
{
await httpStream.CopyToAsync(fileStream).ConfigureAwait(false);
return;
}
}
}
}
switch (message.StatusCode)
{
case HttpStatusCode.TooManyRequests:
{
retryCount++;
TimeSpan delay = message.Headers.RetryAfter?.Delta ?? retryCountToDelay[retryCount];
logger.LogInformation("Retry download '{Uri}' after {Delay}.", uri, delay);
await Task.Delay(delay).ConfigureAwait(false);
break;
}
switch (responseMessage.StatusCode)
{
case HttpStatusCode.TooManyRequests:
{
retryCount++;
TimeSpan delay = responseMessage.Headers.RetryAfter?.Delta ?? retryCountToDelay[retryCount];
logger.LogInformation("Retry download '{Uri}' after {Delay}.", uri, delay);
await Task.Delay(delay).ConfigureAwait(false);
break;
}
default:
return;
default:
return;
}
}
}
}

View File

@@ -22,7 +22,7 @@ internal static class SettingKeys
public const string DataFolderPath = "DataFolderPath";
public const string Major1Minor10Revision0GuideState = "Major1Minor10Revision0GuideState1";
public const string StaticResourceImageQuality = "StaticResourceImageQuality";
public const string StaticResourceUseTrimmedArchive = "StaticResourceUseTrimmedArchive";
public const string StaticResourceImageArchive = "StaticResourceImageArchive";
public const string HotKeyMouseClickRepeatForever = "HotKeyMouseClickRepeatForever";
public const string IsAllocConsoleDebugModeEnabled = "IsAllocConsoleDebugModeEnabled2";
#endregion

View File

@@ -14,10 +14,10 @@ namespace Snap.Hutao;
internal sealed partial class GuideWindow : Window, IWindowOptionsSource, IMinMaxInfoHandler
{
private const int MinWidth = 1000;
private const int MinHeight = 600;
private const int MinHeight = 650;
private const int MaxWidth = 1200;
private const int MaxHeight = 750;
private const int MaxHeight = 800;
private readonly WindowOptions windowOptions;

View File

@@ -1382,6 +1382,9 @@
<data name="ViewGachaLogHeader" xml:space="preserve">
<value>祈愿记录</value>
</data>
<data name="ViewGuideStaticResourceDownloadSize" xml:space="preserve">
<value>预计下载大小:{0}</value>
</data>
<data name="ViewGuideStepAgreementIHaveReadText" xml:space="preserve">
<value>我已阅读并同意</value>
</data>
@@ -1440,10 +1443,10 @@
<value>图片资源包体</value>
</data>
<data name="ViewGuideStepStaticResourceSettingMinimumOff" xml:space="preserve">
<value>全部资源(节省 0% 磁盘空间占用)</value>
<value>完整包体</value>
</data>
<data name="ViewGuideStepStaticResourceSettingMinimumOn" xml:space="preserve">
<value>部分资源(节省 13.5% 磁盘空间占用)</value>
<value>精简包体</value>
</data>
<data name="ViewGuideStepStaticResourceSettingQualityHeader" xml:space="preserve">
<value>图片资源质量</value>
@@ -1638,10 +1641,10 @@
<value>下载资源文件中,请稍候</value>
</data>
<data name="ViewModelGuideStaticResourceQualityHigh" xml:space="preserve">
<value>高质量(节省 72.8% 磁盘空间占用)</value>
<value>高质量</value>
</data>
<data name="ViewModelGuideStaticResourceQualityRaw" xml:space="preserve">
<value>原图(节省 0% 磁盘空间占用)</value>
<value>原图</value>
</data>
<data name="ViewModelHutaoPassportEmailNotValidHint" xml:space="preserve">
<value>请输入正确的邮箱</value>

View File

@@ -44,6 +44,8 @@
<SelfContained>true</SelfContained>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<WindowsAppSdkUndockedRegFreeWinRTInitialize>false</WindowsAppSdkUndockedRegFreeWinRTInitialize>
<ServerGarbageCollection>true</ServerGarbageCollection>
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
</PropertyGroup>
<ItemGroup>
@@ -303,8 +305,8 @@
<PackageReference Include="CommunityToolkit.WinUI.Controls.TokenizingTextBox" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.3">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -319,7 +321,7 @@
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.8.8" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240311000" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240404000" />
<PackageReference Include="QRCoder" Version="1.4.3" />
<PackageReference Include="Snap.Discord.GameSDK" Version="1.6.0" />
<PackageReference Include="Snap.Hutao.Deployment.Runtime" Version="1.16.0">

View File

@@ -6,7 +6,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
xmlns:shc="using:Snap.Hutao.Control"
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shvg="using:Snap.Hutao.ViewModel.Guide"
@@ -224,10 +223,14 @@
ItemsSource="{Binding StaticResourceOptions.ImageQualities}"
SelectedItem="{Binding StaticResourceOptions.ImageQuality, Mode=TwoWay}"/>
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="{shcm:ResourceString Name=ViewGuideStepStaticResourceSettingMinimumHeader}"/>
<ToggleSwitch
IsOn="{Binding StaticResourceOptions.UseTrimmedArchive, Mode=TwoWay}"
OffContent="{shcm:ResourceString Name=ViewGuideStepStaticResourceSettingMinimumOff}"
OnContent="{shcm:ResourceString Name=ViewGuideStepStaticResourceSettingMinimumOn}"/>
<ListView
MinWidth="320"
Margin="0,8,0,32"
DisplayMemberPath="Name"
ItemsSource="{Binding StaticResourceOptions.ImageArchives}"
SelectedItem="{Binding StaticResourceOptions.ImageArchive, Mode=TwoWay}"/>
<TextBlock Margin="0,16,0,0" Text="{Binding StaticResourceOptions.SizeInformationText, Mode=OneWay}"/>
</StackPanel>
<TextBlock
@@ -266,12 +269,20 @@
HorizontalAlignment="Center"
IsHitTestVisible="False"
SelectedIndex="{Binding State, Mode=TwoWay}">
<!--
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepLanguage}" Icon="{shcm:FontIcon Glyph=&#xF2B7;}"/>
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepDocument}" Icon="{shcm:FontIcon Glyph=&#xF28B;}"/>
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepEnvironment}" Icon="{shcm:FontIcon Glyph=&#xE81E;}"/>
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepCommonSetting}" Icon="{shcm:FontIcon Glyph=&#xE713;}"/>
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepStaticResourceSetting}" Icon="{shcm:FontIcon Glyph=&#xE8BA;}"/>
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepStaticResource}" Icon="{shcm:FontIcon Glyph=&#xE8B9;}"/>
-->
<cwc:SegmentedItem Icon="{shcm:FontIcon Glyph=&#xF2B7;}"/>
<cwc:SegmentedItem Icon="{shcm:FontIcon Glyph=&#xF28B;}"/>
<cwc:SegmentedItem Icon="{shcm:FontIcon Glyph=&#xE81E;}"/>
<cwc:SegmentedItem Icon="{shcm:FontIcon Glyph=&#xE713;}"/>
<cwc:SegmentedItem Icon="{shcm:FontIcon Glyph=&#xE8BA;}"/>
<cwc:SegmentedItem Icon="{shcm:FontIcon Glyph=&#xE8B9;}"/>
</cwc:Segmented>
<Button
Command="{Binding NextOrCompleteCommand}"

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control.Extension;
using Snap.Hutao.ViewModel.Guide;
namespace Snap.Hutao.View.Guide;
@@ -14,6 +15,6 @@ internal sealed partial class GuideView : UserControl
public GuideView()
{
InitializeComponent();
DataContext = Ioc.Default.GetRequiredService<GuideViewModel>();
DataContext = this.ServiceProvider().GetRequiredService<GuideViewModel>();
}
}

View File

@@ -7,6 +7,8 @@ using Snap.Hutao.Core.Setting;
using Snap.Hutao.Model;
using Snap.Hutao.Service;
using Snap.Hutao.Web.Hoyolab;
using Snap.Hutao.Web.Hutao;
using Snap.Hutao.Web.Hutao.Response;
using System.Collections.ObjectModel;
using System.Globalization;
@@ -164,6 +166,19 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
set => SetProperty(ref downloadSummaries, value);
}
protected override async ValueTask<bool> InitializeUIAsync()
{
HutaoInfrastructureClient hutaoInfrastructureClient = serviceProvider.GetRequiredService<HutaoInfrastructureClient>();
HutaoResponse<StaticResourceSizeInformation> response = await hutaoInfrastructureClient.GetStaticSizeAsync().ConfigureAwait(false);
if (response.IsOk())
{
await taskContext.SwitchToMainThreadAsync();
StaticResourceOptions.SizeInformation = response.Data;
}
return true;
}
[Command("NextOrCompleteCommand")]
private void NextOrComplete()
{

View File

@@ -46,31 +46,31 @@ internal static class StaticResource
private static readonly ApplicationDataCompositeValue LatestResourceVersionMap = new()
{
{ "AchievementIcon", 1 },
{ "AvatarCard", 1 },
{ "AvatarIcon", 4 },
{ "Bg", 2 },
{ "ChapterIcon", 2 },
{ "AchievementIcon", 2 },
{ "AvatarCard", 2 },
{ "AvatarIcon", 5 },
{ "Bg", 3 },
{ "ChapterIcon", 3 },
{ "CodexMonster", 0 },
{ "Costume", 1 },
{ "EmotionIcon", 2 },
{ "EquipIcon", 3 },
{ "GachaAvatarIcon", 3 },
{ "GachaAvatarImg", 3 },
{ "GachaEquipIcon", 3 },
{ "Costume", 2 },
{ "EmotionIcon", 3 },
{ "EquipIcon", 4 },
{ "GachaAvatarIcon", 4 },
{ "GachaAvatarImg", 4 },
{ "GachaEquipIcon", 4 },
{ "GcgCharAvatarIcon", 0 },
{ "IconElement", 2 },
{ "ItemIcon", 3 },
{ "LoadingPic", 1 },
{ "MonsterIcon", 2 },
{ "MonsterSmallIcon", 1 },
{ "NameCardIcon", 2 },
{ "NameCardPic", 3 },
{ "IconElement", 3 },
{ "ItemIcon", 4 },
{ "LoadingPic", 2 },
{ "MonsterIcon", 3 },
{ "MonsterSmallIcon", 2 },
{ "NameCardIcon", 3 },
{ "NameCardPic", 4 },
{ "NameCardPicAlpha", 0 },
{ "Property", 1 },
{ "RelicIcon", 3 },
{ "Skill", 3 },
{ "Talent", 3 },
{ "Property", 2 },
{ "RelicIcon", 4 },
{ "Skill", 4 },
{ "Talent", 4 },
};
public static void FulfillAll()

View File

@@ -0,0 +1,14 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.ViewModel.Guide;
[Localization]
internal enum StaticResourceArchive
{
[LocalizationKey(nameof(SH.ViewGuideStepStaticResourceSettingMinimumOff))]
Full,
[LocalizationKey(nameof(SH.ViewGuideStepStaticResourceSettingMinimumOn))]
Minimum,
}

View File

@@ -15,6 +15,12 @@ internal static class StaticResourceHttpHeaderBuilderExtension
{
return builder
.SetHeader("x-quality", $"{UnsafeLocalSetting.Get(SettingKeys.StaticResourceImageQuality, StaticResourceQuality.Raw)}")
.SetHeader("x-minimum", $"{LocalSetting.Get(SettingKeys.StaticResourceUseTrimmedArchive, false)}");
.SetHeader("x-archive", $"{UnsafeLocalSetting.Get(SettingKeys.StaticResourceImageArchive, StaticResourceArchive.Full)}");
}
public static TBuilder SetStaticResourceControlHeadersIf<TBuilder>(this TBuilder builder, bool condition)
where TBuilder : IHttpHeadersBuilder<HttpHeaders>
{
return condition ? builder.SetStaticResourceControlHeaders() : builder;
}
}

View File

@@ -1,41 +1,82 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Common;
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Model;
using Snap.Hutao.Web.Hutao;
namespace Snap.Hutao.ViewModel.Guide;
[Injection(InjectAs.Singleton)]
internal sealed class StaticResourceOptions
internal sealed class StaticResourceOptions : ObservableObject
{
private readonly List<NameValue<StaticResourceQuality>> imageQualities = CollectionsNameValue.FromEnum<StaticResourceQuality>(q => q.GetLocalizedDescription());
private readonly List<NameValue<StaticResourceArchive>> imageArchives = CollectionsNameValue.FromEnum<StaticResourceArchive>(a => a.GetLocalizedDescription());
private NameValue<StaticResourceQuality>? imageQuality;
private NameValue<StaticResourceArchive>? imageArchive;
private string? sizeInformationText;
public StaticResourceOptions()
{
ImageQuality = ImageQualities.First(q => q.Value == UnsafeLocalSetting.Get(SettingKeys.StaticResourceImageQuality, StaticResourceQuality.Raw));
}
private StaticResourceSizeInformation? sizeInformation;
public List<NameValue<StaticResourceQuality>> ImageQualities { get => imageQualities; }
public NameValue<StaticResourceQuality>? ImageQuality
{
get => imageQuality;
get => imageQuality ??= ImageQualities.First(q => q.Value == UnsafeLocalSetting.Get(SettingKeys.StaticResourceImageQuality, StaticResourceQuality.Raw));
set
{
if (value is not null)
if (SetProperty(ref imageQuality, value) && value is not null)
{
imageQuality = value;
UnsafeLocalSetting.Set(SettingKeys.StaticResourceImageQuality, value.Value);
UpdateSizeInformationText();
}
}
}
public bool UseTrimmedArchive
public List<NameValue<StaticResourceArchive>> ImageArchives { get => imageArchives; }
public NameValue<StaticResourceArchive>? ImageArchive
{
get => LocalSetting.Get(SettingKeys.StaticResourceUseTrimmedArchive, false);
set => LocalSetting.Set(SettingKeys.StaticResourceUseTrimmedArchive, value);
get => imageArchive ??= ImageArchives.First(a => a.Value == UnsafeLocalSetting.Get(SettingKeys.StaticResourceImageArchive, StaticResourceArchive.Full));
set
{
if (SetProperty(ref imageArchive, value) && value is not null)
{
UnsafeLocalSetting.Set(SettingKeys.StaticResourceImageArchive, value.Value);
UpdateSizeInformationText();
}
}
}
public StaticResourceSizeInformation? SizeInformation
{
get => sizeInformation;
set
{
sizeInformation = value;
UpdateSizeInformationText();
}
}
public string? SizeInformationText { get => sizeInformationText; set => SetProperty(ref sizeInformationText, value); }
private void UpdateSizeInformationText()
{
if (SizeInformation is not null)
{
long result = (ImageQuality?.Value, ImageArchive?.Value) switch
{
(StaticResourceQuality.Raw, StaticResourceArchive.Full) => SizeInformation.RawFull,
(StaticResourceQuality.Raw, StaticResourceArchive.Minimum) => SizeInformation.RawMinimum,
(StaticResourceQuality.High, StaticResourceArchive.Full) => SizeInformation.HighFull,
(StaticResourceQuality.High, StaticResourceArchive.Minimum) => SizeInformation.HighMinimum,
_ => 0,
};
SizeInformationText = SH.FormatViewGuideStaticResourceDownloadSize(Converters.ToFileSizeString(result));
}
}
}

View File

@@ -19,12 +19,10 @@ internal sealed partial class PandaClient
public async ValueTask<Response<UrlWrapper>> QRCodeFetchAsync(CancellationToken token = default)
{
// Use 12 (zzz) instead of 4 (gi) temporarily to get legacy game token
GameLoginRequest options = GameLoginRequest.Create(12, HoyolabOptions.DeviceId40);
GameLoginRequest options = GameLoginRequest.Create(4, HoyolabOptions.DeviceId40);
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(ApiEndpoints.QrCodeFetch)
.SetHeader("x-rpc-device_id", HoyolabOptions.DeviceId40)
.PostJson(options);
Response<UrlWrapper>? resp = await builder
@@ -36,11 +34,10 @@ internal sealed partial class PandaClient
public async ValueTask<Response<GameLoginResult>> QRCodeQueryAsync(string ticket, CancellationToken token = default)
{
GameLoginRequest options = GameLoginRequest.Create(12, HoyolabOptions.DeviceId40, ticket);
GameLoginRequest options = GameLoginRequest.Create(4, HoyolabOptions.DeviceId40, ticket);
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(ApiEndpoints.QrCodeQuery)
.SetHeader("x-rpc-device_id", HoyolabOptions.DeviceId40)
.PostJson(options);
Response<GameLoginResult>? resp = await builder

View File

@@ -79,7 +79,6 @@ internal sealed partial class PassportClient2
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(ApiEndpoints.AccountGetSTokenByGameToken)
.SetHeader("x-rpc-device_id", HoyolabOptions.DeviceId40)
.PostJson(data);
Response<LoginResult>? resp = await builder

View File

@@ -17,6 +17,16 @@ internal sealed partial class HutaoInfrastructureClient
private readonly ILogger<HutaoInfrastructureClient> logger;
private readonly HttpClient httpClient;
public async ValueTask<HutaoResponse<StaticResourceSizeInformation>> GetStaticSizeAsync(CancellationToken token = default)
{
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(HutaoEndpoints.StaticSize)
.Get();
HutaoResponse<StaticResourceSizeInformation>? resp = await builder.TryCatchSendAsync<HutaoResponse<StaticResourceSizeInformation>>(httpClient, logger, token).ConfigureAwait(false);
return Web.Response.Response.DefaultIfNull(resp);
}
public async ValueTask<HutaoResponse<IPInformation>> GetIPInformationAsync(CancellationToken token = default)
{
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()

View File

@@ -0,0 +1,19 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hutao;
internal sealed partial class StaticResourceSizeInformation
{
[JsonPropertyName("raw_full")]
public long RawFull { get; set; }
[JsonPropertyName("raw_minimum")]
public long RawMinimum { get; set; }
[JsonPropertyName("tiny_full")]
public long HighFull { get; set; }
[JsonPropertyName("tiny_minimum")]
public long HighMinimum { get; set; }
}

View File

@@ -270,6 +270,8 @@ internal static class HutaoEndpoints
{
return $"{ApiSnapGenshinStaticZip}/{fileName}.zip";
}
public const string StaticSize = $"{ApiSnapGenshin}/static/size";
#endregion
#region Wallpaper