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>
/// 实现日期的转换
/// 此转换器无法实现无损往返
/// 必须在反序列化后调整 Offset
/// </summary>
[HighQuality]
internal class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
@@ -18,7 +20,10 @@ internal class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
{
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;
@@ -27,6 +32,6 @@ internal class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
/// <inheritdoc/>
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;
}
}
[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.
// Licensed under the MIT license.
using Snap.Hutao.Web.Hoyolab;
using System.Runtime.InteropServices;
namespace Snap.Hutao.Model.InterChange.GachaLog;
@@ -10,12 +11,12 @@ namespace Snap.Hutao.Model.InterChange.GachaLog;
/// https://uigf.org/standards/UIGF.html
/// </summary>
[HighQuality]
internal sealed class UIGF
internal sealed class UIGF : IJsonOnSerializing, IJsonOnDeserialized
{
/// <summary>
/// 当前版本
/// </summary>
public const string CurrentVersion = "v2.3";
public const string CurrentVersion = "v2.4";
/// <summary>
/// 信息
@@ -30,6 +31,25 @@ internal sealed class UIGF
[JsonPropertyName("list")]
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>
/// 确认当前UIGF对象的版本是否受支持
/// </summary>
@@ -42,6 +62,7 @@ internal sealed class UIGF
"v2.1" => UIGFVersion.Major2Minor2OrLower,
"v2.2" => UIGFVersion.Major2Minor2OrLower,
"v2.3" => UIGFVersion.Major2Minor3OrHigher,
"v2.4" => UIGFVersion.Major2Minor3OrHigher,
_ => UIGFVersion.NotSupported,
};
@@ -82,4 +103,14 @@ internal sealed class UIGF
id = 0;
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.Abstraction;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Web.Hoyolab;
namespace Snap.Hutao.Model.InterChange.GachaLog;
@@ -58,6 +59,12 @@ internal sealed class UIGFInfo : IMappingFrom<UIGFInfo, RuntimeOptions, Metadata
[JsonPropertyName("uigf_version")]
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)
{
return new()
@@ -68,6 +75,7 @@ internal sealed class UIGFInfo : IMappingFrom<UIGFInfo, RuntimeOptions, Metadata
ExportApp = SH.AppName,
ExportAppVersion = runtimeOptions.Version.ToString(),
UIGFVersion = UIGF.CurrentVersion,
RegionTimeZone = PlayerUid.GetRegionTimeZoneUtcOffset(uid).Hours,
};
}
}

View File

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

View File

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

View File

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

View File

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