support UIGF v2.4-preview

This commit is contained in:
Lightczx
2023-11-10 11:37:45 +08:00
parent 3eb2556393
commit 7442f7f1ec
10 changed files with 125 additions and 30 deletions

View File

@@ -7,6 +7,8 @@ namespace Snap.Hutao.Core.Json.Converter;
/// <summary> /// <summary>
/// 实现日期的转换 /// 实现日期的转换
/// 此转换器无法实现无损往返
/// 必须在反序列化后调整 Offset
/// </summary> /// </summary>
[HighQuality] [HighQuality]
internal class DateTimeOffsetConverter : JsonConverter<DateTimeOffset> internal class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
@@ -18,7 +20,10 @@ internal class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
{ {
if (reader.GetString() is { } dataTimeString) if (reader.GetString() is { } dataTimeString)
{ {
return DateTimeOffset.ParseExact(dataTimeString, Format, CultureInfo.CurrentCulture); // By doing so, the DateTimeOffset parsed out will be a
// no offset datetime, and need to be adjusted later
DateTime dateTime = DateTime.ParseExact(dataTimeString, Format, CultureInfo.InvariantCulture);
return new DateTimeOffset(dateTime, default);
} }
return default; return default;
@@ -27,6 +32,6 @@ internal class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
/// <inheritdoc/> /// <inheritdoc/>
public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
{ {
writer.WriteStringValue(value.ToLocalTime().ToString(Format, CultureInfo.CurrentCulture)); writer.WriteStringValue(value.DateTime.ToString(Format, CultureInfo.InvariantCulture));
} }
} }

View File

@@ -42,4 +42,18 @@ internal static class DateTimeOffsetExtension
return defaultValue; return defaultValue;
} }
} }
[SuppressMessage("", "SH002")]
public static unsafe DateTimeOffset UnsafeAdjustOffsetOnly(this DateTimeOffset dateTimeOffset, in TimeSpan offset)
{
UnsafeWritableDateTimeOffset* pUnsafe = (UnsafeWritableDateTimeOffset*)&dateTimeOffset;
pUnsafe->OffsetMinutes = (short)(offset.Ticks / TimeSpan.TicksPerMinute);
return dateTimeOffset;
}
private struct UnsafeWritableDateTimeOffset
{
public DateTime DateTime;
public short OffsetMinutes;
}
} }

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Web.Hoyolab;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Snap.Hutao.Model.InterChange.GachaLog; namespace Snap.Hutao.Model.InterChange.GachaLog;
@@ -10,12 +11,12 @@ namespace Snap.Hutao.Model.InterChange.GachaLog;
/// https://uigf.org/standards/UIGF.html /// https://uigf.org/standards/UIGF.html
/// </summary> /// </summary>
[HighQuality] [HighQuality]
internal sealed class UIGF internal sealed class UIGF : IJsonOnSerializing, IJsonOnDeserialized
{ {
/// <summary> /// <summary>
/// 当前版本 /// 当前版本
/// </summary> /// </summary>
public const string CurrentVersion = "v2.3"; public const string CurrentVersion = "v2.4";
/// <summary> /// <summary>
/// 信息 /// 信息
@@ -30,6 +31,25 @@ internal sealed class UIGF
[JsonPropertyName("list")] [JsonPropertyName("list")]
public List<UIGFItem> List { get; set; } = default!; public List<UIGFItem> List { get; set; } = default!;
public void OnSerializing()
{
TimeSpan offset = GetRegionTimeZoneUtcOffset();
foreach (UIGFItem item in List)
{
item.Time = item.Time.ToOffset(offset);
}
}
public void OnDeserialized()
{
// Adjust items timezone
TimeSpan offset = GetRegionTimeZoneUtcOffset();
foreach (UIGFItem item in List)
{
item.Time = item.Time.UnsafeAdjustOffsetOnly(offset);
}
}
/// <summary> /// <summary>
/// 确认当前UIGF对象的版本是否受支持 /// 确认当前UIGF对象的版本是否受支持
/// </summary> /// </summary>
@@ -42,6 +62,7 @@ internal sealed class UIGF
"v2.1" => UIGFVersion.Major2Minor2OrLower, "v2.1" => UIGFVersion.Major2Minor2OrLower,
"v2.2" => UIGFVersion.Major2Minor2OrLower, "v2.2" => UIGFVersion.Major2Minor2OrLower,
"v2.3" => UIGFVersion.Major2Minor3OrHigher, "v2.3" => UIGFVersion.Major2Minor3OrHigher,
"v2.4" => UIGFVersion.Major2Minor3OrHigher,
_ => UIGFVersion.NotSupported, _ => UIGFVersion.NotSupported,
}; };
@@ -82,4 +103,14 @@ internal sealed class UIGF
id = 0; id = 0;
return true; return true;
} }
private TimeSpan GetRegionTimeZoneUtcOffset()
{
if (Info.RegionTimeZone is int offsetHours)
{
return new TimeSpan(offsetHours, 0, 0);
}
return PlayerUid.GetRegionTimeZoneUtcOffset(Info.Uid);
}
} }

View File

@@ -4,6 +4,7 @@
using Snap.Hutao.Core; using Snap.Hutao.Core;
using Snap.Hutao.Core.Abstraction; using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Service.Metadata; using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Web.Hoyolab;
namespace Snap.Hutao.Model.InterChange.GachaLog; namespace Snap.Hutao.Model.InterChange.GachaLog;
@@ -58,6 +59,12 @@ internal sealed class UIGFInfo : IMappingFrom<UIGFInfo, RuntimeOptions, Metadata
[JsonPropertyName("uigf_version")] [JsonPropertyName("uigf_version")]
public string UIGFVersion { get; set; } = default!; public string UIGFVersion { get; set; } = default!;
/// <summary>
/// 时区偏移
/// </summary>
[JsonPropertyName("region_time_zone")]
public int? RegionTimeZone { get; set; } = default!;
public static UIGFInfo From(RuntimeOptions runtimeOptions, MetadataOptions metadataOptions, string uid) public static UIGFInfo From(RuntimeOptions runtimeOptions, MetadataOptions metadataOptions, string uid)
{ {
return new() return new()
@@ -68,6 +75,7 @@ internal sealed class UIGFInfo : IMappingFrom<UIGFInfo, RuntimeOptions, Metadata
ExportApp = SH.AppName, ExportApp = SH.AppName,
ExportAppVersion = runtimeOptions.Version.ToString(), ExportAppVersion = runtimeOptions.Version.ToString(),
UIGFVersion = UIGF.CurrentVersion, UIGFVersion = UIGF.CurrentVersion,
RegionTimeZone = PlayerUid.GetRegionTimeZoneUtcOffset(uid).Hours,
}; };
} }
} }

View File

@@ -2753,6 +2753,9 @@
<data name="WebGameResourcePathCopySucceed" xml:space="preserve"> <data name="WebGameResourcePathCopySucceed" xml:space="preserve">
<value>下载链接复制成功</value> <value>下载链接复制成功</value>
</data> </data>
<data name="WebHoyolabInvalidUid" xml:space="preserve">
<value>无效的 UID</value>
</data>
<data name="WebIndexOrSpiralAbyssVerificationFailed" xml:space="preserve"> <data name="WebIndexOrSpiralAbyssVerificationFailed" xml:space="preserve">
<value>验证失败,请手动验证或前往「米游社-我的角色」页面查看</value> <value>验证失败,请手动验证或前往「米游社-我的角色」页面查看</value>
</data> </data>

View File

@@ -5,6 +5,7 @@ using Microsoft.Web.WebView2.Core;
using Snap.Hutao.View.Control; using Snap.Hutao.View.Control;
using Snap.Hutao.ViewModel.User; using Snap.Hutao.ViewModel.User;
using Snap.Hutao.Web.Bridge; using Snap.Hutao.Web.Bridge;
using Snap.Hutao.Web.Hoyolab;
using Snap.Hutao.Web.Request.QueryString; using Snap.Hutao.Web.Request.QueryString;
namespace Snap.Hutao.ViewModel.DailyNote; namespace Snap.Hutao.ViewModel.DailyNote;

View File

@@ -36,7 +36,7 @@ internal sealed class SummaryItem : Item
/// </summary> /// </summary>
public string TimeFormatted public string TimeFormatted
{ {
get => $"{Time:yyy.MM.dd HH:mm:ss}"; get => $"{Time.ToLocalTime():yyy.MM.dd HH:mm:ss}";
} }
/// <summary> /// <summary>

View File

@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Web.Request.QueryString; using System.Text.RegularExpressions;
using Windows.ApplicationModel.Store.Preview.InstallControl; using Windows.ApplicationModel.Store.Preview.InstallControl;
namespace Snap.Hutao.Web.Hoyolab; namespace Snap.Hutao.Web.Hoyolab;
@@ -10,7 +10,7 @@ namespace Snap.Hutao.Web.Hoyolab;
/// 玩家 Uid /// 玩家 Uid
/// </summary> /// </summary>
[HighQuality] [HighQuality]
internal readonly struct PlayerUid internal readonly partial struct PlayerUid
{ {
/// <summary> /// <summary>
/// UID 的实际值 /// UID 的实际值
@@ -29,65 +29,68 @@ internal readonly struct PlayerUid
/// <param name="region">服务器,当提供该参数时会无条件信任</param> /// <param name="region">服务器,当提供该参数时会无条件信任</param>
public PlayerUid(string value, string? region = default) public PlayerUid(string value, string? region = default)
{ {
Must.Argument(value.Length == 9, "uid 应为9位数字"); Must.Argument(UidRegex().IsMatch(value), SH.WebHoyolabInvalidUid);
Value = value; Value = value;
Region = region ?? EvaluateRegion(value.AsSpan()[0]); Region = region ?? EvaluateRegion(value.AsSpan()[0]);
} }
public static implicit operator PlayerUid(string source) public static implicit operator PlayerUid(string source)
{ {
return new(source); return FromUidString(source);
}
public static PlayerUid FromUidString(string uid)
{
return new(uid);
} }
/// <summary> /// <summary>
/// 判断是否为国际服 /// 判断是否为国际服
/// We make this a static method rather than property,
/// to avoid unnecessary memory allocation.
/// </summary> /// </summary>
/// <param name="uid">uid</param> /// <param name="uid">uid</param>
/// <returns>是否为国际服</returns> /// <returns>是否为国际服</returns>
public static bool IsOversea(string uid) public static bool IsOversea(string uid)
{ {
return uid[0] switch // We make this a static method rather than property,
// to avoid unnecessary memory allocation (Region field).
Must.Argument(UidRegex().IsMatch(uid), SH.WebHoyolabInvalidUid);
return uid.AsSpan()[0] switch
{ {
>= '1' and <= '5' => false, >= '1' and <= '5' => false,
_ => true, _ => true,
}; };
} }
public TimeZoneInfo GetTimeZoneInfo() public static TimeZoneInfo GetTimeZoneInfo(string uid)
{ {
// We make this a static method rather than property,
// to avoid unnecessary memory allocation (Region field).
Must.Argument(UidRegex().IsMatch(uid), SH.WebHoyolabInvalidUid);
// 美服 UTC-05 // 美服 UTC-05
// 欧服 UTC+01 // 欧服 UTC+01
// 其他 UTC+08 // 其他 UTC+08
return Region switch return uid.AsSpan()[0] switch
{ {
"os_usa" => ServerTimeZoneInfo.AmericaTimeZone, '6' => ServerTimeZoneInfo.AmericaTimeZone,
"os_euro" => ServerTimeZoneInfo.EuropeTimeZone, '7' => ServerTimeZoneInfo.EuropeTimeZone,
_ => ServerTimeZoneInfo.CommonTimeZone, _ => ServerTimeZoneInfo.CommonTimeZone,
}; };
} }
public static TimeSpan GetRegionTimeZoneUtcOffset(string uid)
{
return GetTimeZoneInfo(uid).BaseUtcOffset;
}
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString() public override string ToString()
{ {
return Value; return Value;
} }
/// <summary> private static string EvaluateRegion(in char first)
/// 转换到查询字符串
/// </summary>
/// <returns>查询字符串</returns>
public QueryString ToQueryString()
{
QueryString queryString = new();
queryString.Set("role_id", Value);
queryString.Set("server", Region);
return queryString;
}
private static string EvaluateRegion(char first)
{ {
return first switch return first switch
{ {
@@ -103,4 +106,7 @@ internal readonly struct PlayerUid
_ => throw Must.NeverHappen(), _ => throw Must.NeverHappen(),
}; };
} }
[GeneratedRegex("[1-9][0-9]{8}")]
private static partial Regex UidRegex();
} }

View File

@@ -0,0 +1,18 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Web.Request.QueryString;
namespace Snap.Hutao.Web.Hoyolab;
internal static class PlayerUidExtension
{
public static QueryString ToQueryString(this in PlayerUid playerUid)
{
QueryString queryString = new();
queryString.Set("role_id", playerUid.Value);
queryString.Set("server", playerUid.Region);
return queryString;
}
}

View File

@@ -9,9 +9,18 @@ internal static class ServerTimeZoneInfo
private static readonly TimeZoneInfo EuropeTimeZoneValue = TimeZoneInfo.CreateCustomTimeZone("Server:UTC+01", new TimeSpan(+01, 0, 0), "UTC+01", "UTC+01"); private static readonly TimeZoneInfo EuropeTimeZoneValue = TimeZoneInfo.CreateCustomTimeZone("Server:UTC+01", new TimeSpan(+01, 0, 0), "UTC+01", "UTC+01");
private static readonly TimeZoneInfo CommonTimeZoneValue = TimeZoneInfo.CreateCustomTimeZone("Server:UTC+08", new TimeSpan(+08, 0, 0), "UTC+08", "UTC+08"); private static readonly TimeZoneInfo CommonTimeZoneValue = TimeZoneInfo.CreateCustomTimeZone("Server:UTC+08", new TimeSpan(+08, 0, 0), "UTC+08", "UTC+08");
/// <summary>
/// UTC-05
/// </summary>
public static TimeZoneInfo AmericaTimeZone { get => AmericaTimeZoneValue; } public static TimeZoneInfo AmericaTimeZone { get => AmericaTimeZoneValue; }
/// <summary>
/// UTC+01
/// </summary>
public static TimeZoneInfo EuropeTimeZone { get => EuropeTimeZoneValue; } public static TimeZoneInfo EuropeTimeZone { get => EuropeTimeZoneValue; }
/// <summary>
/// UTC+08
/// </summary>
public static TimeZoneInfo CommonTimeZone { get => CommonTimeZoneValue; } public static TimeZoneInfo CommonTimeZone { get => CommonTimeZoneValue; }
} }