mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Merge pull request #1192 from DGP-Studio/feat/ann
This commit is contained in:
@@ -127,4 +127,6 @@ internal sealed partial class SettingEntry
|
||||
/// 自定义极验接口
|
||||
/// </summary>
|
||||
public const string GeetestCustomCompositeUrl = "GeetestCustomCompositeUrl";
|
||||
|
||||
public const string AnnouncementRegion = "AnnouncementRegion";
|
||||
}
|
||||
|
||||
@@ -2306,6 +2306,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>
|
||||
@@ -2852,9 +2858,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);
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
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 +26,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 +46,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())
|
||||
|
||||
@@ -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 List<NameValue<Region>> Regions { get; } = 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.Regions.SingleOrDefault(c => c.Value.Value == appOptions.Region.Value);
|
||||
}
|
||||
}
|
||||
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,9 @@ 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("ja")),
|
||||
ToNameValue(CultureInfo.GetCultureInfo("id")),
|
||||
ToNameValue(CultureInfo.GetCultureInfo("ko")),
|
||||
];
|
||||
|
||||
public static List<NameValue<CultureInfo>> Get()
|
||||
|
||||
@@ -305,6 +305,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.Regions}"
|
||||
SelectedItem="{Binding SelectedRegion, Mode=TwoWay}"/>
|
||||
</shc:SizeRestrictedContentControl>
|
||||
</cwc:SettingsCard>
|
||||
|
||||
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingGameHeader}"/>
|
||||
<cwc:SettingsCard
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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,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>
|
||||
/// 等级
|
||||
|
||||
Reference in New Issue
Block a user