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!; /// /// 等级