mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
support UIGF v2.4-preview
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
18
src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUidExtension.cs
Normal file
18
src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUidExtension.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
Reference in New Issue
Block a user