Compare commits

..

3 Commits

Author SHA1 Message Date
qhy040404
676fe87f04 Update Snap.Hutao.csproj 2024-04-21 23:49:53 +08:00
qhy040404
2056fa8f4d extract launcher 2024-04-20 15:28:07 +08:00
qhy040404
47debe42ec allow to open elevated app from taskbar 2024-04-20 15:16:22 +08:00
81 changed files with 536 additions and 1164 deletions

View File

@@ -33,17 +33,17 @@ internal static class IocConfiguration
return services return services
.AddTransient(typeof(Database.ScopedDbCurrent<,>)) .AddTransient(typeof(Database.ScopedDbCurrent<,>))
.AddTransient(typeof(Database.ScopedDbCurrent<,,>)) .AddTransient(typeof(Database.ScopedDbCurrent<,,>))
.AddDbContextPool<AppDbContext>(AddDbContextCore); .AddDbContext<AppDbContext>(AddDbContextCore);
} }
private static void AddDbContextCore(IServiceProvider serviceProvider, DbContextOptionsBuilder builder) private static void AddDbContextCore(IServiceProvider provider, DbContextOptionsBuilder builder)
{ {
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>(); RuntimeOptions runtimeOptions = provider.GetRequiredService<RuntimeOptions>();
string dbFile = System.IO.Path.Combine(runtimeOptions.DataFolder, "Userdata.db"); string dbFile = System.IO.Path.Combine(runtimeOptions.DataFolder, "Userdata.db");
string sqlConnectionString = $"Data Source={dbFile}"; string sqlConnectionString = $"Data Source={dbFile}";
// Temporarily create a context // Temporarily create a context
using (AppDbContext context = AppDbContext.Create(serviceProvider, sqlConnectionString)) using (AppDbContext context = AppDbContext.Create(sqlConnectionString))
{ {
if (context.Database.GetPendingMigrations().Any()) if (context.Database.GetPendingMigrations().Any())
{ {

View File

@@ -1,7 +1,5 @@
// 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.Core.Logging;
namespace Snap.Hutao.Core.Diagnostics; namespace Snap.Hutao.Core.Diagnostics;
internal readonly struct MeasureExecutionToken : IDisposable internal readonly struct MeasureExecutionToken : IDisposable
@@ -19,6 +17,6 @@ internal readonly struct MeasureExecutionToken : IDisposable
public void Dispose() public void Dispose()
{ {
logger.LogColorizedDebug(("{Caller} toke {Time} ms", ConsoleColor.Gray), (callerName, ConsoleColor.Yellow), (stopwatch.GetElapsedTime().TotalMilliseconds, ConsoleColor.DarkGreen)); logger.LogDebug("{Caller} toke {Time} ms", callerName, stopwatch.GetElapsedTime().TotalMilliseconds);
} }
} }

View File

@@ -1,13 +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.Win32.Foundation;
using Snap.Hutao.Win32.System.Com;
using Snap.Hutao.Win32.UI.Shell;
using System.IO; using System.IO;
using static Snap.Hutao.Win32.Macros;
using static Snap.Hutao.Win32.Ole32;
using static Snap.Hutao.Win32.Shell32;
namespace Snap.Hutao.Core.IO; namespace Snap.Hutao.Core.IO;
@@ -45,57 +39,4 @@ internal static class FileOperation
File.Move(sourceFileName, destFileName, false); File.Move(sourceFileName, destFileName, false);
return true; return true;
} }
public static unsafe bool UnsafeMove(string sourceFileName, string destFileName)
{
bool result = false;
if (SUCCEEDED(CoCreateInstance(in Win32.UI.Shell.FileOperation.CLSID, default, CLSCTX.CLSCTX_INPROC_SERVER, in IFileOperation.IID, out IFileOperation* pFileOperation)))
{
if (SUCCEEDED(SHCreateItemFromParsingName(sourceFileName, default, in IShellItem.IID, out IShellItem* pSourceShellItem)))
{
if (SUCCEEDED(SHCreateItemFromParsingName(destFileName, default, in IShellItem.IID, out IShellItem* pDestShellItem)))
{
pFileOperation->MoveItem(pSourceShellItem, pDestShellItem, default, default);
if (SUCCEEDED(pFileOperation->PerformOperations()))
{
result = true;
}
pDestShellItem->Release();
}
pSourceShellItem->Release();
}
pFileOperation->Release();
}
return result;
}
public static unsafe bool UnsafeDelete(string path)
{
bool result = false;
if (SUCCEEDED(CoCreateInstance(in Win32.UI.Shell.FileOperation.CLSID, default, CLSCTX.CLSCTX_INPROC_SERVER, in IFileOperation.IID, out IFileOperation* pFileOperation)))
{
if (SUCCEEDED(SHCreateItemFromParsingName(path, default, in IShellItem.IID, out IShellItem* pShellItem)))
{
pFileOperation->DeleteItem(pShellItem, default);
if (SUCCEEDED(pFileOperation->PerformOperations()))
{
result = true;
}
pShellItem->Release();
}
pFileOperation->Release();
}
return result;
}
} }

View File

@@ -10,7 +10,7 @@ internal static class Hash
{ {
public static string SHA1HexString(string input) public static string SHA1HexString(string input)
{ {
return HashCore(System.Convert.ToHexString, SHA1.HashData, Encoding.UTF8.GetBytes, input); return HashCore(BitConverter.ToString, SHA1.HashData, Encoding.UTF8.GetBytes, input);
} }
private static TResult HashCore<TInput, TResult>(Func<byte[], TResult> resultConverter, Func<byte[], byte[]> hashMethod, Func<TInput, byte[]> bytesConverter, TInput input) private static TResult HashCore<TInput, TResult>(Func<byte[], TResult> resultConverter, Func<byte[], byte[]> hashMethod, Func<TInput, byte[]> bytesConverter, TInput input)

View File

@@ -1,40 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.IO;
namespace Snap.Hutao.Core.IO;
internal sealed class StreamReaderWriter : IDisposable
{
private readonly StreamReader reader;
private readonly StreamWriter writer;
public StreamReaderWriter(StreamReader reader, StreamWriter writer)
{
this.reader = reader;
this.writer = writer;
}
public StreamReader Reader { get => reader; }
public StreamWriter Writer { get => writer; }
/// <inheritdoc cref="StreamReader.ReadLineAsync(CancellationToken)"/>
public ValueTask<string?> ReadLineAsync(CancellationToken token)
{
return reader.ReadLineAsync(token);
}
/// <inheritdoc cref="StreamWriter.WriteAsync(string?)"/>
public Task WriteAsync(string value)
{
return writer.WriteAsync(value);
}
public void Dispose()
{
writer.Dispose();
reader.Dispose();
}
}

View File

@@ -21,11 +21,6 @@ internal readonly struct LogArgument
return new(argument); return new(argument);
} }
public static implicit operator LogArgument(double argument)
{
return new(argument);
}
public static implicit operator LogArgument((object? Argument, ConsoleColor Foreground) tuple) public static implicit operator LogArgument((object? Argument, ConsoleColor Foreground) tuple)
{ {
return new(tuple.Argument, tuple.Foreground); return new(tuple.Argument, tuple.Foreground);

View File

@@ -20,15 +20,10 @@ internal sealed partial class ShellLinkInterop : IShellLinkInterop
public async ValueTask<bool> TryCreateDesktopShoutcutForElevatedLaunchAsync() public async ValueTask<bool> TryCreateDesktopShoutcutForElevatedLaunchAsync()
{ {
string targetLogoPath = Path.Combine(runtimeOptions.DataFolder, "ShellLinkLogo.ico");
string elevatedLauncherPath = Path.Combine(runtimeOptions.DataFolder, "Snap.Hutao.Elevated.Launcher.exe"); string elevatedLauncherPath = Path.Combine(runtimeOptions.DataFolder, "Snap.Hutao.Elevated.Launcher.exe");
try try
{ {
Uri sourceLogoUri = "ms-appx:///Assets/Logo.ico".ToUri();
StorageFile iconFile = await StorageFile.GetFileFromApplicationUriAsync(sourceLogoUri);
await iconFile.OverwriteCopyAsync(targetLogoPath).ConfigureAwait(false);
Uri elevatedLauncherUri = "ms-appx:///Snap.Hutao.Elevated.Launcher.exe".ToUri(); Uri elevatedLauncherUri = "ms-appx:///Snap.Hutao.Elevated.Launcher.exe".ToUri();
StorageFile launcherFile = await StorageFile.GetFileFromApplicationUriAsync(elevatedLauncherUri); StorageFile launcherFile = await StorageFile.GetFileFromApplicationUriAsync(elevatedLauncherUri);
await launcherFile.OverwriteCopyAsync(elevatedLauncherPath).ConfigureAwait(false); await launcherFile.OverwriteCopyAsync(elevatedLauncherPath).ConfigureAwait(false);
@@ -38,10 +33,10 @@ internal sealed partial class ShellLinkInterop : IShellLinkInterop
return false; return false;
} }
return UnsafeTryCreateDesktopShoutcutForElevatedLaunch(targetLogoPath, elevatedLauncherPath); return UnsafeTryCreateDesktopShoutcutForElevatedLaunch(elevatedLauncherPath);
} }
private unsafe bool UnsafeTryCreateDesktopShoutcutForElevatedLaunch(string targetLogoPath, string elevatedLauncherPath) private unsafe bool UnsafeTryCreateDesktopShoutcutForElevatedLaunch(string elevatedLauncherPath)
{ {
bool result = false; bool result = false;
@@ -52,7 +47,6 @@ internal sealed partial class ShellLinkInterop : IShellLinkInterop
pShellLink->SetPath(elevatedLauncherPath); pShellLink->SetPath(elevatedLauncherPath);
pShellLink->SetArguments(runtimeOptions.FamilyName); pShellLink->SetArguments(runtimeOptions.FamilyName);
pShellLink->SetShowCmd(SHOW_WINDOW_CMD.SW_NORMAL); pShellLink->SetShowCmd(SHOW_WINDOW_CMD.SW_NORMAL);
pShellLink->SetIconLocation(targetLogoPath, 0);
if (SUCCEEDED(pShellLink->QueryInterface(in IPersistFile.IID, out IPersistFile* pPersistFile))) if (SUCCEEDED(pShellLink->QueryInterface(in IPersistFile.IID, out IPersistFile* pPersistFile)))
{ {

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Snap.Hutao.Core.Logging; using Snap.Hutao.Core.Logging;
using Snap.Hutao.Model.Entity.Configuration; using Snap.Hutao.Model.Entity.Configuration;
using System.Diagnostics; using System.Diagnostics;
@@ -25,8 +24,18 @@ internal sealed class AppDbContext : DbContext
public AppDbContext(DbContextOptions<AppDbContext> options) public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options) : base(options)
{ {
logger = this.GetService<ILogger<AppDbContext>>(); }
logger?.LogColorizedInformation("{Name}[{Id}] {Action}", nameof(AppDbContext), (ContextId, ConsoleColor.DarkCyan), ("created", ConsoleColor.Green));
/// <summary>
/// 构造一个新的应用程序数据库上下文
/// </summary>
/// <param name="options">选项</param>
/// <param name="logger">日志器</param>
public AppDbContext(DbContextOptions<AppDbContext> options, ILogger<AppDbContext> logger)
: this(options)
{
this.logger = logger;
logger.LogColorizedInformation("{Name}[{Id}] {Action}", nameof(AppDbContext), (ContextId, ConsoleColor.DarkCyan), ("created", ConsoleColor.Green));
} }
public DbSet<SettingEntry> Settings { get; set; } = default!; public DbSet<SettingEntry> Settings { get; set; } = default!;
@@ -65,14 +74,14 @@ internal sealed class AppDbContext : DbContext
public DbSet<SpiralAbyssEntry> SpiralAbysses { get; set; } = default!; public DbSet<SpiralAbyssEntry> SpiralAbysses { get; set; } = default!;
public static AppDbContext Create(IServiceProvider serviceProvider, string sqlConnectionString) /// <summary>
/// 构造一个临时的应用程序数据库上下文
/// </summary>
/// <param name="sqlConnectionString">连接字符串</param>
/// <returns>应用程序数据库上下文</returns>
public static AppDbContext Create(string sqlConnectionString)
{ {
DbContextOptions<AppDbContext> options = new DbContextOptionsBuilder<AppDbContext>() return new(new DbContextOptionsBuilder<AppDbContext>().UseSqlite(sqlConnectionString).Options);
.UseApplicationServiceProvider(serviceProvider)
.UseSqlite(sqlConnectionString)
.Options;
return new(options);
} }
/// <inheritdoc/> /// <inheritdoc/>

View File

@@ -18,7 +18,7 @@ internal sealed class AppDbContextDesignTimeFactory : IDesignTimeDbContextFactor
#if DEBUG #if DEBUG
// TODO: replace with your own database file path. // TODO: replace with your own database file path.
string userdataDbName = @"D:\Hutao\Userdata.db"; string userdataDbName = @"D:\Hutao\Userdata.db";
return AppDbContext.Create(default!, $"Data Source={userdataDbName}"); return AppDbContext.Create($"Data Source={userdataDbName}");
#else #else
throw Must.NeverHappen(); throw Must.NeverHappen();
#endif #endif

View File

@@ -1,16 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Intrinsic;
internal enum QuestType
{
AQ,
FQ,
LQ,
EQ,
DQ,
IQ,
VQ,
WQ,
}

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Snap.Hutao.Core;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using WinRT; using WinRT;

View File

@@ -2922,19 +2922,13 @@
<value>Weapon WIKI</value> <value>Weapon WIKI</value>
</data> </data>
<data name="WebAnnouncementMatchPermanentActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchPermanentActivityTime" xml:space="preserve">
<value>(?:〓Event Duration〓|〓Quest Start Time〓).*?Permanently.*?Version.*?(\d\.\d).*?update</value> <value>(?:〓Event Duration〓|〓Quest Start Time〓).*?\d\.\dthe Version update(?:after|)Permanently available</value>
</data> </data>
<data name="WebAnnouncementMatchPersistentActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchPersistentActivityTime" xml:space="preserve">
<value>〓Event Duration〓.*?Available throughout the entirety of Version (\d\.\d)</value> <value>〓Event Duration〓.*?\d\.\d Available throughout the entirety of Version</value>
</data> </data>
<data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve">
<value>(?:〓Event Duration〓|Event Wish Duration|【Availability Duration】|〓Discount Period〓).*?After.*?(\d\.\d).*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>(?:〓Event Duration〓|Event Wish Duration|【Availability Duration】|〓Discount Period〓).*?(\d\.\dAfter the Version update).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data>
<data name="WebAnnouncementMatchVersionUpdatePreviewTime" xml:space="preserve">
<value>Dear.*?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data>
<data name="WebAnnouncementMatchVersionUpdatePreviewTitle" xml:space="preserve">
<value>Version \d\.\d Update Maintenance Preview</value>
</data> </data>
<data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve"> <data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve">
<value>〓Update Maintenance Duration〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>〓Update Maintenance Duration〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>

View File

@@ -2922,19 +2922,13 @@
<value>Senjata WIKI</value> <value>Senjata WIKI</value>
</data> </data>
<data name="WebAnnouncementMatchPermanentActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchPermanentActivityTime" xml:space="preserve">
<value>(?:〓Durasi Event〓|〓Waktu Mulai Misi〓).*?(\d\.\d)the Version update(?:after|)Selamanya Tersedia</value> <value>(?:〓Durasi Event〓|〓Waktu Mulai Misi〓).*?\d\.\dthe Version update(?:after|)Selamanya Tersedia</value>
</data> </data>
<data name="WebAnnouncementMatchPersistentActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchPersistentActivityTime" xml:space="preserve">
<value>〓Durasi Event〓.*?(\d\.\d) Tersedia selama versi ini</value> <value>〓Durasi Event〓.*?\d\.\d Tersedia selama versi ini</value>
</data> </data>
<data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve">
<value>(?:〓Waktu Acara〓|Waktu Menginginkan|【Waktu Peluncuran】|〓Waktu Diskon〓).*?(\d\.\d) Setelah Pembaruan Versi.*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>(?:〓Waktu Acara〓|Waktu Menginginkan|【Waktu Peluncuran】|〓Waktu Diskon〓).*?(\d\.\d Setelah Pembaruan Versi).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data>
<data name="WebAnnouncementMatchVersionUpdatePreviewTime" xml:space="preserve">
<value>将于&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;进行版本更新维护</value>
</data>
<data name="WebAnnouncementMatchVersionUpdatePreviewTitle" xml:space="preserve">
<value>\d\.\d版本更新维护预告</value>
</data> </data>
<data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve"> <data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve">
<value>〓Durasi Pemeliharaan Pembaruan.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>〓Durasi Pemeliharaan Pembaruan.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>

View File

@@ -2922,25 +2922,19 @@
<value>武器一覧</value> <value>武器一覧</value>
</data> </data>
<data name="WebAnnouncementMatchPermanentActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchPermanentActivityTime" xml:space="preserve">
<value>(?:〓イベント期間〓|〓任務開(?:始|放)時間〓).*?(\d\.\d).*?開放</value> <value>(?:〓イベント期間〓|〓任務開時間〓).*?\d\.\dバージョンアップ(?:完了|)後常設オープン</value>
</data> </data>
<data name="WebAnnouncementMatchPersistentActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchPersistentActivityTime" xml:space="preserve">
<value>〓イベント期間〓.*?(\d\.\d)バージョン</value> <value>〓イベント期間〓.*?\d\.\dバージョン期間オープン</value>
</data> </data>
<data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve">
<value>(?:〓イベント期間〓|祈願期間|【開始日時】).*?(\d\.\d)バージョンアップ.*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>(?:〓イベント期間〓|祈願期間|【開始日時】).*?(\d\.\dバージョンアップ完了後).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data>
<data name="WebAnnouncementMatchVersionUpdatePreviewTime" xml:space="preserve">
<value>親愛.*?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data>
<data name="WebAnnouncementMatchVersionUpdatePreviewTitle" xml:space="preserve">
<value>Ver\.\d\.\dバージョンアップのお知らせ</value>
</data> </data>
<data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve"> <data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve">
<value>〓メンテナンス時間〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>〓メンテナンス時間〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data> </data>
<data name="WebAnnouncementMatchVersionUpdateTitle" xml:space="preserve"> <data name="WebAnnouncementMatchVersionUpdateTitle" xml:space="preserve">
<value>Ver\.\d\.\d.*?正式リリース</value> <value>Ver.\d\.\d.+正式リリース</value>
</data> </data>
<data name="WebAnnouncementTimeDaysBeginFormat" xml:space="preserve"> <data name="WebAnnouncementTimeDaysBeginFormat" xml:space="preserve">
<value>{0} 日後に開始</value> <value>{0} 日後に開始</value>

View File

@@ -2922,19 +2922,13 @@
<value>무기 자료</value> <value>무기 자료</value>
</data> </data>
<data name="WebAnnouncementMatchPermanentActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchPermanentActivityTime" xml:space="preserve">
<value>(?:〓活动时间〓|〓任务开放时间〓).*?(\d\.\d)版本更新(?:完成|)后永久开放</value> <value>(?:〓活动时间〓|〓任务开放时间〓).*?\d\.\d版本更新(?:完成|)后永久开放</value>
</data> </data>
<data name="WebAnnouncementMatchPersistentActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchPersistentActivityTime" xml:space="preserve">
<value>〓活动时间〓.*?(\d\.\d)版本期间持续开放</value> <value>〓活动时间〓.*?\d\.\d版本期间持续开放</value>
</data> </data>
<data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve">
<value>(?:〓活动时间〓|祈愿时间|【上架时间】|〓折扣时间〓).*?(\d\.\d)版本更新后.*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>(?:〓活动时间〓|祈愿时间|【上架时间】).*?(\d\.\d版本更新后).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data>
<data name="WebAnnouncementMatchVersionUpdatePreviewTime" xml:space="preserve">
<value>将于&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;进行版本更新维护</value>
</data>
<data name="WebAnnouncementMatchVersionUpdatePreviewTitle" xml:space="preserve">
<value>\d\.\d版本更新维护预告</value>
</data> </data>
<data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve"> <data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve">
<value>〓更新时间〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>〓更新时间〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>

View File

@@ -2922,19 +2922,13 @@
<value>Wiki de armas</value> <value>Wiki de armas</value>
</data> </data>
<data name="WebAnnouncementMatchPermanentActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchPermanentActivityTime" xml:space="preserve">
<value>(?:〓Duração do evento〓|〓Hora de início da missão〓).*?(\d\.\d)a atualização da versão(?:after|)Disponível permanentemente</value> <value>(?:〓Duração do evento〓|〓Hora de início da missão〓).*?\d\.\da atualização da versão(?:after|)Disponível permanentemente</value>
</data> </data>
<data name="WebAnnouncementMatchPersistentActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchPersistentActivityTime" xml:space="preserve">
<value>〓Duração do evento〓.*?(\d\.\d) Disponível em toda as versões</value> <value>〓Duração do evento〓.*?\d\.\d Disponível em toda as versões</value>
</data> </data>
<data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve">
<value>(?:〓Duração do evento〓|Duração da oração do evento|【Duração da disponibilidade】).*?(\d\.\d)Após a atualização da versão.*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>(?:〓Duração do evento〓|Duração da oração do evento|【Duração da disponibilidade】).*?(\d\.\dApós a atualização da versão).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data>
<data name="WebAnnouncementMatchVersionUpdatePreviewTime" xml:space="preserve">
<value>将于&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;进行版本更新维护</value>
</data>
<data name="WebAnnouncementMatchVersionUpdatePreviewTitle" xml:space="preserve">
<value>\d\.\d版本更新维护预告</value>
</data> </data>
<data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve"> <data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve">
<value>〓Duração da manutenção da atualização.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>〓Duração da manutenção da atualização.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>

View File

@@ -1361,9 +1361,6 @@
<data name="ViewDialogSettingDeleteUserDataTitle" xml:space="preserve"> <data name="ViewDialogSettingDeleteUserDataTitle" xml:space="preserve">
<value>是否永久删除用户数据</value> <value>是否永久删除用户数据</value>
</data> </data>
<data name="ViewDialogUpdatePackageDownloadUpdatelogLinkContent" xml:space="preserve">
<value>查看更新日志</value>
</data>
<data name="ViewDialogUserDocumentAction" xml:space="preserve"> <data name="ViewDialogUserDocumentAction" xml:space="preserve">
<value>立即前往</value> <value>立即前往</value>
</data> </data>
@@ -1724,9 +1721,6 @@
<data name="ViewModelSettingGeetestCustomUrlSucceed" xml:space="preserve"> <data name="ViewModelSettingGeetestCustomUrlSucceed" xml:space="preserve">
<value>无感验证复合 Url 配置成功</value> <value>无感验证复合 Url 配置成功</value>
</data> </data>
<data name="ViewModelSettingResetStaticResourceProgress" xml:space="preserve">
<value>正在重置图片资源</value>
</data>
<data name="ViewModelSettingSetDataFolderSuccess" xml:space="preserve"> <data name="ViewModelSettingSetDataFolderSuccess" xml:space="preserve">
<value>设置数据目录成功,重启以应用更改</value> <value>设置数据目录成功,重启以应用更改</value>
</data> </data>
@@ -2895,7 +2889,7 @@
<value>上传数据</value> <value>上传数据</value>
</data> </data>
<data name="ViewTitileUpdatePackageDownloadContent" xml:space="preserve"> <data name="ViewTitileUpdatePackageDownloadContent" xml:space="preserve">
<value>是否立即下载</value> <value>是否立即下载</value>
</data> </data>
<data name="ViewTitileUpdatePackageDownloadFailedMessage" xml:space="preserve"> <data name="ViewTitileUpdatePackageDownloadFailedMessage" xml:space="preserve">
<value>下载更新失败</value> <value>下载更新失败</value>
@@ -2994,19 +2988,13 @@
<value>武器资料</value> <value>武器资料</value>
</data> </data>
<data name="WebAnnouncementMatchPermanentActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchPermanentActivityTime" xml:space="preserve">
<value>(?:〓活动时间〓|〓任务开放时间〓).*?(\d\.\d)版本更新(?:完成|)后永久开放</value> <value>(?:〓活动时间〓|〓任务开放时间〓).*?\d\.\d版本更新(?:完成|)后永久开放</value>
</data> </data>
<data name="WebAnnouncementMatchPersistentActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchPersistentActivityTime" xml:space="preserve">
<value>〓活动时间〓.*?(\d\.\d)版本期间持续开放</value> <value>〓活动时间〓.*?\d\.\d版本期间持续开放</value>
</data> </data>
<data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve">
<value>(?:〓活动时间〓|祈愿时间|【上架时间】|〓折扣时间〓).*?(\d\.\d)版本更新后.*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>(?:〓活动时间〓|祈愿时间|【上架时间】|〓折扣时间〓).*?(\d\.\d版本更新后).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data>
<data name="WebAnnouncementMatchVersionUpdatePreviewTime" xml:space="preserve">
<value>将于&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;进行版本更新维护</value>
</data>
<data name="WebAnnouncementMatchVersionUpdatePreviewTitle" xml:space="preserve">
<value>\d\.\d版本更新维护预告</value>
</data> </data>
<data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve"> <data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve">
<value>〓更新时间〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>〓更新时间〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>

View File

@@ -2922,19 +2922,13 @@
<value>武器资料</value> <value>武器资料</value>
</data> </data>
<data name="WebAnnouncementMatchPermanentActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchPermanentActivityTime" xml:space="preserve">
<value>(?:〓活动时间〓|〓任务开放时间〓).*?(\d\.\d)版本更新(?:完成|)后永久开放</value> <value>(?:〓活动时间〓|〓任务开放时间〓).*?\d\.\d版本更新(?:完成|)后永久开放</value>
</data> </data>
<data name="WebAnnouncementMatchPersistentActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchPersistentActivityTime" xml:space="preserve">
<value>〓活动时间〓.*?(\d\.\d)版本期间持续开放</value> <value>〓活动时间〓.*?\d\.\d版本期间持续开放</value>
</data> </data>
<data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve">
<value>(?:〓活动时间〓|祈愿时间|【上架时间】|〓折扣时间〓).*?(\d\.\d)版本更新后.*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>(?:〓活动时间〓|祈愿时间|【上架时间】).*?(\d\.\d版本更新后).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data>
<data name="WebAnnouncementMatchVersionUpdatePreviewTime" xml:space="preserve">
<value>将于&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;进行版本更新维护</value>
</data>
<data name="WebAnnouncementMatchVersionUpdatePreviewTitle" xml:space="preserve">
<value>\d\.\d版本更新维护预告</value>
</data> </data>
<data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve"> <data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve">
<value>〓更新时间〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>〓更新时间〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>

View File

@@ -2922,19 +2922,13 @@
<value>武器資料</value> <value>武器資料</value>
</data> </data>
<data name="WebAnnouncementMatchPermanentActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchPermanentActivityTime" xml:space="preserve">
<value>(?:〓活動時間〓|〓任務開放時間〓).*?(\d\.\d)版本更新(?:完成|)後永久開放</value> <value>(?:〓活動時間〓|〓任務開放時間〓).*?\d\.\d版本更新(?:完成|)後永久開放</value>
</data> </data>
<data name="WebAnnouncementMatchPersistentActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchPersistentActivityTime" xml:space="preserve">
<value>〓活動時間〓.*?(\d\.\d)版本期間持續開放</value> <value>〓活動時間〓.*?\d\.\d版本期間持續開放</value>
</data> </data>
<data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve">
<value>(?:〓活動時間〓|祈願時間|【上架時間】|〓折扣時間〓).*?(\d\.\d).*?版本更新後.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>(?:〓活動時間〓|祈願時間|【上架時間】|〓折扣時間〓).*?(\d\.\d版本更新後).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data>
<data name="WebAnnouncementMatchVersionUpdatePreviewTime" xml:space="preserve">
<value>親愛.*?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data>
<data name="WebAnnouncementMatchVersionUpdatePreviewTitle" xml:space="preserve">
<value>\d\.\d版本更新維護預告</value>
</data> </data>
<data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve"> <data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve">
<value>〓更新時間〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>〓更新時間〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>

View File

@@ -7,7 +7,6 @@ using Snap.Hutao.Service.Announcement;
using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab;
using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement; using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
using Snap.Hutao.Web.Response; using Snap.Hutao.Web.Response;
using System.Collections.Specialized;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@@ -111,68 +110,39 @@ internal sealed partial class AnnouncementService : IAnnouncementService
.Single(wrapper => wrapper.TypeId == 1) .Single(wrapper => wrapper.TypeId == 1)
.List; .List;
// 游戏公告
List<WebAnnouncement> announcements = announcementListWrappers
.Single(wrapper => wrapper.TypeId == 2)
.List;
Dictionary<string, DateTimeOffset> versionStartTimes = [];
// 更新公告 // 更新公告
if (announcements.SingleOrDefault(ann => AnnouncementRegex.VersionUpdateTitleRegex.IsMatch(ann.Title)) is { } versionUpdate) WebAnnouncement versionUpdate = announcementListWrappers
{ .Single(wrapper => wrapper.TypeId == 2)
if (AnnouncementRegex.VersionUpdateTimeRegex.Match(versionUpdate.Content) is not { Success: true } versionUpdateMatch) .List
{ .Single(ann => AnnouncementRegex.VersionUpdateTitleRegex.IsMatch(ann.Title));
return;
}
DateTimeOffset versionUpdateTime = UnsafeDateTimeOffset.ParseDateTime(versionUpdateMatch.Groups[1].ValueSpan, offset); if (AnnouncementRegex.VersionUpdateTimeRegex.Match(versionUpdate.Content) is not { Success: true } versionMatch)
versionStartTimes.TryAdd(VersionRegex().Match(versionUpdate.Title).Groups[1].Value, versionUpdateTime); {
return;
} }
// 更新预告 DateTimeOffset versionUpdateTime = UnsafeDateTimeOffset.ParseDateTime(versionMatch.Groups[1].ValueSpan, offset);
if (announcements.SingleOrDefault(ann => AnnouncementRegex.VersionUpdatePreviewTitleRegex.IsMatch(ann.Title)) is { } versionUpdatePreview)
{
if (AnnouncementRegex.VersionUpdatePreviewTimeRegex.Match(versionUpdatePreview.Content) is not { Success: true } versionUpdatePreviewMatch)
{
return;
}
DateTimeOffset versionUpdatePreviewTime = UnsafeDateTimeOffset.ParseDateTime(versionUpdatePreviewMatch.Groups[1].ValueSpan, offset);
versionStartTimes.TryAdd(VersionRegex().Match(versionUpdatePreview.Title).Groups[1].Value, versionUpdatePreviewTime);
}
foreach (ref readonly WebAnnouncement announcement in CollectionsMarshal.AsSpan(activities)) foreach (ref readonly WebAnnouncement announcement in CollectionsMarshal.AsSpan(activities))
{ {
DateTimeOffset versionStartTime;
if (AnnouncementRegex.PermanentActivityAfterUpdateTimeRegex.Match(announcement.Content) is { Success: true } permanent) if (AnnouncementRegex.PermanentActivityAfterUpdateTimeRegex.Match(announcement.Content) is { Success: true } permanent)
{ {
if (versionStartTimes.TryGetValue(permanent.Groups[1].Value, out versionStartTime)) announcement.StartTime = versionUpdateTime;
{ continue;
announcement.StartTime = versionStartTime;
continue;
}
} }
if (AnnouncementRegex.PersistentActivityAfterUpdateTimeRegex.Match(announcement.Content) is { Success: true } persistent) if (AnnouncementRegex.PersistentActivityAfterUpdateTimeRegex.Match(announcement.Content) is { Success: true } persistent)
{ {
if (versionStartTimes.TryGetValue(persistent.Groups[1].Value, out versionStartTime)) announcement.StartTime = versionUpdateTime;
{ announcement.EndTime = versionUpdateTime + TimeSpan.FromDays(42);
announcement.StartTime = versionStartTime; continue;
announcement.EndTime = versionStartTime + TimeSpan.FromDays(42);
continue;
}
} }
if (AnnouncementRegex.TransientActivityAfterUpdateTimeRegex.Match(announcement.Content) is { Success: true } transient) if (AnnouncementRegex.TransientActivityAfterUpdateTimeRegex.Match(announcement.Content) is { Success: true } transient)
{ {
if (versionStartTimes.TryGetValue(transient.Groups[1].Value, out versionStartTime)) announcement.StartTime = versionUpdateTime;
{ announcement.EndTime = UnsafeDateTimeOffset.ParseDateTime(transient.Groups[2].ValueSpan, offset);
announcement.StartTime = versionStartTime; continue;
announcement.EndTime = UnsafeDateTimeOffset.ParseDateTime(transient.Groups[2].ValueSpan, offset);
continue;
}
} }
MatchCollection matches = AnnouncementRegex.XmlTimeTagRegex().Matches(announcement.Content); MatchCollection matches = AnnouncementRegex.XmlTimeTagRegex().Matches(announcement.Content);
@@ -207,7 +177,4 @@ internal sealed partial class AnnouncementService : IAnnouncementService
announcement.EndTime = max; announcement.EndTime = max;
} }
} }
[GeneratedRegex("(\\d\\.\\d)")]
private static partial Regex VersionRegex();
} }

View File

@@ -12,9 +12,7 @@ using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord; using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar; using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
using Snap.Hutao.Web.Response; using Snap.Hutao.Web.Response;
using System.Reflection.Emit;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using CalculateAvatar = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Avatar; using CalculateAvatar = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Avatar;
using EnkaAvatarInfo = Snap.Hutao.Web.Enka.Model.AvatarInfo; using EnkaAvatarInfo = Snap.Hutao.Web.Enka.Model.AvatarInfo;
using EntityAvatarInfo = Snap.Hutao.Model.Entity.AvatarInfo; using EntityAvatarInfo = Snap.Hutao.Model.Entity.AvatarInfo;
@@ -23,6 +21,9 @@ using RecordPlayerInfo = Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.PlayerInfo;
namespace Snap.Hutao.Service.AvatarInfo; namespace Snap.Hutao.Service.AvatarInfo;
/// <summary>
/// 角色信息数据库操作
/// </summary>
[HighQuality] [HighQuality]
[ConstructorGenerated] [ConstructorGenerated]
[Injection(InjectAs.Singleton)] [Injection(InjectAs.Singleton)]
@@ -31,10 +32,18 @@ internal sealed partial class AvatarInfoDbBulkOperation
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
private readonly IAvatarInfoDbService avatarInfoDbService; private readonly IAvatarInfoDbService avatarInfoDbService;
public async ValueTask<List<EntityAvatarInfo>> UpdateDbAvatarInfosByShowcaseAsync(string uid, IEnumerable<EnkaAvatarInfo> webInfos, CancellationToken token) /// <summary>
/// 更新数据库角色信息
/// </summary>
/// <param name="uid">uid</param>
/// <param name="webInfos">Enka信息</param>
/// <param name="token">取消令牌</param>
/// <returns>角色列表</returns>
public List<EntityAvatarInfo> UpdateDbAvatarInfosByShowcase(string uid, IEnumerable<EnkaAvatarInfo> webInfos, CancellationToken token)
{ {
List<EntityAvatarInfo> dbInfos = await avatarInfoDbService.GetAvatarInfoListByUidAsync(uid).ConfigureAwait(false); token.ThrowIfCancellationRequested();
EnsureItemsAvatarIdUnique(ref dbInfos, uid, out Dictionary<AvatarId, EntityAvatarInfo> dbInfoMap); List<EntityAvatarInfo> dbInfos = avatarInfoDbService.GetAvatarInfoListByUid(uid);
EnsureItemsAvatarIdDistinct(ref dbInfos, uid);
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
{ {
@@ -47,19 +56,28 @@ internal sealed partial class AvatarInfoDbBulkOperation
continue; continue;
} }
EntityAvatarInfo? entity = dbInfoMap.GetValueOrDefault(webInfo.AvatarId); token.ThrowIfCancellationRequested();
EntityAvatarInfo? entity = dbInfos.SingleOrDefault(i => i.Info.AvatarId == webInfo.AvatarId);
AddOrUpdateAvatarInfo(entity, uid, appDbContext, webInfo); AddOrUpdateAvatarInfo(entity, uid, appDbContext, webInfo);
} }
return await avatarInfoDbService.GetAvatarInfoListByUidAsync(uid).ConfigureAwait(false); token.ThrowIfCancellationRequested();
return avatarInfoDbService.GetAvatarInfoListByUid(uid);
} }
} }
/// <summary>
/// 米游社我的角色方式 更新数据库角色信息
/// </summary>
/// <param name="userAndUid">用户与角色</param>
/// <param name="token">取消令牌</param>
/// <returns>角色列表</returns>
public async ValueTask<List<EntityAvatarInfo>> UpdateDbAvatarInfosByGameRecordCharacterAsync(UserAndUid userAndUid, CancellationToken token) public async ValueTask<List<EntityAvatarInfo>> UpdateDbAvatarInfosByGameRecordCharacterAsync(UserAndUid userAndUid, CancellationToken token)
{ {
token.ThrowIfCancellationRequested();
string uid = userAndUid.Uid.Value; string uid = userAndUid.Uid.Value;
List<EntityAvatarInfo> dbInfos = await avatarInfoDbService.GetAvatarInfoListByUidAsync(uid).ConfigureAwait(false); List<EntityAvatarInfo> dbInfos = await avatarInfoDbService.GetAvatarInfoListByUidAsync(uid).ConfigureAwait(false);
EnsureItemsAvatarIdUnique(ref dbInfos, uid, out Dictionary<AvatarId, EntityAvatarInfo> dbInfoMap); EnsureItemsAvatarIdDistinct(ref dbInfos, uid);
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
{ {
@@ -72,47 +90,51 @@ internal sealed partial class AvatarInfoDbBulkOperation
.GetPlayerInfoAsync(userAndUid, token) .GetPlayerInfoAsync(userAndUid, token)
.ConfigureAwait(false); .ConfigureAwait(false);
if (!playerInfoResponse.IsOk()) if (playerInfoResponse.IsOk())
{ {
goto Return; Response<CharacterWrapper> charactersResponse = await gameRecordClient
} .GetCharactersAsync(userAndUid, playerInfoResponse.Data, token)
.ConfigureAwait(false);
Response<CharacterWrapper> charactersResponse = await gameRecordClient token.ThrowIfCancellationRequested();
.GetCharactersAsync(userAndUid, playerInfoResponse.Data, token)
.ConfigureAwait(false);
if (!charactersResponse.IsOk()) if (charactersResponse.IsOk())
{
goto Return;
}
List<RecordCharacter> characters = charactersResponse.Data.Avatars;
GameRecordCharacterAvatarInfoTransformer transformer = serviceProvider
.GetRequiredService<GameRecordCharacterAvatarInfoTransformer>();
foreach (RecordCharacter character in characters)
{
if (AvatarIds.IsPlayer(character.Id))
{ {
continue; List<RecordCharacter> characters = charactersResponse.Data.Avatars;
}
EntityAvatarInfo? entity = dbInfoMap.GetValueOrDefault(character.Id); GameRecordCharacterAvatarInfoTransformer transformer = serviceProvider
AddOrUpdateAvatarInfo(entity, character.Id, uid, appDbContext, transformer, character); .GetRequiredService<GameRecordCharacterAvatarInfoTransformer>();
foreach (RecordCharacter character in characters)
{
if (AvatarIds.IsPlayer(character.Id))
{
continue;
}
token.ThrowIfCancellationRequested();
EntityAvatarInfo? entity = dbInfos.SingleOrDefault(i => i.Info.AvatarId == character.Id);
AddOrUpdateAvatarInfo(entity, character.Id, uid, appDbContext, transformer, character);
}
}
} }
} }
Return:
return await avatarInfoDbService.GetAvatarInfoListByUidAsync(uid).ConfigureAwait(false); return await avatarInfoDbService.GetAvatarInfoListByUidAsync(uid).ConfigureAwait(false);
} }
/// <summary>
/// 米游社养成计算方式 更新数据库角色信息
/// </summary>
/// <param name="userAndUid">用户与角色</param>
/// <param name="token">取消令牌</param>
/// <returns>角色列表</returns>
public async ValueTask<List<EntityAvatarInfo>> UpdateDbAvatarInfosByCalculateAvatarDetailAsync(UserAndUid userAndUid, CancellationToken token) public async ValueTask<List<EntityAvatarInfo>> UpdateDbAvatarInfosByCalculateAvatarDetailAsync(UserAndUid userAndUid, CancellationToken token)
{ {
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
string uid = userAndUid.Uid.Value; string uid = userAndUid.Uid.Value;
List<EntityAvatarInfo> dbInfos = await avatarInfoDbService.GetAvatarInfoListByUidAsync(uid).ConfigureAwait(false); List<EntityAvatarInfo> dbInfos = await avatarInfoDbService.GetAvatarInfoListByUidAsync(uid).ConfigureAwait(false);
EnsureItemsAvatarIdUnique(ref dbInfos, uid, out Dictionary<AvatarId, EntityAvatarInfo> dbInfoMap); EnsureItemsAvatarIdDistinct(ref dbInfos, uid);
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
{ {
@@ -133,6 +155,8 @@ internal sealed partial class AvatarInfoDbBulkOperation
continue; continue;
} }
token.ThrowIfCancellationRequested();
Response<AvatarDetail> detailAvatarResponse = await calculateClient Response<AvatarDetail> detailAvatarResponse = await calculateClient
.GetAvatarDetailAsync(userAndUid, avatar, token) .GetAvatarDetailAsync(userAndUid, avatar, token)
.ConfigureAwait(false); .ConfigureAwait(false);
@@ -142,7 +166,8 @@ internal sealed partial class AvatarInfoDbBulkOperation
continue; continue;
} }
EntityAvatarInfo? entity = dbInfoMap.GetValueOrDefault(avatar.Id); token.ThrowIfCancellationRequested();
EntityAvatarInfo? entity = dbInfos.SingleOrDefault(i => i.Info.AvatarId == avatar.Id);
AddOrUpdateAvatarInfo(entity, avatar.Id, uid, appDbContext, transformer, detailAvatarResponse.Data); AddOrUpdateAvatarInfo(entity, avatar.Id, uid, appDbContext, transformer, detailAvatarResponse.Data);
} }
} }
@@ -156,14 +181,15 @@ internal sealed partial class AvatarInfoDbBulkOperation
if (entity is null) if (entity is null)
{ {
entity = EntityAvatarInfo.From(uid, webInfo); entity = EntityAvatarInfo.From(uid, webInfo);
entity.ShowcaseRefreshTime = DateTimeOffset.UtcNow;
appDbContext.AvatarInfos.AddAndSave(entity);
} }
else else
{ {
entity.Info = webInfo; entity.Info = webInfo;
entity.ShowcaseRefreshTime = DateTimeOffset.UtcNow;
appDbContext.AvatarInfos.UpdateAndSave(entity);
} }
entity.ShowcaseRefreshTime = DateTimeOffset.UtcNow;
appDbContext.AvatarInfos.UpdateAndSave(entity);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -174,16 +200,17 @@ internal sealed partial class AvatarInfoDbBulkOperation
EnkaAvatarInfo avatarInfo = new() { AvatarId = avatarId }; EnkaAvatarInfo avatarInfo = new() { AvatarId = avatarId };
transformer.Transform(ref avatarInfo, source); transformer.Transform(ref avatarInfo, source);
entity = EntityAvatarInfo.From(uid, avatarInfo); entity = EntityAvatarInfo.From(uid, avatarInfo);
entity.CalculatorRefreshTime = DateTimeOffset.UtcNow;
appDbContext.AvatarInfos.AddAndSave(entity);
} }
else else
{ {
EnkaAvatarInfo avatarInfo = entity.Info; EnkaAvatarInfo avatarInfo = entity.Info;
transformer.Transform(ref avatarInfo, source); transformer.Transform(ref avatarInfo, source);
entity.Info = avatarInfo; entity.Info = avatarInfo;
entity.CalculatorRefreshTime = DateTimeOffset.UtcNow;
appDbContext.AvatarInfos.UpdateAndSave(entity);
} }
entity.CalculatorRefreshTime = DateTimeOffset.UtcNow;
appDbContext.AvatarInfos.UpdateAndSave(entity);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -194,29 +221,29 @@ internal sealed partial class AvatarInfoDbBulkOperation
EnkaAvatarInfo avatarInfo = new() { AvatarId = avatarId }; EnkaAvatarInfo avatarInfo = new() { AvatarId = avatarId };
transformer.Transform(ref avatarInfo, source); transformer.Transform(ref avatarInfo, source);
entity = EntityAvatarInfo.From(uid, avatarInfo); entity = EntityAvatarInfo.From(uid, avatarInfo);
entity.GameRecordRefreshTime = DateTimeOffset.UtcNow;
appDbContext.AvatarInfos.AddAndSave(entity);
} }
else else
{ {
EnkaAvatarInfo avatarInfo = entity.Info; EnkaAvatarInfo avatarInfo = entity.Info;
transformer.Transform(ref avatarInfo, source); transformer.Transform(ref avatarInfo, source);
entity.Info = avatarInfo; entity.Info = avatarInfo;
entity.GameRecordRefreshTime = DateTimeOffset.UtcNow;
appDbContext.AvatarInfos.UpdateAndSave(entity);
} }
entity.GameRecordRefreshTime = DateTimeOffset.UtcNow;
appDbContext.AvatarInfos.UpdateAndSave(entity);
} }
private void EnsureItemsAvatarIdUnique(ref List<EntityAvatarInfo> dbInfos, string uid, out Dictionary<AvatarId, EntityAvatarInfo> dbInfoMap) private void EnsureItemsAvatarIdDistinct(ref List<EntityAvatarInfo> dbInfos, string uid)
{ {
dbInfoMap = []; int distinctCount = dbInfos.Select(info => info.Info.AvatarId).ToHashSet().Count;
foreach (ref readonly EntityAvatarInfo info in CollectionsMarshal.AsSpan(dbInfos))
// Avatars are actually less than the list told us.
// This means that there are duplicate items.
if (distinctCount < dbInfos.Count)
{ {
if (!dbInfoMap.TryAdd(info.Info.AvatarId, info)) avatarInfoDbService.RemoveAvatarInfoRangeByUid(uid);
{ dbInfos = [];
avatarInfoDbService.RemoveAvatarInfoRangeByUid(uid);
dbInfoMap.Clear();
dbInfos.Clear();
}
} }
} }
} }

View File

@@ -4,7 +4,6 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Database; using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity.Database; using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.Service.Abstraction;
using EntityAvatarInfo = Snap.Hutao.Model.Entity.AvatarInfo; using EntityAvatarInfo = Snap.Hutao.Model.Entity.AvatarInfo;
namespace Snap.Hutao.Service.AvatarInfo; namespace Snap.Hutao.Service.AvatarInfo;
@@ -15,25 +14,44 @@ internal sealed partial class AvatarInfoDbService : IAvatarInfoDbService
{ {
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
public IServiceProvider ServiceProvider { get => serviceProvider; }
public List<EntityAvatarInfo> GetAvatarInfoListByUid(string uid) public List<EntityAvatarInfo> GetAvatarInfoListByUid(string uid)
{ {
return this.List(i => i.Uid == uid); using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
IQueryable<EntityAvatarInfo> result = appDbContext.AvatarInfos.AsNoTracking().Where(i => i.Uid == uid);
return [.. result];
}
} }
public ValueTask<List<EntityAvatarInfo>> GetAvatarInfoListByUidAsync(string uid, CancellationToken token = default) public async ValueTask<List<EntityAvatarInfo>> GetAvatarInfoListByUidAsync(string uid)
{ {
return this.ListAsync(i => i.Uid == uid, token); using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.AvatarInfos
.AsNoTracking()
.Where(i => i.Uid == uid)
.ToListAsync()
.ConfigureAwait(false);
}
} }
public void RemoveAvatarInfoRangeByUid(string uid) public void RemoveAvatarInfoRangeByUid(string uid)
{ {
this.Execute(dbset => dbset.Where(i => i.Uid == uid).ExecuteDelete()); using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
appDbContext.AvatarInfos.ExecuteDeleteWhere(i => i.Uid == uid);
}
} }
public async ValueTask RemoveAvatarInfoRangeByUidAsync(string uid, CancellationToken token = default) public async ValueTask RemoveAvatarInfoRangeByUidAsync(string uid)
{ {
await this.ExecuteAsync((dbset, token) => dbset.Where(i => i.Uid == uid).ExecuteDeleteAsync(token), token).ConfigureAwait(false); using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.AvatarInfos.ExecuteDeleteWhereAsync(i => i.Uid == uid).ConfigureAwait(false);
}
} }
} }

View File

@@ -27,58 +27,63 @@ internal sealed partial class AvatarInfoService : IAvatarInfoService
public async ValueTask<ValueResult<RefreshResult, Summary?>> GetSummaryAsync(UserAndUid userAndUid, RefreshOption refreshOption, CancellationToken token = default) public async ValueTask<ValueResult<RefreshResult, Summary?>> GetSummaryAsync(UserAndUid userAndUid, RefreshOption refreshOption, CancellationToken token = default)
{ {
if (!await metadataService.InitializeAsync().ConfigureAwait(false)) if (await metadataService.InitializeAsync().ConfigureAwait(false))
{
token.ThrowIfCancellationRequested();
switch (refreshOption)
{
case RefreshOption.RequestFromEnkaAPI:
{
EnkaResponse? resp = await GetEnkaResponseAsync(userAndUid.Uid, token).ConfigureAwait(false);
token.ThrowIfCancellationRequested();
if (resp is null)
{
return new(RefreshResult.APIUnavailable, default);
}
if (!string.IsNullOrEmpty(resp.Message))
{
return new(RefreshResult.StatusCodeNotSucceed, new Summary { Message = resp.Message });
}
if (!resp.IsValid)
{
return new(RefreshResult.ShowcaseNotOpen, default);
}
List<EntityAvatarInfo> list = avatarInfoDbBulkOperation.UpdateDbAvatarInfosByShowcase(userAndUid.Uid.Value, resp.AvatarInfoList, token);
Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false);
return new(RefreshResult.Ok, summary);
}
case RefreshOption.RequestFromHoyolabGameRecord:
{
List<EntityAvatarInfo> list = await avatarInfoDbBulkOperation.UpdateDbAvatarInfosByGameRecordCharacterAsync(userAndUid, token).ConfigureAwait(false);
Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false);
return new(RefreshResult.Ok, summary);
}
case RefreshOption.RequestFromHoyolabCalculate:
{
List<EntityAvatarInfo> list = await avatarInfoDbBulkOperation.UpdateDbAvatarInfosByCalculateAvatarDetailAsync(userAndUid, token).ConfigureAwait(false);
Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false);
return new(RefreshResult.Ok, summary);
}
default:
{
List<EntityAvatarInfo> list = await avatarInfoDbService.GetAvatarInfoListByUidAsync(userAndUid.Uid.Value).ConfigureAwait(false);
Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false);
token.ThrowIfCancellationRequested();
return new(RefreshResult.Ok, summary.Avatars.Count == 0 ? null : summary);
}
}
}
else
{ {
return new(RefreshResult.MetadataNotInitialized, null); return new(RefreshResult.MetadataNotInitialized, null);
} }
switch (refreshOption)
{
case RefreshOption.RequestFromEnkaAPI:
{
EnkaResponse? resp = await GetEnkaResponseAsync(userAndUid.Uid, token).ConfigureAwait(false);
if (resp is null)
{
return new(RefreshResult.APIUnavailable, default);
}
if (!string.IsNullOrEmpty(resp.Message))
{
return new(RefreshResult.StatusCodeNotSucceed, new Summary { Message = resp.Message });
}
if (!resp.IsValid)
{
return new(RefreshResult.ShowcaseNotOpen, default);
}
List<EntityAvatarInfo> list = await avatarInfoDbBulkOperation.UpdateDbAvatarInfosByShowcaseAsync(userAndUid.Uid.Value, resp.AvatarInfoList, token).ConfigureAwait(false);
Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false);
return new(RefreshResult.Ok, summary);
}
case RefreshOption.RequestFromHoyolabGameRecord:
{
List<EntityAvatarInfo> list = await avatarInfoDbBulkOperation.UpdateDbAvatarInfosByGameRecordCharacterAsync(userAndUid, token).ConfigureAwait(false);
Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false);
return new(RefreshResult.Ok, summary);
}
case RefreshOption.RequestFromHoyolabCalculate:
{
List<EntityAvatarInfo> list = await avatarInfoDbBulkOperation.UpdateDbAvatarInfosByCalculateAvatarDetailAsync(userAndUid, token).ConfigureAwait(false);
Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false);
return new(RefreshResult.Ok, summary);
}
default:
{
List<EntityAvatarInfo> list = await avatarInfoDbService.GetAvatarInfoListByUidAsync(userAndUid.Uid.Value, token).ConfigureAwait(false);
Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false);
return new(RefreshResult.Ok, summary.Avatars.Count == 0 ? null : summary);
}
}
} }
private async ValueTask<EnkaResponse?> GetEnkaResponseAsync(PlayerUid uid, CancellationToken token = default) private async ValueTask<EnkaResponse?> GetEnkaResponseAsync(PlayerUid uid, CancellationToken token = default)

View File

@@ -1,13 +1,9 @@
// 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.ViewModel.AvatarProperty;
namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder; namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder;
internal sealed class AvatarViewBuilder : IAvatarViewBuilder internal sealed class AvatarViewBuilder : IAvatarViewBuilder
{ {
public AvatarView View { get; } = new(); public ViewModel.AvatarProperty.AvatarView AvatarView { get; } = new();
public float Score { get => View.Score; set => View.Score = value; }
} }

View File

@@ -12,7 +12,7 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder;
internal static class AvatarViewBuilderExtension internal static class AvatarViewBuilderExtension
{ {
public static TBuilder SetCostumeIconOrDefault<TBuilder>(this TBuilder builder, Web.Enka.Model.AvatarInfo avatarInfo, Avatar avatar) public static TBuilder ApplyCostumeIconOrDefault<TBuilder>(this TBuilder builder, Web.Enka.Model.AvatarInfo avatarInfo, Avatar avatar)
where TBuilder : IAvatarViewBuilder where TBuilder : IAvatarViewBuilder
{ {
if (avatarInfo.CostumeId.TryGetValue(out CostumeId id)) if (avatarInfo.CostumeId.TryGetValue(out CostumeId id))
@@ -20,13 +20,13 @@ internal static class AvatarViewBuilderExtension
Costume costume = avatar.Costumes.Single(c => c.Id == id); Costume costume = avatar.Costumes.Single(c => c.Id == id);
// Set to costume icon // Set to costume icon
builder.View.Icon = AvatarIconConverter.IconNameToUri(costume.FrontIcon); builder.AvatarView.Icon = AvatarIconConverter.IconNameToUri(costume.FrontIcon);
builder.View.SideIcon = AvatarIconConverter.IconNameToUri(costume.SideIcon); builder.AvatarView.SideIcon = AvatarIconConverter.IconNameToUri(costume.SideIcon);
} }
else else
{ {
builder.View.Icon = AvatarIconConverter.IconNameToUri(avatar.Icon); builder.AvatarView.Icon = AvatarIconConverter.IconNameToUri(avatar.Icon);
builder.View.SideIcon = AvatarIconConverter.IconNameToUri(avatar.SideIcon); builder.AvatarView.SideIcon = AvatarIconConverter.IconNameToUri(avatar.SideIcon);
} }
return builder; return builder;
@@ -41,7 +41,7 @@ internal static class AvatarViewBuilderExtension
public static TBuilder SetCalculatorRefreshTimeFormat<TBuilder>(this TBuilder builder, string calculatorRefreshTimeFormat) public static TBuilder SetCalculatorRefreshTimeFormat<TBuilder>(this TBuilder builder, string calculatorRefreshTimeFormat)
where TBuilder : IAvatarViewBuilder where TBuilder : IAvatarViewBuilder
{ {
return builder.Configure(b => b.View.CalculatorRefreshTimeFormat = calculatorRefreshTimeFormat); return builder.Configure(b => b.AvatarView.CalculatorRefreshTimeFormat = calculatorRefreshTimeFormat);
} }
public static TBuilder SetConstellations<TBuilder>(this TBuilder builder, List<Skill> talents, List<SkillId>? talentIds) public static TBuilder SetConstellations<TBuilder>(this TBuilder builder, List<Skill> talents, List<SkillId>? talentIds)
@@ -65,7 +65,7 @@ internal static class AvatarViewBuilderExtension
public static TBuilder SetConstellations<TBuilder>(this TBuilder builder, List<ConstellationView> constellations) public static TBuilder SetConstellations<TBuilder>(this TBuilder builder, List<ConstellationView> constellations)
where TBuilder : IAvatarViewBuilder where TBuilder : IAvatarViewBuilder
{ {
return builder.Configure(b => b.View.Constellations = constellations); return builder.Configure(b => b.AvatarView.Constellations = constellations);
} }
public static TBuilder SetCritScore<TBuilder>(this TBuilder builder, Dictionary<FightProperty, float>? fightPropMap) public static TBuilder SetCritScore<TBuilder>(this TBuilder builder, Dictionary<FightProperty, float>? fightPropMap)
@@ -90,13 +90,19 @@ internal static class AvatarViewBuilderExtension
public static TBuilder SetCritScore<TBuilder>(this TBuilder builder, float critScore) public static TBuilder SetCritScore<TBuilder>(this TBuilder builder, float critScore)
where TBuilder : IAvatarViewBuilder where TBuilder : IAvatarViewBuilder
{ {
return builder.Configure(b => b.View.CritScore = critScore); return builder.SetCritScore($"{critScore:F2}");
}
public static TBuilder SetCritScore<TBuilder>(this TBuilder builder, string critScore)
where TBuilder : IAvatarViewBuilder
{
return builder.Configure(b => b.AvatarView.CritScore = critScore);
} }
public static TBuilder SetElement<TBuilder>(this TBuilder builder, ElementType element) public static TBuilder SetElement<TBuilder>(this TBuilder builder, ElementType element)
where TBuilder : IAvatarViewBuilder where TBuilder : IAvatarViewBuilder
{ {
return builder.Configure(b => b.View.Element = element); return builder.Configure(b => b.AvatarView.Element = element);
} }
public static TBuilder SetFetterLevel<TBuilder>(this TBuilder builder, FetterLevel? level) public static TBuilder SetFetterLevel<TBuilder>(this TBuilder builder, FetterLevel? level)
@@ -104,7 +110,7 @@ internal static class AvatarViewBuilderExtension
{ {
if (level.TryGetValue(out FetterLevel value)) if (level.TryGetValue(out FetterLevel value))
{ {
return builder.Configure(b => b.View.FetterLevel = value); return builder.Configure(b => b.AvatarView.FetterLevel = value);
} }
return builder; return builder;
@@ -113,7 +119,7 @@ internal static class AvatarViewBuilderExtension
public static TBuilder SetFetterLevel<TBuilder>(this TBuilder builder, uint level) public static TBuilder SetFetterLevel<TBuilder>(this TBuilder builder, uint level)
where TBuilder : IAvatarViewBuilder where TBuilder : IAvatarViewBuilder
{ {
return builder.Configure(b => b.View.FetterLevel = level); return builder.Configure(b => b.AvatarView.FetterLevel = level);
} }
public static TBuilder SetGameRecordRefreshTimeFormat<TBuilder>(this TBuilder builder, DateTimeOffset refreshTime, Func<object?, string> format, string defaultValue) public static TBuilder SetGameRecordRefreshTimeFormat<TBuilder>(this TBuilder builder, DateTimeOffset refreshTime, Func<object?, string> format, string defaultValue)
@@ -125,13 +131,13 @@ internal static class AvatarViewBuilderExtension
public static TBuilder SetGameRecordRefreshTimeFormat<TBuilder>(this TBuilder builder, string gameRecordRefreshTimeFormat) public static TBuilder SetGameRecordRefreshTimeFormat<TBuilder>(this TBuilder builder, string gameRecordRefreshTimeFormat)
where TBuilder : IAvatarViewBuilder where TBuilder : IAvatarViewBuilder
{ {
return builder.Configure(b => b.View.GameRecordRefreshTimeFormat = gameRecordRefreshTimeFormat); return builder.Configure(b => b.AvatarView.GameRecordRefreshTimeFormat = gameRecordRefreshTimeFormat);
} }
public static TBuilder SetId<TBuilder>(this TBuilder builder, AvatarId id) public static TBuilder SetId<TBuilder>(this TBuilder builder, AvatarId id)
where TBuilder : IAvatarViewBuilder where TBuilder : IAvatarViewBuilder
{ {
return builder.Configure(b => b.View.Id = id); return builder.Configure(b => b.AvatarView.Id = id);
} }
public static TBuilder SetLevelNumber<TBuilder>(this TBuilder builder, uint? levelNumber) public static TBuilder SetLevelNumber<TBuilder>(this TBuilder builder, uint? levelNumber)
@@ -139,7 +145,7 @@ internal static class AvatarViewBuilderExtension
{ {
if (levelNumber.TryGetValue(out uint value)) if (levelNumber.TryGetValue(out uint value))
{ {
return builder.Configure(b => b.View.LevelNumber = value); return builder.Configure(b => b.AvatarView.LevelNumber = value);
} }
return builder; return builder;
@@ -148,31 +154,43 @@ internal static class AvatarViewBuilderExtension
public static TBuilder SetName<TBuilder>(this TBuilder builder, string name) public static TBuilder SetName<TBuilder>(this TBuilder builder, string name)
where TBuilder : IAvatarViewBuilder where TBuilder : IAvatarViewBuilder
{ {
return builder.Configure(b => b.View.Name = name); return builder.Configure(b => b.AvatarView.Name = name);
} }
public static TBuilder SetNameCard<TBuilder>(this TBuilder builder, Uri nameCard) public static TBuilder SetNameCard<TBuilder>(this TBuilder builder, Uri nameCard)
where TBuilder : IAvatarViewBuilder where TBuilder : IAvatarViewBuilder
{ {
return builder.Configure(b => b.View.NameCard = nameCard); return builder.Configure(b => b.AvatarView.NameCard = nameCard);
} }
public static TBuilder SetProperties<TBuilder>(this TBuilder builder, List<AvatarProperty> properties) public static TBuilder SetProperties<TBuilder>(this TBuilder builder, List<AvatarProperty> properties)
where TBuilder : IAvatarViewBuilder where TBuilder : IAvatarViewBuilder
{ {
return builder.Configure(b => b.View.Properties = properties); return builder.Configure(b => b.AvatarView.Properties = properties);
} }
public static TBuilder SetQuality<TBuilder>(this TBuilder builder, QualityType quality) public static TBuilder SetQuality<TBuilder>(this TBuilder builder, QualityType quality)
where TBuilder : IAvatarViewBuilder where TBuilder : IAvatarViewBuilder
{ {
return builder.Configure(b => b.View.Quality = quality); return builder.Configure(b => b.AvatarView.Quality = quality);
} }
public static TBuilder SetReliquaries<TBuilder>(this TBuilder builder, List<ReliquaryView> reliquaries) public static TBuilder SetReliquaries<TBuilder>(this TBuilder builder, List<ReliquaryView> reliquaries)
where TBuilder : IAvatarViewBuilder where TBuilder : IAvatarViewBuilder
{ {
return builder.Configure(b => b.View.Reliquaries = reliquaries); return builder.Configure(b => b.AvatarView.Reliquaries = reliquaries);
}
public static TBuilder SetScore<TBuilder>(this TBuilder builder, float score)
where TBuilder : IAvatarViewBuilder
{
return builder.SetScore($"{score:F2}");
}
public static TBuilder SetScore<TBuilder>(this TBuilder builder, string score)
where TBuilder : IAvatarViewBuilder
{
return builder.Configure(b => b.AvatarView.Score = score);
} }
public static TBuilder SetShowcaseRefreshTimeFormat<TBuilder>(this TBuilder builder, DateTimeOffset refreshTime, Func<object?, string> format, string defaultValue) public static TBuilder SetShowcaseRefreshTimeFormat<TBuilder>(this TBuilder builder, DateTimeOffset refreshTime, Func<object?, string> format, string defaultValue)
@@ -184,7 +202,7 @@ internal static class AvatarViewBuilderExtension
public static TBuilder SetShowcaseRefreshTimeFormat<TBuilder>(this TBuilder builder, string showcaseRefreshTimeFormat) public static TBuilder SetShowcaseRefreshTimeFormat<TBuilder>(this TBuilder builder, string showcaseRefreshTimeFormat)
where TBuilder : IAvatarViewBuilder where TBuilder : IAvatarViewBuilder
{ {
return builder.Configure(b => b.View.ShowcaseRefreshTimeFormat = showcaseRefreshTimeFormat); return builder.Configure(b => b.AvatarView.ShowcaseRefreshTimeFormat = showcaseRefreshTimeFormat);
} }
public static TBuilder SetSkills<TBuilder>(this TBuilder builder, Dictionary<SkillId, SkillLevel>? skillLevelMap, Dictionary<SkillGroupId, SkillLevel>? proudSkillExtraLevelMap, List<ProudableSkill> proudSkills) public static TBuilder SetSkills<TBuilder>(this TBuilder builder, Dictionary<SkillId, SkillLevel>? skillLevelMap, Dictionary<SkillGroupId, SkillLevel>? proudSkillExtraLevelMap, List<ProudableSkill> proudSkills)
@@ -231,12 +249,12 @@ internal static class AvatarViewBuilderExtension
public static TBuilder SetSkills<TBuilder>(this TBuilder builder, List<SkillView> skills) public static TBuilder SetSkills<TBuilder>(this TBuilder builder, List<SkillView> skills)
where TBuilder : IAvatarViewBuilder where TBuilder : IAvatarViewBuilder
{ {
return builder.Configure(b => b.View.Skills = skills); return builder.Configure(b => b.AvatarView.Skills = skills);
} }
public static TBuilder SetWeapon<TBuilder>(this TBuilder builder, WeaponView? weapon) public static TBuilder SetWeapon<TBuilder>(this TBuilder builder, WeaponView? weapon)
where TBuilder : IAvatarViewBuilder where TBuilder : IAvatarViewBuilder
{ {
return builder.Configure(b => b.View.Weapon = weapon); return builder.Configure(b => b.AvatarView.Weapon = weapon);
} }
} }

View File

@@ -1,33 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction.Extension;
using Snap.Hutao.Model;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.ViewModel.AvatarProperty;
namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder;
internal static class EquipViewBuilderExtension
{
public static TBuilder SetLevel<TBuilder, T>(this TBuilder builder, string level)
where TBuilder : IEquipViewBuilder<T>
where T : EquipView
{
return builder.Configure(b => b.View.Level = level);
}
public static TBuilder SetQuality<TBuilder, T>(this TBuilder builder, QualityType quality)
where TBuilder : IEquipViewBuilder<T>
where T : EquipView
{
return builder.Configure(b => b.View.Quality = quality);
}
public static TBuilder SetMainProperty<TBuilder, T>(this TBuilder builder, NameValue<string> mainProperty)
where TBuilder : IEquipViewBuilder<T>
where T : EquipView
{
return builder.Configure(b => b.View.MainProperty = mainProperty);
}
}

View File

@@ -2,11 +2,10 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction; using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.ViewModel.AvatarProperty;
namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder; namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder;
internal interface IAvatarViewBuilder : IBuilder, IScoreAccess internal interface IAvatarViewBuilder : IBuilder
{ {
AvatarView View { get; } ViewModel.AvatarProperty.AvatarView AvatarView { get; }
} }

View File

@@ -1,11 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.ViewModel.AvatarProperty;
namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder;
internal interface IEquipViewBuilder<T> : INameIconDescriptionBuilder<T>
where T : EquipView
{
}

View File

@@ -1,13 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.ViewModel.AvatarProperty;
namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder;
internal interface INameIconDescriptionBuilder<T> : IBuilder
where T : NameIconDescription
{
T View { get; }
}

View File

@@ -1,10 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.ViewModel.AvatarProperty;
namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder;
internal interface IReliquaryViewBuilder : IEquipViewBuilder<ReliquaryView>, IScoreAccess
{
}

View File

@@ -1,9 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder;
internal interface IScoreAccess
{
float Score { get; set; }
}

View File

@@ -1,10 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.ViewModel.AvatarProperty;
namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder;
internal interface IWeaponViewBuilder : IEquipViewBuilder<WeaponView>
{
}

View File

@@ -1,33 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction.Extension;
using Snap.Hutao.Model;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.ViewModel.AvatarProperty;
namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder;
internal static class NameIconDescriptionBuilderExtension
{
public static TBuilder SetDescription<TBuilder, T>(this TBuilder builder, string description)
where TBuilder : INameIconDescriptionBuilder<T>
where T : NameIconDescription
{
return builder.Configure(b => b.View.Description = description);
}
public static TBuilder SetIcon<TBuilder, T>(this TBuilder builder, Uri icon)
where TBuilder : INameIconDescriptionBuilder<T>
where T : NameIconDescription
{
return builder.Configure(b => b.View.Icon = icon);
}
public static TBuilder SetName<TBuilder, T>(this TBuilder builder, string name)
where TBuilder : INameIconDescriptionBuilder<T>
where T : NameIconDescription
{
return builder.Configure(b => b.View.Name = name);
}
}

View File

@@ -1,13 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.ViewModel.AvatarProperty;
namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder;
internal sealed class ReliquaryViewBuilder : IReliquaryViewBuilder
{
public ReliquaryView View { get; } = new();
public float Score { get => View.Score; set => View.Score = value; }
}

View File

@@ -1,66 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction.Extension;
using Snap.Hutao.Model;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.ViewModel.AvatarProperty;
namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder;
internal static class ReliquaryViewBuilderExtension
{
public static TBuilder SetComposedSubProperties<TBuilder>(this TBuilder builder, List<ReliquaryComposedSubProperty> composedSubProperties)
where TBuilder : IReliquaryViewBuilder
{
return builder.Configure(b => b.View.ComposedSubProperties = composedSubProperties);
}
public static TBuilder SetDescription<TBuilder>(this TBuilder builder, string description)
where TBuilder : IReliquaryViewBuilder
{
return builder.SetDescription<TBuilder, ReliquaryView>(description);
}
public static TBuilder SetIcon<TBuilder>(this TBuilder builder, Uri icon)
where TBuilder : IReliquaryViewBuilder
{
return builder.SetIcon<TBuilder, ReliquaryView>(icon);
}
public static TBuilder SetLevel<TBuilder>(this TBuilder builder, string level)
where TBuilder : IReliquaryViewBuilder
{
return builder.SetLevel<TBuilder, ReliquaryView>(level);
}
public static TBuilder SetMainProperty<TBuilder>(this TBuilder builder, NameValue<string> mainProperty)
where TBuilder : IReliquaryViewBuilder
{
return builder.SetMainProperty<TBuilder, ReliquaryView>(mainProperty);
}
public static TBuilder SetName<TBuilder>(this TBuilder builder, string name)
where TBuilder : IReliquaryViewBuilder
{
return builder.SetName<TBuilder, ReliquaryView>(name);
}
public static TBuilder SetPrimarySubProperties<TBuilder>(this TBuilder builder, List<ReliquarySubProperty> primarySubProperties)
where TBuilder : IReliquaryViewBuilder
{
return builder.Configure(b => b.View.PrimarySubProperties = primarySubProperties);
}
public static TBuilder SetQuality<TBuilder>(this TBuilder builder, QualityType quality)
where TBuilder : IReliquaryViewBuilder
{
return builder.SetQuality<TBuilder, ReliquaryView>(quality);
}
public static TBuilder SetSecondarySubProperties<TBuilder>(this TBuilder builder, List<ReliquarySubProperty> secondarySubProperties)
where TBuilder : IReliquaryViewBuilder
{
return builder.Configure(b => b.View.SecondarySubProperties = secondarySubProperties);
}
}

View File

@@ -1,16 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Core.Abstraction.Extension;
namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder;
internal static class ScoreAccessExtension
{
public static TBuilder SetScore<TBuilder>(this TBuilder builder, float score)
where TBuilder : IBuilder, IScoreAccess
{
return builder.Configure(b => b.Score = score);
}
}

View File

@@ -1,11 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.ViewModel.AvatarProperty;
namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder;
internal sealed class WeaponViewBuilder : IWeaponViewBuilder
{
public WeaponView View { get; } = new();
}

View File

@@ -1,99 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction.Extension;
using Snap.Hutao.Model;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.ViewModel.AvatarProperty;
using Snap.Hutao.Web.Enka.Model;
namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder;
internal static class WeaponViewBuilderExtension
{
public static TBuilder SetAffixLevelNumber<TBuilder>(this TBuilder builder, uint affixLevelNumber)
where TBuilder : IWeaponViewBuilder
{
return builder.Configure(b => b.View.AffixLevelNumber = affixLevelNumber);
}
public static TBuilder SetAffixDescription<TBuilder>(this TBuilder builder, string? affixDescription)
where TBuilder : IWeaponViewBuilder
{
return builder.Configure(b => b.View.AffixDescription = affixDescription ?? string.Empty);
}
public static TBuilder SetAffixName<TBuilder>(this TBuilder builder, string? affixName)
where TBuilder : IWeaponViewBuilder
{
return builder.Configure(b => b.View.AffixName = affixName ?? string.Empty);
}
public static TBuilder SetDescription<TBuilder>(this TBuilder builder, string description)
where TBuilder : IWeaponViewBuilder
{
return builder.SetDescription<TBuilder, WeaponView>(description);
}
public static TBuilder SetIcon<TBuilder>(this TBuilder builder, Uri icon)
where TBuilder : IWeaponViewBuilder
{
return builder.SetIcon<TBuilder, WeaponView>(icon);
}
public static TBuilder SetId<TBuilder>(this TBuilder builder, WeaponId id)
where TBuilder : IWeaponViewBuilder
{
return builder.Configure(b => b.View.Id = id);
}
public static TBuilder SetLevel<TBuilder>(this TBuilder builder, string level)
where TBuilder : IWeaponViewBuilder
{
return builder.SetLevel<TBuilder, WeaponView>(level);
}
public static TBuilder SetLevelNumber<TBuilder>(this TBuilder builder, uint levelNumber)
where TBuilder : IWeaponViewBuilder
{
return builder.Configure(b => b.View.LevelNumber = levelNumber);
}
public static TBuilder SetMainProperty<TBuilder>(this TBuilder builder, WeaponStat? mainStat)
where TBuilder : IWeaponViewBuilder
{
return builder.SetMainProperty(mainStat is not null ? FightPropertyFormat.ToNameValue(mainStat.AppendPropId, mainStat.StatValue) : NameValueDefaults.String);
}
public static TBuilder SetMainProperty<TBuilder>(this TBuilder builder, NameValue<string> mainProperty)
where TBuilder : IWeaponViewBuilder
{
return builder.SetMainProperty<TBuilder, WeaponView>(mainProperty);
}
public static TBuilder SetName<TBuilder>(this TBuilder builder, string name)
where TBuilder : IWeaponViewBuilder
{
return builder.SetName<TBuilder, WeaponView>(name);
}
public static TBuilder SetQuality<TBuilder>(this TBuilder builder, QualityType quality)
where TBuilder : IWeaponViewBuilder
{
return builder.SetQuality<TBuilder, WeaponView>(quality);
}
public static TBuilder SetSubProperty<TBuilder>(this TBuilder builder, NameDescription subProperty)
where TBuilder : IWeaponViewBuilder
{
return builder.Configure(b => b.View.SubProperty = subProperty);
}
public static TBuilder SetWeaponType<TBuilder>(this TBuilder builder, WeaponType weaponType)
where TBuilder : IWeaponViewBuilder
{
return builder.Configure(b => b.View.WeaponType = weaponType);
}
}

View File

@@ -6,13 +6,15 @@ using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Intrinsic.Format; using Snap.Hutao.Model.Intrinsic.Format;
using Snap.Hutao.Model.Metadata.Converter; using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Service.AvatarInfo.Factory.Builder; using Snap.Hutao.Service.AvatarInfo.Factory.Builder;
using Snap.Hutao.ViewModel.AvatarProperty;
using Snap.Hutao.Web.Enka.Model; using Snap.Hutao.Web.Enka.Model;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using EntityAvatarInfo = Snap.Hutao.Model.Entity.AvatarInfo; using EntityAvatarInfo = Snap.Hutao.Model.Entity.AvatarInfo;
using MetadataAvatar = Snap.Hutao.Model.Metadata.Avatar.Avatar; using MetadataAvatar = Snap.Hutao.Model.Metadata.Avatar.Avatar;
using MetadataWeapon = Snap.Hutao.Model.Metadata.Weapon.Weapon; using MetadataWeapon = Snap.Hutao.Model.Metadata.Weapon.Weapon;
using ModelAvatarInfo = Snap.Hutao.Web.Enka.Model.AvatarInfo; using ModelAvatarInfo = Snap.Hutao.Web.Enka.Model.AvatarInfo;
using PropertyAvatar = Snap.Hutao.ViewModel.AvatarProperty.AvatarView;
using PropertyReliquary = Snap.Hutao.ViewModel.AvatarProperty.ReliquaryView;
using PropertyWeapon = Snap.Hutao.ViewModel.AvatarProperty.WeaponView;
namespace Snap.Hutao.Service.AvatarInfo.Factory; namespace Snap.Hutao.Service.AvatarInfo.Factory;
@@ -35,17 +37,17 @@ internal sealed class SummaryAvatarFactory
calculatorRefreshTime = avatarInfo.CalculatorRefreshTime; calculatorRefreshTime = avatarInfo.CalculatorRefreshTime;
} }
public static AvatarView Create(SummaryFactoryMetadataContext context, EntityAvatarInfo avatarInfo) public static PropertyAvatar Create(SummaryFactoryMetadataContext context, EntityAvatarInfo avatarInfo)
{ {
return new SummaryAvatarFactory(context, avatarInfo).Create(); return new SummaryAvatarFactory(context, avatarInfo).Create();
} }
public AvatarView Create() public PropertyAvatar Create()
{ {
ReliquaryAndWeapon reliquaryAndWeapon = ProcessEquip(avatarInfo.EquipList.EmptyIfNull()); ReliquaryAndWeapon reliquaryAndWeapon = ProcessEquip(avatarInfo.EquipList.EmptyIfNull());
MetadataAvatar avatar = context.IdAvatarMap[avatarInfo.AvatarId]; MetadataAvatar avatar = context.IdAvatarMap[avatarInfo.AvatarId];
AvatarView propertyAvatar = new AvatarViewBuilder() PropertyAvatar propertyAvatar = new AvatarViewBuilder()
.SetId(avatar.Id) .SetId(avatar.Id)
.SetName(avatar.Name) .SetName(avatar.Name)
.SetQuality(avatar.Quality) .SetQuality(avatar.Quality)
@@ -63,16 +65,16 @@ internal sealed class SummaryAvatarFactory
.SetShowcaseRefreshTimeFormat(showcaseRefreshTime, SH.FormatServiceAvatarInfoSummaryShowcaseRefreshTimeFormat, SH.ServiceAvatarInfoSummaryShowcaseNotRefreshed) .SetShowcaseRefreshTimeFormat(showcaseRefreshTime, SH.FormatServiceAvatarInfoSummaryShowcaseRefreshTimeFormat, SH.ServiceAvatarInfoSummaryShowcaseNotRefreshed)
.SetGameRecordRefreshTimeFormat(gameRecordRefreshTime, SH.FormatServiceAvatarInfoSummaryGameRecordRefreshTimeFormat, SH.ServiceAvatarInfoSummaryGameRecordNotRefreshed) .SetGameRecordRefreshTimeFormat(gameRecordRefreshTime, SH.FormatServiceAvatarInfoSummaryGameRecordRefreshTimeFormat, SH.ServiceAvatarInfoSummaryGameRecordNotRefreshed)
.SetCalculatorRefreshTimeFormat(calculatorRefreshTime, SH.FormatServiceAvatarInfoSummaryCalculatorRefreshTimeFormat, SH.ServiceAvatarInfoSummaryCalculatorNotRefreshed) .SetCalculatorRefreshTimeFormat(calculatorRefreshTime, SH.FormatServiceAvatarInfoSummaryCalculatorRefreshTimeFormat, SH.ServiceAvatarInfoSummaryCalculatorNotRefreshed)
.SetCostumeIconOrDefault(avatarInfo, avatar) .ApplyCostumeIconOrDefault(avatarInfo, avatar)
.View; .AvatarView;
return propertyAvatar; return propertyAvatar;
} }
private ReliquaryAndWeapon ProcessEquip(List<Equip> equipments) private ReliquaryAndWeapon ProcessEquip(List<Equip> equipments)
{ {
List<ReliquaryView> reliquaryList = []; List<PropertyReliquary> reliquaryList = [];
WeaponView? weapon = null; PropertyWeapon? weapon = null;
foreach (ref readonly Equip equip in CollectionsMarshal.AsSpan(equipments)) foreach (ref readonly Equip equip in CollectionsMarshal.AsSpan(equipments))
{ {
@@ -90,7 +92,7 @@ internal sealed class SummaryAvatarFactory
return new(reliquaryList, weapon); return new(reliquaryList, weapon);
} }
private WeaponView CreateWeapon(Equip equip) private PropertyWeapon CreateWeapon(Equip equip)
{ {
MetadataWeapon weapon = context.IdWeaponMap[equip.ItemId]; MetadataWeapon weapon = context.IdWeaponMap[equip.ItemId];
@@ -116,29 +118,36 @@ internal sealed class SummaryAvatarFactory
ArgumentNullException.ThrowIfNull(equip.Weapon); ArgumentNullException.ThrowIfNull(equip.Weapon);
return new WeaponViewBuilder() return new()
.SetName(weapon.Name) {
.SetIcon(EquipIconConverter.IconNameToUri(weapon.Icon)) // NameIconDescription
.SetDescription(weapon.Description) Name = weapon.Name,
.SetLevel($"Lv.{equip.Weapon.Level.Value}") Icon = EquipIconConverter.IconNameToUri(weapon.Icon),
.SetQuality(weapon.Quality) Description = weapon.Description,
.SetMainProperty(mainStat)
.SetId(weapon.Id) // EquipBase
.SetLevelNumber(equip.Weapon.Level) Level = $"Lv.{equip.Weapon.Level.Value}",
.SetSubProperty(subProperty) Quality = weapon.Quality,
.SetAffixLevelNumber(affixLevel + 1) MainProperty = mainStat is not null
.SetAffixName(weapon.Affix?.Name) ? FightPropertyFormat.ToNameValue(mainStat.AppendPropId, mainStat.StatValue)
.SetAffixDescription(weapon.Affix?.Descriptions.Single(a => a.Level == affixLevel).Description) : NameValueDefaults.String,
.SetWeaponType(weapon.WeaponType)
.View; // Weapon
Id = weapon.Id,
LevelNumber = equip.Weapon.Level,
SubProperty = subProperty,
AffixLevelNumber = affixLevel + 1,
AffixName = weapon.Affix?.Name ?? string.Empty,
AffixDescription = weapon.Affix?.Descriptions.Single(a => a.Level == affixLevel).Description ?? string.Empty,
};
} }
private readonly struct ReliquaryAndWeapon private readonly struct ReliquaryAndWeapon
{ {
public readonly List<ReliquaryView> Reliquaries; public readonly List<PropertyReliquary> Reliquaries;
public readonly WeaponView? Weapon; public readonly PropertyWeapon? Weapon;
public ReliquaryAndWeapon(List<ReliquaryView> reliquaries, WeaponView? weapon) public ReliquaryAndWeapon(List<PropertyReliquary> reliquaries, PropertyWeapon? weapon)
{ {
Reliquaries = reliquaries; Reliquaries = reliquaries;
Weapon = weapon; Weapon = weapon;

View File

@@ -13,6 +13,11 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory;
[HighQuality] [HighQuality]
internal static class SummaryAvatarProperties internal static class SummaryAvatarProperties
{ {
/// <summary>
/// 创建角色属性
/// </summary>
/// <param name="fightPropMap">属性映射</param>
/// <returns>列表</returns>
public static List<AvatarProperty> Create(Dictionary<FightProperty, float>? fightPropMap) public static List<AvatarProperty> Create(Dictionary<FightProperty, float>? fightPropMap)
{ {
if (fightPropMap is null) if (fightPropMap is null)
@@ -20,27 +25,32 @@ internal static class SummaryAvatarProperties
return []; return [];
} }
List<AvatarProperty> properties = AvatarProperty hpProp = ToAvatarProperty(FightProperty.FIGHT_PROP_BASE_HP, fightPropMap);
[ AvatarProperty atkProp = ToAvatarProperty(FightProperty.FIGHT_PROP_BASE_ATTACK, fightPropMap);
ToAvatarProperty(FightProperty.FIGHT_PROP_BASE_HP, fightPropMap), AvatarProperty defProp = ToAvatarProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE, fightPropMap);
ToAvatarProperty(FightProperty.FIGHT_PROP_BASE_ATTACK, fightPropMap), AvatarProperty emProp = FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_ELEMENT_MASTERY, fightPropMap);
ToAvatarProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE, fightPropMap), AvatarProperty critRateProp = FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_CRITICAL, fightPropMap);
FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_ELEMENT_MASTERY, fightPropMap), AvatarProperty critDMGProp = FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_CRITICAL_HURT, fightPropMap);
FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_CRITICAL, fightPropMap), AvatarProperty chargeEffProp = FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, fightPropMap);
FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_CRITICAL_HURT, fightPropMap),
FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, fightPropMap) List<AvatarProperty> properties = new(9) { hpProp, atkProp, defProp, emProp, critRateProp, critDMGProp, chargeEffProp };
];
// 元素伤害 // 元素伤害
if (TryGetBonusFightProperty(fightPropMap, out FightProperty bonusProperty, out float value) && value > 0) if (TryGetBonusFightProperty(fightPropMap, out FightProperty bonusProperty, out float value))
{ {
properties.Add(FightPropertyFormat.ToAvatarProperty(bonusProperty, value)); if (value > 0)
{
properties.Add(FightPropertyFormat.ToAvatarProperty(bonusProperty, value));
}
} }
// 物伤 可以和其他元素伤害并存,所以分别判断 // 物伤 可以和其他元素伤害并存,所以分别判断
if (fightPropMap.TryGetValue(FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, out float addValue) && addValue > 0) if (fightPropMap.TryGetValue(FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, out float addValue))
{ {
properties.Add(FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, addValue)); if (addValue > 0)
{
properties.Add(FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, addValue));
}
} }
return properties; return properties;

View File

@@ -28,12 +28,11 @@ internal sealed partial class SummaryFactory : ISummaryFactory
IOrderedEnumerable<AvatarView> avatars = avatarInfos IOrderedEnumerable<AvatarView> avatars = avatarInfos
.Where(a => !AvatarIds.IsPlayer(a.Info.AvatarId)) .Where(a => !AvatarIds.IsPlayer(a.Info.AvatarId))
.Select(a => SummaryAvatarFactory.Create(context, a)) .Select(a => SummaryAvatarFactory.Create(context, a))
.OrderByDescending(a => a.Quality) .OrderByDescending(a => a.LevelNumber)
.ThenByDescending(a => a.LevelNumber) .ThenByDescending(a => a.FetterLevel)
.ThenBy(a => a.Element) .ThenBy(a => a.Element);
.ThenBy(a => a.Weapon?.WeaponType)
.ThenByDescending(a => a.FetterLevel);
// TODO: thenby weapon type
return new() return new()
{ {
Avatars = [.. avatars], Avatars = [.. avatars],

View File

@@ -18,7 +18,6 @@ internal sealed class SummaryFactoryMetadataContext : IMetadataContext,
IMetadataDictionaryIdReliquaryAffixWeightSource, IMetadataDictionaryIdReliquaryAffixWeightSource,
IMetadataDictionaryIdReliquaryMainPropertySource, IMetadataDictionaryIdReliquaryMainPropertySource,
IMetadataDictionaryIdReliquarySubAffixSource, IMetadataDictionaryIdReliquarySubAffixSource,
IMetadataDictionaryIdReliquarySource,
IMetadataListReliquaryMainAffixLevelSource IMetadataListReliquaryMainAffixLevelSource
{ {
public Dictionary<AvatarId, MetadataAvatar> IdAvatarMap { get; set; } = default!; public Dictionary<AvatarId, MetadataAvatar> IdAvatarMap { get; set; } = default!;
@@ -33,5 +32,5 @@ internal sealed class SummaryFactoryMetadataContext : IMetadataContext,
public List<ReliquaryMainAffixLevel> ReliquaryMainAffixLevels { get; set; } = default!; public List<ReliquaryMainAffixLevel> ReliquaryMainAffixLevels { get; set; } = default!;
public Dictionary<ReliquaryId, MetadataReliquary> IdReliquaryMap { get; set; } = default!; public List<MetadataReliquary> Reliquaries { get; set; } = default!;
} }

View File

@@ -1,14 +1,21 @@
// 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.Core.ExceptionService;
using Snap.Hutao.Model.Primitive; using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Service.AvatarInfo.Factory; namespace Snap.Hutao.Service.AvatarInfo.Factory;
/// <summary>
/// 简述帮助类
/// </summary>
[HighQuality] [HighQuality]
internal static class SummaryHelper internal static class SummaryHelper
{ {
/// <summary>
/// 获取副属性对应的最大属性的Id
/// </summary>
/// <param name="appendId">属性Id</param>
/// <returns>最大属性Id</returns>
public static ReliquarySubAffixId GetAffixMaxId(in ReliquarySubAffixId appendId) public static ReliquarySubAffixId GetAffixMaxId(in ReliquarySubAffixId appendId)
{ {
// axxxxx -> a // axxxxx -> a
@@ -20,13 +27,18 @@ internal static class SummaryHelper
1 => 2, 1 => 2,
2 => 3, 2 => 3,
3 or 4 or 5 => 4, 3 or 4 or 5 => 4,
_ => throw HutaoException.Throw($"不支持的 ReliquarySubAffixId: {appendId}"), _ => throw Must.NeverHappen(),
}; };
// axxxxb -> axxxx -> axxxx0 -> axxxxm // axxxxb -> axxxx -> axxxx0 -> axxxxm
return ((appendId / 10) * 10) + max; return ((appendId / 10) * 10) + max;
} }
/// <summary>
/// 获取百分比属性副词条分数
/// </summary>
/// <param name="appendId">id</param>
/// <returns>分数</returns>
public static float GetPercentSubAffixScore(in ReliquarySubAffixId appendId) public static float GetPercentSubAffixScore(in ReliquarySubAffixId appendId)
{ {
// 圣遗物相同类型副词条强化档位一共为 4/3/2 档 // 圣遗物相同类型副词条强化档位一共为 4/3/2 档
@@ -53,7 +65,7 @@ internal static class SummaryHelper
(1, 0) => 100F, (1, 0) => 100F,
(1, 1) => 80F, (1, 1) => 80F,
_ => throw HutaoException.Throw($"Unexpected AppendId: {appendId} Delta: {delta}"), _ => throw Must.NeverHappen($"Unexpected AppendId: {appendId.Value} Delta: {delta}"),
}; };
} }
} }

View File

@@ -6,7 +6,6 @@ using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Converter; using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Model.Metadata.Reliquary; using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Primitive; using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.AvatarInfo.Factory.Builder;
using Snap.Hutao.ViewModel.AvatarProperty; using Snap.Hutao.ViewModel.AvatarProperty;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using MetadataReliquary = Snap.Hutao.Model.Metadata.Reliquary.Reliquary; using MetadataReliquary = Snap.Hutao.Model.Metadata.Reliquary.Reliquary;
@@ -14,6 +13,9 @@ using ModelAvatarInfo = Snap.Hutao.Web.Enka.Model.AvatarInfo;
namespace Snap.Hutao.Service.AvatarInfo.Factory; namespace Snap.Hutao.Service.AvatarInfo.Factory;
/// <summary>
/// 圣遗物工厂
/// </summary>
[HighQuality] [HighQuality]
internal sealed class SummaryReliquaryFactory internal sealed class SummaryReliquaryFactory
{ {
@@ -35,42 +37,47 @@ internal sealed class SummaryReliquaryFactory
public ReliquaryView Create() public ReliquaryView Create()
{ {
MetadataReliquary reliquary = metadataContext.IdReliquaryMap[equip.ItemId]; MetadataReliquary reliquary = metadataContext.Reliquaries.Single(r => r.Ids.Contains(equip.ItemId));
ArgumentNullException.ThrowIfNull(equip.Reliquary); ArgumentNullException.ThrowIfNull(equip.Reliquary);
List<ReliquarySubProperty> subProperties = equip.Reliquary.AppendPropIdList.EmptyIfNull().SelectList(CreateSubProperty); List<ReliquarySubProperty> subProperty = equip.Reliquary.AppendPropIdList.EmptyIfNull().SelectList(CreateSubProperty);
ReliquaryViewBuilder reliquaryViewBuilder = new ReliquaryViewBuilder()
.SetName(reliquary.Name)
.SetIcon(RelicIconConverter.IconNameToUri(reliquary.Icon))
.SetDescription(reliquary.Description)
.SetLevel($"+{equip.Reliquary.Level - 1U}")
.SetQuality(reliquary.RankLevel);
int affixCount = GetSecondaryAffixCount(reliquary, equip.Reliquary); int affixCount = GetSecondaryAffixCount(reliquary, equip.Reliquary);
if (subProperties.Count > 0) ReliquaryView result = new()
{ {
reliquaryViewBuilder // NameIconDescription
.SetPrimarySubProperties(subProperties.GetRange(..^affixCount)) Name = reliquary.Name,
.SetSecondarySubProperties(subProperties.GetRange(^affixCount..)) Icon = RelicIconConverter.IconNameToUri(reliquary.Icon),
.SetComposedSubProperties(CreateComposedSubProperties(equip.Reliquary.AppendPropIdList)); Description = reliquary.Description,
// EquipBase
Level = $"+{equip.Reliquary.Level - 1U}",
Quality = reliquary.RankLevel,
};
if (subProperty.Count > 0)
{
result.PrimarySubProperties = subProperty.GetRange(..^affixCount);
result.SecondarySubProperties = subProperty.GetRange(^affixCount..);
ArgumentNullException.ThrowIfNull(equip.Flat.ReliquarySubstats);
result.ComposedSubProperties = CreateComposedSubProperties(equip.Reliquary.AppendPropIdList);
ReliquaryMainAffixLevel relicLevel = metadataContext.ReliquaryMainAffixLevels.Single(r => r.Level == equip.Reliquary.Level && r.Rank == reliquary.RankLevel); ReliquaryMainAffixLevel relicLevel = metadataContext.ReliquaryMainAffixLevels.Single(r => r.Level == equip.Reliquary.Level && r.Rank == reliquary.RankLevel);
FightProperty property = metadataContext.IdReliquaryMainPropertyMap[equip.Reliquary.MainPropId]; FightProperty property = metadataContext.IdReliquaryMainPropertyMap[equip.Reliquary.MainPropId];
reliquaryViewBuilder result.MainProperty = FightPropertyFormat.ToNameValue(property, relicLevel.PropertyMap[property]);
.SetMainProperty(FightPropertyFormat.ToNameValue(property, relicLevel.PropertyMap[property])) result.Score = ScoreReliquary(property, reliquary, relicLevel, subProperty);
.SetScore(ScoreReliquary(property, reliquary, relicLevel, subProperties));
} }
return reliquaryViewBuilder.View; return result;
} }
private static int GetSecondaryAffixCount(MetadataReliquary metaReliquary, Web.Enka.Model.Reliquary enkaReliquary) private static int GetSecondaryAffixCount(MetadataReliquary reliquary, Web.Enka.Model.Reliquary enkaReliquary)
{ {
// 强化词条个数 // 强化词条个数
return (metaReliquary.RankLevel, enkaReliquary.Level.Value) switch return (reliquary.RankLevel, enkaReliquary.Level.Value) switch
{ {
(QualityType.QUALITY_ORANGE, > 20U) => 5, (QualityType.QUALITY_ORANGE, > 20U) => 5,
(QualityType.QUALITY_ORANGE, > 16U) => 4, (QualityType.QUALITY_ORANGE, > 16U) => 4,
@@ -111,8 +118,18 @@ internal sealed class SummaryReliquaryFactory
info.Value += subAffix.Value; info.Value += subAffix.Value;
} }
HutaoException.ThrowIf(infos.Count > 4, "无效的圣遗物数据"); if (infos.Count > 4)
return infos.SelectList(info => info.ToReliquaryComposedSubProperty()); {
ThrowHelper.InvalidOperation("无效的圣遗物数据");
}
List<ReliquaryComposedSubProperty> results = [];
foreach (ref readonly SummaryReliquarySubPropertyCompositionInfo info in CollectionsMarshal.AsSpan(infos))
{
results.Add(info.ToReliquaryComposedSubProperty());
}
return results;
} }
private float ScoreReliquary(FightProperty property, MetadataReliquary reliquary, ReliquaryMainAffixLevel relicLevel, List<ReliquarySubProperty> subProperties) private float ScoreReliquary(FightProperty property, MetadataReliquary reliquary, ReliquaryMainAffixLevel relicLevel, List<ReliquarySubProperty> subProperties)
@@ -148,42 +165,42 @@ internal sealed class SummaryReliquaryFactory
FightProperty property = affix.Type; FightProperty property = affix.Type;
return new(property, FightPropertyFormat.FormatValue(property, affix.Value), ScoreSubAffix(appendPropId)); return new(property, FightPropertyFormat.FormatValue(property, affix.Value), ScoreSubAffix(appendPropId));
}
float ScoreSubAffix(in ReliquarySubAffixId appendId) private float ScoreSubAffix(in ReliquarySubAffixId appendId)
{
ReliquarySubAffix affix = metadataContext.IdReliquarySubAffixMap[appendId];
ReliquaryAffixWeight affixWeight = metadataContext.IdReliquaryAffixWeightMap.GetValueOrDefault(avatarInfo.AvatarId, ReliquaryAffixWeight.Default);
float weight = affixWeight[affix.Type] / 100F;
// 数字词条,转换到等效百分比计算
if (affix.Type is FightProperty.FIGHT_PROP_HP or FightProperty.FIGHT_PROP_ATTACK or FightProperty.FIGHT_PROP_DEFENSE)
{ {
ReliquarySubAffix affix = metadataContext.IdReliquarySubAffixMap[appendId]; // 等效百分比 [ 当前小字词条 / 角色基本属性 ]
float equalPercent = affix.Value / avatarInfo.FightPropMap[affix.Type - 1];
ReliquaryAffixWeight affixWeight = metadataContext.IdReliquaryAffixWeightMap.GetValueOrDefault(avatarInfo.AvatarId, ReliquaryAffixWeight.Default); // 获取对应百分比词条权重
float weight = affixWeight[affix.Type] / 100F; weight = affixWeight[affix.Type + 1] / 100F;
// 数字词条,转换到等效百分比计算 // 最大同属性百分比Id
if (affix.Type is FightProperty.FIGHT_PROP_HP or FightProperty.FIGHT_PROP_ATTACK or FightProperty.FIGHT_PROP_DEFENSE) // 第四五位是战斗属性位
{ // 小字的加成词条在十位加一后即变换为百分比词条
// 等效百分比 [ 当前小字词条 / 角色基本属性 ] ReliquarySubAffixId maxPercentAffixId = SummaryHelper.GetAffixMaxId(appendId + 10U);
float equalPercent = affix.Value / avatarInfo.FightPropMap[affix.Type - 1];
// 获取对应百分比词条权重 // 最大同属性百分比数值
weight = affixWeight[affix.Type + 1] / 100F; ReliquarySubAffix maxPercentAffix = metadataContext.IdReliquarySubAffixMap[maxPercentAffixId];
Must.Argument(
maxPercentAffix.Type
is FightProperty.FIGHT_PROP_HP_PERCENT
or FightProperty.FIGHT_PROP_ATTACK_PERCENT
or FightProperty.FIGHT_PROP_DEFENSE_PERCENT,
"ReliquarySubAffix transform failed");
float equalScore = equalPercent / maxPercentAffix.Value;
// 最大同属性百分比Id return weight * equalScore * 100;
// 第四五位是战斗属性位
// 小字的加成词条在十位加一后即变换为百分比词条
ReliquarySubAffixId maxPercentAffixId = SummaryHelper.GetAffixMaxId(appendId + 10U);
// 最大同属性百分比数值
ReliquarySubAffix maxPercentAffix = metadataContext.IdReliquarySubAffixMap[maxPercentAffixId];
HutaoException.ThrowIfNot(
maxPercentAffix.Type
is FightProperty.FIGHT_PROP_HP_PERCENT
or FightProperty.FIGHT_PROP_ATTACK_PERCENT
or FightProperty.FIGHT_PROP_DEFENSE_PERCENT,
"ReliquarySubAffix transform failed");
float equalScore = equalPercent / maxPercentAffix.Value;
return weight * equalScore * 100;
}
return weight * SummaryHelper.GetPercentSubAffixScore(appendId);
} }
return weight * SummaryHelper.GetPercentSubAffixScore(appendId);
} }
} }

View File

@@ -1,18 +1,17 @@
// 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.Service.Abstraction;
using EntityAvatarInfo = Snap.Hutao.Model.Entity.AvatarInfo; using EntityAvatarInfo = Snap.Hutao.Model.Entity.AvatarInfo;
namespace Snap.Hutao.Service.AvatarInfo; namespace Snap.Hutao.Service.AvatarInfo;
internal interface IAvatarInfoDbService : IAppDbService<EntityAvatarInfo> internal interface IAvatarInfoDbService
{ {
void RemoveAvatarInfoRangeByUid(string uid); void RemoveAvatarInfoRangeByUid(string uid);
List<EntityAvatarInfo> GetAvatarInfoListByUid(string uid); List<EntityAvatarInfo> GetAvatarInfoListByUid(string uid);
ValueTask<List<EntityAvatarInfo>> GetAvatarInfoListByUidAsync(string uid, CancellationToken token = default); ValueTask<List<EntityAvatarInfo>> GetAvatarInfoListByUidAsync(string uid);
ValueTask RemoveAvatarInfoRangeByUidAsync(string uid, CancellationToken token = default); ValueTask RemoveAvatarInfoRangeByUidAsync(string uid);
} }

View File

@@ -14,7 +14,7 @@ namespace Snap.Hutao.Service.AvatarInfo.Transformer;
internal sealed class CalculateAvatarDetailAvatarInfoTransformer : IAvatarInfoTransformer<AvatarDetail> internal sealed class CalculateAvatarDetailAvatarInfoTransformer : IAvatarInfoTransformer<AvatarDetail>
{ {
/// <inheritdoc/> /// <inheritdoc/>
public void Transform(ref readonly Web.Enka.Model.AvatarInfo avatarInfo, AvatarDetail source) public void Transform(ref Web.Enka.Model.AvatarInfo avatarInfo, AvatarDetail source)
{ {
// update skills // update skills
avatarInfo.SkillLevelMap = source.SkillList.ToDictionary(s => (SkillId)s.Id, s => (SkillLevel)s.LevelCurrent); avatarInfo.SkillLevelMap = source.SkillList.ToDictionary(s => (SkillId)s.Id, s => (SkillLevel)s.LevelCurrent);

View File

@@ -15,7 +15,7 @@ namespace Snap.Hutao.Service.AvatarInfo.Transformer;
internal sealed class GameRecordCharacterAvatarInfoTransformer : IAvatarInfoTransformer<Character> internal sealed class GameRecordCharacterAvatarInfoTransformer : IAvatarInfoTransformer<Character>
{ {
/// <inheritdoc/> /// <inheritdoc/>
public void Transform(ref readonly Web.Enka.Model.AvatarInfo avatarInfo, Character source) public void Transform(ref Web.Enka.Model.AvatarInfo avatarInfo, Character source)
{ {
// update fetter // update fetter
avatarInfo.FetterInfo ??= new(); avatarInfo.FetterInfo ??= new();

View File

@@ -15,5 +15,5 @@ internal interface IAvatarInfoTransformer<in TSource>
/// </summary> /// </summary>
/// <param name="avatarInfo">基底角色Id必定存在</param> /// <param name="avatarInfo">基底角色Id必定存在</param>
/// <param name="source">源</param> /// <param name="source">源</param>
void Transform(ref readonly Web.Enka.Model.AvatarInfo avatarInfo, TSource source); void Transform(ref Web.Enka.Model.AvatarInfo avatarInfo, TSource source);
} }

View File

@@ -17,7 +17,7 @@ namespace Snap.Hutao.Service.Hutao;
internal sealed partial class HutaoAsAService : IHutaoAsAService internal sealed partial class HutaoAsAService : IHutaoAsAService
{ {
private const int AnnouncementDuration = 30; private const int AnnouncementDuration = 30;
private readonly IServiceScopeFactory serviceScopeFactory; private readonly HutaoAsAServiceClient hutaoAsServiceClient;
private ObservableCollection<HutaoAnnouncement>? announcements; private ObservableCollection<HutaoAnnouncement>? announcements;
@@ -29,13 +29,7 @@ internal sealed partial class HutaoAsAService : IHutaoAsAService
ApplicationDataCompositeValue excludedIds = LocalSetting.Get(SettingKeys.ExcludedAnnouncementIds, new ApplicationDataCompositeValue()); ApplicationDataCompositeValue excludedIds = LocalSetting.Get(SettingKeys.ExcludedAnnouncementIds, new ApplicationDataCompositeValue());
List<long> data = excludedIds.Select(kvp => long.Parse(kvp.Key, CultureInfo.InvariantCulture)).ToList(); List<long> data = excludedIds.Select(kvp => long.Parse(kvp.Key, CultureInfo.InvariantCulture)).ToList();
Response<List<HutaoAnnouncement>> response = await hutaoAsServiceClient.GetAnnouncementListAsync(data, token).ConfigureAwait(false);
Response<List<HutaoAnnouncement>> response;
using (IServiceScope scope = serviceScopeFactory.CreateScope())
{
HutaoAsAServiceClient hutaoAsAServiceClient = scope.ServiceProvider.GetRequiredService<HutaoAsAServiceClient>();
response = await hutaoAsAServiceClient.GetAnnouncementListAsync(data, token).ConfigureAwait(false);
}
if (response.IsOk()) if (response.IsOk())
{ {

View File

@@ -15,7 +15,7 @@ internal sealed partial class HutaoUserService : IHutaoUserService, IHutaoUserSe
{ {
private readonly TaskCompletionSource initializeCompletionSource = new(); private readonly TaskCompletionSource initializeCompletionSource = new();
private readonly IServiceScopeFactory serviceScopeFactory; private readonly HutaoPassportClient passportClient;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
private readonly HutaoUserOptions options; private readonly HutaoUserOptions options;
@@ -40,25 +40,20 @@ internal sealed partial class HutaoUserService : IHutaoUserService, IHutaoUserSe
} }
else else
{ {
using (IServiceScope scope = serviceScopeFactory.CreateScope()) Web.Response.Response<string> response = await passportClient.LoginAsync(userName, password, token).ConfigureAwait(false);
if (response.IsOk())
{ {
HutaoPassportClient hutaoPassportClient = scope.ServiceProvider.GetRequiredService<HutaoPassportClient>(); if (await options.PostLoginSucceedAsync(passportClient, taskContext, userName, password, response.Data).ConfigureAwait(false))
Web.Response.Response<string> response = await hutaoPassportClient.LoginAsync(userName, password, token).ConfigureAwait(false);
if (response.IsOk())
{ {
if (await options.PostLoginSucceedAsync(hutaoPassportClient, taskContext, userName, password, response.Data).ConfigureAwait(false)) isInitialized = true;
{
isInitialized = true;
}
}
else
{
await taskContext.SwitchToMainThreadAsync();
options.PostLoginFailed();
} }
} }
else
{
await taskContext.SwitchToMainThreadAsync();
options.PostLoginFailed();
}
} }
initializeCompletionSource.TrySetResult(); initializeCompletionSource.TrySetResult();

View File

@@ -1,12 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataDictionaryIdReliquarySource
{
public Dictionary<ReliquaryId, Reliquary> IdReliquaryMap { get; set; }
}

View File

@@ -62,11 +62,6 @@ internal static class MetadataServiceContextExtension
dictionaryIdMaterialSource.IdMaterialMap = await metadataService.GetIdToMaterialMapAsync(token).ConfigureAwait(false); dictionaryIdMaterialSource.IdMaterialMap = await metadataService.GetIdToMaterialMapAsync(token).ConfigureAwait(false);
} }
if (context is IMetadataDictionaryIdReliquarySource dictionaryIdReliquarySource)
{
dictionaryIdReliquarySource.IdReliquaryMap = await metadataService.GetIdToReliquaryMapAsync(token).ConfigureAwait(false);
}
if (context is IMetadataDictionaryIdReliquaryAffixWeightSource dictionaryIdReliquaryAffixWeightSource) if (context is IMetadataDictionaryIdReliquaryAffixWeightSource dictionaryIdReliquaryAffixWeightSource)
{ {
dictionaryIdReliquaryAffixWeightSource.IdReliquaryAffixWeightMap = await metadataService.GetIdToReliquaryAffixWeightMapAsync(token).ConfigureAwait(false); dictionaryIdReliquaryAffixWeightSource.IdReliquaryAffixWeightMap = await metadataService.GetIdToReliquaryAffixWeightMapAsync(token).ConfigureAwait(false);

View File

@@ -6,7 +6,6 @@ using Snap.Hutao.Core;
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Core.Diagnostics; using Snap.Hutao.Core.Diagnostics;
using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Core.IO;
using Snap.Hutao.Core.IO.Hashing; using Snap.Hutao.Core.IO.Hashing;
using Snap.Hutao.Core.Setting; using Snap.Hutao.Core.Setting;
using Snap.Hutao.Service.Notification; using Snap.Hutao.Service.Notification;
@@ -30,12 +29,12 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
private readonly TaskCompletionSource initializeCompletionSource = new(); private readonly TaskCompletionSource initializeCompletionSource = new();
private readonly IServiceScopeFactory serviceScopeFactory;
private readonly ILogger<MetadataService> logger; private readonly ILogger<MetadataService> logger;
private readonly MetadataOptions metadataOptions; private readonly MetadataOptions metadataOptions;
private readonly IInfoBarService infoBarService; private readonly IInfoBarService infoBarService;
private readonly JsonSerializerOptions options; private readonly JsonSerializerOptions options;
private readonly IMemoryCache memoryCache; private readonly IMemoryCache memoryCache;
private readonly HttpClient httpClient;
private bool isInitialized; private bool isInitialized;
@@ -86,7 +85,7 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
else else
{ {
FileNotFoundException exception = new(SH.ServiceMetadataFileNotFound, fileName); FileNotFoundException exception = new(SH.ServiceMetadataFileNotFound, fileName);
throw HutaoException.Throw(SH.ServiceMetadataFileNotFound, exception); throw ThrowHelper.UserdataCorrupted(SH.ServiceMetadataFileNotFound, exception);
} }
} }
@@ -120,17 +119,10 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
Dictionary<string, string>? metadataFileHashs; Dictionary<string, string>? metadataFileHashs;
try try
{ {
using (IServiceScope scope = serviceScopeFactory.CreateScope()) // download meta check file
{ metadataFileHashs = await httpClient
IHttpClientFactory httpClientFactory = scope.ServiceProvider.GetRequiredService<IHttpClientFactory>(); .GetFromJsonAsync<Dictionary<string, string>>(metadataOptions.GetLocalizedRemoteFile(MetaFileName), options, token)
using (HttpClient httpClient = httpClientFactory.CreateClient(nameof(MetadataService))) .ConfigureAwait(false);
{
// Download meta check file
metadataFileHashs = await httpClient
.GetFromJsonAsync<Dictionary<string, string>>(metadataOptions.GetLocalizedRemoteFile(MetaFileName), options, token)
.ConfigureAwait(false);
}
}
if (metadataFileHashs is null) if (metadataFileHashs is null)
{ {
@@ -184,28 +176,23 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
private async ValueTask DownloadMetadataSourceFilesAsync(string fileFullName, CancellationToken token) private async ValueTask DownloadMetadataSourceFilesAsync(string fileFullName, CancellationToken token)
{ {
Stream sourceStream; Stream sourceStream = await httpClient
using (IServiceScope scope = serviceScopeFactory.CreateScope()) .GetStreamAsync(metadataOptions.GetLocalizedRemoteFile(fileFullName), token)
{ .ConfigureAwait(false);
IHttpClientFactory httpClientFactory = scope.ServiceProvider.GetRequiredService<IHttpClientFactory>();
using (HttpClient httpClient = httpClientFactory.CreateClient(nameof(MetadataService)))
{
sourceStream = await httpClient
.GetStreamAsync(metadataOptions.GetLocalizedRemoteFile(fileFullName), token)
.ConfigureAwait(false);
}
}
// Write stream while convert LF to CRLF // Write stream while convert LF to CRLF
using (StreamReaderWriter readerWriter = new(new(sourceStream), File.CreateText(metadataOptions.GetLocalizedLocalFile(fileFullName)))) using (StreamReader streamReader = new(sourceStream))
{ {
while (await readerWriter.ReadLineAsync(token).ConfigureAwait(false) is { } line) using (StreamWriter streamWriter = File.CreateText(metadataOptions.GetLocalizedLocalFile(fileFullName)))
{ {
await readerWriter.WriteAsync(line).ConfigureAwait(false); while (await streamReader.ReadLineAsync(token).ConfigureAwait(false) is { } line)
if (!readerWriter.Reader.EndOfStream)
{ {
await readerWriter.WriteAsync(StringLiterals.CRLF).ConfigureAwait(false); await streamWriter.WriteAsync(line).ConfigureAwait(false);
if (!streamReader.EndOfStream)
{
await streamWriter.WriteAsync(StringLiterals.CRLF).ConfigureAwait(false);
}
} }
} }
} }

View File

@@ -25,12 +25,22 @@ internal static class MetadataServiceDictionaryExtension
public static ValueTask<Dictionary<ExtendedEquipAffixId, ReliquarySet>> GetExtendedEquipAffixIdToReliquarySetMapAsync(this IMetadataService metadataService, CancellationToken token = default) public static ValueTask<Dictionary<ExtendedEquipAffixId, ReliquarySet>> GetExtendedEquipAffixIdToReliquarySetMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{ {
return metadataService.FromCacheAsDictionaryAsync(FileNameReliquarySet, (List<ReliquarySet> list) => list.SelectMany(set => set.EquipAffixIds, (set, id) => (Id: id, Set: set)), token); return metadataService.FromCacheAsDictionaryAsync<ReliquarySet, (ExtendedEquipAffixId Id, ReliquarySet Set), ExtendedEquipAffixId, ReliquarySet>(
FileNameReliquarySet,
list => list.SelectMany(set => set.EquipAffixIds.Select(id => (Id: id, Set: set))),
tuple => tuple.Id,
tuple => tuple.Set,
token);
} }
public static ValueTask<Dictionary<TowerLevelGroupId, List<TowerLevel>>> GetGroupIdToTowerLevelGroupMapAsync(this IMetadataService metadataService, CancellationToken token = default) public static ValueTask<Dictionary<TowerLevelGroupId, List<TowerLevel>>> GetGroupIdToTowerLevelGroupMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{ {
return metadataService.FromCacheAsDictionaryAsync(FileNameTowerLevel, (List<TowerLevel> list) => list.GroupBy(l => l.GroupId), g => g.Key, g => g.ToList(), token); return metadataService.FromCacheAsDictionaryAsync<TowerLevel, IGrouping<TowerLevelGroupId, TowerLevel>, TowerLevelGroupId, List<TowerLevel>>(
FileNameTowerLevel,
list => list.GroupBy(l => l.GroupId),
g => g.Key,
g => g.ToList(),
token);
} }
public static ValueTask<Dictionary<AchievementId, Model.Metadata.Achievement.Achievement>> GetIdToAchievementMapAsync(this IMetadataService metadataService, CancellationToken token = default) public static ValueTask<Dictionary<AchievementId, Model.Metadata.Achievement.Achievement>> GetIdToAchievementMapAsync(this IMetadataService metadataService, CancellationToken token = default)
@@ -71,11 +81,6 @@ internal static class MetadataServiceDictionaryExtension
return metadataService.FromCacheAsDictionaryAsync<MaterialId, Material>(FileNameMaterial, a => a.Id, token); return metadataService.FromCacheAsDictionaryAsync<MaterialId, Material>(FileNameMaterial, a => a.Id, token);
} }
public static ValueTask<Dictionary<ReliquaryId, Reliquary>> GetIdToReliquaryMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheAsDictionaryAsync(FileNameReliquary, (List<Reliquary> list) => list.SelectMany(r => r.Ids, (r, i) => (Index: i, Reliquary: r)), token);
}
public static ValueTask<Dictionary<AvatarId, ReliquaryAffixWeight>> GetIdToReliquaryAffixWeightMapAsync(this IMetadataService metadataService, CancellationToken token = default) public static ValueTask<Dictionary<AvatarId, ReliquaryAffixWeight>> GetIdToReliquaryAffixWeightMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{ {
return metadataService.FromCacheAsDictionaryAsync<AvatarId, ReliquaryAffixWeight>(FileNameReliquaryAffixWeight, r => r.AvatarId, token); return metadataService.FromCacheAsDictionaryAsync<AvatarId, ReliquaryAffixWeight>(FileNameReliquaryAffixWeight, r => r.AvatarId, token);
@@ -172,12 +177,6 @@ internal static class MetadataServiceDictionaryExtension
return metadataService.MemoryCache.Set(cacheKey, dict); return metadataService.MemoryCache.Set(cacheKey, dict);
} }
private static ValueTask<Dictionary<TKey, TValue>> FromCacheAsDictionaryAsync<TData, TKey, TValue>(this IMetadataService metadataService, string fileName, Func<List<TData>, IEnumerable<(TKey Key, TValue Value)>> listSelector, CancellationToken token)
where TKey : notnull
{
return FromCacheAsDictionaryAsync(metadataService, fileName, listSelector, kvp => kvp.Key, kvp => kvp.Value, token);
}
private static async ValueTask<Dictionary<TKey, TValue>> FromCacheAsDictionaryAsync<TData, TMiddle, TKey, TValue>(this IMetadataService metadataService, string fileName, Func<List<TData>, IEnumerable<TMiddle>> listSelector, Func<TMiddle, TKey> keySelector, Func<TMiddle, TValue> valueSelector, CancellationToken token) private static async ValueTask<Dictionary<TKey, TValue>> FromCacheAsDictionaryAsync<TData, TMiddle, TKey, TValue>(this IMetadataService metadataService, string fileName, Func<List<TData>, IEnumerable<TMiddle>> listSelector, Func<TMiddle, TKey> keySelector, Func<TMiddle, TValue> valueSelector, CancellationToken token)
where TKey : notnull where TKey : notnull
{ {

View File

@@ -12,7 +12,7 @@ namespace Snap.Hutao.Service.User;
[Injection(InjectAs.Singleton, typeof(IUserFingerprintService))] [Injection(InjectAs.Singleton, typeof(IUserFingerprintService))]
internal sealed partial class UserFingerprintService : IUserFingerprintService internal sealed partial class UserFingerprintService : IUserFingerprintService
{ {
private readonly IServiceScopeFactory serviceScopeFactory; private readonly DeviceFpClient deviceFpClient;
public async ValueTask TryInitializeAsync(ViewModel.User.User user, CancellationToken token = default) public async ValueTask TryInitializeAsync(ViewModel.User.User user, CancellationToken token = default)
{ {
@@ -101,13 +101,7 @@ internal sealed partial class UserFingerprintService : IUserFingerprintService
DeviceFp = string.IsNullOrEmpty(user.Fingerprint) ? Core.Random.GetLowerHexString(13) : user.Fingerprint, DeviceFp = string.IsNullOrEmpty(user.Fingerprint) ? Core.Random.GetLowerHexString(13) : user.Fingerprint,
}; };
Response<DeviceFpWrapper> response; Response<DeviceFpWrapper> response = await deviceFpClient.GetFingerprintAsync(data, token).ConfigureAwait(false);
using (IServiceScope scope = serviceScopeFactory.CreateScope())
{
DeviceFpClient deviceFpClient = scope.ServiceProvider.GetRequiredService<DeviceFpClient>();
response = await deviceFpClient.GetFingerprintAsync(data, token).ConfigureAwait(false);
}
user.TryUpdateFingerprint(response.IsOk() ? response.Data.DeviceFp : string.Empty); user.TryUpdateFingerprint(response.IsOk() ? response.Data.DeviceFp : string.Empty);
user.NeedDbUpdateAfterResume = true; user.NeedDbUpdateAfterResume = true;

View File

@@ -181,7 +181,6 @@
<None Remove="View\Dialog\LaunchGameConfigurationFixDialog.xaml" /> <None Remove="View\Dialog\LaunchGameConfigurationFixDialog.xaml" />
<None Remove="View\Dialog\LaunchGamePackageConvertDialog.xaml" /> <None Remove="View\Dialog\LaunchGamePackageConvertDialog.xaml" />
<None Remove="View\Dialog\ReconfirmDialog.xaml" /> <None Remove="View\Dialog\ReconfirmDialog.xaml" />
<None Remove="View\Dialog\UpdatePackageDownloadConfirmDialog.xaml" />
<None Remove="View\Dialog\UserDialog.xaml" /> <None Remove="View\Dialog\UserDialog.xaml" />
<None Remove="View\Dialog\UserQRCodeDialog.xaml" /> <None Remove="View\Dialog\UserQRCodeDialog.xaml" />
<None Remove="View\Guide\GuideView.xaml" /> <None Remove="View\Guide\GuideView.xaml" />
@@ -329,7 +328,7 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Snap.Hutao.Elevated.Launcher.Runtime" Version="1.1.1"> <PackageReference Include="Snap.Hutao.Elevated.Launcher.Runtime" Version="1.1.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference> </PackageReference>
@@ -354,11 +353,6 @@
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnablePreviewMsixTooling)'=='true'"> <ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnablePreviewMsixTooling)'=='true'">
<ProjectCapability Include="Msix" /> <ProjectCapability Include="Msix" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Page Update="View\Dialog\UpdatePackageDownloadConfirmDialog.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup> <ItemGroup>
<Page Update="View\Dialog\LaunchGameConfigurationFixDialog.xaml"> <Page Update="View\Dialog\LaunchGameConfigurationFixDialog.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>

View File

@@ -28,8 +28,8 @@
<cwc:Segmented <cwc:Segmented
x:Name="SkillSelectorSegmented" x:Name="SkillSelectorSegmented"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
ItemTemplate="{StaticResource SkillHeaderTemplate}" SelectionChanged="OnSkillSelectorSegmentedSelectionChanged"
SelectionChanged="OnSkillSelectorSegmentedSelectionChanged"/> ItemTemplate="{StaticResource SkillHeaderTemplate}"/>
<ContentPresenter Content="{x:Bind Selected, Mode=OneWay}" ContentTemplate="{x:Bind ItemTemplate}"/> <ContentPresenter Content="{x:Bind Selected, Mode=OneWay}" ContentTemplate="{x:Bind ItemTemplate}"/>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

View File

@@ -19,8 +19,6 @@
PlaceholderText="{shcm:ResourceString Name=ViewPageHutaoPassportUserNameHint}" PlaceholderText="{shcm:ResourceString Name=ViewPageHutaoPassportUserNameHint}"
Text="{x:Bind UserName, Mode=TwoWay}"/> Text="{x:Bind UserName, Mode=TwoWay}"/>
<PasswordBox <PasswordBox
Width="360"
MaxWidth="360"
Margin="0,16,0,0" Margin="0,16,0,0"
Password="{x:Bind Password, Mode=TwoWay}" Password="{x:Bind Password, Mode=TwoWay}"
PlaceholderText="{shcm:ResourceString Name=ViewPageHutaoPassportPasswordHint}"/> PlaceholderText="{shcm:ResourceString Name=ViewPageHutaoPassportPasswordHint}"/>

View File

@@ -31,8 +31,6 @@
Content="{shcm:ResourceString Name=ViewPageHutaoPassportVerifyCodeAction}"/> Content="{shcm:ResourceString Name=ViewPageHutaoPassportVerifyCodeAction}"/>
</Grid> </Grid>
<PasswordBox <PasswordBox
Width="360"
MaxWidth="360"
Margin="0,16,0,0" Margin="0,16,0,0"
IsEnabled="{x:Bind VerifyCode, Converter={StaticResource StringBoolConverter}, Mode=OneWay}" IsEnabled="{x:Bind VerifyCode, Converter={StaticResource StringBoolConverter}, Mode=OneWay}"
Password="{x:Bind Password, Mode=TwoWay}" Password="{x:Bind Password, Mode=TwoWay}"

View File

@@ -14,7 +14,7 @@ namespace Snap.Hutao.View.Dialog;
[DependencyProperty("VerifyCode", typeof(string))] [DependencyProperty("VerifyCode", typeof(string))]
internal sealed partial class HutaoPassportRegisterDialog : ContentDialog internal sealed partial class HutaoPassportRegisterDialog : ContentDialog
{ {
private readonly IServiceScopeFactory serviceScopeFactory; private readonly HutaoPassportClient homaPassportClient;
private readonly IInfoBarService infoBarService; private readonly IInfoBarService infoBarService;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
@@ -23,7 +23,7 @@ internal sealed partial class HutaoPassportRegisterDialog : ContentDialog
InitializeComponent(); InitializeComponent();
taskContext = serviceProvider.GetRequiredService<ITaskContext>(); taskContext = serviceProvider.GetRequiredService<ITaskContext>();
serviceScopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>(); homaPassportClient = serviceProvider.GetRequiredService<HutaoPassportClient>();
infoBarService = serviceProvider.GetRequiredService<IInfoBarService>(); infoBarService = serviceProvider.GetRequiredService<IInfoBarService>();
} }
@@ -49,12 +49,7 @@ internal sealed partial class HutaoPassportRegisterDialog : ContentDialog
return; return;
} }
using (IServiceScope scope = serviceScopeFactory.CreateScope()) HutaoResponse response = await homaPassportClient.RequestVerifyAsync(UserName, VerifyCodeRequestType.Registration).ConfigureAwait(false);
{ infoBarService.Information(response.GetLocalizationMessage());
HutaoPassportClient hutaoPassportClient = scope.ServiceProvider.GetRequiredService<HutaoPassportClient>();
HutaoResponse response = await hutaoPassportClient.RequestVerifyAsync(UserName, VerifyCodeRequestType.Registration).ConfigureAwait(false);
infoBarService.Information(response.GetLocalizationMessage());
}
} }
} }

View File

@@ -31,8 +31,6 @@
Content="{shcm:ResourceString Name=ViewPageHutaoPassportVerifyCodeAction}"/> Content="{shcm:ResourceString Name=ViewPageHutaoPassportVerifyCodeAction}"/>
</Grid> </Grid>
<PasswordBox <PasswordBox
Width="360"
MaxWidth="360"
Margin="0,16,0,0" Margin="0,16,0,0"
IsEnabled="{x:Bind VerifyCode, Converter={StaticResource StringBoolConverter}, Mode=OneWay}" IsEnabled="{x:Bind VerifyCode, Converter={StaticResource StringBoolConverter}, Mode=OneWay}"
Password="{x:Bind Password, Mode=TwoWay}" Password="{x:Bind Password, Mode=TwoWay}"

View File

@@ -14,7 +14,7 @@ namespace Snap.Hutao.View.Dialog;
[DependencyProperty("VerifyCode", typeof(string))] [DependencyProperty("VerifyCode", typeof(string))]
internal sealed partial class HutaoPassportResetPasswordDialog : ContentDialog internal sealed partial class HutaoPassportResetPasswordDialog : ContentDialog
{ {
private readonly IServiceScopeFactory serviceScopeFactory; private readonly HutaoPassportClient homaPassportClient;
private readonly IInfoBarService infoBarService; private readonly IInfoBarService infoBarService;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
@@ -23,7 +23,7 @@ internal sealed partial class HutaoPassportResetPasswordDialog : ContentDialog
InitializeComponent(); InitializeComponent();
taskContext = serviceProvider.GetRequiredService<ITaskContext>(); taskContext = serviceProvider.GetRequiredService<ITaskContext>();
serviceScopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>(); homaPassportClient = serviceProvider.GetRequiredService<HutaoPassportClient>();
infoBarService = serviceProvider.GetRequiredService<IInfoBarService>(); infoBarService = serviceProvider.GetRequiredService<IInfoBarService>();
} }
@@ -49,12 +49,7 @@ internal sealed partial class HutaoPassportResetPasswordDialog : ContentDialog
return; return;
} }
using (IServiceScope scope = serviceScopeFactory.CreateScope()) HutaoResponse response = await homaPassportClient.RequestVerifyAsync(UserName, VerifyCodeRequestType.ResetPassword).ConfigureAwait(false);
{ infoBarService.Information(response.GetLocalizationMessage());
HutaoPassportClient hutaoPassportClient = scope.ServiceProvider.GetRequiredService<HutaoPassportClient>();
HutaoResponse response = await hutaoPassportClient.RequestVerifyAsync(UserName, VerifyCodeRequestType.ResetPassword).ConfigureAwait(false);
infoBarService.Information(response.GetLocalizationMessage());
}
} }
} }

View File

@@ -37,8 +37,6 @@
Content="{shcm:ResourceString Name=ViewPageHutaoPassportVerifyCodeAction}"/> Content="{shcm:ResourceString Name=ViewPageHutaoPassportVerifyCodeAction}"/>
</Grid> </Grid>
<PasswordBox <PasswordBox
Width="360"
MaxWidth="360"
Margin="0,16,0,0" Margin="0,16,0,0"
IsEnabled="{x:Bind VerifyCode, Converter={StaticResource StringBoolConverter}, Mode=OneWay}" IsEnabled="{x:Bind VerifyCode, Converter={StaticResource StringBoolConverter}, Mode=OneWay}"
Password="{x:Bind Password, Mode=TwoWay}" Password="{x:Bind Password, Mode=TwoWay}"

View File

@@ -14,7 +14,7 @@ namespace Snap.Hutao.View.Dialog;
[DependencyProperty("VerifyCode", typeof(string))] [DependencyProperty("VerifyCode", typeof(string))]
internal sealed partial class HutaoPassportUnregisterDialog : ContentDialog internal sealed partial class HutaoPassportUnregisterDialog : ContentDialog
{ {
private readonly IServiceScopeFactory serviceScopeFactory; private readonly HutaoPassportClient homaPassportClient;
private readonly IInfoBarService infoBarService; private readonly IInfoBarService infoBarService;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
@@ -23,7 +23,7 @@ internal sealed partial class HutaoPassportUnregisterDialog : ContentDialog
InitializeComponent(); InitializeComponent();
taskContext = serviceProvider.GetRequiredService<ITaskContext>(); taskContext = serviceProvider.GetRequiredService<ITaskContext>();
serviceScopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>(); homaPassportClient = serviceProvider.GetRequiredService<HutaoPassportClient>();
infoBarService = serviceProvider.GetRequiredService<IInfoBarService>(); infoBarService = serviceProvider.GetRequiredService<IInfoBarService>();
} }
@@ -49,12 +49,7 @@ internal sealed partial class HutaoPassportUnregisterDialog : ContentDialog
return; return;
} }
using (IServiceScope scope = serviceScopeFactory.CreateScope()) HutaoResponse response = await homaPassportClient.RequestVerifyAsync(UserName, VerifyCodeRequestType.CancelRegistration).ConfigureAwait(false);
{ infoBarService.Information(response.GetLocalizationMessage());
HutaoPassportClient hutaoPassportClient = scope.ServiceProvider.GetRequiredService<HutaoPassportClient>();
HutaoResponse response = await hutaoPassportClient.RequestVerifyAsync(UserName, VerifyCodeRequestType.CancelRegistration).ConfigureAwait(false);
infoBarService.Information(response.GetLocalizationMessage());
}
} }
} }

View File

@@ -1,24 +0,0 @@
<ContentDialog
x:Class="Snap.Hutao.View.Dialog.UpdatePackageDownloadConfirmDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
CloseButtonText="{shcm:ResourceString Name=ContentDialogCancelCloseButtonText}"
DefaultButton="Primary"
PrimaryButtonText="{shcm:ResourceString Name=ContentDialogConfirmPrimaryButtonText}"
Style="{ThemeResource DefaultContentDialogStyle}"
mc:Ignorable="d">
<StackPanel Spacing="16">
<TextBlock Text="">
<Run Text="{shcm:ResourceString Name=ViewTitileUpdatePackageDownloadContent}"/>
<Hyperlink NavigateUri="https://hut.ao/statements/update-log.html">
<Run Text="{shcm:ResourceString Name=ViewDialogUpdatePackageDownloadUpdatelogLinkContent}"/>
</Hyperlink>
<!-- We leave a Run here to prevent the Hyperlink Stretch -->
<Run/>
</TextBlock>
</StackPanel>
</ContentDialog>

View File

@@ -1,14 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.View.Dialog;
internal sealed partial class UpdatePackageDownloadConfirmDialog : ContentDialog
{
public UpdatePackageDownloadConfirmDialog()
{
InitializeComponent();
}
}

View File

@@ -229,7 +229,7 @@
DisplayMemberPath="Name" DisplayMemberPath="Name"
ItemsSource="{Binding StaticResourceOptions.ImageArchives}" ItemsSource="{Binding StaticResourceOptions.ImageArchives}"
SelectedItem="{Binding StaticResourceOptions.ImageArchive, Mode=TwoWay}"/> SelectedItem="{Binding StaticResourceOptions.ImageArchive, Mode=TwoWay}"/>
<TextBlock Margin="0,16,0,0" Text="{Binding StaticResourceOptions.SizeInformationText, Mode=OneWay}"/> <TextBlock Margin="0,16,0,0" Text="{Binding StaticResourceOptions.SizeInformationText, Mode=OneWay}"/>
</StackPanel> </StackPanel>

View File

@@ -735,7 +735,7 @@
HorizontalAlignment="Right" HorizontalAlignment="Right"
Foreground="#FFFFFFFF" Foreground="#FFFFFFFF"
Style="{StaticResource TitleTextBlockStyle}" Style="{StaticResource TitleTextBlockStyle}"
Text="{Binding SelectedAvatar.ScoreFormatted}"/> Text="{Binding SelectedAvatar.Score}"/>
<TextBlock <TextBlock
HorizontalAlignment="Right" HorizontalAlignment="Right"
Foreground="#FFFFFFFF" Foreground="#FFFFFFFF"
@@ -747,7 +747,7 @@
HorizontalAlignment="Right" HorizontalAlignment="Right"
Foreground="#FFFFFFFF" Foreground="#FFFFFFFF"
Style="{StaticResource TitleTextBlockStyle}" Style="{StaticResource TitleTextBlockStyle}"
Text="{Binding SelectedAvatar.CritScoreFormatted}"/> Text="{Binding SelectedAvatar.CritScore}"/>
<TextBlock <TextBlock
HorizontalAlignment="Right" HorizontalAlignment="Right"
Foreground="#FFFFFFFF" Foreground="#FFFFFFFF"

View File

@@ -5,7 +5,6 @@ using Snap.Hutao.Model;
using Snap.Hutao.Model.Calculable; using Snap.Hutao.Model.Calculable;
using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive; using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.AvatarInfo.Factory.Builder;
namespace Snap.Hutao.ViewModel.AvatarProperty; namespace Snap.Hutao.ViewModel.AvatarProperty;
@@ -82,12 +81,12 @@ internal sealed class AvatarView : INameIconSide, ICalculableSource<ICalculableA
/// <summary> /// <summary>
/// 评分 /// 评分
/// </summary> /// </summary>
public string ScoreFormatted { get => $"{Score:F2}"; } public string Score { get; set; } = default!;
/// <summary> /// <summary>
/// 双爆评分 /// 双爆评分
/// </summary> /// </summary>
public string CritScoreFormatted { get => $"{CritScore:F2}"; } public string CritScore { get; set; } = default!;
/// <summary> /// <summary>
/// 好感度等级 /// 好感度等级
@@ -110,10 +109,6 @@ internal sealed class AvatarView : INameIconSide, ICalculableSource<ICalculableA
/// </summary> /// </summary>
internal uint LevelNumber { get; set; } internal uint LevelNumber { get; set; }
internal float Score { get; set; }
internal float CritScore { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public ICalculableAvatar ToCalculable() public ICalculableAvatar ToCalculable()
{ {

View File

@@ -10,7 +10,7 @@ namespace Snap.Hutao.ViewModel.AvatarProperty;
/// 装备基类 /// 装备基类
/// </summary> /// </summary>
[HighQuality] [HighQuality]
internal abstract class EquipView : NameIconDescription internal abstract class Equip : NameIconDescription
{ {
/// <summary> /// <summary>
/// 等级 /// 等级

View File

@@ -7,7 +7,7 @@ namespace Snap.Hutao.ViewModel.AvatarProperty;
/// 圣遗物 /// 圣遗物
/// </summary> /// </summary>
[HighQuality] [HighQuality]
internal sealed class ReliquaryView : EquipView internal sealed class ReliquaryView : Equip
{ {
/// <summary> /// <summary>
/// 初始词条 /// 初始词条

View File

@@ -3,7 +3,6 @@
using Snap.Hutao.Model; using Snap.Hutao.Model;
using Snap.Hutao.Model.Calculable; using Snap.Hutao.Model.Calculable;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive; using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.ViewModel.AvatarProperty; namespace Snap.Hutao.ViewModel.AvatarProperty;
@@ -12,7 +11,7 @@ namespace Snap.Hutao.ViewModel.AvatarProperty;
/// 武器 /// 武器
/// </summary> /// </summary>
[HighQuality] [HighQuality]
internal sealed class WeaponView : EquipView, ICalculableSource<ICalculableWeapon> internal sealed class WeaponView : Equip, ICalculableSource<ICalculableWeapon>
{ {
/// <summary> /// <summary>
/// 副属性 /// 副属性
@@ -54,8 +53,6 @@ internal sealed class WeaponView : EquipView, ICalculableSource<ICalculableWeapo
/// </summary> /// </summary>
internal uint MaxLevel { get => Model.Metadata.Weapon.Weapon.GetMaxLevelByQuality(Quality); } internal uint MaxLevel { get => Model.Metadata.Weapon.Weapon.GetMaxLevelByQuality(Quality); }
internal WeaponType WeaponType { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public ICalculableWeapon ToCalculable() public ICalculableWeapon ToCalculable()
{ {

View File

@@ -20,7 +20,7 @@ namespace Snap.Hutao.ViewModel.Setting;
internal sealed partial class HutaoPassportViewModel : Abstraction.ViewModel internal sealed partial class HutaoPassportViewModel : Abstraction.ViewModel
{ {
private readonly IContentDialogFactory contentDialogFactory; private readonly IContentDialogFactory contentDialogFactory;
private readonly IServiceScopeFactory serviceScopeFactory; private readonly HutaoPassportClient homaPassportClient;
private readonly HutaoUserOptions hutaoUserOptions; private readonly HutaoUserOptions hutaoUserOptions;
private readonly IInfoBarService infoBarService; private readonly IInfoBarService infoBarService;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
@@ -46,17 +46,12 @@ internal sealed partial class HutaoPassportViewModel : Abstraction.ViewModel
return; return;
} }
using (IServiceScope scope = serviceScopeFactory.CreateScope()) HutaoResponse<string> response = await homaPassportClient.RegisterAsync(username, password, verifyCode).ConfigureAwait(false);
if (response.IsOk())
{ {
HutaoPassportClient hutaoPassportClient = scope.ServiceProvider.GetRequiredService<HutaoPassportClient>(); infoBarService.Information(response.GetLocalizationMessageOrMessage());
await hutaoUserOptions.PostLoginSucceedAsync(homaPassportClient, taskContext, username, password, response.Data).ConfigureAwait(false);
HutaoResponse<string> response = await hutaoPassportClient.RegisterAsync(username, password, verifyCode).ConfigureAwait(false);
if (response.IsOk())
{
infoBarService.Information(response.GetLocalizationMessageOrMessage());
await hutaoUserOptions.PostLoginSucceedAsync(hutaoPassportClient, taskContext, username, password, response.Data).ConfigureAwait(false);
}
} }
} }
} }
@@ -76,19 +71,14 @@ internal sealed partial class HutaoPassportViewModel : Abstraction.ViewModel
return; return;
} }
using (IServiceScope scope = serviceScopeFactory.CreateScope()) HutaoResponse response = await homaPassportClient.UnregisterAsync(username, password, verifyCode).ConfigureAwait(false);
if (response.IsOk())
{ {
HutaoPassportClient hutaoPassportClient = scope.ServiceProvider.GetRequiredService<HutaoPassportClient>(); infoBarService.Information(response.GetLocalizationMessageOrMessage());
HutaoResponse response = await hutaoPassportClient.UnregisterAsync(username, password, verifyCode).ConfigureAwait(false); await taskContext.SwitchToMainThreadAsync();
hutaoUserOptions.PostLogoutOrUnregister();
if (response.IsOk())
{
infoBarService.Information(response.GetLocalizationMessageOrMessage());
await taskContext.SwitchToMainThreadAsync();
hutaoUserOptions.PostLogoutOrUnregister();
}
} }
} }
} }
@@ -108,17 +98,12 @@ internal sealed partial class HutaoPassportViewModel : Abstraction.ViewModel
return; return;
} }
using (IServiceScope scope = serviceScopeFactory.CreateScope()) HutaoResponse<string> response = await homaPassportClient.LoginAsync(username, password).ConfigureAwait(false);
if (response.IsOk())
{ {
HutaoPassportClient hutaoPassportClient = scope.ServiceProvider.GetRequiredService<HutaoPassportClient>(); infoBarService.Information(response.GetLocalizationMessageOrMessage());
await hutaoUserOptions.PostLoginSucceedAsync(homaPassportClient, taskContext, username, password, response.Data).ConfigureAwait(false);
HutaoResponse<string> response = await hutaoPassportClient.LoginAsync(username, password).ConfigureAwait(false);
if (response.IsOk())
{
infoBarService.Information(response.GetLocalizationMessageOrMessage());
await hutaoUserOptions.PostLoginSucceedAsync(hutaoPassportClient, taskContext, username, password, response.Data).ConfigureAwait(false);
}
} }
} }
} }
@@ -144,17 +129,12 @@ internal sealed partial class HutaoPassportViewModel : Abstraction.ViewModel
return; return;
} }
using (IServiceScope scope = serviceScopeFactory.CreateScope()) HutaoResponse<string> response = await homaPassportClient.ResetPasswordAsync(username, password, verifyCode).ConfigureAwait(false);
if (response.IsOk())
{ {
HutaoPassportClient hutaoPassportClient = scope.ServiceProvider.GetRequiredService<HutaoPassportClient>(); infoBarService.Information(response.GetLocalizationMessageOrMessage());
await hutaoUserOptions.PostLoginSucceedAsync(homaPassportClient, taskContext, username, password, response.Data).ConfigureAwait(false);
HutaoResponse<string> response = await hutaoPassportClient.ResetPasswordAsync(username, password, verifyCode).ConfigureAwait(false);
if (response.IsOk())
{
infoBarService.Information(response.GetLocalizationMessageOrMessage());
await hutaoUserOptions.PostLoginSucceedAsync(hutaoPassportClient, taskContext, username, password, response.Data).ConfigureAwait(false);
}
} }
} }
} }

View File

@@ -5,9 +5,7 @@ using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Microsoft.Windows.AppLifecycle; using Microsoft.Windows.AppLifecycle;
using Snap.Hutao.Control.Extension;
using Snap.Hutao.Core; using Snap.Hutao.Core;
using Snap.Hutao.Core.Caching;
using Snap.Hutao.Core.Setting; using Snap.Hutao.Core.Setting;
using Snap.Hutao.Core.Shell; using Snap.Hutao.Core.Shell;
using Snap.Hutao.Core.Windowing; using Snap.Hutao.Core.Windowing;
@@ -222,6 +220,14 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
return ValueTask.FromResult(true); return ValueTask.FromResult(true);
} }
[Command("ResetStaticResourceCommand")]
private static void ResetStaticResource()
{
StaticResource.FailAll();
UnsafeLocalSetting.Set(SettingKeys.Major1Minor10Revision0GuideState, GuideState.StaticResourceBegin);
AppInstance.Restart(string.Empty);
}
[Command("StoreReviewCommand")] [Command("StoreReviewCommand")]
private static async Task StoreReviewAsync() private static async Task StoreReviewAsync()
{ {
@@ -234,24 +240,6 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
await Launcher.LaunchUriAsync(new("ms-windows-store://pdp/?productid=9PH4NXJ2JN52")); await Launcher.LaunchUriAsync(new("ms-windows-store://pdp/?productid=9PH4NXJ2JN52"));
} }
[Command("ResetStaticResourceCommand")]
private async Task ResetStaticResource()
{
ContentDialog dialog = await contentDialogFactory
.CreateForIndeterminateProgressAsync(SH.ViewModelSettingResetStaticResourceProgress)
.ConfigureAwait(false);
await using (await dialog.BlockAsync(taskContext).ConfigureAwait(false))
{
await taskContext.SwitchToBackgroundAsync();
StaticResource.FailAll();
Directory.Delete(Path.Combine(runtimeOptions.LocalCache, nameof(ImageCache)), true);
UnsafeLocalSetting.Set(SettingKeys.Major1Minor10Revision0GuideState, GuideState.StaticResourceBegin);
}
AppInstance.Restart(string.Empty);
}
[Command("DeleteGameWebCacheCommand")] [Command("DeleteGameWebCacheCommand")]
private void DeleteGameWebCache() private void DeleteGameWebCache()
{ {

View File

@@ -10,7 +10,6 @@ using Snap.Hutao.Factory.Progress;
using Snap.Hutao.Service.Abstraction; using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Notification; using Snap.Hutao.Service.Notification;
using Snap.Hutao.Service.Update; using Snap.Hutao.Service.Update;
using Snap.Hutao.View.Dialog;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Text; using System.Text;
@@ -70,15 +69,14 @@ internal sealed partial class TitleViewModel : Abstraction.ViewModel
if (checkUpdateResult.Kind is CheckUpdateResultKind.NeedDownload) if (checkUpdateResult.Kind is CheckUpdateResultKind.NeedDownload)
{ {
UpdatePackageDownloadConfirmDialog dialog = await contentDialogFactory ContentDialogResult downloadUpdateUserConsentResult = await contentDialogFactory
.CreateInstanceAsync<UpdatePackageDownloadConfirmDialog>() .CreateForConfirmCancelAsync(
SH.FormatViewTitileUpdatePackageDownloadTitle(UpdateStatus?.Version),
SH.ViewTitileUpdatePackageDownloadContent,
ContentDialogButton.Primary)
.ConfigureAwait(false); .ConfigureAwait(false);
await taskContext.SwitchToMainThreadAsync(); if (downloadUpdateUserConsentResult is ContentDialogResult.Primary)
dialog.Title = SH.FormatViewTitileUpdatePackageDownloadTitle(UpdateStatus?.Version);
if (await dialog.ShowAsync() is ContentDialogResult.Primary)
{ {
// This method will set CheckUpdateResult.Kind to NeedInstall if download success // This method will set CheckUpdateResult.Kind to NeedInstall if download success
if (!await DownloadPackageAsync(progress, checkUpdateResult).ConfigureAwait(false)) if (!await DownloadPackageAsync(progress, checkUpdateResult).ConfigureAwait(false))

View File

@@ -13,12 +13,6 @@ internal static partial class AnnouncementRegex
/// <inheritdoc cref="SH.WebAnnouncementMatchVersionUpdateTime"/> /// <inheritdoc cref="SH.WebAnnouncementMatchVersionUpdateTime"/>
public static readonly Regex VersionUpdateTimeRegex = new(SH.WebAnnouncementMatchVersionUpdateTime, RegexOptions.Compiled); public static readonly Regex VersionUpdateTimeRegex = new(SH.WebAnnouncementMatchVersionUpdateTime, RegexOptions.Compiled);
/// <inheritdoc cref="SH.WebAnnouncementMatchVersionUpdatePreviewTitle"/>
public static readonly Regex VersionUpdatePreviewTitleRegex = new(SH.WebAnnouncementMatchVersionUpdatePreviewTitle, RegexOptions.Compiled);
/// <inheritdoc cref="SH.WebAnnouncementMatchVersionUpdatePreviewTime"/>
public static readonly Regex VersionUpdatePreviewTimeRegex = new(SH.WebAnnouncementMatchVersionUpdatePreviewTime, RegexOptions.Compiled);
/// <inheritdoc cref="SH.WebAnnouncementMatchTransientActivityTime"/> /// <inheritdoc cref="SH.WebAnnouncementMatchTransientActivityTime"/>
public static readonly Regex TransientActivityAfterUpdateTimeRegex = new(SH.WebAnnouncementMatchTransientActivityTime, RegexOptions.Compiled); public static readonly Regex TransientActivityAfterUpdateTimeRegex = new(SH.WebAnnouncementMatchTransientActivityTime, RegexOptions.Compiled);

View File

@@ -25,7 +25,7 @@ internal sealed partial class CardClient
{ {
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create() HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(ApiEndpoints.CardCreateVerification(true)) .SetRequestUri(ApiEndpoints.CardCreateVerification(true))
.SetUserCookieAndFpHeader(user, CookieType.Cookie) .SetUserCookieAndFpHeader(user, CookieType.LToken)
.SetHeader("x-rpc-challenge_game", $"{headers.ChallengeGame}") .SetHeader("x-rpc-challenge_game", $"{headers.ChallengeGame}")
.SetHeader("x-rpc-challenge_path", headers.ChallengePath) .SetHeader("x-rpc-challenge_path", headers.ChallengePath)
.Get(); .Get();
@@ -39,11 +39,10 @@ internal sealed partial class CardClient
return Response.Response.DefaultIfNull(resp); return Response.Response.DefaultIfNull(resp);
} }
public async ValueTask<Response<VerificationResult>> VerifyVerificationAsync(User user, CardVerifiationHeaders headers, string challenge, string validate, CancellationToken token) public async ValueTask<Response<VerificationResult>> VerifyVerificationAsync(CardVerifiationHeaders headers, string challenge, string validate, CancellationToken token)
{ {
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create() HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(ApiEndpoints.CardVerifyVerification) .SetRequestUri(ApiEndpoints.CardVerifyVerification)
.SetUserCookieAndFpHeader(user, CookieType.Cookie)
.SetHeader("x-rpc-challenge_game", $"{headers.ChallengeGame}") .SetHeader("x-rpc-challenge_game", $"{headers.ChallengeGame}")
.SetHeader("x-rpc-challenge_path", headers.ChallengePath) .SetHeader("x-rpc-challenge_path", headers.ChallengePath)
.PostJson(new VerificationData(challenge, validate)); .PostJson(new VerificationData(challenge, validate));

View File

@@ -24,7 +24,7 @@ internal sealed partial class HomaGeetestCardVerifier : IGeetestCardVerifier
if (response is { Code: 0, Data.Validate: string validate }) if (response is { Code: 0, Data.Validate: string validate })
{ {
Response.Response<VerificationResult> verifyResponse = await cardClient.VerifyVerificationAsync(user, headers, registration.Challenge, validate, token).ConfigureAwait(false); Response.Response<VerificationResult> verifyResponse = await cardClient.VerifyVerificationAsync(headers, registration.Challenge, validate, token).ConfigureAwait(false);
if (verifyResponse.IsOk()) if (verifyResponse.IsOk())
{ {
VerificationResult result = verifyResponse.Data; VerificationResult result = verifyResponse.Data;

View File

@@ -1,7 +1,6 @@
// 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.Core;
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Web.Hutao.Response; using Snap.Hutao.Web.Hutao.Response;
using Snap.Hutao.Web.Request.Builder; using Snap.Hutao.Web.Request.Builder;
@@ -16,7 +15,6 @@ internal sealed partial class HutaoInfrastructureClient
{ {
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory; private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly ILogger<HutaoInfrastructureClient> logger; private readonly ILogger<HutaoInfrastructureClient> logger;
private readonly RuntimeOptions runtimeOptions;
private readonly HttpClient httpClient; private readonly HttpClient httpClient;
public async ValueTask<HutaoResponse<StaticResourceSizeInformation>> GetStaticSizeAsync(CancellationToken token = default) public async ValueTask<HutaoResponse<StaticResourceSizeInformation>> GetStaticSizeAsync(CancellationToken token = default)
@@ -43,7 +41,6 @@ internal sealed partial class HutaoInfrastructureClient
{ {
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create() HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(HutaoEndpoints.PatchSnapHutao) .SetRequestUri(HutaoEndpoints.PatchSnapHutao)
.SetHeader("x-device-id", runtimeOptions.DeviceId)
.Get(); .Get();
HutaoResponse<HutaoVersionInformation>? resp = await builder.TryCatchSendAsync<HutaoResponse<HutaoVersionInformation>>(httpClient, logger, token).ConfigureAwait(false); HutaoResponse<HutaoVersionInformation>? resp = await builder.TryCatchSendAsync<HutaoResponse<HutaoVersionInformation>>(httpClient, logger, token).ConfigureAwait(false);

View File

@@ -1,20 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Snap.Hutao.Win32.UI.Shell;
[Guid("3AD05575-8857-4850-9277-11B85BDB8E09")]
internal readonly struct FileOperation
{
internal static unsafe ref readonly Guid CLSID
{
get
{
ReadOnlySpan<byte> data = [0x75, 0x55, 0xD0, 0x3A, 0x57, 0x88, 0x50, 0x48, 0x92, 0x77, 0x11, 0xB8, 0x5B, 0xDB, 0x8E, 0x09];
return ref Unsafe.As<byte, Guid>(ref MemoryMarshal.GetReference(data));
}
}
}

View File

@@ -47,24 +47,6 @@ internal unsafe struct IFileOperation
return ThisPtr->IUnknownVftbl.Release((IUnknown*)Unsafe.AsPointer(ref this)); return ThisPtr->IUnknownVftbl.Release((IUnknown*)Unsafe.AsPointer(ref this));
} }
public unsafe HRESULT DeleteItem(IShellItem* psiItem, IFileOperationProgressSink* pfopsItem)
{
return ThisPtr->DeleteItem((IFileOperation*)Unsafe.AsPointer(ref this), psiItem, pfopsItem);
}
public unsafe HRESULT MoveItem(IShellItem* psiItem, IShellItem* psiDestinationFolder, [AllowNull] string szNewName, IFileOperationProgressSink* pfopsItem)
{
fixed (char* pszNewName = szNewName)
{
return ThisPtr->MoveItem((IFileOperation*)Unsafe.AsPointer(ref this), psiItem, psiDestinationFolder, pszNewName, pfopsItem);
}
}
public unsafe HRESULT PerformOperations()
{
return ThisPtr->PerformOperations((IFileOperation*)Unsafe.AsPointer(ref this));
}
internal readonly struct Vftbl internal readonly struct Vftbl
{ {
internal readonly IUnknown.Vftbl IUnknownVftbl; internal readonly IUnknown.Vftbl IUnknownVftbl;