diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.Constant.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.Constant.cs
index b18f0ef8..29249345 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.Constant.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.Constant.cs
@@ -127,4 +127,6 @@ internal sealed partial class SettingEntry
/// 自定义极验接口
///
public const string GeetestCustomCompositeUrl = "GeetestCustomCompositeUrl";
+
+ public const string AnnouncementRegion = "AnnouncementRegion";
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
index 4a0d4ea4..25fb91cf 100644
--- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
+++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
@@ -2306,6 +2306,12 @@
无感验证
+
+ 选择想要获取公告的游戏服务器
+
+
+ 公告所属服务器
+
管理主页仪表板中的卡片
@@ -2852,9 +2858,30 @@
下载链接复制成功
+
+ 无效的服务器
+
无效的 UID
+
+ 国服 官方服
+
+
+ 国服 渠道服
+
+
+ 国际服 亚服
+
+
+ 国际服 台服
+
+
+ 国际服 欧服
+
+
+ 国际服 美服
+
胡桃服务维护中
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IAnnouncementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IAnnouncementService.cs
index 81a298f6..a6754d27 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IAnnouncementService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IAnnouncementService.cs
@@ -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
///
/// 异步获取游戏公告与活动,通常会进行缓存
///
+ /// 语言代码
+ /// 服务器
/// 取消令牌
/// 公告包装器
- ValueTask GetAnnouncementWrapperAsync(CancellationToken cancellationToken = default);
+ ValueTask GetAnnouncementWrapperAsync(string languageCode, Region region, CancellationToken cancellationToken = default);
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AnnouncementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/AnnouncementService.cs
index df39f93b..fcd004cc 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/AnnouncementService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/AnnouncementService.cs
@@ -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;
///
- public async ValueTask GetAnnouncementWrapperAsync(CancellationToken cancellationToken = default)
+ public async ValueTask 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 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> announcementContentResponse = await announcementClient
- .GetAnnouncementContentsAsync(cancellationToken)
+ .GetAnnouncementContentsAsync(languageCode, region, cancellationToken)
.ConfigureAwait(false);
if (!announcementContentResponse.IsOk())
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs
index aeb46ccc..be60187b 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs
@@ -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> 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);
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppOptionsExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppOptionsExtension.cs
index d05ac848..a534248f 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/AppOptionsExtension.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/AppOptionsExtension.cs
@@ -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? GetCurrentRegionForSelectionOrDefault(this AppOptions appOptions)
+ {
+ return appOptions.Regions.SingleOrDefault(c => c.Value.Value == appOptions.Region.Value);
+ }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/KnownRegions.cs b/src/Snap.Hutao/Snap.Hutao/Service/KnownRegions.cs
new file mode 100644
index 00000000..1d256c9c
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Service/KnownRegions.cs
@@ -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> 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),
+ ];
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/SupportedCultures.cs b/src/Snap.Hutao/Snap.Hutao/Service/SupportedCultures.cs
index 8e352ef8..975f437f 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/SupportedCultures.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/SupportedCultures.cs
@@ -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> Get()
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml
index 090bf170..57e80abf 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml
@@ -305,6 +305,17 @@
+
+
+
+
+
? selectedBackdropType;
private NameValue? selectedCulture;
+ private NameValue? selectedRegion;
private IPInformation? ipInformation;
private FolderViewModel? cacheFolderView;
private FolderViewModel? dataFolderView;
@@ -104,6 +106,18 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
}
}
+ public NameValue? 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); }
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/ApiEndpoints.cs b/src/Snap.Hutao/Snap.Hutao/Web/ApiEndpoints.cs
index 881b3c55..c3b863db 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/ApiEndpoints.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/ApiEndpoints.cs
@@ -281,12 +281,24 @@ internal static class ApiEndpoints
///
/// 公告列表
///
- public const string AnnList = $"{Hk4eApiAnnouncementApi}/getAnnList?{AnnouncementQuery}";
+ /// 语言代码
+ /// 服务器
+ /// 公告列表Url
+ public static string AnnList(string languageCode, in Region region)
+ {
+ return $"{Hk4eApiAnnouncementApi}/getAnnList?{AnnouncementQuery(languageCode, region)}";
+ }
///
/// 公告内容
///
- public const string AnnContent = $"{Hk4eApiAnnouncementApi}/getAnnContent?{AnnouncementQuery}";
+ /// 语言代码
+ /// 服务器
+ /// 公告列表Url
+ 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
///
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
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/ApiOsEndpoints.cs b/src/Snap.Hutao/Snap.Hutao/Web/ApiOsEndpoints.cs
index 26cbc006..eb17d057 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/ApiOsEndpoints.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/ApiOsEndpoints.cs
@@ -100,7 +100,7 @@ internal static class ApiOsEndpoints
///
/// 地区代号
/// 用户游戏角色字符串
- 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
+
+ ///
+ /// 公告列表
+ ///
+ /// 语言代码
+ /// 服务器
+ /// 公告列表Url
+ public static string AnnList(string languageCode, in Region region)
+ {
+ return $"{Hk4eApiOsAnnouncementApi}/getAnnList?{AnnouncementQuery(languageCode, region)}";
+ }
+
+ ///
+ /// 公告内容
+ ///
+ /// 语言代码
+ /// 服务器
+ /// 公告内容Url
+ public static string AnnContent(string languageCode, in Region region)
+ {
+ return $"{Hk4eApiOsAnnouncementApi}/getAnnContent?{AnnouncementQuery(languageCode, region)}";
+ }
+
+ #endregion
+
#region SgPublicApi
///
@@ -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
///
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
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementClient.cs
index 37cc18f1..9f467c37 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementClient.cs
@@ -23,12 +23,18 @@ internal sealed partial class AnnouncementClient
///
/// 异步获取公告列表
///
+ /// 语言代码
+ /// 服务器
/// 取消令牌
/// 公告列表
- public async ValueTask> GetAnnouncementsAsync(CancellationToken token = default)
+ public async ValueTask> 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? resp = await builder
@@ -41,12 +47,18 @@ internal sealed partial class AnnouncementClient
///
/// 异步获取公告内容列表
///
+ /// 语言代码
+ /// 服务器
/// 取消令牌
/// 公告内容列表
- public async ValueTask>> GetAnnouncementContentsAsync(CancellationToken token = default)
+ public async ValueTask>> 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>? resp = await builder
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaLogPage.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaLogPage.cs
index e4a6dc7f..1dd4da2a 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaLogPage.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaLogPage.cs
@@ -41,7 +41,8 @@ internal sealed class GachaLogPage : IJsonOnDeserialized
/// 地区
///
[JsonPropertyName("region")]
- public string Region { get; set; } = default!;
+ [JsonConverter(typeof(RegionConverter))]
+ public Region Region { get; set; } = default!;
public void OnDeserialized()
{
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabRegex.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabRegex.cs
new file mode 100644
index 00000000..395fdf37
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabRegex.cs
@@ -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();
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs
index 7ac48128..fcb7f7c6 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs
@@ -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;
///
@@ -19,18 +17,18 @@ internal readonly partial struct PlayerUid
///
/// 地区代码
///
- public readonly string Region;
+ public readonly Region Region;
///
/// 构造一个新的玩家 Uid 结构
///
/// uid
/// 服务器,当提供该参数时会无条件信任
- 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();
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUidExtension.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUidExtension.cs
index 538827a8..411d39e2 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUidExtension.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUidExtension.cs
@@ -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();
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Region.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Region.cs
new file mode 100644
index 00000000..9b8cdb5c
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Region.cs
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/RegionConverter.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/RegionConverter.cs
new file mode 100644
index 00000000..6f5e1936
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/RegionConverter.cs
@@ -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
+{
+ 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);
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/GenAuthKeyData.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/GenAuthKeyData.cs
index 429049a6..fd657b70 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/GenAuthKeyData.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/GenAuthKeyData.cs
@@ -50,7 +50,7 @@ internal sealed class GenAuthKeyData
/// 区域
///
[JsonPropertyName("region")]
- public string Region { get; set; } = default!;
+ public Region Region { get; set; } = default!;
///
/// 创建为祈愿记录验证密钥提交数据
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRole.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRole.cs
index 2856598f..e3322d57 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRole.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRole.cs
@@ -19,7 +19,7 @@ internal sealed class UserGameRole
/// 服务器
///
[JsonPropertyName("region")]
- public string Region { get; set; } = default!;
+ public Region Region { get; set; } = default!;
///
/// 游戏Uid
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInData.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInData.cs
index d3f5b4cd..6f8b1239 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInData.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInData.cs
@@ -31,7 +31,7 @@ internal sealed class SignInData
/// 地区代码
///
[JsonPropertyName("region")]
- public string Region { get; }
+ public Region Region { get; }
///
/// Uid
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/CalculateClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/CalculateClient.cs
index 8d08d3be..88257ea7 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/CalculateClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/CalculateClient.cs
@@ -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
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/CharacterData.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/CharacterData.cs
index b60cef12..eb45a306 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/CharacterData.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/CharacterData.cs
@@ -39,5 +39,5 @@ internal sealed class CharacterData
/// 服务器
///
[JsonPropertyName("server")]
- public string Server { get; }
+ public Region Server { get; }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Role.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Role.cs
index 2c28e0a2..ec870534 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Role.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/Avatar/Role.cs
@@ -22,10 +22,10 @@ internal sealed class Role
public string Nickname { get; set; } = default!;
///
- /// 服务器
+ /// 服务器名称
///
[JsonPropertyName("region")]
- public string Region { get; set; } = default!;
+ public string RegionName { get; set; } = default!;
///
/// 等级
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/BasicRoleInfo.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/BasicRoleInfo.cs
index 311ee399..a5fcd812 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/BasicRoleInfo.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/BasicRoleInfo.cs
@@ -23,10 +23,10 @@ internal sealed class BasicRoleInfo
public string Nickname { get; set; } = default!;
///
- /// 区域代码
+ /// 服务器名称
///
[JsonPropertyName("region")]
- public string Region { get; set; } = default!;
+ public string RegionName { get; set; } = default!;
///
/// 等级