mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Merge pull request #1207 from DGP-Studio/develop
This commit is contained in:
6
.github/workflows/alpha.yml
vendored
6
.github/workflows/alpha.yml
vendored
@@ -57,7 +57,9 @@ jobs:
|
||||
> 普通用户请[点击这里](https://github.com/DGP-Studio/Snap.Hutao/releases/latest/)下载最新的稳定版本
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 请安装 [Snap.Hutao.CI.cer](https://github.com/DGP-Automation/Hutao-Auto-Release/releases/download/certificate/Snap.Hutao.CI.cer) 以安装测试版安装包
|
||||
> 请注意,从 Snap Hutao Alpha 2023.12.21.3 开始,我们将使用全新的 CI 证书,原有的 Snap.Hutao.CI.cer 将在几天后过期停止使用。
|
||||
>
|
||||
> 请安装 [DGP_Studio_CA.crt](https://github.com/DGP-Automation/Hutao-Auto-Release/releases/download/certificate-ca/DGP_Studio_CA.crt) 以安装测试版安装包
|
||||
"
|
||||
|
||||
echo $summary >> $Env:GITHUB_STEP_SUMMARY
|
||||
echo $summary >> $Env:GITHUB_STEP_SUMMARY
|
||||
|
||||
@@ -114,7 +114,7 @@ Task("Generate AppxManifest")
|
||||
.Replace("胡桃", "胡桃 Alpha")
|
||||
.Replace("DGP Studio", "DGP Studio CI");
|
||||
content = System.Text.RegularExpressions.Regex.Replace(content, " Name=\"([^\"]*)\"", " Name=\"7f0db578-026f-4e0b-a75b-d5d06bb0a74c\"");
|
||||
content = System.Text.RegularExpressions.Regex.Replace(content, " Publisher=\"([^\"]*)\"", " Publisher=\"CN=DGP Studio CI\"");
|
||||
content = System.Text.RegularExpressions.Regex.Replace(content, " Publisher=\"([^\"]*)\"", " Publisher=\"E=admin@dgp-studio.cn, CN=DGP Studio CI, OU=CI, O=DGP-Studio, L=San Jose, S=CA, C=US\"");
|
||||
content = System.Text.RegularExpressions.Regex.Replace(content, " Version=\"([0-9\\.]+)\"", $" Version=\"{version}\"");
|
||||
}
|
||||
else if (AppVeyor.IsRunningOnAppVeyor)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
@@ -65,6 +66,20 @@ public sealed class JsonSerializeTest
|
||||
Assert.AreEqual(result, """{"Array":"AQIDBAU="}""");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void InterfaceDefaultMethodCanSerializeActualInstanceMember()
|
||||
{
|
||||
ISampleInterface sample = new SampleClassImplementedInterface()
|
||||
{
|
||||
A = 1,
|
||||
B = 2,
|
||||
};
|
||||
|
||||
string result = sample.ToJson();
|
||||
Console.WriteLine(result);
|
||||
Assert.AreEqual(result, """{"A":1,"B":2}""");
|
||||
}
|
||||
|
||||
private sealed class SampleDelegatePropertyClass
|
||||
{
|
||||
public int A { get => B; set => B = value; }
|
||||
@@ -81,4 +96,22 @@ public sealed class JsonSerializeTest
|
||||
{
|
||||
public byte[]? Array { get; set; }
|
||||
}
|
||||
|
||||
private sealed class SampleClassImplementedInterface : ISampleInterface
|
||||
{
|
||||
public int A { get; set; }
|
||||
|
||||
public int B { get; set; }
|
||||
}
|
||||
|
||||
[JsonDerivedType(typeof(SampleClassImplementedInterface))]
|
||||
private interface ISampleInterface
|
||||
{
|
||||
int A { get; set; }
|
||||
|
||||
string ToJson()
|
||||
{
|
||||
return JsonSerializer.Serialize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,16 @@ CloseHandle
|
||||
CreateEventW
|
||||
CreateRemoteThread
|
||||
FreeConsole
|
||||
GetConsoleMode
|
||||
GetModuleHandleW
|
||||
GetProcAddress
|
||||
GetStdHandle
|
||||
K32EnumProcessModules
|
||||
K32GetModuleBaseNameW
|
||||
K32GetModuleInformation
|
||||
ReadProcessMemory
|
||||
SetConsoleMode
|
||||
SetConsoleTitle
|
||||
SetEvent
|
||||
VirtualAlloc
|
||||
VirtualAllocEx
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control;
|
||||
|
||||
/// <summary>
|
||||
/// 封装的值
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class BoxedValues
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="true"/>
|
||||
/// </summary>
|
||||
public static readonly object True = true;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="false"/>
|
||||
/// </summary>
|
||||
public static readonly object False = false;
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
namespace Snap.Hutao.Control;
|
||||
|
||||
internal interface IScopedPageScopeReferenceTracker
|
||||
internal interface IScopedPageScopeReferenceTracker : IDisposable
|
||||
{
|
||||
IServiceScope CreateScope();
|
||||
}
|
||||
@@ -9,10 +9,6 @@ using Snap.Hutao.ViewModel.Abstraction;
|
||||
|
||||
namespace Snap.Hutao.Control;
|
||||
|
||||
/// <summary>
|
||||
/// 表示支持取消加载的异步页面
|
||||
/// 在被导航到其他页面前触发取消异步通知
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[SuppressMessage("", "CA1001")]
|
||||
internal class ScopedPage : Page
|
||||
@@ -21,9 +17,8 @@ internal class ScopedPage : Page
|
||||
private readonly CancellationTokenSource viewCancellationTokenSource = new();
|
||||
private readonly IServiceScope currentScope;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的页面
|
||||
/// </summary>
|
||||
private bool inFrame = true;
|
||||
|
||||
protected ScopedPage()
|
||||
{
|
||||
unloadEventHandler = OnUnloaded;
|
||||
@@ -31,11 +26,6 @@ internal class ScopedPage : Page
|
||||
currentScope = Ioc.Default.GetRequiredService<IScopedPageScopeReferenceTracker>().CreateScope();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步通知接收器
|
||||
/// </summary>
|
||||
/// <param name="extra">额外内容</param>
|
||||
/// <returns>任务</returns>
|
||||
public async ValueTask NotifyRecipientAsync(INavigationData extra)
|
||||
{
|
||||
if (extra.Data is not null && DataContext is INavigationRecipient recipient)
|
||||
@@ -61,6 +51,32 @@ internal class ScopedPage : Page
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
|
||||
{
|
||||
DisposeViewModel();
|
||||
inFrame = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
if (e.Parameter is INavigationData extra)
|
||||
{
|
||||
NotifyRecipientAsync(extra).SafeForget();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUnloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (inFrame)
|
||||
{
|
||||
DisposeViewModel();
|
||||
}
|
||||
|
||||
DataContext = null;
|
||||
Unloaded -= unloadEventHandler;
|
||||
}
|
||||
|
||||
private void DisposeViewModel()
|
||||
{
|
||||
using (viewCancellationTokenSource)
|
||||
{
|
||||
@@ -79,19 +95,4 @@ internal class ScopedPage : Page
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
if (e.Parameter is INavigationData extra)
|
||||
{
|
||||
NotifyRecipientAsync(extra).SafeForget();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUnloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DataContext = null;
|
||||
Unloaded -= unloadEventHandler;
|
||||
}
|
||||
}
|
||||
23
src/Snap.Hutao/Snap.Hutao/Core/IO/Hashing/SHA256.cs
Normal file
23
src/Snap.Hutao/Snap.Hutao/Core/IO/Hashing/SHA256.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Hashing;
|
||||
|
||||
internal static class SHA256
|
||||
{
|
||||
public static async ValueTask<string> HashFileAsync(string filePath, CancellationToken token = default)
|
||||
{
|
||||
using (FileStream stream = File.OpenRead(filePath))
|
||||
{
|
||||
return await HashAsync(stream, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static async ValueTask<string> HashAsync(Stream stream, CancellationToken token = default)
|
||||
{
|
||||
byte[] bytes = await System.Security.Cryptography.SHA256.HashDataAsync(stream, token).ConfigureAwait(false);
|
||||
return System.Convert.ToHexString(bytes);
|
||||
}
|
||||
}
|
||||
@@ -130,7 +130,7 @@ internal sealed class HttpShardCopyWorker<TStatus> : IDisposable
|
||||
BytesRead = bytesRead;
|
||||
}
|
||||
|
||||
public int BytesRead { get; set; }
|
||||
public int BytesRead { get; }
|
||||
}
|
||||
|
||||
private sealed class ShardProgress : IProgress<ShardStatus>
|
||||
@@ -152,11 +152,11 @@ internal sealed class HttpShardCopyWorker<TStatus> : IDisposable
|
||||
public void Report(ShardStatus value)
|
||||
{
|
||||
Interlocked.Add(ref totalBytesRead, value.BytesRead);
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds > 500)
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds > 1000 || totalBytesRead == contentLength)
|
||||
{
|
||||
lock (syncRoot)
|
||||
{
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds > 500)
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds > 1000 || totalBytesRead == contentLength)
|
||||
{
|
||||
workerProgress.Report(statusFactory(totalBytesRead, contentLength));
|
||||
stopwatch = ValueStopwatch.StartNew();
|
||||
|
||||
@@ -65,7 +65,7 @@ internal class StreamCopyWorker<TStatus>
|
||||
await destination.WriteAsync(buffer[..bytesRead]).ConfigureAwait(false);
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds > 500)
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds > 1000)
|
||||
{
|
||||
progress.Report(statusFactory(totalBytesRead));
|
||||
stopwatch = ValueStopwatch.StartNew();
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.System.Console;
|
||||
using static Windows.Win32.PInvoke;
|
||||
|
||||
namespace Snap.Hutao.Core.Logging;
|
||||
@@ -15,6 +17,17 @@ internal sealed class ConsoleWindowLifeTime : IDisposable
|
||||
if (LocalSetting.Get(SettingKeys.IsAllocConsoleDebugModeEnabled, false))
|
||||
{
|
||||
consoleWindowAllocated = AllocConsole();
|
||||
if (consoleWindowAllocated)
|
||||
{
|
||||
HANDLE inputHandle = GetStdHandle(STD_HANDLE.STD_INPUT_HANDLE);
|
||||
if (GetConsoleMode(inputHandle, out CONSOLE_MODE mode))
|
||||
{
|
||||
mode &= ~CONSOLE_MODE.ENABLE_QUICK_EDIT_MODE;
|
||||
SetConsoleMode(inputHandle, mode);
|
||||
}
|
||||
|
||||
SetConsoleTitle("Snap Hutao Debug Console");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,20 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
|
||||
internal static class UnsafeDateTimeOffset
|
||||
{
|
||||
[SuppressMessage("", "SH002")]
|
||||
public static DateTimeOffset ParseDateTime(ReadOnlySpan<char> span, TimeSpan offset)
|
||||
{
|
||||
DateTime dateTime = DateTime.Parse(span, CultureInfo.InvariantCulture);
|
||||
return new(dateTime, offset);
|
||||
}
|
||||
|
||||
[Pure]
|
||||
[SuppressMessage("", "SH002")]
|
||||
public static unsafe DateTimeOffset AdjustOffsetOnly(DateTimeOffset dateTimeOffset, in TimeSpan offset)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Control;
|
||||
using Snap.Hutao.Core.Windowing;
|
||||
using Windows.Foundation;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
@@ -20,9 +21,6 @@ internal sealed partial class MainWindow : Window, IWindowOptionsSource, IMinMax
|
||||
private const int MinHeight = 600;
|
||||
|
||||
private readonly WindowOptions windowOptions;
|
||||
private readonly ILogger<MainWindow> logger;
|
||||
private readonly TypedEventHandler<object, WindowEventArgs> closedEventHander;
|
||||
private readonly TypedEventHandler<object, WindowSizeChangedEventArgs> sizeChangedEventHandler;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的主窗体
|
||||
@@ -33,13 +31,6 @@ internal sealed partial class MainWindow : Window, IWindowOptionsSource, IMinMax
|
||||
InitializeComponent();
|
||||
windowOptions = new(this, TitleBarView.DragArea, new(1200, 741), true);
|
||||
this.InitializeController(serviceProvider);
|
||||
logger = serviceProvider.GetRequiredService<ILogger<MainWindow>>();
|
||||
|
||||
closedEventHander = OnClosed;
|
||||
sizeChangedEventHandler = OnSizeChanged;
|
||||
|
||||
Closed += closedEventHander;
|
||||
SizeChanged += sizeChangedEventHandler;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -51,13 +42,4 @@ internal sealed partial class MainWindow : Window, IWindowOptionsSource, IMinMax
|
||||
pInfo.ptMinTrackSize.X = (int)Math.Max(MinWidth * scalingFactor, pInfo.ptMinTrackSize.X);
|
||||
pInfo.ptMinTrackSize.Y = (int)Math.Max(MinHeight * scalingFactor, pInfo.ptMinTrackSize.Y);
|
||||
}
|
||||
|
||||
private void OnClosed(object sender, WindowEventArgs args)
|
||||
{
|
||||
logger.LogInformation("MainWindow Closed");
|
||||
}
|
||||
|
||||
private void OnSizeChanged(object sender, WindowSizeChangedEventArgs args)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -127,4 +127,6 @@ internal sealed partial class SettingEntry
|
||||
/// 自定义极验接口
|
||||
/// </summary>
|
||||
public const string GeetestCustomCompositeUrl = "GeetestCustomCompositeUrl";
|
||||
|
||||
public const string AnnouncementRegion = "AnnouncementRegion";
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<Identity
|
||||
Name="60568DGPStudio.SnapHutao"
|
||||
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
|
||||
Version="1.8.5.0" />
|
||||
Version="1.9.0.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Snap Hutao</DisplayName>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<Identity
|
||||
Name="60568DGPStudio.SnapHutaoDev"
|
||||
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
|
||||
Version="1.8.4.0" />
|
||||
Version="1.9.0.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Snap Hutao Dev</DisplayName>
|
||||
|
||||
@@ -995,6 +995,9 @@
|
||||
<data name="ServiceUIGFImportUnsupportedVersion" xml:space="preserve">
|
||||
<value>Unsupported UIGF version</value>
|
||||
</data>
|
||||
<data name="ServiceUpdateStatusVersionDescription" xml:space="preserve">
|
||||
<value>A new version {0} is available.</value>
|
||||
</data>
|
||||
<data name="ServiceUserCurrentMultiMatched" xml:space="preserve">
|
||||
<value>Multiple users recorded as selected</value>
|
||||
</data>
|
||||
@@ -1649,6 +1652,9 @@
|
||||
<data name="ViewPageAnnouncementGame" xml:space="preserve">
|
||||
<value>Game Notice</value>
|
||||
</data>
|
||||
<data name="ViewPageAnnouncementViewDetails" xml:space="preserve">
|
||||
<value>View Details</value>
|
||||
</data>
|
||||
<data name="ViewPageAvatarPropertyArtifactScore" xml:space="preserve">
|
||||
<value>Artifacts Rating</value>
|
||||
</data>
|
||||
@@ -2306,6 +2312,12 @@
|
||||
<data name="ViewPageSettingGeetestVerificationHeader" xml:space="preserve">
|
||||
<value>CAPTCHA</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingHomeAnnouncementRegionDescription" xml:space="preserve">
|
||||
<value>Select the game server for which you want to get announcements</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingHomeAnnouncementRegionHeader" xml:space="preserve">
|
||||
<value>Announcement Server</value>
|
||||
</data>
|
||||
<data name="ViewpageSettingHomeCardDescription" xml:space="preserve">
|
||||
<value>Manage cards on home dashboard</value>
|
||||
</data>
|
||||
@@ -2612,6 +2624,12 @@
|
||||
<data name="ViewSpiralAbyssUploadRecord" xml:space="preserve">
|
||||
<value>Upload Data</value>
|
||||
</data>
|
||||
<data name="ViewTitileUpdatePackageReadyContent" xml:space="preserve">
|
||||
<value>Install now?</value>
|
||||
</data>
|
||||
<data name="ViewTitileUpdatePackageReadyTitle" xml:space="preserve">
|
||||
<value>Snap Hutao version {0} is ready</value>
|
||||
</data>
|
||||
<data name="ViewTitleAutoClicking" xml:space="preserve">
|
||||
<value>Auto Click</value>
|
||||
</data>
|
||||
@@ -2706,7 +2724,7 @@
|
||||
<value>〓Update Maintenance Duration.+?&lt;t class=\"t_(?:gl|lc)\".*?&gt;(.*?)&lt;/t&gt;</value>
|
||||
</data>
|
||||
<data name="WebAnnouncementMatchVersionUpdateTitle" xml:space="preserve">
|
||||
<value>Version \d\.\d Update Maintenance Preview</value>
|
||||
<value>Version \d\.\d Update Details</value>
|
||||
</data>
|
||||
<data name="WebAnnouncementTimeDaysBeginFormat" xml:space="preserve">
|
||||
<value>Start in {0} days</value>
|
||||
@@ -2852,9 +2870,30 @@
|
||||
<data name="WebGameResourcePathCopySucceed" xml:space="preserve">
|
||||
<value>Copy Link Successful</value>
|
||||
</data>
|
||||
<data name="WebHoyolabInvalidRegion" xml:space="preserve">
|
||||
<value>Invalid server</value>
|
||||
</data>
|
||||
<data name="WebHoyolabInvalidUid" xml:space="preserve">
|
||||
<value>Invalid UID</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionCNGF01" xml:space="preserve">
|
||||
<value>CN Server: Official</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionCNQD01" xml:space="preserve">
|
||||
<value>CN Server: bilibili</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSASIA" xml:space="preserve">
|
||||
<value>Oversea Server: Asian</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSCHT" xml:space="preserve">
|
||||
<value>Oversea Server: TW/HK/MU server</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSEURO" xml:space="preserve">
|
||||
<value>Oversea Server: EU</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSUSA" xml:space="preserve">
|
||||
<value>Oversea Server: NA</value>
|
||||
</data>
|
||||
<data name="WebHutaoServiceUnAvailable" xml:space="preserve">
|
||||
<value>Snap Hutao server is under maintenance</value>
|
||||
</data>
|
||||
|
||||
2912
src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.id.resx
Normal file
2912
src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.id.resx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -995,6 +995,9 @@
|
||||
<data name="ServiceUIGFImportUnsupportedVersion" xml:space="preserve">
|
||||
<value>サポートされていないUIGFバージョン</value>
|
||||
</data>
|
||||
<data name="ServiceUpdateStatusVersionDescription" xml:space="preserve">
|
||||
<value>发现新版本 {0}</value>
|
||||
</data>
|
||||
<data name="ServiceUserCurrentMultiMatched" xml:space="preserve">
|
||||
<value>ユーザー情報を複数選択しています。</value>
|
||||
</data>
|
||||
@@ -1649,6 +1652,9 @@
|
||||
<data name="ViewPageAnnouncementGame" xml:space="preserve">
|
||||
<value>重要</value>
|
||||
</data>
|
||||
<data name="ViewPageAnnouncementViewDetails" xml:space="preserve">
|
||||
<value>查看详情</value>
|
||||
</data>
|
||||
<data name="ViewPageAvatarPropertyArtifactScore" xml:space="preserve">
|
||||
<value>聖遺物スコア</value>
|
||||
</data>
|
||||
@@ -2306,6 +2312,12 @@
|
||||
<data name="ViewPageSettingGeetestVerificationHeader" xml:space="preserve">
|
||||
<value>CAPTCHA</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingHomeAnnouncementRegionDescription" xml:space="preserve">
|
||||
<value>选择想要获取公告的游戏服务器</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingHomeAnnouncementRegionHeader" xml:space="preserve">
|
||||
<value>公告所属服务器</value>
|
||||
</data>
|
||||
<data name="ViewpageSettingHomeCardDescription" xml:space="preserve">
|
||||
<value>ダッシュボードを整理する</value>
|
||||
</data>
|
||||
@@ -2612,6 +2624,12 @@
|
||||
<data name="ViewSpiralAbyssUploadRecord" xml:space="preserve">
|
||||
<value>データをアップロード</value>
|
||||
</data>
|
||||
<data name="ViewTitileUpdatePackageReadyContent" xml:space="preserve">
|
||||
<value>是否立即安装?</value>
|
||||
</data>
|
||||
<data name="ViewTitileUpdatePackageReadyTitle" xml:space="preserve">
|
||||
<value>胡桃 {0} 版本已准备就绪</value>
|
||||
</data>
|
||||
<data name="ViewTitleAutoClicking" xml:space="preserve">
|
||||
<value>オートクリック</value>
|
||||
</data>
|
||||
@@ -2852,9 +2870,30 @@
|
||||
<data name="WebGameResourcePathCopySucceed" xml:space="preserve">
|
||||
<value>ダウンロードリンクのコピーに成功しました</value>
|
||||
</data>
|
||||
<data name="WebHoyolabInvalidRegion" xml:space="preserve">
|
||||
<value>无效的服务器</value>
|
||||
</data>
|
||||
<data name="WebHoyolabInvalidUid" xml:space="preserve">
|
||||
<value>無効なUIDです</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionCNGF01" xml:space="preserve">
|
||||
<value>国服 官方服</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionCNQD01" xml:space="preserve">
|
||||
<value>国服 渠道服</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSASIA" xml:space="preserve">
|
||||
<value>国际服 亚服</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSCHT" xml:space="preserve">
|
||||
<value>国际服 港澳台服</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSEURO" xml:space="preserve">
|
||||
<value>国际服 欧服</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSUSA" xml:space="preserve">
|
||||
<value>国际服 美服</value>
|
||||
</data>
|
||||
<data name="WebHutaoServiceUnAvailable" xml:space="preserve">
|
||||
<value>胡桃サーバがメンテナンス中です</value>
|
||||
</data>
|
||||
|
||||
@@ -995,6 +995,9 @@
|
||||
<data name="ServiceUIGFImportUnsupportedVersion" xml:space="preserve">
|
||||
<value>不支持的 UIGF 版本</value>
|
||||
</data>
|
||||
<data name="ServiceUpdateStatusVersionDescription" xml:space="preserve">
|
||||
<value>发现新版本 {0}</value>
|
||||
</data>
|
||||
<data name="ServiceUserCurrentMultiMatched" xml:space="preserve">
|
||||
<value>여러 사용자가 선택되었습니다</value>
|
||||
</data>
|
||||
@@ -1649,6 +1652,9 @@
|
||||
<data name="ViewPageAnnouncementGame" xml:space="preserve">
|
||||
<value>게임 공지</value>
|
||||
</data>
|
||||
<data name="ViewPageAnnouncementViewDetails" xml:space="preserve">
|
||||
<value>查看详情</value>
|
||||
</data>
|
||||
<data name="ViewPageAvatarPropertyArtifactScore" xml:space="preserve">
|
||||
<value>성유물 점수</value>
|
||||
</data>
|
||||
@@ -2306,6 +2312,12 @@
|
||||
<data name="ViewPageSettingGeetestVerificationHeader" xml:space="preserve">
|
||||
<value>无感验证</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingHomeAnnouncementRegionDescription" xml:space="preserve">
|
||||
<value>选择想要获取公告的游戏服务器</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingHomeAnnouncementRegionHeader" xml:space="preserve">
|
||||
<value>公告所属服务器</value>
|
||||
</data>
|
||||
<data name="ViewpageSettingHomeCardDescription" xml:space="preserve">
|
||||
<value>管理主页仪表板中的卡片</value>
|
||||
</data>
|
||||
@@ -2612,6 +2624,12 @@
|
||||
<data name="ViewSpiralAbyssUploadRecord" xml:space="preserve">
|
||||
<value>데이터 업로드</value>
|
||||
</data>
|
||||
<data name="ViewTitileUpdatePackageReadyContent" xml:space="preserve">
|
||||
<value>是否立即安装?</value>
|
||||
</data>
|
||||
<data name="ViewTitileUpdatePackageReadyTitle" xml:space="preserve">
|
||||
<value>胡桃 {0} 版本已准备就绪</value>
|
||||
</data>
|
||||
<data name="ViewTitleAutoClicking" xml:space="preserve">
|
||||
<value>自动连点</value>
|
||||
</data>
|
||||
@@ -2852,9 +2870,30 @@
|
||||
<data name="WebGameResourcePathCopySucceed" xml:space="preserve">
|
||||
<value>下载链接复制成功</value>
|
||||
</data>
|
||||
<data name="WebHoyolabInvalidRegion" xml:space="preserve">
|
||||
<value>无效的服务器</value>
|
||||
</data>
|
||||
<data name="WebHoyolabInvalidUid" xml:space="preserve">
|
||||
<value>无效的 UID</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionCNGF01" xml:space="preserve">
|
||||
<value>国服 官方服</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionCNQD01" xml:space="preserve">
|
||||
<value>国服 渠道服</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSASIA" xml:space="preserve">
|
||||
<value>国际服 亚服</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSCHT" xml:space="preserve">
|
||||
<value>国际服 港澳台服</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSEURO" xml:space="preserve">
|
||||
<value>国际服 欧服</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSUSA" xml:space="preserve">
|
||||
<value>国际服 美服</value>
|
||||
</data>
|
||||
<data name="WebHutaoServiceUnAvailable" xml:space="preserve">
|
||||
<value>胡桃服务维护中</value>
|
||||
</data>
|
||||
|
||||
@@ -995,6 +995,9 @@
|
||||
<data name="ServiceUIGFImportUnsupportedVersion" xml:space="preserve">
|
||||
<value>不支持的 UIGF 版本</value>
|
||||
</data>
|
||||
<data name="ServiceUpdateStatusVersionDescription" xml:space="preserve">
|
||||
<value>发现新版本 {0}</value>
|
||||
</data>
|
||||
<data name="ServiceUserCurrentMultiMatched" xml:space="preserve">
|
||||
<value>多个用户记录为选中状态</value>
|
||||
</data>
|
||||
@@ -1649,6 +1652,9 @@
|
||||
<data name="ViewPageAnnouncementGame" xml:space="preserve">
|
||||
<value>游戏公告</value>
|
||||
</data>
|
||||
<data name="ViewPageAnnouncementViewDetails" xml:space="preserve">
|
||||
<value>查看详情</value>
|
||||
</data>
|
||||
<data name="ViewPageAvatarPropertyArtifactScore" xml:space="preserve">
|
||||
<value>圣遗物评分</value>
|
||||
</data>
|
||||
@@ -2306,6 +2312,12 @@
|
||||
<data name="ViewPageSettingGeetestVerificationHeader" xml:space="preserve">
|
||||
<value>无感验证</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingHomeAnnouncementRegionDescription" xml:space="preserve">
|
||||
<value>选择想要获取公告的游戏服务器</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingHomeAnnouncementRegionHeader" xml:space="preserve">
|
||||
<value>公告所属服务器</value>
|
||||
</data>
|
||||
<data name="ViewpageSettingHomeCardDescription" xml:space="preserve">
|
||||
<value>管理主页仪表板中的卡片</value>
|
||||
</data>
|
||||
@@ -2612,6 +2624,12 @@
|
||||
<data name="ViewSpiralAbyssUploadRecord" xml:space="preserve">
|
||||
<value>上传数据</value>
|
||||
</data>
|
||||
<data name="ViewTitileUpdatePackageReadyContent" xml:space="preserve">
|
||||
<value>是否立即安装?</value>
|
||||
</data>
|
||||
<data name="ViewTitileUpdatePackageReadyTitle" xml:space="preserve">
|
||||
<value>胡桃 {0} 版本已准备就绪</value>
|
||||
</data>
|
||||
<data name="ViewTitleAutoClicking" xml:space="preserve">
|
||||
<value>自动连点</value>
|
||||
</data>
|
||||
@@ -2852,9 +2870,30 @@
|
||||
<data name="WebGameResourcePathCopySucceed" xml:space="preserve">
|
||||
<value>下载链接复制成功</value>
|
||||
</data>
|
||||
<data name="WebHoyolabInvalidRegion" xml:space="preserve">
|
||||
<value>无效的服务器</value>
|
||||
</data>
|
||||
<data name="WebHoyolabInvalidUid" xml:space="preserve">
|
||||
<value>无效的 UID</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionCNGF01" xml:space="preserve">
|
||||
<value>国服 官方服</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionCNQD01" xml:space="preserve">
|
||||
<value>国服 渠道服</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSASIA" xml:space="preserve">
|
||||
<value>国际服 亚服</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSCHT" xml:space="preserve">
|
||||
<value>国际服 港澳台服</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSEURO" xml:space="preserve">
|
||||
<value>国际服 欧服</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSUSA" xml:space="preserve">
|
||||
<value>国际服 美服</value>
|
||||
</data>
|
||||
<data name="WebHutaoServiceUnAvailable" xml:space="preserve">
|
||||
<value>胡桃服务维护中</value>
|
||||
</data>
|
||||
|
||||
2912
src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.ru.resx
Normal file
2912
src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.ru.resx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -995,6 +995,9 @@
|
||||
<data name="ServiceUIGFImportUnsupportedVersion" xml:space="preserve">
|
||||
<value>不支援的 UIGF 版本</value>
|
||||
</data>
|
||||
<data name="ServiceUpdateStatusVersionDescription" xml:space="preserve">
|
||||
<value>发现新版本 {0}</value>
|
||||
</data>
|
||||
<data name="ServiceUserCurrentMultiMatched" xml:space="preserve">
|
||||
<value>已选中多条用户记录</value>
|
||||
</data>
|
||||
@@ -1649,6 +1652,9 @@
|
||||
<data name="ViewPageAnnouncementGame" xml:space="preserve">
|
||||
<value>遊戲公告</value>
|
||||
</data>
|
||||
<data name="ViewPageAnnouncementViewDetails" xml:space="preserve">
|
||||
<value>查看详情</value>
|
||||
</data>
|
||||
<data name="ViewPageAvatarPropertyArtifactScore" xml:space="preserve">
|
||||
<value>聖遺物評分</value>
|
||||
</data>
|
||||
@@ -2306,6 +2312,12 @@
|
||||
<data name="ViewPageSettingGeetestVerificationHeader" xml:space="preserve">
|
||||
<value>無感驗證</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingHomeAnnouncementRegionDescription" xml:space="preserve">
|
||||
<value>选择想要获取公告的游戏服务器</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingHomeAnnouncementRegionHeader" xml:space="preserve">
|
||||
<value>公告所属服务器</value>
|
||||
</data>
|
||||
<data name="ViewpageSettingHomeCardDescription" xml:space="preserve">
|
||||
<value>管理主頁儀表板中的卡片</value>
|
||||
</data>
|
||||
@@ -2612,6 +2624,12 @@
|
||||
<data name="ViewSpiralAbyssUploadRecord" xml:space="preserve">
|
||||
<value>上傳資料</value>
|
||||
</data>
|
||||
<data name="ViewTitileUpdatePackageReadyContent" xml:space="preserve">
|
||||
<value>是否立即安装?</value>
|
||||
</data>
|
||||
<data name="ViewTitileUpdatePackageReadyTitle" xml:space="preserve">
|
||||
<value>胡桃 {0} 版本已准备就绪</value>
|
||||
</data>
|
||||
<data name="ViewTitleAutoClicking" xml:space="preserve">
|
||||
<value>自動連續點按</value>
|
||||
</data>
|
||||
@@ -2852,9 +2870,30 @@
|
||||
<data name="WebGameResourcePathCopySucceed" xml:space="preserve">
|
||||
<value>下載連結複製成功</value>
|
||||
</data>
|
||||
<data name="WebHoyolabInvalidRegion" xml:space="preserve">
|
||||
<value>无效的服务器</value>
|
||||
</data>
|
||||
<data name="WebHoyolabInvalidUid" xml:space="preserve">
|
||||
<value>無效的 UID</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionCNGF01" xml:space="preserve">
|
||||
<value>国服 官方服</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionCNQD01" xml:space="preserve">
|
||||
<value>国服 渠道服</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSASIA" xml:space="preserve">
|
||||
<value>国际服 亚服</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSCHT" xml:space="preserve">
|
||||
<value>国际服 港澳台服</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSEURO" xml:space="preserve">
|
||||
<value>国际服 欧服</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSUSA" xml:space="preserve">
|
||||
<value>国际服 美服</value>
|
||||
</data>
|
||||
<data name="WebHutaoServiceUnAvailable" xml:space="preserve">
|
||||
<value>胡桃服務維護中</value>
|
||||
</data>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
|
||||
|
||||
namespace Snap.Hutao.Service.Abstraction;
|
||||
@@ -14,7 +15,9 @@ internal interface IAnnouncementService
|
||||
/// <summary>
|
||||
/// 异步获取游戏公告与活动,通常会进行缓存
|
||||
/// </summary>
|
||||
/// <param name="languageCode">语言代码</param>
|
||||
/// <param name="region">服务器</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>公告包装器</returns>
|
||||
ValueTask<AnnouncementWrapper> GetAnnouncementWrapperAsync(CancellationToken cancellationToken = default);
|
||||
ValueTask<AnnouncementWrapper> GetAnnouncementWrapperAsync(string languageCode, Region region, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.Globalization;
|
||||
@@ -25,17 +27,17 @@ internal sealed partial class AnnouncementService : IAnnouncementService
|
||||
private readonly IMemoryCache memoryCache;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<AnnouncementWrapper> GetAnnouncementWrapperAsync(CancellationToken cancellationToken = default)
|
||||
public async ValueTask<AnnouncementWrapper> GetAnnouncementWrapperAsync(string languageCode, Region region, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 缓存中存在记录,直接返回
|
||||
if (memoryCache.TryGetRequiredValue(CacheKey, out AnnouncementWrapper? cache))
|
||||
if (memoryCache.TryGetRequiredValue($"{CacheKey}.{languageCode}.{region}", out AnnouncementWrapper? cache))
|
||||
{
|
||||
return cache;
|
||||
}
|
||||
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
Response<AnnouncementWrapper> announcementWrapperResponse = await announcementClient
|
||||
.GetAnnouncementsAsync(cancellationToken)
|
||||
.GetAnnouncementsAsync(languageCode, region, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!announcementWrapperResponse.IsOk())
|
||||
@@ -45,7 +47,7 @@ internal sealed partial class AnnouncementService : IAnnouncementService
|
||||
|
||||
AnnouncementWrapper wrapper = announcementWrapperResponse.Data;
|
||||
Response<ListWrapper<AnnouncementContent>> announcementContentResponse = await announcementClient
|
||||
.GetAnnouncementContentsAsync(cancellationToken)
|
||||
.GetAnnouncementContentsAsync(languageCode, region, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!announcementContentResponse.IsOk())
|
||||
@@ -61,12 +63,12 @@ internal sealed partial class AnnouncementService : IAnnouncementService
|
||||
// 将活动公告置于前方
|
||||
wrapper.List.Reverse();
|
||||
|
||||
PreprocessAnnouncements(contentMap, wrapper.List);
|
||||
PreprocessAnnouncements(contentMap, wrapper.List, new(wrapper.TimeZone, 0, 0));
|
||||
|
||||
return memoryCache.Set(CacheKey, wrapper, TimeSpan.FromMinutes(30));
|
||||
}
|
||||
|
||||
private static void PreprocessAnnouncements(Dictionary<int, string> contentMap, List<AnnouncementListWrapper> announcementListWrappers)
|
||||
private static void PreprocessAnnouncements(Dictionary<int, string> contentMap, List<AnnouncementListWrapper> announcementListWrappers, in TimeSpan offset)
|
||||
{
|
||||
// 将公告内容联入公告列表
|
||||
foreach (ref readonly AnnouncementListWrapper listWrapper in CollectionsMarshal.AsSpan(announcementListWrappers))
|
||||
@@ -78,7 +80,7 @@ internal sealed partial class AnnouncementService : IAnnouncementService
|
||||
}
|
||||
}
|
||||
|
||||
AdjustAnnouncementTime(announcementListWrappers);
|
||||
AdjustAnnouncementTime(announcementListWrappers, offset);
|
||||
|
||||
foreach (ref readonly AnnouncementListWrapper listWrapper in CollectionsMarshal.AsSpan(announcementListWrappers))
|
||||
{
|
||||
@@ -90,7 +92,7 @@ internal sealed partial class AnnouncementService : IAnnouncementService
|
||||
}
|
||||
}
|
||||
|
||||
private static void AdjustAnnouncementTime(List<AnnouncementListWrapper> announcementListWrappers)
|
||||
private static void AdjustAnnouncementTime(List<AnnouncementListWrapper> announcementListWrappers, in TimeSpan offset)
|
||||
{
|
||||
// 活动公告
|
||||
List<Announcement> activities = announcementListWrappers
|
||||
@@ -103,12 +105,12 @@ internal sealed partial class AnnouncementService : IAnnouncementService
|
||||
.List
|
||||
.Single(ann => AnnouncementRegex.VersionUpdateTitleRegex.IsMatch(ann.Title));
|
||||
|
||||
if (AnnouncementRegex.VersionUpdateTimeRegex.Match(versionUpdate.Content) is not { Success: true } match)
|
||||
if (AnnouncementRegex.VersionUpdateTimeRegex.Match(versionUpdate.Content) is not { Success: true } versionMatch)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DateTimeOffset versionUpdateTime = DateTimeOffset.Parse(match.Groups[1].ValueSpan, CultureInfo.InvariantCulture);
|
||||
DateTimeOffset versionUpdateTime = UnsafeDateTimeOffset.ParseDateTime(versionMatch.Groups[1].ValueSpan, offset);
|
||||
|
||||
foreach (ref readonly Announcement announcement in CollectionsMarshal.AsSpan(activities))
|
||||
{
|
||||
@@ -128,7 +130,7 @@ internal sealed partial class AnnouncementService : IAnnouncementService
|
||||
if (AnnouncementRegex.TransientActivityAfterUpdateTimeRegex.Match(announcement.Content) is { Success: true } transient)
|
||||
{
|
||||
announcement.StartTime = versionUpdateTime;
|
||||
announcement.EndTime = DateTimeOffset.Parse(transient.Groups[2].ValueSpan, CultureInfo.InvariantCulture);
|
||||
announcement.EndTime = UnsafeDateTimeOffset.ParseDateTime(transient.Groups[2].ValueSpan, offset);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -138,7 +140,12 @@ internal sealed partial class AnnouncementService : IAnnouncementService
|
||||
continue;
|
||||
}
|
||||
|
||||
List<DateTimeOffset> dateTimes = matches.Select(match => DateTimeOffset.Parse(match.Groups[1].ValueSpan, CultureInfo.InvariantCulture)).ToList();
|
||||
List<DateTimeOffset> dateTimes = [];
|
||||
foreach (Match timeMatch in (IList<Match>)matches)
|
||||
{
|
||||
dateTimes.Add(UnsafeDateTimeOffset.ParseDateTime(timeMatch.Groups[1].ValueSpan, offset));
|
||||
}
|
||||
|
||||
DateTimeOffset min = DateTimeOffset.MaxValue;
|
||||
DateTimeOffset max = DateTimeOffset.MinValue;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Snap.Hutao.Core.Windowing;
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
@@ -19,6 +20,7 @@ internal sealed partial class AppOptions : DbStoreOptions
|
||||
private bool? isEmptyHistoryWishVisible;
|
||||
private BackdropType? backdropType;
|
||||
private CultureInfo? currentCulture;
|
||||
private Region? region;
|
||||
private string? geetestCustomCompositeUrl;
|
||||
|
||||
public string PowerShellPath
|
||||
@@ -72,6 +74,14 @@ internal sealed partial class AppOptions : DbStoreOptions
|
||||
set => SetOption(ref currentCulture, SettingEntry.Culture, value, value => value.Name);
|
||||
}
|
||||
|
||||
public Lazy<List<NameValue<Region>>> LazyRegions { get; } = new(KnownRegions.Get);
|
||||
|
||||
public Region Region
|
||||
{
|
||||
get => GetOption(ref region, SettingEntry.AnnouncementRegion, v => Region.FromRegionString(v), Region.CNGF01).Value;
|
||||
set => SetOption(ref region, SettingEntry.AnnouncementRegion, value, value => value.ToStringOrEmpty());
|
||||
}
|
||||
|
||||
public string GeetestCustomCompositeUrl
|
||||
{
|
||||
get => GetOption(ref geetestCustomCompositeUrl, SettingEntry.GeetestCustomCompositeUrl);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Snap.Hutao.Service;
|
||||
@@ -12,4 +13,9 @@ internal static class AppOptionsExtension
|
||||
{
|
||||
return appOptions.Cultures.SingleOrDefault(c => c.Value == appOptions.CurrentCulture);
|
||||
}
|
||||
|
||||
public static NameValue<Region>? GetCurrentRegionForSelectionOrDefault(this AppOptions appOptions)
|
||||
{
|
||||
return appOptions.LazyRegions.Value.SingleOrDefault(c => c.Value.Value == appOptions.Region.Value);
|
||||
}
|
||||
}
|
||||
@@ -133,23 +133,9 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
|
||||
|
||||
private static int IndexOfPattern(in ReadOnlySpan<byte> memory)
|
||||
{
|
||||
// E8 ?? ?? ?? ?? 85 C0 7E 07 E8 ?? ?? ?? ?? EB 05
|
||||
int second = 0;
|
||||
ReadOnlySpan<byte> secondPart = [0x85, 0xC0, 0x7E, 0x07, 0xE8,];
|
||||
ReadOnlySpan<byte> thirdPart = [0xEB, 0x05,];
|
||||
|
||||
while (second >= 0 && second < memory.Length)
|
||||
{
|
||||
second += memory[second..].IndexOf(secondPart);
|
||||
if (memory[second - 5].Equals(0xE8) && memory.Slice(second + 9, 2).SequenceEqual(thirdPart))
|
||||
{
|
||||
return second - 5;
|
||||
}
|
||||
|
||||
second += 5;
|
||||
}
|
||||
|
||||
return -1;
|
||||
// B9 3C 00 00 00 FF 15
|
||||
ReadOnlySpan<byte> part = [0xB9, 0x3C, 0x00, 0x00, 0x00, 0xFF, 0x15];
|
||||
return memory.IndexOf(part);
|
||||
}
|
||||
|
||||
private static FindModuleResult UnsafeGetGameModuleInfo(in HANDLE hProcess, out GameModule info)
|
||||
@@ -241,8 +227,8 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
|
||||
nuint localMemoryUserAssemblyAddress = localMemoryUnityPlayerAddress + unityPlayer.Size;
|
||||
|
||||
nuint rip = localMemoryUserAssemblyAddress + (uint)offset;
|
||||
rip += *(uint*)(rip + 1) + 5;
|
||||
rip += *(uint*)(rip + 3) + 7;
|
||||
rip += 5U;
|
||||
rip += (nuint)(*(int*)(rip + 2) + 6);
|
||||
|
||||
nuint address = userAssembly.Address + (rip - localMemoryUserAssemblyAddress);
|
||||
|
||||
|
||||
23
src/Snap.Hutao/Snap.Hutao/Service/KnownRegions.cs
Normal file
23
src/Snap.Hutao/Snap.Hutao/Service/KnownRegions.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
namespace Snap.Hutao.Service;
|
||||
|
||||
internal static class KnownRegions
|
||||
{
|
||||
public static List<NameValue<Region>> Get()
|
||||
{
|
||||
return
|
||||
[
|
||||
new(SH.WebHoyolabRegionCNGF01, Region.CNGF01),
|
||||
new(SH.WebHoyolabRegionCNQD01, Region.CNQD01),
|
||||
new(SH.WebHoyolabRegionOSUSA, Region.OSUSA),
|
||||
new(SH.WebHoyolabRegionOSEURO, Region.OSEURO),
|
||||
new(SH.WebHoyolabRegionOSASIA, Region.OSASIA),
|
||||
new(SH.WebHoyolabRegionOSCHT, Region.OSCHT),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,10 @@ internal static class SupportedCultures
|
||||
ToNameValue(CultureInfo.GetCultureInfo("zh-Hans")),
|
||||
ToNameValue(CultureInfo.GetCultureInfo("zh-Hant")),
|
||||
ToNameValue(CultureInfo.GetCultureInfo("en")),
|
||||
ToNameValue(CultureInfo.GetCultureInfo("ko")),
|
||||
ToNameValue(CultureInfo.GetCultureInfo("ru")),
|
||||
ToNameValue(CultureInfo.GetCultureInfo("ja")),
|
||||
ToNameValue(CultureInfo.GetCultureInfo("id")),
|
||||
ToNameValue(CultureInfo.GetCultureInfo("ko")),
|
||||
];
|
||||
|
||||
public static List<NameValue<CultureInfo>> Get()
|
||||
|
||||
13
src/Snap.Hutao/Snap.Hutao/Service/Update/IUpdateService.cs
Normal file
13
src/Snap.Hutao/Snap.Hutao/Service/Update/IUpdateService.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Service.Update;
|
||||
|
||||
namespace Snap.Hutao.Service.Abstraction;
|
||||
|
||||
internal interface IUpdateService
|
||||
{
|
||||
ValueTask<bool> CheckForUpdateAndDownloadAsync(IProgress<UpdateStatus> progress, CancellationToken token = default);
|
||||
|
||||
void LaunchInstaller();
|
||||
}
|
||||
123
src/Snap.Hutao/Snap.Hutao/Service/Update/UpdateService.cs
Normal file
123
src/Snap.Hutao/Snap.Hutao/Service/Update/UpdateService.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.IO.Hashing;
|
||||
using Snap.Hutao.Core.IO.Http.Sharding;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Web.Hutao;
|
||||
using Snap.Hutao.Web.Hutao.Response;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Snap.Hutao.Service.Update;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Singleton, typeof(IUpdateService))]
|
||||
internal sealed partial class UpdateService : IUpdateService
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
public async ValueTask<bool> CheckForUpdateAndDownloadAsync(IProgress<UpdateStatus> progress, CancellationToken token = default)
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
ITaskContext taskContext = scope.ServiceProvider.GetRequiredService<ITaskContext>();
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
|
||||
HutaoInfrastructureClient infrastructureClient = serviceProvider.GetRequiredService<HutaoInfrastructureClient>();
|
||||
HutaoResponse<HutaoVersionInformation> response = await infrastructureClient.GetHutaoVersionInfomationAsync(token).ConfigureAwait(false);
|
||||
|
||||
if (!response.IsOk())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
HutaoVersionInformation versionInformation = response.Data;
|
||||
string msixPath = GetUpdatePackagePath();
|
||||
|
||||
if (scope.ServiceProvider.GetRequiredService<RuntimeOptions>().Version >= versionInformation.Version)
|
||||
{
|
||||
if (File.Exists(msixPath))
|
||||
{
|
||||
File.Delete(msixPath);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
progress.Report(new(versionInformation.Version.ToString(), 0, 0));
|
||||
|
||||
if (versionInformation.Sha256 is not { } sha256)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (File.Exists(msixPath) && await CheckUpdateCacheSHA256Async(msixPath, sha256, token).ConfigureAwait(false))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return await DownloadUpdatePackageAsync(versionInformation, msixPath, progress, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void LaunchInstaller()
|
||||
{
|
||||
Process.Start(new ProcessStartInfo()
|
||||
{
|
||||
UseShellExecute = true,
|
||||
FileName = GetUpdatePackagePath(),
|
||||
});
|
||||
}
|
||||
|
||||
private static async ValueTask<bool> CheckUpdateCacheSHA256Async(string filePath, string remoteHash, CancellationToken token = default)
|
||||
{
|
||||
string localHash = await SHA256.HashFileAsync(filePath, token).ConfigureAwait(false);
|
||||
return string.Equals(localHash, remoteHash, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private string GetUpdatePackagePath()
|
||||
{
|
||||
string dataFolder = serviceProvider.GetRequiredService<RuntimeOptions>().DataFolder;
|
||||
string directory = Path.Combine(dataFolder, "UpdateCache");
|
||||
Directory.CreateDirectory(directory);
|
||||
return Path.Combine(directory, "Snap.Hutao.msix");
|
||||
}
|
||||
|
||||
private async ValueTask<bool> DownloadUpdatePackageAsync(HutaoVersionInformation versionInformation, string filePath, IProgress<UpdateStatus> progress, CancellationToken token = default)
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
using (HttpClient httpClient = scope.ServiceProvider.GetRequiredService<HttpClient>())
|
||||
{
|
||||
string version = versionInformation.Version.ToString();
|
||||
foreach (string url in versionInformation.Urls)
|
||||
{
|
||||
HttpShardCopyWorkerOptions<UpdateStatus> options = new()
|
||||
{
|
||||
HttpClient = httpClient,
|
||||
SourceUrl = url,
|
||||
DestinationFilePath = filePath,
|
||||
StatusFactory = (bytesRead, totalBytes) => new UpdateStatus(version, bytesRead, totalBytes),
|
||||
};
|
||||
|
||||
using (HttpShardCopyWorker<UpdateStatus> worker = await HttpShardCopyWorker<UpdateStatus>.CreateAsync(options).ConfigureAwait(false))
|
||||
{
|
||||
await worker.CopyAsync(progress, token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
string? remoteHash = versionInformation.Sha256;
|
||||
ArgumentNullException.ThrowIfNull(remoteHash);
|
||||
if (await CheckUpdateCacheSHA256Async(filePath, remoteHash, token).ConfigureAwait(false))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
30
src/Snap.Hutao/Snap.Hutao/Service/Update/UpdateStatus.cs
Normal file
30
src/Snap.Hutao/Snap.Hutao/Service/Update/UpdateStatus.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Common;
|
||||
|
||||
namespace Snap.Hutao.Service.Update;
|
||||
|
||||
internal sealed class UpdateStatus
|
||||
{
|
||||
public UpdateStatus(string version, long bytesRead, long totalBytes)
|
||||
{
|
||||
Version = version;
|
||||
VersionDescription = SH.FormatServiceUpdateStatusVersionDescription(Version);
|
||||
BytesRead = bytesRead;
|
||||
TotalBytes = totalBytes;
|
||||
ProgressDescription = bytesRead != totalBytes
|
||||
? $"{Converters.ToFileSizeString(bytesRead)}/{Converters.ToFileSizeString(totalBytes)}"
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
public string? Version { get; set; }
|
||||
|
||||
public string VersionDescription { get; }
|
||||
|
||||
public double BytesRead { get; set; }
|
||||
|
||||
public double TotalBytes { get; set; }
|
||||
|
||||
public string ProgressDescription { get; }
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
xmlns:shc="using:Snap.Hutao.Control"
|
||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||
xmlns:shmm="using:Snap.Hutao.Model.Metadata"
|
||||
Visibility="{x:Bind SelectedItem.Parameters.Count, Converter={StaticResource Int32ToVisibilityConverter}, Mode=OneWay}"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<Thickness x:Key="SettingsCardPadding">16,8</Thickness>
|
||||
@@ -29,8 +30,7 @@
|
||||
Header="{shcm:ResourceString Name=ViewControlBaseValueSliderLevel}"
|
||||
IsExpanded="True"
|
||||
ItemTemplate="{StaticResource ParameterDescriptionTemplate}"
|
||||
ItemsSource="{x:Bind SelectedItem.Parameters, Mode=OneWay}"
|
||||
Visibility="{x:Bind SelectedItem.Parameters.Count, Converter={StaticResource Int32ToVisibilityConverter}, Mode=OneWay}">
|
||||
ItemsSource="{x:Bind SelectedItem.Parameters, Mode=OneWay}">
|
||||
<shc:SizeRestrictedContentControl Margin="0,-8">
|
||||
<ComboBox
|
||||
x:Name="LevelSelectorComboBox"
|
||||
|
||||
@@ -16,7 +16,7 @@ internal sealed class StringBoolConverter : EmptyStringToObjectConverter
|
||||
/// </summary>
|
||||
public StringBoolConverter()
|
||||
{
|
||||
EmptyValue = BoxedValues.False;
|
||||
NotEmptyValue = BoxedValues.True;
|
||||
EmptyValue = false;
|
||||
NotEmptyValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Control;
|
||||
|
||||
namespace Snap.Hutao.View.Converter;
|
||||
|
||||
[DependencyProperty("VisibleValue", typeof(object))]
|
||||
[DependencyProperty("CollapsedValue", typeof(object))]
|
||||
internal sealed partial class VisibilityToObjectConverter : DependencyValueConverter<Visibility, object>
|
||||
{
|
||||
public override object Convert(Visibility from)
|
||||
{
|
||||
return from is Visibility.Visible ? VisibleValue : CollapsedValue;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cw="using:CommunityToolkit.WinUI"
|
||||
xmlns:cwa="using:CommunityToolkit.WinUI.Animations"
|
||||
xmlns:cwb="using:CommunityToolkit.WinUI.Behaviors"
|
||||
xmlns:cww="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
@@ -172,7 +171,7 @@
|
||||
Message="{Binding Content}"
|
||||
Severity="{Binding Severity}">
|
||||
<StackPanel Margin="0,0,0,8" Orientation="Horizontal">
|
||||
<HyperlinkButton Content="查看详情" NavigateUri="{Binding Link}"/>
|
||||
<HyperlinkButton Content="{shcm:ResourceString Name=ViewPageAnnouncementViewDetails}" NavigateUri="{Binding Link}"/>
|
||||
<TextBlock
|
||||
Margin="8,0,0,2"
|
||||
VerticalAlignment="Center"
|
||||
@@ -199,7 +198,6 @@
|
||||
Margin="16,16,16,0"
|
||||
Style="{StaticResource TitleTextBlockStyle}"
|
||||
Text="{Binding GreetingText}"/>
|
||||
<TextBlock Margin="16,0" Text="{Binding HutaoUserOptions.UserName}"/>
|
||||
|
||||
<ItemsControl
|
||||
Margin="16,8,12,0"
|
||||
@@ -237,4 +235,4 @@
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</shc:ScopedPage>
|
||||
</shc:ScopedPage>
|
||||
|
||||
@@ -51,16 +51,6 @@
|
||||
ColumnSpacing="8"
|
||||
Columns="2"
|
||||
RowSpacing="8">
|
||||
<HyperlinkButton
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Command="{Binding UpdateCheckCommand}"
|
||||
Content="{shcm:ResourceString Name=ViewPageSettingUpdateCheckAction}"/>
|
||||
<HyperlinkButton
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Command="{Binding StoreReviewCommand}"
|
||||
Content="{shcm:ResourceString Name=ViewPageSettingStoreReviewNavigate}"/>
|
||||
<HyperlinkButton
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
@@ -305,6 +295,17 @@
|
||||
</cwc:SettingsCard>
|
||||
</cwc:SettingsExpander.Items>
|
||||
</cwc:SettingsExpander>
|
||||
<cwc:SettingsCard
|
||||
Description="{shcm:ResourceString Name=ViewPageSettingHomeAnnouncementRegionDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageSettingHomeAnnouncementRegionHeader}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}">
|
||||
<shc:SizeRestrictedContentControl>
|
||||
<ComboBox
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{Binding AppOptions.LazyRegions.Value}"
|
||||
SelectedItem="{Binding SelectedRegion, Mode=TwoWay}"/>
|
||||
</shc:SizeRestrictedContentControl>
|
||||
</cwc:SettingsCard>
|
||||
|
||||
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingGameHeader}"/>
|
||||
<cwc:SettingsCard
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||
xmlns:shcp="using:Snap.Hutao.Control.Panel"
|
||||
xmlns:shct="using:Snap.Hutao.Control.Text"
|
||||
xmlns:shvc="using:Snap.Hutao.View.Control"
|
||||
xmlns:shvcont="using:Snap.Hutao.View.Control"
|
||||
xmlns:shvconv="using:Snap.Hutao.View.Converter"
|
||||
xmlns:shvcp="using:Snap.Hutao.View.Card.Primitive"
|
||||
xmlns:shvw="using:Snap.Hutao.ViewModel.Wiki"
|
||||
d:DataContext="{d:DesignInstance Type=shvw:WikiAvatarViewModel}"
|
||||
@@ -24,20 +25,29 @@
|
||||
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
<Page.Resources>
|
||||
<shvconv:VisibilityToObjectConverter
|
||||
x:Key="VisibilityToColumnSpanConverter"
|
||||
CollapsedValue="{shcm:Int32 Value=2}"
|
||||
VisibleValue="{shcm:Int32 Value=1}"/>
|
||||
|
||||
<DataTemplate x:Key="SkillDataTemplate">
|
||||
<Grid Margin="0,16,0,0" ColumnSpacing="16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*"/>
|
||||
<ColumnDefinition Width="3*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<shct:DescriptionTextBlock VerticalAlignment="Top" Description="{Binding Description}">
|
||||
<shct:DescriptionTextBlock
|
||||
Grid.ColumnSpan="{Binding ElementName=ProudSelector, Path=Visibility, Converter={StaticResource VisibilityToColumnSpanConverter}}"
|
||||
VerticalAlignment="Top"
|
||||
Description="{Binding Description}">
|
||||
<shct:DescriptionTextBlock.Resources>
|
||||
<Style BasedOn="{StaticResource BodyTextBlockStyle}" TargetType="TextBlock">
|
||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||
</Style>
|
||||
</shct:DescriptionTextBlock.Resources>
|
||||
</shct:DescriptionTextBlock>
|
||||
<shvc:DescParamComboBox
|
||||
<shvcont:DescParamComboBox
|
||||
x:Name="ProudSelector"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
@@ -47,7 +57,7 @@
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="PropertyDataTemplate">
|
||||
<shvc:DescParamComboBox
|
||||
<shvcont:DescParamComboBox
|
||||
HorizontalAlignment="Stretch"
|
||||
PreferredSelectedIndex="13"
|
||||
Source="{Binding Converter={StaticResource PropertyDescriptor}}"/>
|
||||
@@ -86,7 +96,7 @@
|
||||
<DataTemplate x:Key="CultivationItemTemplate">
|
||||
<shvcp:HorizontalCard>
|
||||
<shvcp:HorizontalCard.Left>
|
||||
<shvc:ItemIcon
|
||||
<shvcont:ItemIcon
|
||||
Width="40"
|
||||
Height="40"
|
||||
Icon="{Binding Icon, Converter={StaticResource ItemIconConverter}}"
|
||||
@@ -104,7 +114,7 @@
|
||||
<DataTemplate x:Key="CollocationTemplate">
|
||||
<shvcp:HorizontalCard>
|
||||
<shvcp:HorizontalCard.Left>
|
||||
<shvc:ItemIcon
|
||||
<shvcont:ItemIcon
|
||||
Width="48"
|
||||
Height="48"
|
||||
Icon="{Binding Icon}"
|
||||
@@ -131,7 +141,7 @@
|
||||
<x:Int32>0</x:Int32>
|
||||
</cwc:Case.Value>
|
||||
<Grid>
|
||||
<shvc:ItemIcon
|
||||
<shvcont:ItemIcon
|
||||
Width="48"
|
||||
Height="48"
|
||||
Icon="{StaticResource UI_ItemIcon_None}"
|
||||
@@ -143,7 +153,7 @@
|
||||
<x:Int32>1</x:Int32>
|
||||
</cwc:Case.Value>
|
||||
<Grid>
|
||||
<shvc:ItemIcon
|
||||
<shvcont:ItemIcon
|
||||
Width="48"
|
||||
Height="48"
|
||||
Icon="{Binding Icons[0]}"
|
||||
@@ -155,7 +165,7 @@
|
||||
<x:Int32>2</x:Int32>
|
||||
</cwc:Case.Value>
|
||||
<Grid>
|
||||
<shvc:ItemIcon
|
||||
<shvcont:ItemIcon
|
||||
Width="48"
|
||||
Height="48"
|
||||
Quality="QUALITY_ORANGE"/>
|
||||
@@ -225,9 +235,9 @@
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="AvatarGridTemplate">
|
||||
<shvc:BottomTextControl Text="{Binding Name}">
|
||||
<shvc:ItemIcon Icon="{Binding Icon, Converter={StaticResource AvatarIconConverter}, Mode=OneWay}" Quality="{Binding Quality}"/>
|
||||
</shvc:BottomTextControl>
|
||||
<shvcont:BottomTextControl Text="{Binding Name}">
|
||||
<shvcont:ItemIcon Icon="{Binding Icon, Converter={StaticResource AvatarIconConverter}, Mode=OneWay}" Quality="{Binding Quality}"/>
|
||||
</shvcont:BottomTextControl>
|
||||
</DataTemplate>
|
||||
</Page.Resources>
|
||||
|
||||
@@ -339,7 +349,7 @@
|
||||
Height="32"
|
||||
Source="{Binding Selected.Weapon, Converter={StaticResource WeaponTypeIconConverter}}"/>
|
||||
</Grid>
|
||||
<shvc:ItemIcon
|
||||
<shvcont:ItemIcon
|
||||
Width="128"
|
||||
Height="128"
|
||||
Icon="{Binding Selected.Icon, Converter={StaticResource AvatarIconConverter}, Mode=OneWay}"
|
||||
@@ -463,7 +473,7 @@
|
||||
<SolidColorBrush x:Key="SettingsCardBackgroundPointerOver" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="SettingsCardBackgroundPressed" Color="Transparent"/>
|
||||
</Border.Resources>
|
||||
<shvc:BaseValueSlider
|
||||
<shvcont:BaseValueSlider
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
BaseValueInfo="{Binding BaseValueInfo, Mode=OneWay}"/>
|
||||
@@ -476,7 +486,7 @@
|
||||
Padding="16"
|
||||
Background="{ThemeResource SystemControlAcrylicElementMediumHighBrush}"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}">
|
||||
<shvc:SkillPivot ItemTemplate="{StaticResource SkillDataTemplate}" Skills="{Binding Selected.SkillDepot.CompositeSkills}"/>
|
||||
<shvcont:SkillPivot ItemTemplate="{StaticResource SkillDataTemplate}" Skills="{Binding Selected.SkillDepot.CompositeSkills}"/>
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
@@ -486,7 +496,7 @@
|
||||
Padding="16"
|
||||
Background="{ThemeResource SystemControlAcrylicElementMediumHighBrush}"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}">
|
||||
<shvc:SkillPivot ItemTemplate="{StaticResource TalentDataTemplate}" Skills="{Binding Selected.SkillDepot.Talents}"/>
|
||||
<shvcont:SkillPivot ItemTemplate="{StaticResource TalentDataTemplate}" Skills="{Binding Selected.SkillDepot.Talents}"/>
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
@@ -594,25 +604,25 @@
|
||||
Grid.Column="0"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewPageWiKiAvatarSpecialFoodTitle}"/>
|
||||
<shvc:BottomTextControl
|
||||
<shvcont:BottomTextControl
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="0,16,0,0"
|
||||
Text="{Binding Item.Name}">
|
||||
<shvc:ItemIcon Icon="{Binding Item.Icon, Converter={StaticResource ItemIconConverter}}" Quality="{Binding Item.RankLevel}"/>
|
||||
</shvc:BottomTextControl>
|
||||
<shvcont:ItemIcon Icon="{Binding Item.Icon, Converter={StaticResource ItemIconConverter}}" Quality="{Binding Item.RankLevel}"/>
|
||||
</shvcont:BottomTextControl>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Margin="16,0,0,0"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewPageWiKiAvatarOriginalFoodTitle}"/>
|
||||
<shvc:BottomTextControl
|
||||
<shvcont:BottomTextControl
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="16,16,0,0"
|
||||
Text="{Binding OriginItem.Name}">
|
||||
<shvc:ItemIcon Icon="{Binding OriginItem.Icon, Converter={StaticResource ItemIconConverter}}" Quality="{Binding OriginItem.RankLevel}"/>
|
||||
</shvc:BottomTextControl>
|
||||
<shvcont:ItemIcon Icon="{Binding OriginItem.Icon, Converter={StaticResource ItemIconConverter}}" Quality="{Binding OriginItem.RankLevel}"/>
|
||||
</shvcont:BottomTextControl>
|
||||
<StackPanel
|
||||
Grid.RowSpan="4"
|
||||
Grid.Column="2"
|
||||
@@ -716,6 +726,6 @@
|
||||
</cwc:Case>
|
||||
</cwc:SwitchPresenter>
|
||||
</Grid>
|
||||
<shvc:LoadingView IsLoading="{Binding Avatars, Converter={StaticResource EmptyObjectToBoolRevertConverter}}"/>
|
||||
<shvcont:LoadingView IsLoading="{Binding Avatars, Converter={StaticResource EmptyObjectToBoolRevertConverter}}"/>
|
||||
</Grid>
|
||||
</shc:ScopedPage>
|
||||
@@ -24,31 +24,65 @@
|
||||
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
Margin="0,0,6,0"
|
||||
Orientation="Horizontal"
|
||||
Spacing="6"
|
||||
Visibility="{x:Bind RuntimeOptions.IsElevated, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<ToggleButton
|
||||
Margin="0,0,6,0"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{x:Bind HotKeyOptions.IsMouseClickRepeatForeverOn, Mode=OneWay}"
|
||||
IsHitTestVisible="False"
|
||||
Visibility="{x:Bind HotKeyOptions.MouseClickRepeatForeverKeyCombination.IsEnabled, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{shcm:ResourceString Name=ViewTitleAutoClicking}"/>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind HotKeyOptions.MouseClickRepeatForeverKeyCombination.DisplayName, Mode=OneWay}"/>
|
||||
</Grid>
|
||||
</ToggleButton>
|
||||
Spacing="6">
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="6"
|
||||
Visibility="{x:Bind RuntimeOptions.IsElevated, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<ToggleButton
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{x:Bind HotKeyOptions.IsMouseClickRepeatForeverOn, Mode=OneWay}"
|
||||
IsHitTestVisible="False"
|
||||
Visibility="{x:Bind HotKeyOptions.MouseClickRepeatForeverKeyCombination.IsEnabled, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{shcm:ResourceString Name=ViewTitleAutoClicking}"/>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind HotKeyOptions.MouseClickRepeatForeverKeyCombination.DisplayName, Mode=OneWay}"/>
|
||||
</Grid>
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
|
||||
<Grid
|
||||
Margin="0,6"
|
||||
Padding="12,0"
|
||||
ColumnSpacing="12"
|
||||
Style="{ThemeResource GridCardStyle}"
|
||||
Visibility="{x:Bind UpdateStatus, Converter={StaticResource EmptyObjectToVisibilityConverter}, Mode=OneWay}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ProgressBar
|
||||
Grid.ColumnSpan="2"
|
||||
MinHeight="32"
|
||||
Margin="-12,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="Transparent"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}"
|
||||
Maximum="{x:Bind UpdateStatus.TotalBytes, Mode=OneWay}"
|
||||
Opacity="{ThemeResource LargeBackgroundProgressBarOpacity}"
|
||||
Value="{x:Bind UpdateStatus.BytesRead, Mode=OneWay}"/>
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind UpdateStatus.ProgressDescription, Mode=OneWay}"/>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind UpdateStatus.VersionDescription, Mode=OneWay}"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,10 +1,15 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Windowing.HotKey;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Factory.Progress;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Update;
|
||||
|
||||
namespace Snap.Hutao.View;
|
||||
|
||||
@@ -12,19 +17,19 @@ namespace Snap.Hutao.View;
|
||||
/// 标题视图
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[INotifyPropertyChanged]
|
||||
internal sealed partial class TitleView : UserControl
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的标题视图
|
||||
/// </summary>
|
||||
private CancellationTokenSource checkUpdateTaskCancellationTokenSource = new();
|
||||
private UpdateStatus? updateStatus;
|
||||
|
||||
public TitleView()
|
||||
{
|
||||
Loaded += OnTitleViewLoaded;
|
||||
Unloaded += OnTitleViewUnloaded;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标题
|
||||
/// </summary>
|
||||
public string Title
|
||||
{
|
||||
[SuppressMessage("", "IDE0027")]
|
||||
@@ -38,9 +43,6 @@ internal sealed partial class TitleView : UserControl
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取可拖动区域
|
||||
/// </summary>
|
||||
public FrameworkElement DragArea
|
||||
{
|
||||
get => DragableGrid;
|
||||
@@ -49,4 +51,44 @@ internal sealed partial class TitleView : UserControl
|
||||
public RuntimeOptions RuntimeOptions { get; } = Ioc.Default.GetRequiredService<RuntimeOptions>();
|
||||
|
||||
public HotKeyOptions HotKeyOptions { get; } = Ioc.Default.GetRequiredService<HotKeyOptions>();
|
||||
}
|
||||
|
||||
public UpdateStatus? UpdateStatus { get => updateStatus; set => SetProperty(ref updateStatus, value); }
|
||||
|
||||
private void OnTitleViewLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DoCheckUpdateAsync(checkUpdateTaskCancellationTokenSource.Token).SafeForget();
|
||||
Loaded -= OnTitleViewLoaded;
|
||||
}
|
||||
|
||||
private void OnTitleViewUnloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
checkUpdateTaskCancellationTokenSource.Cancel();
|
||||
Unloaded -= OnTitleViewUnloaded;
|
||||
}
|
||||
|
||||
private async ValueTask DoCheckUpdateAsync(CancellationToken token)
|
||||
{
|
||||
IServiceProvider serviceProvider = Ioc.Default;
|
||||
IUpdateService updateService = serviceProvider.GetRequiredService<IUpdateService>();
|
||||
|
||||
IProgressFactory progressFactory = serviceProvider.GetRequiredService<IProgressFactory>();
|
||||
IProgress<UpdateStatus> progress = progressFactory.CreateForMainThread<UpdateStatus>(status => UpdateStatus = status);
|
||||
if (await updateService.CheckForUpdateAndDownloadAsync(progress, token).ConfigureAwait(false))
|
||||
{
|
||||
ContentDialogResult result = await serviceProvider
|
||||
.GetRequiredService<IContentDialogFactory>()
|
||||
.CreateForConfirmCancelAsync(
|
||||
SH.FormatViewTitileUpdatePackageReadyTitle(UpdateStatus?.Version),
|
||||
SH.ViewTitileUpdatePackageReadyContent,
|
||||
ContentDialogButton.Primary)
|
||||
.ConfigureAwait(false);
|
||||
if (result == ContentDialogResult.Primary)
|
||||
{
|
||||
updateService.LaunchInstaller();
|
||||
}
|
||||
}
|
||||
|
||||
await serviceProvider.GetRequiredService<ITaskContext>().SwitchToMainThreadAsync();
|
||||
UpdateStatus = null;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ internal static class StaticResource
|
||||
|
||||
private static readonly ApplicationDataCompositeValue DefaultResourceVersionMap = new()
|
||||
{
|
||||
// DO NOT MIDIFY THIS MAP
|
||||
{ "AchievementIcon", 0 },
|
||||
{ "AvatarCard", 0 },
|
||||
{ "AvatarIcon", 0 },
|
||||
@@ -47,29 +48,29 @@ internal static class StaticResource
|
||||
{
|
||||
{ "AchievementIcon", 1 },
|
||||
{ "AvatarCard", 1 },
|
||||
{ "AvatarIcon", 3 },
|
||||
{ "AvatarIcon", 4 },
|
||||
{ "Bg", 2 },
|
||||
{ "ChapterIcon", 1 },
|
||||
{ "ChapterIcon", 2 },
|
||||
{ "CodexMonster", 0 },
|
||||
{ "Costume", 1 },
|
||||
{ "EmotionIcon", 1 },
|
||||
{ "EquipIcon", 2 },
|
||||
{ "GachaAvatarIcon", 2 },
|
||||
{ "GachaAvatarImg", 2 },
|
||||
{ "GachaEquipIcon", 2 },
|
||||
{ "EmotionIcon", 2 },
|
||||
{ "EquipIcon", 3 },
|
||||
{ "GachaAvatarIcon", 3 },
|
||||
{ "GachaAvatarImg", 3 },
|
||||
{ "GachaEquipIcon", 3 },
|
||||
{ "GcgCharAvatarIcon", 0 },
|
||||
{ "IconElement", 2 },
|
||||
{ "ItemIcon", 2 },
|
||||
{ "ItemIcon", 3 },
|
||||
{ "LoadingPic", 1 },
|
||||
{ "MonsterIcon", 2 },
|
||||
{ "MonsterSmallIcon", 1 },
|
||||
{ "NameCardIcon", 1 },
|
||||
{ "NameCardPic", 2 },
|
||||
{ "NameCardIcon", 2 },
|
||||
{ "NameCardPic", 3 },
|
||||
{ "NameCardPicAlpha", 0 },
|
||||
{ "Property", 1 },
|
||||
{ "RelicIcon", 2 },
|
||||
{ "Skill", 2 },
|
||||
{ "Talent", 2 },
|
||||
{ "RelicIcon", 3 },
|
||||
{ "Skill", 3 },
|
||||
{ "Talent", 3 },
|
||||
};
|
||||
|
||||
public static void FulfillAll()
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Service;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Hutao;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.View.Card;
|
||||
using Snap.Hutao.View.Card.Primitive;
|
||||
using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
|
||||
@@ -23,6 +25,8 @@ internal sealed partial class AnnouncementViewModel : Abstraction.ViewModel
|
||||
private readonly IAnnouncementService announcementService;
|
||||
private readonly HutaoUserOptions hutaoUserOptions;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly MetadataOptions metadataOptions;
|
||||
private readonly AppOptions appOptions;
|
||||
|
||||
private AnnouncementWrapper? announcement;
|
||||
private string greetingText = SH.ViewPageHomeGreetingTextDefault;
|
||||
@@ -61,7 +65,7 @@ internal sealed partial class AnnouncementViewModel : Abstraction.ViewModel
|
||||
{
|
||||
try
|
||||
{
|
||||
AnnouncementWrapper announcementWrapper = await announcementService.GetAnnouncementWrapperAsync(CancellationToken).ConfigureAwait(false);
|
||||
AnnouncementWrapper announcementWrapper = await announcementService.GetAnnouncementWrapperAsync(metadataOptions.LanguageCode, appOptions.Region, CancellationToken).ConfigureAwait(false);
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
Announcement = announcementWrapper;
|
||||
}
|
||||
@@ -138,4 +142,4 @@ internal sealed partial class AnnouncementViewModel : Abstraction.ViewModel
|
||||
|
||||
Cards = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ using Snap.Hutao.Service.Notification;
|
||||
using Snap.Hutao.Service.User;
|
||||
using Snap.Hutao.View.Dialog;
|
||||
using Snap.Hutao.ViewModel.Guide;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hutao;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.Globalization;
|
||||
@@ -61,6 +62,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
||||
|
||||
private NameValue<BackdropType>? selectedBackdropType;
|
||||
private NameValue<CultureInfo>? selectedCulture;
|
||||
private NameValue<Region>? selectedRegion;
|
||||
private IPInformation? ipInformation;
|
||||
private FolderViewModel? cacheFolderView;
|
||||
private FolderViewModel? dataFolderView;
|
||||
@@ -104,6 +106,18 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
||||
}
|
||||
}
|
||||
|
||||
public NameValue<Region>? SelectedRegion
|
||||
{
|
||||
get => selectedRegion ??= AppOptions.GetCurrentRegionForSelectionOrDefault();
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref selectedRegion, value) && value is not null)
|
||||
{
|
||||
AppOptions.Region = value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FolderViewModel? CacheFolderView { get => cacheFolderView; set => SetProperty(ref cacheFolderView, value); }
|
||||
|
||||
public FolderViewModel? DataFolderView { get => dataFolderView; set => SetProperty(ref dataFolderView, value); }
|
||||
|
||||
@@ -55,7 +55,7 @@ internal sealed partial class TestViewModel : Abstraction.ViewModel
|
||||
private void ResetMainWindowSize()
|
||||
{
|
||||
double scale = mainWindow.WindowOptions.GetRasterizationScale();
|
||||
mainWindow.AppWindow.Resize(new Windows.Graphics.SizeInt32(1280, 720).Scale(scale));
|
||||
mainWindow.AppWindow.Resize(new Windows.Graphics.SizeInt32(1372, 772).Scale(scale));
|
||||
}
|
||||
|
||||
[Command("UploadAnnouncementCommand")]
|
||||
|
||||
@@ -281,12 +281,24 @@ internal static class ApiEndpoints
|
||||
/// <summary>
|
||||
/// 公告列表
|
||||
/// </summary>
|
||||
public const string AnnList = $"{Hk4eApiAnnouncementApi}/getAnnList?{AnnouncementQuery}";
|
||||
/// <param name="languageCode">语言代码</param>
|
||||
/// <param name="region">服务器</param>
|
||||
/// <returns>公告列表Url</returns>
|
||||
public static string AnnList(string languageCode, in Region region)
|
||||
{
|
||||
return $"{Hk4eApiAnnouncementApi}/getAnnList?{AnnouncementQuery(languageCode, region)}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 公告内容
|
||||
/// </summary>
|
||||
public const string AnnContent = $"{Hk4eApiAnnouncementApi}/getAnnContent?{AnnouncementQuery}";
|
||||
/// <param name="languageCode">语言代码</param>
|
||||
/// <param name="region">服务器</param>
|
||||
/// <returns>公告列表Url</returns>
|
||||
public static string AnnContent(string languageCode, in Region region)
|
||||
{
|
||||
return $"{Hk4eApiAnnouncementApi}/getAnnContent?{AnnouncementQuery(languageCode, region)}";
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Hk4eSdk
|
||||
@@ -422,6 +434,9 @@ internal static class ApiEndpoints
|
||||
/// </summary>
|
||||
public const string WebStaticMihoyoReferer = "https://webstatic.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";
|
||||
private static string AnnouncementQuery(string languageCode, in Region region)
|
||||
{
|
||||
return $"game=hk4e&game_biz=hk4e_cn&lang={languageCode}&bundle_id=hk4e_cn&platform=pc®ion={region}&level=55&uid=100000000";
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -100,7 +100,7 @@ internal static class ApiOsEndpoints
|
||||
/// </summary>
|
||||
/// <param name="region">地区代号</param>
|
||||
/// <returns>用户游戏角色字符串</returns>
|
||||
public static string UserGameRolesByLtoken(string region)
|
||||
public static string UserGameRolesByLtoken(in Region region)
|
||||
{
|
||||
return $"{ApiAccountOsBindingApi}/getUserGameRolesByLtoken?game_biz=hk4e_global®ion={region}";
|
||||
}
|
||||
@@ -189,6 +189,32 @@ internal static class ApiOsEndpoints
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Hk4eApiOsAnnouncementApi
|
||||
|
||||
/// <summary>
|
||||
/// 公告列表
|
||||
/// </summary>
|
||||
/// <param name="languageCode">语言代码</param>
|
||||
/// <param name="region">服务器</param>
|
||||
/// <returns>公告列表Url</returns>
|
||||
public static string AnnList(string languageCode, in Region region)
|
||||
{
|
||||
return $"{Hk4eApiOsAnnouncementApi}/getAnnList?{AnnouncementQuery(languageCode, region)}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 公告内容
|
||||
/// </summary>
|
||||
/// <param name="languageCode">语言代码</param>
|
||||
/// <param name="region">服务器</param>
|
||||
/// <returns>公告内容Url</returns>
|
||||
public static string AnnContent(string languageCode, in Region region)
|
||||
{
|
||||
return $"{Hk4eApiOsAnnouncementApi}/getAnnContent?{AnnouncementQuery(languageCode, region)}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SgPublicApi
|
||||
|
||||
/// <summary>
|
||||
@@ -307,6 +333,7 @@ internal static class ApiOsEndpoints
|
||||
private const string BbsApiOsGameRecordAppApi = $"{BbsApiOs}/game_record/app/genshin/api";
|
||||
|
||||
private const string Hk4eApiOs = "https://hk4e-api-os.hoyoverse.com";
|
||||
private const string Hk4eApiOsAnnouncementApi = $"{Hk4eApiOs}/common/hk4e_global/announcement/api";
|
||||
private const string Hk4eApiOsGachaInfoApi = $"{Hk4eApiOs}/gacha_info/api";
|
||||
|
||||
private const string SdkOsStatic = "https://sdk-os-static.mihoyo.com";
|
||||
@@ -333,5 +360,10 @@ internal static class ApiOsEndpoints
|
||||
/// </summary>
|
||||
public const string AppHoyolabReferer = "https://app.hoyolab.com/";
|
||||
|
||||
private static string AnnouncementQuery(string languageCode, in Region region)
|
||||
{
|
||||
return $"game=hk4e&game_biz=hk4e_global&lang={languageCode}&bundle_id=hk4e_global&platform=pc®ion={region}&level=55&uid=100000000";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -86,7 +86,7 @@ internal class MiHoYoJSBridge
|
||||
/// </summary>
|
||||
/// <param name="param">参数</param>
|
||||
/// <returns>响应</returns>
|
||||
protected virtual async ValueTask<IJsResult?> ClosePageAsync(JsParam param)
|
||||
protected virtual async ValueTask<IJsBridgeResult?> ClosePageAsync(JsParam param)
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
if (coreWebView2.CanGoBack)
|
||||
@@ -106,7 +106,7 @@ internal class MiHoYoJSBridge
|
||||
/// </summary>
|
||||
/// <param name="param">参数</param>
|
||||
/// <returns>响应</returns>
|
||||
protected virtual IJsResult? ConfigureShare(JsParam param)
|
||||
protected virtual IJsBridgeResult? ConfigureShare(JsParam param)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -116,7 +116,7 @@ internal class MiHoYoJSBridge
|
||||
/// </summary>
|
||||
/// <param name="jsParam">参数</param>
|
||||
/// <returns>响应</returns>
|
||||
protected virtual async ValueTask<IJsResult?> GetActionTicketAsync(JsParam<ActionTypePayload> jsParam)
|
||||
protected virtual async ValueTask<IJsBridgeResult?> GetActionTicketAsync(JsParam<ActionTypePayload> jsParam)
|
||||
{
|
||||
return await serviceProvider
|
||||
.GetRequiredService<AuthClient>()
|
||||
@@ -299,7 +299,7 @@ internal class MiHoYoJSBridge
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual async ValueTask<IJsResult?> PushPageAsync(JsParam<PushPagePayload> param)
|
||||
protected virtual async ValueTask<IJsBridgeResult?> PushPageAsync(JsParam<PushPagePayload> param)
|
||||
{
|
||||
const string bbsSchema = "mihoyobbs://";
|
||||
string pageUrl = param.Payload.Page;
|
||||
@@ -323,7 +323,7 @@ internal class MiHoYoJSBridge
|
||||
return null;
|
||||
}
|
||||
|
||||
protected virtual IJsResult? Share(JsParam<SharePayload> param)
|
||||
protected virtual IJsBridgeResult? Share(JsParam<SharePayload> param)
|
||||
{
|
||||
return new JsResult<Dictionary<string, string>>()
|
||||
{
|
||||
@@ -334,47 +334,47 @@ internal class MiHoYoJSBridge
|
||||
};
|
||||
}
|
||||
|
||||
protected virtual ValueTask<IJsResult?> ShowAlertDialogAsync(JsParam param)
|
||||
protected virtual ValueTask<IJsBridgeResult?> ShowAlertDialogAsync(JsParam param)
|
||||
{
|
||||
return ValueTask.FromException<IJsResult?>(new NotSupportedException());
|
||||
return ValueTask.FromException<IJsBridgeResult?>(new NotSupportedException());
|
||||
}
|
||||
|
||||
protected virtual IJsResult? StartRealPersonValidation(JsParam param)
|
||||
protected virtual IJsBridgeResult? StartRealPersonValidation(JsParam param)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected virtual IJsResult? StartRealnameAuth(JsParam param)
|
||||
protected virtual IJsBridgeResult? StartRealnameAuth(JsParam param)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected virtual IJsResult? GenAuthKey(JsParam param)
|
||||
protected virtual IJsBridgeResult? GenAuthKey(JsParam param)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected virtual IJsResult? GenAppAuthKey(JsParam param)
|
||||
protected virtual IJsBridgeResult? GenAppAuthKey(JsParam param)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected virtual IJsResult? OpenSystemBrowser(JsParam param)
|
||||
protected virtual IJsBridgeResult? OpenSystemBrowser(JsParam param)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected virtual IJsResult? SaveLoginTicket(JsParam param)
|
||||
protected virtual IJsBridgeResult? SaveLoginTicket(JsParam param)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected virtual ValueTask<IJsResult?> GetNotificationSettingsAsync(JsParam param)
|
||||
protected virtual ValueTask<IJsBridgeResult?> GetNotificationSettingsAsync(JsParam param)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected virtual IJsResult? ShowToast(JsParam param)
|
||||
protected virtual IJsBridgeResult? ShowToast(JsParam param)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -404,20 +404,12 @@ internal class MiHoYoJSBridge
|
||||
logger?.LogInformation("[{Id}][ExecuteScript: {callback}]\n{payload}", interfaceId, callback, payload);
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
try
|
||||
if (coreWebView2 is null || coreWebView2.IsDisposed())
|
||||
{
|
||||
if (coreWebView2 is not null)
|
||||
{
|
||||
return await coreWebView2.ExecuteScriptAsync(js);
|
||||
}
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
// COMException (0x8007139F): 组或资源的状态不是执行请求操作的正确状态。 (0x8007139F)
|
||||
// webview is disposing or disposed
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
return await coreWebView2.ExecuteScriptAsync(js);
|
||||
}
|
||||
|
||||
private async void OnWebMessageReceived(CoreWebView2 webView2, CoreWebView2WebMessageReceivedEventArgs args)
|
||||
@@ -430,7 +422,7 @@ internal class MiHoYoJSBridge
|
||||
logger.LogInformation("[OnMessage]\nMethod : {method}\nPayload : {payload}\nCallback: {callback}", param.Method, param.Payload, param.Callback);
|
||||
using (await webMessageSemaphore.EnterAsync().ConfigureAwait(false))
|
||||
{
|
||||
IJsResult? result = await TryGetJsResultFromJsParamAsync(param).ConfigureAwait(false);
|
||||
IJsBridgeResult? result = await TryGetJsResultFromJsParamAsync(param).ConfigureAwait(false);
|
||||
|
||||
if (result is not null && param.Callback is not null)
|
||||
{
|
||||
@@ -440,13 +432,13 @@ internal class MiHoYoJSBridge
|
||||
}
|
||||
|
||||
[SuppressMessage("", "CA2254")]
|
||||
private IJsResult? LogUnhandledMessage(string message, params object?[] param)
|
||||
private IJsBridgeResult? LogUnhandledMessage(string message, params object?[] param)
|
||||
{
|
||||
logger.LogWarning(message, param);
|
||||
return default;
|
||||
}
|
||||
|
||||
private async ValueTask<IJsResult?> TryGetJsResultFromJsParamAsync(JsParam param)
|
||||
private async ValueTask<IJsBridgeResult?> TryGetJsResultFromJsParamAsync(JsParam param)
|
||||
{
|
||||
if (coreWebView2.IsDisposed())
|
||||
{
|
||||
|
||||
@@ -7,11 +7,4 @@ namespace Snap.Hutao.Web.Bridge.Model;
|
||||
/// 指示此为Js结果
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal interface IJsResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 转换到Json字符串表示
|
||||
/// </summary>
|
||||
/// <returns>JSON字符串</returns>
|
||||
string ToJson();
|
||||
}
|
||||
internal interface IJsBridgeResult;
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Bridge.Model;
|
||||
|
||||
internal static class JsBridgeResultExtension
|
||||
{
|
||||
public static string ToJson<T>(this T result)
|
||||
where T : IJsBridgeResult
|
||||
{
|
||||
return JsonSerializer.Serialize(result, result.GetType());
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ namespace Snap.Hutao.Web.Bridge.Model;
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">内部数据类型</typeparam>
|
||||
[HighQuality]
|
||||
internal sealed class JsResult<TData> : IJsResult
|
||||
internal sealed class JsResult<TData> : IJsBridgeResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 代码
|
||||
@@ -28,10 +28,4 @@ internal sealed class JsResult<TData> : IJsResult
|
||||
/// </summary>
|
||||
[JsonPropertyName("data")]
|
||||
public TData Data { get; set; } = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
string IJsResult.ToJson()
|
||||
{
|
||||
return JsonSerializer.Serialize(this);
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ internal sealed class Announcement : AnnouncementContent
|
||||
/// </summary>
|
||||
public string TimeFormatted
|
||||
{
|
||||
get => $"{StartTime:yyyy.MM.dd HH:mm} - {EndTime:yyyy.MM.dd HH:mm}";
|
||||
get => $"{StartTime.ToLocalTime():yyyy.MM.dd HH:mm} - {EndTime.ToLocalTime():yyyy.MM.dd HH:mm}";
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -23,12 +23,18 @@ internal sealed partial class AnnouncementClient
|
||||
/// <summary>
|
||||
/// 异步获取公告列表
|
||||
/// </summary>
|
||||
/// <param name="languageCode">语言代码</param>
|
||||
/// <param name="region">服务器</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>公告列表</returns>
|
||||
public async ValueTask<Response<AnnouncementWrapper>> GetAnnouncementsAsync(CancellationToken token = default)
|
||||
public async ValueTask<Response<AnnouncementWrapper>> GetAnnouncementsAsync(string languageCode, Region region, CancellationToken token = default)
|
||||
{
|
||||
string annListUrl = region.IsOversea()
|
||||
? ApiOsEndpoints.AnnList(languageCode, region)
|
||||
: ApiEndpoints.AnnList(languageCode, region);
|
||||
|
||||
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
|
||||
.SetRequestUri(ApiEndpoints.AnnList)
|
||||
.SetRequestUri(annListUrl)
|
||||
.Get();
|
||||
|
||||
Response<AnnouncementWrapper>? resp = await builder
|
||||
@@ -41,12 +47,18 @@ internal sealed partial class AnnouncementClient
|
||||
/// <summary>
|
||||
/// 异步获取公告内容列表
|
||||
/// </summary>
|
||||
/// <param name="languageCode">语言代码</param>
|
||||
/// <param name="region">服务器</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>公告内容列表</returns>
|
||||
public async ValueTask<Response<ListWrapper<AnnouncementContent>>> GetAnnouncementContentsAsync(CancellationToken token = default)
|
||||
public async ValueTask<Response<ListWrapper<AnnouncementContent>>> GetAnnouncementContentsAsync(string languageCode, Region region, CancellationToken token = default)
|
||||
{
|
||||
string annContentUrl = region.IsOversea()
|
||||
? ApiOsEndpoints.AnnContent(languageCode, region)
|
||||
: ApiEndpoints.AnnContent(languageCode, region);
|
||||
|
||||
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
|
||||
.SetRequestUri(ApiEndpoints.AnnContent)
|
||||
.SetRequestUri(annContentUrl)
|
||||
.Get();
|
||||
|
||||
Response<ListWrapper<AnnouncementContent>>? resp = await builder
|
||||
|
||||
@@ -7,19 +7,23 @@ namespace Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
|
||||
|
||||
internal static partial class AnnouncementRegex
|
||||
{
|
||||
/// <inheritdoc cref="SH.WebAnnouncementMatchVersionUpdateTitle"/>
|
||||
public static readonly Regex VersionUpdateTitleRegex = new(SH.WebAnnouncementMatchVersionUpdateTitle, RegexOptions.Compiled);
|
||||
|
||||
/// <inheritdoc cref="SH.WebAnnouncementMatchVersionUpdateTime"/>
|
||||
public static readonly Regex VersionUpdateTimeRegex = new(SH.WebAnnouncementMatchVersionUpdateTime, RegexOptions.Compiled);
|
||||
|
||||
/// <inheritdoc cref="SH.WebAnnouncementMatchTransientActivityTime"/>
|
||||
public static readonly Regex TransientActivityAfterUpdateTimeRegex = new(SH.WebAnnouncementMatchTransientActivityTime, RegexOptions.Compiled);
|
||||
|
||||
/// <inheritdoc cref="SH.WebAnnouncementMatchPersistentActivityTime"/>
|
||||
public static readonly Regex PersistentActivityAfterUpdateTimeRegex = new(SH.WebAnnouncementMatchPersistentActivityTime, RegexOptions.Compiled);
|
||||
|
||||
/// <inheritdoc cref="SH.WebAnnouncementMatchPermanentActivityTime"/>
|
||||
public static readonly Regex PermanentActivityAfterUpdateTimeRegex = new(SH.WebAnnouncementMatchPermanentActivityTime, RegexOptions.Compiled);
|
||||
|
||||
public static readonly Regex XmlTimeTagRegex = XmlTimeTagRegexInner ??= XmlTagRegex();
|
||||
|
||||
private static readonly Regex? XmlTimeTagRegexInner;
|
||||
/// <inheritdoc cref="XmlTagRegex"/>
|
||||
public static readonly Regex XmlTimeTagRegex = XmlTagRegex();
|
||||
|
||||
[GeneratedRegex("<t class=\"t_(?:gl|lc)\".*?>(.*?)</t>", RegexOptions.Multiline)]
|
||||
private static partial Regex XmlTagRegex();
|
||||
|
||||
@@ -41,7 +41,8 @@ internal sealed class GachaLogPage : IJsonOnDeserialized
|
||||
/// 地区
|
||||
/// </summary>
|
||||
[JsonPropertyName("region")]
|
||||
public string Region { get; set; } = default!;
|
||||
[JsonConverter(typeof(RegionConverter))]
|
||||
public Region Region { get; set; } = default!;
|
||||
|
||||
public void OnDeserialized()
|
||||
{
|
||||
|
||||
15
src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabRegex.cs
Normal file
15
src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabRegex.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
internal static partial class HoyolabRegex
|
||||
{
|
||||
[GeneratedRegex("^[1-9][0-9]{8}$")]
|
||||
public static partial Regex UidRegex();
|
||||
|
||||
[GeneratedRegex("^(cn_gf01|cn_qd01|os_usa|os_euro|os_asia|os_cht)$")]
|
||||
public static partial Regex RegionRegex();
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
/// <summary>
|
||||
@@ -19,18 +17,18 @@ internal readonly partial struct PlayerUid
|
||||
/// <summary>
|
||||
/// 地区代码
|
||||
/// </summary>
|
||||
public readonly string Region;
|
||||
public readonly Region Region;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的玩家 Uid 结构
|
||||
/// </summary>
|
||||
/// <param name="value">uid</param>
|
||||
/// <param name="region">服务器,当提供该参数时会无条件信任</param>
|
||||
public PlayerUid(string value, string? region = default)
|
||||
public PlayerUid(string value, in Region? region = default)
|
||||
{
|
||||
Must.Argument(UidRegex().IsMatch(value), SH.WebHoyolabInvalidUid);
|
||||
Must.Argument(HoyolabRegex.UidRegex().IsMatch(value), SH.WebHoyolabInvalidUid);
|
||||
Value = value;
|
||||
Region = region ?? EvaluateRegion(value.AsSpan()[0]);
|
||||
Region = region ?? Region.FromUidString(value);
|
||||
}
|
||||
|
||||
public static implicit operator PlayerUid(string source)
|
||||
@@ -45,7 +43,7 @@ internal readonly partial struct PlayerUid
|
||||
|
||||
public static bool IsOversea(string uid)
|
||||
{
|
||||
Must.Argument(UidRegex().IsMatch(uid), SH.WebHoyolabInvalidUid);
|
||||
Must.Argument(HoyolabRegex.UidRegex().IsMatch(uid), SH.WebHoyolabInvalidUid);
|
||||
|
||||
return uid.AsSpan()[0] switch
|
||||
{
|
||||
@@ -56,7 +54,7 @@ internal readonly partial struct PlayerUid
|
||||
|
||||
public static TimeSpan GetRegionTimeZoneUtcOffsetForUid(string uid)
|
||||
{
|
||||
Must.Argument(UidRegex().IsMatch(uid), SH.WebHoyolabInvalidUid);
|
||||
Must.Argument(HoyolabRegex.UidRegex().IsMatch(uid), SH.WebHoyolabInvalidUid);
|
||||
|
||||
// 美服 UTC-05
|
||||
// 欧服 UTC+01
|
||||
@@ -69,12 +67,12 @@ internal readonly partial struct PlayerUid
|
||||
};
|
||||
}
|
||||
|
||||
public static TimeSpan GetRegionTimeZoneUtcOffsetForRegion(string region)
|
||||
public static TimeSpan GetRegionTimeZoneUtcOffsetForRegion(in Region region)
|
||||
{
|
||||
// 美服 UTC-05
|
||||
// 欧服 UTC+01
|
||||
// 其他 UTC+08
|
||||
return region switch
|
||||
return region.Value switch
|
||||
{
|
||||
"os_usa" => ServerRegionTimeZone.AmericaServerOffset,
|
||||
"os_euro" => ServerRegionTimeZone.EuropeServerOffset,
|
||||
@@ -87,24 +85,4 @@ internal readonly partial struct PlayerUid
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
private static string EvaluateRegion(in char first)
|
||||
{
|
||||
return first switch
|
||||
{
|
||||
// CN
|
||||
>= '1' and <= '4' => "cn_gf01", // 国服
|
||||
'5' => "cn_qd01", // 渠道
|
||||
|
||||
// OS
|
||||
'6' => "os_usa", // 美服
|
||||
'7' => "os_euro", // 欧服
|
||||
'8' => "os_asia", // 亚服
|
||||
'9' => "os_cht", // 台服
|
||||
_ => throw Must.NeverHappen(),
|
||||
};
|
||||
}
|
||||
|
||||
[GeneratedRegex("^[1-9][0-9]{8}$")]
|
||||
private static partial Regex UidRegex();
|
||||
}
|
||||
@@ -12,7 +12,7 @@ internal static class PlayerUidExtension
|
||||
{
|
||||
NameValueCollection collection = [];
|
||||
collection.Set("role_id", playerUid.Value);
|
||||
collection.Set("server", playerUid.Region);
|
||||
collection.Set("server", playerUid.Region.Value);
|
||||
|
||||
return collection.ToQueryString();
|
||||
}
|
||||
@@ -21,7 +21,7 @@ internal static class PlayerUidExtension
|
||||
{
|
||||
NameValueCollection collection = [];
|
||||
collection.Set("uid", playerUid.Value);
|
||||
collection.Set("region", playerUid.Region);
|
||||
collection.Set("region", playerUid.Region.Value);
|
||||
|
||||
return collection.ToQueryString();
|
||||
}
|
||||
|
||||
70
src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Region.cs
Normal file
70
src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Region.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
[JsonConverter(typeof(RegionConverter))]
|
||||
internal readonly partial struct Region
|
||||
{
|
||||
public static readonly Region CNGF01 = new("cn_gf01");
|
||||
public static readonly Region CNQD01 = new("cn_qd01");
|
||||
public static readonly Region OSUSA = new("os_usa");
|
||||
public static readonly Region OSEURO = new("os_euro");
|
||||
public static readonly Region OSASIA = new("os_asia");
|
||||
public static readonly Region OSCHT = new("os_cht");
|
||||
|
||||
public readonly string Value;
|
||||
|
||||
public Region(string value)
|
||||
{
|
||||
Must.Argument(HoyolabRegex.RegionRegex().IsMatch(value), SH.WebHoyolabInvalidRegion);
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static implicit operator Region(string value)
|
||||
{
|
||||
return FromRegionString(value);
|
||||
}
|
||||
|
||||
public static Region FromRegionString(string value)
|
||||
{
|
||||
return new(value);
|
||||
}
|
||||
|
||||
public static Region FromUidString(string uid)
|
||||
{
|
||||
return uid.AsSpan()[0] switch
|
||||
{
|
||||
// CN
|
||||
>= '1' and <= '4' => new("cn_gf01"), // 国服
|
||||
'5' => new("cn_qd01"), // 渠道
|
||||
|
||||
// OS
|
||||
'6' => new("os_usa"), // 美服
|
||||
'7' => new("os_euro"), // 欧服
|
||||
'8' => new("os_asia"), // 亚服
|
||||
'9' => new("os_cht"), // 台服
|
||||
_ => throw Must.NeverHappen(),
|
||||
};
|
||||
}
|
||||
|
||||
public static bool IsOversea(string value)
|
||||
{
|
||||
Must.Argument(HoyolabRegex.RegionRegex().IsMatch(value), SH.WebHoyolabInvalidRegion);
|
||||
return value.AsSpan()[..2] switch
|
||||
{
|
||||
"os" => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
public readonly bool IsOversea()
|
||||
{
|
||||
return IsOversea(Value);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
22
src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/RegionConverter.cs
Normal file
22
src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/RegionConverter.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
internal sealed class RegionConverter : JsonConverter<Region>
|
||||
{
|
||||
public override Region Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.GetString() is { } regionValue)
|
||||
{
|
||||
return Region.FromRegionString(regionValue);
|
||||
}
|
||||
|
||||
throw new JsonException();
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, Region value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.Value);
|
||||
}
|
||||
}
|
||||
@@ -50,10 +50,8 @@ internal sealed partial class BindingClient
|
||||
string actionTicket = actionTicketResponse.Data.Ticket;
|
||||
return await GetUserGameRolesByActionTicketAsync(actionTicket, user, token).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Response.Response.DefaultIfNull<ListWrapper<UserGameRole>, ActionTicketWrapper>(actionTicketResponse);
|
||||
}
|
||||
|
||||
return Response.Response.CloneReturnCodeAndMessage<ListWrapper<UserGameRole>, ActionTicketWrapper>(actionTicketResponse);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ internal sealed class GenAuthKeyData
|
||||
/// 区域
|
||||
/// </summary>
|
||||
[JsonPropertyName("region")]
|
||||
public string Region { get; set; } = default!;
|
||||
public Region Region { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 创建为祈愿记录验证密钥提交数据
|
||||
|
||||
@@ -19,7 +19,7 @@ internal sealed class UserGameRole
|
||||
/// 服务器
|
||||
/// </summary>
|
||||
[JsonPropertyName("region")]
|
||||
public string Region { get; set; } = default!;
|
||||
public Region Region { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 游戏Uid
|
||||
|
||||
@@ -31,7 +31,7 @@ internal sealed class SignInData
|
||||
/// 地区代码
|
||||
/// </summary>
|
||||
[JsonPropertyName("region")]
|
||||
public string Region { get; }
|
||||
public Region Region { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Uid
|
||||
|
||||
@@ -181,7 +181,7 @@ internal sealed partial class CalculateClient
|
||||
public string Uid { get; set; } = default!;
|
||||
|
||||
[JsonPropertyName("region")]
|
||||
public string Region { get; set; } = default!;
|
||||
public Region Region { get; set; } = default!;
|
||||
}
|
||||
|
||||
private class IdCount
|
||||
|
||||
@@ -39,5 +39,5 @@ internal sealed class CharacterData
|
||||
/// 服务器
|
||||
/// </summary>
|
||||
[JsonPropertyName("server")]
|
||||
public string Server { get; }
|
||||
public Region Server { get; }
|
||||
}
|
||||
@@ -22,10 +22,10 @@ internal sealed class Role
|
||||
public string Nickname { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 服务器
|
||||
/// 服务器名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("region")]
|
||||
public string Region { get; set; } = default!;
|
||||
public string RegionName { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 等级
|
||||
|
||||
@@ -23,10 +23,10 @@ internal sealed class BasicRoleInfo
|
||||
public string Nickname { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 区域代码
|
||||
/// 服务器名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("region")]
|
||||
public string Region { get; set; } = default!;
|
||||
public string RegionName { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 等级
|
||||
|
||||
@@ -40,5 +40,4 @@ internal sealed class CardVerifiationHeaders
|
||||
Page = page,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,7 +39,7 @@ internal sealed partial class HomaGachaLogClient
|
||||
.TryCatchSendAsync<HutaoResponse<GachaEventStatistics>>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -60,7 +60,7 @@ internal sealed partial class HomaGachaLogClient
|
||||
.TryCatchSendAsync<HutaoResponse<GachaDistribution>>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -80,7 +80,7 @@ internal sealed partial class HomaGachaLogClient
|
||||
.TryCatchSendAsync<HutaoResponse<List<GachaEntry>>>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -101,7 +101,7 @@ internal sealed partial class HomaGachaLogClient
|
||||
.TryCatchSendAsync<HutaoResponse<EndIds>>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -125,7 +125,7 @@ internal sealed partial class HomaGachaLogClient
|
||||
.TryCatchSendAsync<HutaoResponse<List<GachaItem>>>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -149,7 +149,7 @@ internal sealed partial class HomaGachaLogClient
|
||||
.TryCatchSendAsync<HutaoResponse>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -170,7 +170,7 @@ internal sealed partial class HomaGachaLogClient
|
||||
.TryCatchSendAsync<HutaoResponse>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
private sealed class UidAndEndIds
|
||||
|
||||
@@ -34,7 +34,7 @@ internal sealed partial class HutaoAsAServiceClient
|
||||
.TryCatchSendAsync<HutaoResponse<List<Announcement>>>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
public async ValueTask<HutaoResponse> UploadAnnouncementAsync(UploadAnnouncement uploadAnnouncement, CancellationToken token = default)
|
||||
@@ -49,7 +49,7 @@ internal sealed partial class HutaoAsAServiceClient
|
||||
.TryCatchSendAsync<HutaoResponse>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
public async ValueTask<HutaoResponse> GachaLogCompensationAsync(int days, CancellationToken token = default)
|
||||
@@ -64,7 +64,7 @@ internal sealed partial class HutaoAsAServiceClient
|
||||
.TryCatchSendAsync<HutaoResponse>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
public async ValueTask<HutaoResponse> GachaLogDesignationAsync(string userName, int days, CancellationToken token = default)
|
||||
@@ -79,6 +79,6 @@ internal sealed partial class HutaoAsAServiceClient
|
||||
.TryCatchSendAsync<HutaoResponse>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
using Snap.Hutao.Web.Hutao.Response;
|
||||
using Snap.Hutao.Web.Request.Builder;
|
||||
using Snap.Hutao.Web.Request.Builder.Abstraction;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao;
|
||||
@@ -17,13 +17,33 @@ internal sealed partial class HutaoInfrastructureClient
|
||||
private readonly ILogger<HutaoInfrastructureClient> logger;
|
||||
private readonly HttpClient httpClient;
|
||||
|
||||
public async ValueTask<Response<IPInformation>> GetIPInformationAsync(CancellationToken token = default)
|
||||
public async ValueTask<HutaoResponse<IPInformation>> GetIPInformationAsync(CancellationToken token = default)
|
||||
{
|
||||
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
|
||||
.SetRequestUri(HutaoEndpoints.Ip)
|
||||
.Get();
|
||||
|
||||
Response<IPInformation>? resp = await builder.TryCatchSendAsync<Response<IPInformation>>(httpClient, logger, token).ConfigureAwait(false);
|
||||
HutaoResponse<IPInformation>? resp = await builder.TryCatchSendAsync<HutaoResponse<IPInformation>>(httpClient, logger, token).ConfigureAwait(false);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
public async ValueTask<HutaoResponse<HutaoVersionInformation>> GetHutaoVersionInfomationAsync(CancellationToken token = default)
|
||||
{
|
||||
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
|
||||
.SetRequestUri(HutaoEndpoints.PatchSnapHutao)
|
||||
.Get();
|
||||
|
||||
HutaoResponse<HutaoVersionInformation>? resp = await builder.TryCatchSendAsync<HutaoResponse<HutaoVersionInformation>>(httpClient, logger, token).ConfigureAwait(false);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
public async ValueTask<HutaoResponse<YaeVersionInformation>> GetYaeVersionInformationAsync(CancellationToken token = default)
|
||||
{
|
||||
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
|
||||
.SetRequestUri(HutaoEndpoints.PatchYaeAchievement)
|
||||
.Get();
|
||||
|
||||
HutaoResponse<YaeVersionInformation>? resp = await builder.TryCatchSendAsync<HutaoResponse<YaeVersionInformation>>(httpClient, logger, token).ConfigureAwait(false);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ internal sealed partial class HutaoPassportClient
|
||||
.TryCatchSendAsync<HutaoResponse>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -95,7 +95,7 @@ internal sealed partial class HutaoPassportClient
|
||||
.TryCatchSendAsync<HutaoResponse<string>>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
public async ValueTask<HutaoResponse> UnregisterAsync(string email, string password, string verifyCode, CancellationToken token = default)
|
||||
@@ -117,7 +117,7 @@ internal sealed partial class HutaoPassportClient
|
||||
.TryCatchSendAsync<HutaoResponse>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -145,7 +145,7 @@ internal sealed partial class HutaoPassportClient
|
||||
.TryCatchSendAsync<HutaoResponse<string>>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -171,7 +171,7 @@ internal sealed partial class HutaoPassportClient
|
||||
.TryCatchSendAsync<HutaoResponse<string>>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -191,7 +191,7 @@ internal sealed partial class HutaoPassportClient
|
||||
.TryCatchSendAsync<HutaoResponse<UserInfo>>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
private static string Encrypt(string text)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao;
|
||||
|
||||
internal sealed class HutaoVersionInformation
|
||||
{
|
||||
[JsonPropertyName("version")]
|
||||
public Version Version { get; set; } = default!;
|
||||
|
||||
[JsonPropertyName("urls")]
|
||||
public List<string> Urls { get; set; } = default!;
|
||||
|
||||
[JsonPropertyName("sha256")]
|
||||
public string? Sha256 { get; set; } = default!;
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao;
|
||||
|
||||
internal sealed class IPInformation
|
||||
internal sealed partial class IPInformation
|
||||
{
|
||||
private const string Unknown = "Unknown";
|
||||
|
||||
@@ -26,6 +28,10 @@ internal sealed class IPInformation
|
||||
return SH.WebHutaoServiceUnAvailable;
|
||||
}
|
||||
|
||||
return SH.FormatViewPageSettingDeviceIpDescription(Ip, Division);
|
||||
string maskedIp = IpRegex().Replace(Ip, "$1.$2.*.*");
|
||||
return SH.FormatViewPageSettingDeviceIpDescription(maskedIp, Division);
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"(\d+)\.(\d+)\.\d+\.\d+")]
|
||||
private static partial Regex IpRegex();
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using Snap.Hutao.Web.Response;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Response;
|
||||
|
||||
internal sealed class HutaoResponse : Web.Response.Response, ILocalizableResponse
|
||||
internal sealed class HutaoResponse : Web.Response.Response, ILocalizableResponse, ICommonResponse<HutaoResponse>
|
||||
{
|
||||
[JsonConstructor]
|
||||
public HutaoResponse(int returnCode, string message, string? localizationKey)
|
||||
@@ -17,18 +17,9 @@ internal sealed class HutaoResponse : Web.Response.Response, ILocalizableRespons
|
||||
[JsonPropertyName("l10nKey")]
|
||||
public string? LocalizationKey { get; set; }
|
||||
|
||||
public static HutaoResponse DefaultIfNull(HutaoResponse? response, [CallerMemberName] string callerName = default!)
|
||||
static HutaoResponse ICommonResponse<HutaoResponse>.CreateDefault(int returnCode, string message)
|
||||
{
|
||||
// 0x26F19335 is a magic number that hashed from "Snap.Hutao"
|
||||
response ??= new(InternalFailure, SH.FormatWebResponseRequestExceptionFormat(callerName, null), default);
|
||||
return response;
|
||||
}
|
||||
|
||||
public static HutaoResponse<TData> DefaultIfNull<TData>(HutaoResponse<TData>? response, [CallerMemberName] string callerName = default!)
|
||||
{
|
||||
// 0x26F19335 is a magic number that hashed from "Snap.Hutao"
|
||||
response ??= new(InternalFailure, SH.FormatWebResponseRequestExceptionFormat(callerName, typeof(TData).Name), default, default);
|
||||
return response ?? new(InternalFailure, SH.FormatWebResponseRequestExceptionFormat(callerName, typeof(TData).Name), default, default);
|
||||
return new(returnCode, message, default);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
@@ -38,7 +29,7 @@ internal sealed class HutaoResponse : Web.Response.Response, ILocalizableRespons
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SA1402")]
|
||||
internal sealed class HutaoResponse<TData> : Web.Response.Response<TData>, ILocalizableResponse
|
||||
internal sealed class HutaoResponse<TData> : Response<TData>, ILocalizableResponse, ICommonResponse<HutaoResponse<TData>>
|
||||
{
|
||||
[JsonConstructor]
|
||||
public HutaoResponse(int returnCode, string message, TData? data, string? localizationKey)
|
||||
@@ -50,6 +41,11 @@ internal sealed class HutaoResponse<TData> : Web.Response.Response<TData>, ILoca
|
||||
[JsonPropertyName("l10nKey")]
|
||||
public string? LocalizationKey { get; set; }
|
||||
|
||||
static HutaoResponse<TData> ICommonResponse<HutaoResponse<TData>>.CreateDefault(int returnCode, string message)
|
||||
{
|
||||
return new(returnCode, message, default, default);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return SH.FormatWebResponseFormat(ReturnCode, this.GetLocalizationMessageOrDefault());
|
||||
|
||||
@@ -47,7 +47,7 @@ internal sealed partial class HutaoSpiralAbyssClient
|
||||
.TryCatchSendAsync<HutaoResponse<bool>>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -67,7 +67,7 @@ internal sealed partial class HutaoSpiralAbyssClient
|
||||
.TryCatchSendAsync<HutaoResponse<RankInfo>>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -86,7 +86,7 @@ internal sealed partial class HutaoSpiralAbyssClient
|
||||
.TryCatchSendAsync<HutaoResponse<Overview>>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -105,7 +105,7 @@ internal sealed partial class HutaoSpiralAbyssClient
|
||||
.TryCatchSendAsync<HutaoResponse<List<AvatarAppearanceRank>>>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -124,7 +124,7 @@ internal sealed partial class HutaoSpiralAbyssClient
|
||||
.TryCatchSendAsync<HutaoResponse<List<AvatarUsageRank>>>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -143,7 +143,7 @@ internal sealed partial class HutaoSpiralAbyssClient
|
||||
.TryCatchSendAsync<HutaoResponse<List<AvatarCollocation>>>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -162,7 +162,7 @@ internal sealed partial class HutaoSpiralAbyssClient
|
||||
.TryCatchSendAsync<HutaoResponse<List<WeaponCollocation>>>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -181,7 +181,7 @@ internal sealed partial class HutaoSpiralAbyssClient
|
||||
.TryCatchSendAsync<HutaoResponse<List<AvatarConstellationInfo>>>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -200,7 +200,7 @@ internal sealed partial class HutaoSpiralAbyssClient
|
||||
.TryCatchSendAsync<HutaoResponse<List<TeamAppearance>>>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -259,6 +259,6 @@ internal sealed partial class HutaoSpiralAbyssClient
|
||||
.TryCatchSendAsync<HutaoResponse>(httpClient, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return HutaoResponse.DefaultIfNull(resp);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
}
|
||||
19
src/Snap.Hutao/Snap.Hutao/Web/Hutao/YaeVersionInformation.cs
Normal file
19
src/Snap.Hutao/Snap.Hutao/Web/Hutao/YaeVersionInformation.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao;
|
||||
|
||||
internal sealed class YaeVersionInformation
|
||||
{
|
||||
[JsonPropertyName("tagName")]
|
||||
public Version Version { get; set; } = default!;
|
||||
|
||||
[JsonPropertyName("url")]
|
||||
public string Url { get; set; } = default!;
|
||||
|
||||
[JsonPropertyName("source")]
|
||||
public string Source { get; set; } = default!;
|
||||
|
||||
[JsonPropertyName("frameworkUrl")]
|
||||
public string FrameworkUrl { get; set; } = default!;
|
||||
}
|
||||
@@ -11,28 +11,10 @@ namespace Snap.Hutao.Web;
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[SuppressMessage("", "SA1201")]
|
||||
[SuppressMessage("", "SA1203")]
|
||||
[SuppressMessage("", "SA1124")]
|
||||
internal static class HutaoEndpoints
|
||||
{
|
||||
#region Hutao as a Service
|
||||
public static string Announcement(string locale)
|
||||
{
|
||||
return $"{HomaSnapGenshinApi}/Announcement/List?locale={locale}";
|
||||
}
|
||||
|
||||
public const string AnnouncementUpload = $"{HomaSnapGenshinApi}/Service/Announcement/Upload";
|
||||
|
||||
public static string GachaLogCompensation(int days)
|
||||
{
|
||||
return $"{HomaSnapGenshinApi}/Service/GachaLog/Compensation?days={days}";
|
||||
}
|
||||
|
||||
public static string GachaLogDesignation(string userName, int days)
|
||||
{
|
||||
return $"{HomaSnapGenshinApi}/Service/GachaLog/Designation?userName={userName}&days={days}";
|
||||
}
|
||||
#endregion
|
||||
#region HomaAPI
|
||||
|
||||
#region GachaLog
|
||||
|
||||
@@ -43,28 +25,28 @@ internal static class HutaoEndpoints
|
||||
/// <returns>获取末尾Id Url</returns>
|
||||
public static string GachaLogEndIds(string uid)
|
||||
{
|
||||
return $"{HomaSnapGenshinApi}/GachaLog/EndIds?Uid={uid}";
|
||||
return $"{HomaSnapGenshin}/GachaLog/EndIds?Uid={uid}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取祈愿记录
|
||||
/// </summary>
|
||||
public const string GachaLogRetrieve = $"{HomaSnapGenshinApi}/GachaLog/Retrieve";
|
||||
public const string GachaLogRetrieve = $"{HomaSnapGenshin}/GachaLog/Retrieve";
|
||||
|
||||
/// <summary>
|
||||
/// 上传祈愿记录
|
||||
/// </summary>
|
||||
public const string GachaLogUpload = $"{HomaSnapGenshinApi}/GachaLog/Upload";
|
||||
public const string GachaLogUpload = $"{HomaSnapGenshin}/GachaLog/Upload";
|
||||
|
||||
/// <summary>
|
||||
/// 获取Uid列表
|
||||
/// </summary>
|
||||
public const string GachaLogUids = $"{HomaSnapGenshinApi}/GachaLog/Uids";
|
||||
public const string GachaLogUids = $"{HomaSnapGenshin}/GachaLog/Uids";
|
||||
|
||||
/// <summary>
|
||||
/// 获取Uid列表
|
||||
/// </summary>
|
||||
public const string GachaLogEntries = $"{HomaSnapGenshinApi}/GachaLog/Entries";
|
||||
public const string GachaLogEntries = $"{HomaSnapGenshin}/GachaLog/Entries";
|
||||
|
||||
/// <summary>
|
||||
/// 删除祈愿记录
|
||||
@@ -73,13 +55,13 @@ internal static class HutaoEndpoints
|
||||
/// <returns>删除祈愿记录 Url</returns>
|
||||
public static string GachaLogDelete(string uid)
|
||||
{
|
||||
return $"{HomaSnapGenshinApi}/GachaLog/Delete?Uid={uid}";
|
||||
return $"{HomaSnapGenshin}/GachaLog/Delete?Uid={uid}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取祈愿统计信息
|
||||
/// </summary>
|
||||
public const string GachaLogStatisticsCurrentEvents = $"{HomaSnapGenshinApi}/GachaLog/Statistics/CurrentEventStatistics";
|
||||
public const string GachaLogStatisticsCurrentEvents = $"{HomaSnapGenshin}/GachaLog/Statistics/CurrentEventStatistics";
|
||||
|
||||
/// <summary>
|
||||
/// 获取祈愿统计信息
|
||||
@@ -88,41 +70,27 @@ internal static class HutaoEndpoints
|
||||
/// <returns>祈愿统计信息Url</returns>
|
||||
public static string GachaLogStatisticsDistribution(GachaDistributionType distributionType)
|
||||
{
|
||||
return $"{HomaSnapGenshinApi}/GachaLog/Statistics/Distribution/{distributionType}";
|
||||
return $"{HomaSnapGenshin}/GachaLog/Statistics/Distribution/{distributionType}";
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Passport
|
||||
#region Hutao as a Service
|
||||
public static string Announcement(string locale)
|
||||
{
|
||||
return $"{HomaSnapGenshin}/Announcement/List?locale={locale}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取注册验证码
|
||||
/// </summary>
|
||||
public const string PassportVerify = $"{HomaSnapGenshinApi}/Passport/Verify";
|
||||
public const string AnnouncementUpload = $"{HomaSnapGenshin}/Service/Announcement/Upload";
|
||||
|
||||
/// <summary>
|
||||
/// 注册账号
|
||||
/// </summary>
|
||||
public const string PassportRegister = $"{HomaSnapGenshinApi}/Passport/Register";
|
||||
public static string GachaLogCompensation(int days)
|
||||
{
|
||||
return $"{HomaSnapGenshin}/Service/GachaLog/Compensation?days={days}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注销账号
|
||||
/// </summary>
|
||||
public const string PassportCancel = $"{HomaSnapGenshinApi}/Passport/Cancel";
|
||||
|
||||
/// <summary>
|
||||
/// 重设密码
|
||||
/// </summary>
|
||||
public const string PassportResetPassword = $"{HomaSnapGenshinApi}/Passport/ResetPassword";
|
||||
|
||||
/// <summary>
|
||||
/// 登录
|
||||
/// </summary>
|
||||
public const string PassportLogin = $"{HomaSnapGenshinApi}/Passport/Login";
|
||||
|
||||
/// <summary>
|
||||
/// 用户信息
|
||||
/// </summary>
|
||||
public const string PassportUserInfo = $"{HomaSnapGenshinApi}/Passport/UserInfo";
|
||||
public static string GachaLogDesignation(string userName, int days)
|
||||
{
|
||||
return $"{HomaSnapGenshin}/Service/GachaLog/Designation?userName={userName}&days={days}";
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region LogUpload
|
||||
@@ -130,7 +98,40 @@ internal static class HutaoEndpoints
|
||||
/// <summary>
|
||||
/// 上传日志
|
||||
/// </summary>
|
||||
public const string HutaoLogUpload = $"{HomaSnapGenshinApi}/HutaoLog/Upload";
|
||||
public const string HutaoLogUpload = $"{HomaSnapGenshin}/HutaoLog/Upload";
|
||||
#endregion
|
||||
|
||||
#region Passport
|
||||
|
||||
/// <summary>
|
||||
/// 获取注册验证码
|
||||
/// </summary>
|
||||
public const string PassportVerify = $"{HomaSnapGenshin}/Passport/Verify";
|
||||
|
||||
/// <summary>
|
||||
/// 注册账号
|
||||
/// </summary>
|
||||
public const string PassportRegister = $"{HomaSnapGenshin}/Passport/Register";
|
||||
|
||||
/// <summary>
|
||||
/// 注销账号
|
||||
/// </summary>
|
||||
public const string PassportCancel = $"{HomaSnapGenshin}/Passport/Cancel";
|
||||
|
||||
/// <summary>
|
||||
/// 重设密码
|
||||
/// </summary>
|
||||
public const string PassportResetPassword = $"{HomaSnapGenshin}/Passport/ResetPassword";
|
||||
|
||||
/// <summary>
|
||||
/// 登录
|
||||
/// </summary>
|
||||
public const string PassportLogin = $"{HomaSnapGenshin}/Passport/Login";
|
||||
|
||||
/// <summary>
|
||||
/// 用户信息
|
||||
/// </summary>
|
||||
public const string PassportUserInfo = $"{HomaSnapGenshin}/Passport/UserInfo";
|
||||
#endregion
|
||||
|
||||
#region SpiralAbyss
|
||||
@@ -142,7 +143,7 @@ internal static class HutaoEndpoints
|
||||
/// <returns>路径</returns>
|
||||
public static string RecordCheck(string uid)
|
||||
{
|
||||
return $"{HomaSnapGenshinApi}/Record/Check?uid={uid}";
|
||||
return $"{HomaSnapGenshin}/Record/Check?uid={uid}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -152,50 +153,66 @@ internal static class HutaoEndpoints
|
||||
/// <returns>路径</returns>
|
||||
public static string RecordRank(string uid)
|
||||
{
|
||||
return $"{HomaSnapGenshinApi}/Record/Rank?uid={uid}";
|
||||
return $"{HomaSnapGenshin}/Record/Rank?uid={uid}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传记录
|
||||
/// </summary>
|
||||
public const string RecordUpload = $"{HomaSnapGenshinApi}/Record/Upload";
|
||||
public const string RecordUpload = $"{HomaSnapGenshin}/Record/Upload";
|
||||
|
||||
/// <summary>
|
||||
/// 统计信息
|
||||
/// </summary>
|
||||
public const string StatisticsOverview = $"{HomaSnapGenshinApi}/Statistics/Overview";
|
||||
public const string StatisticsOverview = $"{HomaSnapGenshin}/Statistics/Overview";
|
||||
|
||||
/// <summary>
|
||||
/// 出场率
|
||||
/// </summary>
|
||||
public const string StatisticsAvatarAttendanceRate = $"{HomaSnapGenshinApi}/Statistics/Avatar/AttendanceRate";
|
||||
public const string StatisticsAvatarAttendanceRate = $"{HomaSnapGenshin}/Statistics/Avatar/AttendanceRate";
|
||||
|
||||
/// <summary>
|
||||
/// 使用率
|
||||
/// </summary>
|
||||
public const string StatisticsAvatarUtilizationRate = $"{HomaSnapGenshinApi}/Statistics/Avatar/UtilizationRate";
|
||||
public const string StatisticsAvatarUtilizationRate = $"{HomaSnapGenshin}/Statistics/Avatar/UtilizationRate";
|
||||
|
||||
/// <summary>
|
||||
/// 角色搭配
|
||||
/// </summary>
|
||||
public const string StatisticsAvatarAvatarCollocation = $"{HomaSnapGenshinApi}/Statistics/Avatar/AvatarCollocation";
|
||||
public const string StatisticsAvatarAvatarCollocation = $"{HomaSnapGenshin}/Statistics/Avatar/AvatarCollocation";
|
||||
|
||||
/// <summary>
|
||||
/// 角色持有率
|
||||
/// </summary>
|
||||
public const string StatisticsAvatarHoldingRate = $"{HomaSnapGenshinApi}/Statistics/Avatar/HoldingRate";
|
||||
public const string StatisticsAvatarHoldingRate = $"{HomaSnapGenshin}/Statistics/Avatar/HoldingRate";
|
||||
|
||||
/// <summary>
|
||||
/// 武器搭配
|
||||
/// </summary>
|
||||
public const string StatisticsWeaponWeaponCollocation = $"{HomaSnapGenshinApi}/Statistics/Weapon/WeaponCollocation";
|
||||
public const string StatisticsWeaponWeaponCollocation = $"{HomaSnapGenshin}/Statistics/Weapon/WeaponCollocation";
|
||||
|
||||
/// <summary>
|
||||
/// 持有率
|
||||
/// </summary>
|
||||
public const string StatisticsTeamCombination = $"{HomaSnapGenshinApi}/Statistics/Team/Combination";
|
||||
public const string StatisticsTeamCombination = $"{HomaSnapGenshin}/Statistics/Team/Combination";
|
||||
#endregion
|
||||
|
||||
public static string Website(string path)
|
||||
{
|
||||
return $"{HomaSnapGenshin}/{path}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Infrasturcture
|
||||
|
||||
public static string Enka(in PlayerUid uid)
|
||||
{
|
||||
return $"{ApiSnapGenshinEnka}/{uid}";
|
||||
}
|
||||
|
||||
public const string Ip = $"{ApiSnapGenshin}/ip";
|
||||
|
||||
#region Metadata
|
||||
|
||||
/// <summary>
|
||||
@@ -210,7 +227,12 @@ internal static class HutaoEndpoints
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Static & Zip
|
||||
#region Patch
|
||||
public const string PatchYaeAchievement = $"{ApiSnapGenshinPatch}/yae";
|
||||
public const string PatchSnapHutao = $"{ApiSnapGenshinPatch}/hutao";
|
||||
#endregion
|
||||
|
||||
#region StaticResources
|
||||
|
||||
/// <summary>
|
||||
/// UI_Icon_None
|
||||
@@ -249,21 +271,13 @@ internal static class HutaoEndpoints
|
||||
}
|
||||
#endregion
|
||||
|
||||
public static string Website(string path)
|
||||
{
|
||||
return $"{HomaSnapGenshinApi}/{path}";
|
||||
}
|
||||
#endregion
|
||||
|
||||
public static string Enka(in PlayerUid uid)
|
||||
{
|
||||
return $"{ApiSnapGenshinEnka}/{uid}";
|
||||
}
|
||||
|
||||
public const string Ip = $"{ApiSnapGenshin}/ip";
|
||||
private const string ApiSnapGenshin = "https://api.snapgenshin.com";
|
||||
private const string ApiSnapGenshinMetadata = $"{ApiSnapGenshin}/metadata";
|
||||
private const string ApiSnapGenshinPatch = $"{ApiSnapGenshin}/patch";
|
||||
private const string ApiSnapGenshinStaticRaw = $"{ApiSnapGenshin}/static/raw";
|
||||
private const string ApiSnapGenshinStaticZip = $"{ApiSnapGenshin}/static/zip";
|
||||
private const string ApiSnapGenshinEnka = $"{ApiSnapGenshin}/enka";
|
||||
private const string HomaSnapGenshinApi = "https://homa.snapgenshin.com";
|
||||
private const string HomaSnapGenshin = "https://homa.snapgenshin.com";
|
||||
}
|
||||
14
src/Snap.Hutao/Snap.Hutao/Web/Response/ICommonResponse.cs
Normal file
14
src/Snap.Hutao/Snap.Hutao/Web/Response/ICommonResponse.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Response;
|
||||
|
||||
internal interface ICommonResponse<TResponse>
|
||||
where TResponse : ICommonResponse<TResponse>
|
||||
{
|
||||
int ReturnCode { get; }
|
||||
|
||||
string Message { get; set; }
|
||||
|
||||
static abstract TResponse CreateDefault(int returnCode, string message);
|
||||
}
|
||||
@@ -1,25 +1,17 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using Snap.Hutao.Web.Bridge.Model;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Snap.Hutao.Web.Response;
|
||||
|
||||
/// <summary>
|
||||
/// 提供 <see cref="Response{T}"/> 的非泛型基类
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal class Response
|
||||
internal class Response : ICommonResponse<Response>
|
||||
{
|
||||
public const int InternalFailure = 0x26F19335;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的响应
|
||||
/// </summary>
|
||||
/// <param name="returnCode">返回代码</param>
|
||||
/// <param name="message">消息</param>
|
||||
[JsonConstructor]
|
||||
public Response(int returnCode, string message)
|
||||
{
|
||||
@@ -30,15 +22,9 @@ internal class Response
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回代码
|
||||
/// </summary>
|
||||
[JsonPropertyName("retcode")]
|
||||
public int ReturnCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息
|
||||
/// </summary>
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; set; } = default!;
|
||||
|
||||
@@ -47,16 +33,16 @@ internal class Response
|
||||
return new(response.ReturnCode == 0, response.Message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回本体或带有消息提示的默认值
|
||||
/// </summary>
|
||||
/// <param name="response">本体</param>
|
||||
/// <param name="callerName">调用方法名称</param>
|
||||
/// <returns>本体或默认值,当本体为 null 时 返回默认值</returns>
|
||||
public static Response DefaultIfNull(Response? response, [CallerMemberName] string callerName = default!)
|
||||
static Response ICommonResponse<Response>.CreateDefault(int returnCode, string message)
|
||||
{
|
||||
// 0x26F19335 is a magic number that hashed from "Snap.Hutao"
|
||||
response ??= new(InternalFailure, SH.FormatWebResponseRequestExceptionFormat(callerName, null));
|
||||
return new(returnCode, message);
|
||||
}
|
||||
|
||||
public static TResponse DefaultIfNull<TResponse>(TResponse? response, [CallerMemberName] string callerName = default!)
|
||||
where TResponse : ICommonResponse<TResponse>
|
||||
{
|
||||
string message = SH.FormatWebResponseRequestExceptionFormat(callerName, TypeNameHelper.GetTypeDisplayName(typeof(TResponse)));
|
||||
response ??= TResponse.CreateDefault(InternalFailure, message);
|
||||
|
||||
if (((KnownReturnCode)response.ReturnCode) is KnownReturnCode.PleaseLogin or KnownReturnCode.RET_TOKEN_INVALID)
|
||||
{
|
||||
@@ -66,46 +52,9 @@ internal class Response
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回本体或带有消息提示的默认值
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">类型</typeparam>
|
||||
/// <param name="response">本体</param>
|
||||
/// <param name="callerName">调用方法名称</param>
|
||||
/// <returns>本体或默认值,当本体为 null 时 返回默认值</returns>
|
||||
public static Response<TData> DefaultIfNull<TData>(Response<TData>? response, [CallerMemberName] string callerName = default!)
|
||||
public static Response<TData> CloneReturnCodeAndMessage<TData, TOther>(Response<TOther> response, [CallerMemberName] string callerName = default!)
|
||||
{
|
||||
// 0x26F19335 is a magic number that hashed from "Snap.Hutao"
|
||||
response ??= new(InternalFailure, SH.FormatWebResponseRequestExceptionFormat(callerName, typeof(TData).Name), default);
|
||||
|
||||
if (((KnownReturnCode)response.ReturnCode) is KnownReturnCode.PleaseLogin or KnownReturnCode.RET_TOKEN_INVALID)
|
||||
{
|
||||
response.Message = SH.FormatWebResponseRefreshCookieHintFormat(response.Message);
|
||||
}
|
||||
|
||||
return response ?? new(InternalFailure, SH.FormatWebResponseRequestExceptionFormat(callerName, typeof(TData).Name), default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回本体或带有消息提示的默认值
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">类型</typeparam>
|
||||
/// <typeparam name="TOther">其他类型</typeparam>
|
||||
/// <param name="response">本体</param>
|
||||
/// <param name="callerName">调用方法名称</param>
|
||||
/// <returns>本体或默认值,当本体为 null 时 返回默认值</returns>
|
||||
public static Response<TData> DefaultIfNull<TData, TOther>(Response<TOther>? response, [CallerMemberName] string callerName = default!)
|
||||
{
|
||||
if (response is not null)
|
||||
{
|
||||
Must.Argument(response.ReturnCode != 0, "RetCode has to be 0");
|
||||
return new(response.ReturnCode, response.Message, default);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Magic number that hashed from "Snap.Hutao"
|
||||
return new(InternalFailure, SH.FormatWebResponseRequestExceptionFormat(callerName, typeof(TData).Name), default);
|
||||
}
|
||||
return new(response.ReturnCode, response.Message, default);
|
||||
}
|
||||
|
||||
public virtual bool IsOk(bool showInfoBar = true, IServiceProvider? serviceProvider = null)
|
||||
@@ -126,27 +75,15 @@ internal class Response
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return SH.FormatWebResponseFormat(ReturnCode, Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mihoyo 标准API响应
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">数据类型</typeparam>
|
||||
[SuppressMessage("", "SA1402")]
|
||||
[HighQuality]
|
||||
internal class Response<TData> : Response, IJsResult
|
||||
internal class Response<TData> : Response, ICommonResponse<Response<TData>>, IJsBridgeResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的 Mihoyo 标准API响应
|
||||
/// </summary>
|
||||
/// <param name="returnCode">返回代码</param>
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="data">数据</param>
|
||||
[JsonConstructor]
|
||||
public Response(int returnCode, string message, TData? data)
|
||||
: base(returnCode, message)
|
||||
@@ -154,18 +91,14 @@ internal class Response<TData> : Response, IJsResult
|
||||
Data = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据
|
||||
/// </summary>
|
||||
[JsonPropertyName("data")]
|
||||
public TData? Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 响应是否正常
|
||||
/// </summary>
|
||||
/// <param name="showInfoBar">是否显示错误信息</param>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
/// <returns>是否Ok</returns>
|
||||
static Response<TData> ICommonResponse<Response<TData>>.CreateDefault(int returnCode, string message)
|
||||
{
|
||||
return new(returnCode, message, default);
|
||||
}
|
||||
|
||||
[MemberNotNullWhen(true, nameof(Data))]
|
||||
public override bool IsOk(bool showInfoBar = true, IServiceProvider? serviceProvider = null)
|
||||
{
|
||||
@@ -185,28 +118,4 @@ internal class Response<TData> : Response, IJsResult
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetData([NotNullWhen(true)] out TData? data, IInfoBarService? infoBarService = null, IServiceProvider? serviceProvider = null)
|
||||
{
|
||||
if (ReturnCode == 0)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(Data);
|
||||
data = Data;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
serviceProvider ??= Ioc.Default;
|
||||
infoBarService ??= serviceProvider.GetRequiredService<IInfoBarService>();
|
||||
infoBarService.Error(ToString());
|
||||
data = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string ToJson()
|
||||
{
|
||||
return JsonSerializer.Serialize(this);
|
||||
}
|
||||
}
|
||||
27
src/Snap.Hutao/Snap.Hutao/Web/Response/ResponseExtension.cs
Normal file
27
src/Snap.Hutao/Snap.Hutao/Web/Response/ResponseExtension.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Service.Notification;
|
||||
|
||||
namespace Snap.Hutao.Web.Response;
|
||||
|
||||
internal static class ResponseExtension
|
||||
{
|
||||
public static bool TryGetData<TData>(this Response<TData> response, [NotNullWhen(true)] out TData? data, IInfoBarService? infoBarService = null, IServiceProvider? serviceProvider = null)
|
||||
{
|
||||
if (response.ReturnCode == 0)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(response.Data);
|
||||
data = response.Data;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
serviceProvider ??= Ioc.Default;
|
||||
infoBarService ??= serviceProvider.GetRequiredService<IInfoBarService>();
|
||||
infoBarService.Error(response.ToString());
|
||||
data = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user