This commit is contained in:
DismissedLight
2023-02-07 13:41:53 +08:00
parent 2a77daf2ca
commit 332e09fef0
19 changed files with 240 additions and 161 deletions

View File

@@ -9,7 +9,7 @@
},
"pathSegment": {
"add": {
".*": [ ".cs" ]
".*": [ ".cs", ".resx" ]
}
},
"fileSuffixToExtension": {

View File

@@ -0,0 +1,22 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Database;
/// <summary>
/// 可枚举扩展
/// </summary>
public static class EnumerableExtension
{
/// <summary>
/// 获取选中的值或默认值
/// </summary>
/// <typeparam name="TSource">源类型</typeparam>
/// <param name="source">源</param>
/// <returns>选中的值或默认值</returns>
public static TSource? SelectedOrDefault<TSource>(this IEnumerable<TSource> source)
where TSource : ISelectable
{
return source.SingleOrDefault(i => i.IsSelected);
}
}

View File

@@ -17,6 +17,7 @@ using Snap.Hutao.Model.InterChange.GachaLog;
using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.GachaLog.Factory;
using Snap.Hutao.Service.GachaLog.QueryProvider;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using Snap.Hutao.Web.Response;
@@ -43,7 +44,7 @@ internal class GachaLogService : IGachaLogService
}.ToImmutableList();
private readonly AppDbContext appDbContext;
private readonly IEnumerable<IGachaLogUrlProvider> urlProviders;
private readonly IEnumerable<IGachaLogQueryProvider> urlProviders;
private readonly GachaInfoClient gachaInfoClient;
private readonly IMetadataService metadataService;
private readonly IGachaStatisticsFactory gachaStatisticsFactory;
@@ -72,7 +73,7 @@ internal class GachaLogService : IGachaLogService
/// <param name="messenger">消息器</param>
public GachaLogService(
AppDbContext appDbContext,
IEnumerable<IGachaLogUrlProvider> urlProviders,
IEnumerable<IGachaLogQueryProvider> urlProviders,
GachaInfoClient gachaInfoClient,
IMetadataService metadataService,
IGachaStatisticsFactory gachaStatisticsFactory,
@@ -172,7 +173,7 @@ internal class GachaLogService : IGachaLogService
}
/// <inheritdoc/>
public IGachaLogUrlProvider? GetGachaLogUrlProvider(RefreshOption option)
public IGachaLogQueryProvider? GetGachaLogQueryProvider(RefreshOption option)
{
return option switch
{
@@ -207,7 +208,7 @@ internal class GachaLogService : IGachaLogService
}
/// <inheritdoc/>
public async Task<bool> RefreshGachaLogAsync(string query, RefreshStrategy strategy, IProgress<FetchState> progress, CancellationToken token)
public async Task<bool> RefreshGachaLogAsync(GachaLogQuery query, RefreshStrategy strategy, IProgress<FetchState> progress, CancellationToken token)
{
bool isLazy = strategy switch
{
@@ -241,7 +242,7 @@ internal class GachaLogService : IGachaLogService
return Task.Delay(TimeSpan.FromSeconds(Random.Shared.NextDouble() + 1), token);
}
private async Task<ValueResult<bool, GachaArchive?>> FetchGachaLogsAsync(string query, bool isLazy, IProgress<FetchState> progress, CancellationToken token)
private async Task<ValueResult<bool, GachaArchive?>> FetchGachaLogsAsync(GachaLogQuery query, bool isLazy, IProgress<FetchState> progress, CancellationToken token)
{
GachaArchive? archive = null;
FetchState state = new();

View File

@@ -4,6 +4,7 @@
using Snap.Hutao.Model.Binding.Gacha;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.InterChange.GachaLog;
using Snap.Hutao.Service.GachaLog.QueryProvider;
using System.Collections.ObjectModel;
namespace Snap.Hutao.Service.GachaLog;
@@ -36,7 +37,7 @@ internal interface IGachaLogService
/// </summary>
/// <param name="option">刷新模式</param>
/// <returns>祈愿日志Url提供器</returns>
IGachaLogUrlProvider? GetGachaLogUrlProvider(RefreshOption option);
IGachaLogQueryProvider? GetGachaLogQueryProvider(RefreshOption option);
/// <summary>
/// 获得对应的祈愿统计
@@ -69,7 +70,7 @@ internal interface IGachaLogService
/// <param name="progress">进度</param>
/// <param name="token">取消令牌</param>
/// <returns>验证密钥是否可用</returns>
Task<bool> RefreshGachaLogAsync(string query, RefreshStrategy strategy, IProgress<FetchState> progress, CancellationToken token);
Task<bool> RefreshGachaLogAsync(GachaLogQuery query, RefreshStrategy strategy, IProgress<FetchState> progress, CancellationToken token);
/// <summary>
/// 删除存档

View File

@@ -0,0 +1,52 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.GachaLog.QueryProvider;
/// <summary>
/// 祈愿记录query
/// </summary>
internal readonly struct GachaLogQuery
{
/// <summary>
/// query
/// </summary>
public readonly string Query;
/// <summary>
/// 是否为国际服
/// </summary>
public readonly bool IsOversea;
/// <summary>
/// 消息
/// </summary>
public readonly string Message;
/// <summary>
/// 构造一个新的祈愿记录query
/// </summary>
/// <param name="query">query</param>
/// <param name="isOversea">是否为国际服</param>
public GachaLogQuery(string query, bool isOversea)
{
Query = query;
IsOversea = isOversea;
Message = string.Empty;
}
/// <summary>
/// 构造一个新的失败的祈愿记录query
/// </summary>
/// <param name="message">失败原因</param>
public GachaLogQuery(string message)
{
Message = message;
Query = string.Empty;
}
public static implicit operator GachaLogQuery(string message)
{
return new(message);
}
}

View File

@@ -3,29 +3,29 @@
using Snap.Hutao.View.Dialog;
namespace Snap.Hutao.Service.GachaLog;
namespace Snap.Hutao.Service.GachaLog.QueryProvider;
/// <summary>
/// 手动输入方法
/// </summary>
[Injection(InjectAs.Transient, typeof(IGachaLogUrlProvider))]
internal class GachaLogUrlManualInputProvider : IGachaLogUrlProvider
[Injection(InjectAs.Transient, typeof(IGachaLogQueryProvider))]
internal class GachaLogUrlManualInputProvider : IGachaLogQueryProvider
{
/// <inheritdoc/>
public string Name { get => nameof(GachaLogUrlManualInputProvider); }
/// <inheritdoc/>
public async Task<ValueResult<bool, string>> GetQueryAsync()
public async Task<ValueResult<bool, GachaLogQuery>> GetQueryAsync()
{
// ContentDialog must be created by main thread.
await ThreadHelper.SwitchToMainThreadAsync();
ValueResult<bool, string> result = await new GachaLogUrlDialog().GetInputUrlAsync().ConfigureAwait(false);
(bool isOk, string query) = await new GachaLogUrlDialog().GetInputUrlAsync().ConfigureAwait(false);
if (result.IsOk)
if (isOk)
{
if (result.Value.Contains("&auth_appid=webview_gacha"))
if (query.Contains("&auth_appid=webview_gacha"))
{
return result;
return new(true, new(query, query.Contains("hoyoverse.com")));
}
else
{
@@ -34,7 +34,7 @@ internal class GachaLogUrlManualInputProvider : IGachaLogUrlProvider
}
else
{
return new(false, null!);
return new(false, string.Empty);
}
}
}

View File

@@ -7,13 +7,13 @@ using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
using Snap.Hutao.Web.Response;
namespace Snap.Hutao.Service.GachaLog;
namespace Snap.Hutao.Service.GachaLog.QueryProvider;
/// <summary>
/// 使用Stokn提供祈愿Url
/// </summary>
[Injection(InjectAs.Transient, typeof(IGachaLogUrlProvider))]
internal class GachaLogUrlStokenProvider : IGachaLogUrlProvider
[Injection(InjectAs.Transient, typeof(IGachaLogQueryProvider))]
internal class GachaLogUrlStokenProvider : IGachaLogQueryProvider
{
private readonly IUserService userService;
private readonly BindingClient2 bindingClient2;
@@ -33,7 +33,7 @@ internal class GachaLogUrlStokenProvider : IGachaLogUrlProvider
public string Name { get => nameof(GachaLogUrlStokenProvider); }
/// <inheritdoc/>
public async Task<ValueResult<bool, string>> GetQueryAsync()
public async Task<ValueResult<bool, GachaLogQuery>> GetQueryAsync()
{
if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid))
{
@@ -42,7 +42,7 @@ internal class GachaLogUrlStokenProvider : IGachaLogUrlProvider
if (authkeyResponse.IsOk())
{
return new(true, GachaLogQueryOptions.AsQuery(data, authkeyResponse.Data));
return new(true, new(GachaLogQueryOptions.AsQuery(data, authkeyResponse.Data), false));
}
else
{

View File

@@ -6,13 +6,13 @@ using Snap.Hutao.Service.Game;
using System.IO;
using System.Text;
namespace Snap.Hutao.Service.GachaLog;
namespace Snap.Hutao.Service.GachaLog.QueryProvider;
/// <summary>
/// 浏览器缓存方法
/// </summary>
[Injection(InjectAs.Transient, typeof(IGachaLogUrlProvider))]
internal class GachaLogUrlWebCacheProvider : IGachaLogUrlProvider
[Injection(InjectAs.Transient, typeof(IGachaLogQueryProvider))]
internal class GachaLogUrlWebCacheProvider : IGachaLogQueryProvider
{
private readonly IGameService gameService;
@@ -44,7 +44,7 @@ internal class GachaLogUrlWebCacheProvider : IGachaLogUrlProvider
}
/// <inheritdoc/>
public async Task<ValueResult<bool, string>> GetQueryAsync()
public async Task<ValueResult<bool, GachaLogQuery>> GetQueryAsync()
{
(bool isOk, string path) = await gameService.GetGamePathAsync().ConfigureAwait(false);
@@ -65,7 +65,15 @@ internal class GachaLogUrlWebCacheProvider : IGachaLogUrlProvider
{
await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false);
string? result = Match(memoryStream, cacheFile.Contains(GameConstants.GenshinImpactData));
return new(!string.IsNullOrEmpty(result), result ?? SH.ServiceGachaLogUrlProviderCacheUrlNotFound);
if (!string.IsNullOrEmpty(result))
{
return new(true, new(result, result.Contains("hoyoverse.com")));
}
else
{
return new(false, SH.ServiceGachaLogUrlProviderCacheUrlNotFound);
}
}
}
}

View File

@@ -3,17 +3,17 @@
using Snap.Hutao.Core.Abstraction;
namespace Snap.Hutao.Service.GachaLog;
namespace Snap.Hutao.Service.GachaLog.QueryProvider;
/// <summary>
/// 祈愿记录Url提供器
/// </summary>
internal interface IGachaLogUrlProvider : INamed
internal interface IGachaLogQueryProvider : INamed
{
/// <summary>
/// 异步获取包含验证密钥的查询语句
/// 查询语句可以仅包含?后的内容
/// </summary>
/// <returns>包含验证密钥的查询语句</returns>
Task<ValueResult<bool, string>> GetQueryAsync();
}
Task<ValueResult<bool, GachaLogQuery>> GetQueryAsync();
}

View File

@@ -166,13 +166,11 @@
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border
Margin="0,8,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Style="{StaticResource BorderCardStyle}">
<Grid
MinHeight="48"
Margin="0,8,0,0"
Padding="8">
<Grid MinHeight="48" Padding="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>

View File

@@ -26,6 +26,19 @@ public abstract class ViewModel : ObservableObject, IViewModel
/// <inheritdoc/>
public bool IsViewDisposed { get; set; }
/// <summary>
/// 保证 using scope 内的代码运行完成
/// 防止 视图资源被回收
/// </summary>
/// <returns>解除执行限制</returns>
protected async Task<IDisposable> EnterCriticalExecutionAsync()
{
ThrowIfViewDisposed();
IDisposable disposable = await DisposeLock.EnterAsync(CancellationToken).ConfigureAwait(false);
ThrowIfViewDisposed();
return disposable;
}
/// <summary>
/// 当页面被释放后抛出异常
/// </summary>

View File

@@ -6,6 +6,7 @@ using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI.UI;
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control.Extension;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.IO;
using Snap.Hutao.Core.IO.DataTransfer;
using Snap.Hutao.Core.LifeCycle;
@@ -23,6 +24,7 @@ using Windows.Storage.Pickers;
using BindingAchievement = Snap.Hutao.Model.Binding.Achievement.Achievement;
using BindingAchievementGoal = Snap.Hutao.Model.Binding.Achievement.AchievementGoal;
using EntityAchievementArchive = Snap.Hutao.Model.Entity.AchievementArchive;
using MetadataAchievement = Snap.Hutao.Model.Metadata.Achievement.Achievement;
using MetadataAchievementGoal = Snap.Hutao.Model.Metadata.Achievement.AchievementGoal;
namespace Snap.Hutao.ViewModel;
@@ -246,11 +248,8 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien
List<BindingAchievementGoal> sortedGoals;
ObservableCollection<EntityAchievementArchive> archives;
ThrowIfViewDisposed();
using (await DisposeLock.EnterAsync(CancellationToken).ConfigureAwait(false))
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
{
ThrowIfViewDisposed();
List<MetadataAchievementGoal> goals = await metadataService.GetAchievementGoalsAsync(CancellationToken).ConfigureAwait(false);
sortedGoals = goals
.OrderBy(goal => goal.Order)
@@ -262,7 +261,7 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien
await ThreadHelper.SwitchToMainThreadAsync();
AchievementGoals = sortedGoals;
Archives = archives;
SelectedArchive = Archives.SingleOrDefault(a => a.IsSelected == true);
SelectedArchive = Archives.SelectedOrDefault();
IsInitialized = true;
}
@@ -271,6 +270,7 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien
// User canceled the loading operation,
// Indicate initialization not succeed.
openUICompletionSource.TrySetResult(false);
return;
}
}
@@ -343,30 +343,6 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien
}
#endregion
private void SearchAchievement(string? search)
{
if (Achievements != null)
{
SetProperty(ref selectedAchievementGoal, null);
if (!string.IsNullOrEmpty(search))
{
if (search.Length == 5 && int.TryParse(search, out int achiId))
{
Achievements.Filter = (object o) => ((BindingAchievement)o).Inner.Id == achiId;
}
else
{
Achievements.Filter = (object o) =>
{
BindingAchievement achi = (BindingAchievement)o;
return achi.Inner.Title.Contains(search) || achi.Inner.Description.Contains(search);
};
}
}
}
}
#region
private async Task ExportAsUIAFToFileAsync()
{
@@ -492,9 +468,33 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien
}
#endregion
private void SearchAchievement(string? search)
{
if (Achievements != null)
{
SetProperty(ref selectedAchievementGoal, null);
if (!string.IsNullOrEmpty(search))
{
if (search.Length == 5 && int.TryParse(search, out int achiId))
{
Achievements.Filter = obj => ((BindingAchievement)obj).Inner.Id == achiId;
}
else
{
Achievements.Filter = obj =>
{
BindingAchievement achi = (BindingAchievement)obj;
return achi.Inner.Title.Contains(search) || achi.Inner.Description.Contains(search);
};
}
}
}
}
private async Task UpdateAchievementsAsync(EntityAchievementArchive archive)
{
List<Model.Metadata.Achievement.Achievement> rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false);
List<MetadataAchievement> rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false);
List<BindingAchievement> combined;
try
{
@@ -547,22 +547,22 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien
int count = 0;
if (Achievements != null && AchievementGoals != null)
{
Dictionary<int, GoalAggregation> counter = AchievementGoals.ToDictionary(x => x.Id, x => new GoalAggregation(x));
foreach (BindingAchievement achievement in Achievements.SourceCollection.OfType<BindingAchievement>())
Dictionary<int, GoalStatistics> counter = AchievementGoals.ToDictionary(x => x.Id, x => new GoalStatistics(x));
foreach (BindingAchievement achievement in Achievements.SourceCollection.Cast<BindingAchievement>())
{
ref GoalAggregation aggregation = ref CollectionsMarshal.GetValueRefOrNullRef(counter, achievement.Inner.Goal);
aggregation.Count += 1;
ref GoalStatistics stat = ref CollectionsMarshal.GetValueRefOrNullRef(counter, achievement.Inner.Goal);
stat.Count += 1;
count += 1;
if (achievement.IsChecked)
{
aggregation.Finished += 1;
stat.Finished += 1;
finished += 1;
}
}
foreach (GoalAggregation aggregation1 in counter.Values)
foreach (GoalStatistics statistics in counter.Values)
{
aggregation1.AchievementGoal.UpdateFinishPercent(aggregation1.Finished, aggregation1.Count);
statistics.AchievementGoal.UpdateFinishPercent(statistics.Finished, statistics.Count);
}
FinishDescription = $"{finished}/{count} - {(double)finished / count:P2}";
@@ -578,13 +578,13 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien
}
}
private struct GoalAggregation
private struct GoalStatistics
{
public readonly BindingAchievementGoal AchievementGoal;
public int Finished;
public int Count;
public GoalAggregation(BindingAchievementGoal goal)
public GoalStatistics(BindingAchievementGoal goal)
{
AchievementGoal = goal;
}

View File

@@ -124,13 +124,9 @@ internal class AvatarPropertyViewModel : Abstraction.ViewModel
private Task RefreshByEnkaApiAsync()
{
if (userService.Current is User user)
if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid))
{
if (user.SelectedUserGameRole is UserGameRole role)
{
UserAndUid userAndUid = new(user.Entity, role);
return RefreshCoreAsync(userAndUid, RefreshOption.RequestFromEnkaAPI, CancellationToken);
}
return RefreshCoreAsync(userAndUid, RefreshOption.RequestFromEnkaAPI, CancellationToken);
}
return Task.CompletedTask;
@@ -138,13 +134,9 @@ internal class AvatarPropertyViewModel : Abstraction.ViewModel
private Task RefreshByHoyolabGameRecordAsync()
{
if (userService.Current is User user)
if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid))
{
if (user.SelectedUserGameRole is UserGameRole role)
{
UserAndUid userAndUid = new(user.Entity, role);
return RefreshCoreAsync(userAndUid, RefreshOption.RequestFromHoyolabGameRecord, CancellationToken);
}
return RefreshCoreAsync(userAndUid, RefreshOption.RequestFromHoyolabGameRecord, CancellationToken);
}
return Task.CompletedTask;
@@ -152,13 +144,9 @@ internal class AvatarPropertyViewModel : Abstraction.ViewModel
private Task RefreshByHoyolabCalculateAsync()
{
if (userService.Current is User user)
if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid))
{
if (user.SelectedUserGameRole is UserGameRole role)
{
UserAndUid userAndUid = new(user.Entity, role);
return RefreshCoreAsync(userAndUid, RefreshOption.RequestFromHoyolabCalculate, CancellationToken);
}
return RefreshCoreAsync(userAndUid, RefreshOption.RequestFromHoyolabCalculate, CancellationToken);
}
return Task.CompletedTask;
@@ -169,10 +157,8 @@ internal class AvatarPropertyViewModel : Abstraction.ViewModel
try
{
ValueResult<RefreshResult, Summary?> summaryResult;
ThrowIfViewDisposed();
using (await DisposeLock.EnterAsync(token).ConfigureAwait(false))
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
{
ThrowIfViewDisposed();
ContentDialog dialog = await contentDialogFactory
.CreateForIndeterminateProgressAsync(SH.ViewModelAvatarPropertyFetch)
.ConfigureAwait(false);

View File

@@ -175,7 +175,7 @@ internal class DailyNoteViewModel : Abstraction.ViewModel
{
try
{
UserAndUids = await userService.GetRoleCollectionAsync().ConfigureAwait(false);
UserAndUids = await userService.GetRoleCollectionAsync().ConfigureAwait(true);
}
catch (Core.ExceptionService.UserdataCorruptedException ex)
{
@@ -185,10 +185,8 @@ internal class DailyNoteViewModel : Abstraction.ViewModel
try
{
ThrowIfViewDisposed();
using (await DisposeLock.EnterAsync().ConfigureAwait(false))
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
{
ThrowIfViewDisposed();
await ThreadHelper.SwitchToMainThreadAsync();
refreshSecondsEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.DailyNoteRefreshSeconds, "480");
@@ -203,10 +201,9 @@ internal class DailyNoteViewModel : Abstraction.ViewModel
silentModeEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.DailyNoteSilentWhenPlayingGame, SettingEntryHelper.FalseString);
isSilentWhenPlayingGame = silentModeEntry.GetBoolean();
OnPropertyChanged(nameof(IsSilentWhenPlayingGame));
await ThreadHelper.SwitchToBackgroundAsync();
}
await ThreadHelper.SwitchToBackgroundAsync();
ObservableCollection<DailyNoteEntry> temp = await dailyNoteService.GetDailyNoteEntriesAsync().ConfigureAwait(false);
await ThreadHelper.SwitchToMainThreadAsync();
DailyNoteEntries = temp;

View File

@@ -12,6 +12,7 @@ using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.InterChange.GachaLog;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.GachaLog;
using Snap.Hutao.Service.GachaLog.QueryProvider;
using Snap.Hutao.View.Dialog;
using System.Collections.ObjectModel;
using Windows.Storage.Pickers;
@@ -183,11 +184,11 @@ internal class GachaLogViewModel : Abstraction.ViewModel
private async Task RefreshInternalAsync(RefreshOption option)
{
IGachaLogUrlProvider? provider = gachaLogService.GetGachaLogUrlProvider(option);
IGachaLogQueryProvider? provider = gachaLogService.GetGachaLogQueryProvider(option);
if (provider != null)
{
(bool isOk, string query) = await provider.GetQueryAsync().ConfigureAwait(false);
(bool isOk, GachaLogQuery query) = await provider.GetQueryAsync().ConfigureAwait(false);
if (isOk)
{
@@ -265,31 +266,29 @@ internal class GachaLogViewModel : Abstraction.ViewModel
private async Task ExportToUIGFJsonAsync()
{
if (SelectedArchive == null)
if (SelectedArchive != null)
{
return;
}
FileSavePicker picker = pickerFactory.GetFileSavePicker();
picker.SuggestedStartLocation = PickerLocationId.Desktop;
picker.SuggestedFileName = SelectedArchive.Uid;
picker.CommitButtonText = SH.FilePickerExportCommit;
picker.FileTypeChoices.Add(SH.ViewModelGachaLogExportFileType, ".json".Enumerate().ToList());
FileSavePicker picker = pickerFactory.GetFileSavePicker();
picker.SuggestedStartLocation = PickerLocationId.Desktop;
picker.SuggestedFileName = SelectedArchive.Uid;
picker.CommitButtonText = SH.FilePickerExportCommit;
picker.FileTypeChoices.Add(SH.ViewModelGachaLogExportFileType, ".json".Enumerate().ToList());
(bool isPickerOk, FilePath file) = await picker.TryPickSaveFileAsync().ConfigureAwait(false);
(bool isPickerOk, FilePath file) = await picker.TryPickSaveFileAsync().ConfigureAwait(false);
if (isPickerOk)
{
UIGF uigf = await gachaLogService.ExportToUIGFAsync(SelectedArchive).ConfigureAwait(false);
bool isOk = await file.SerializeToJsonAsync(uigf, options).ConfigureAwait(false);
if (isOk)
if (isPickerOk)
{
infoBarService.Success(SH.ViewModelExportSuccessTitle, SH.ViewModelExportSuccessMessage);
}
else
{
infoBarService.Warning(SH.ViewModelExportWarningTitle, SH.ViewModelExportWarningMessage);
UIGF uigf = await gachaLogService.ExportToUIGFAsync(SelectedArchive).ConfigureAwait(false);
bool isOk = await file.SerializeToJsonAsync(uigf, options).ConfigureAwait(false);
if (isOk)
{
infoBarService.Success(SH.ViewModelExportSuccessTitle, SH.ViewModelExportSuccessMessage);
}
else
{
infoBarService.Warning(SH.ViewModelExportWarningTitle, SH.ViewModelExportWarningMessage);
}
}
}
}

View File

@@ -40,8 +40,7 @@ internal class GachaInfoClient
{
string query = config.AsQuery();
// TODO: fix oversea behavior
string url = query.Contains("hoyoverse.com")
string url = config.IsOversea
? ApiOsEndpoints.GachaInfoGetGachaLog(query)
: ApiEndpoints.GachaInfoGetGachaLog(query);

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Service.GachaLog.QueryProvider;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
using Snap.Hutao.Web.Request.QueryString;
@@ -9,13 +10,18 @@ namespace Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
/// <summary>
/// 祈愿记录请求配置
/// </summary>
public struct GachaLogQueryOptions
internal struct GachaLogQueryOptions
{
/// <summary>
/// 尺寸
/// </summary>
public const int Size = 20;
/// <summary>
/// 是否为国际服
/// </summary>
public readonly bool IsOversea;
/// <summary>
/// Keys required:
/// authkey_ver
@@ -36,9 +42,10 @@ public struct GachaLogQueryOptions
/// <param name="query">原始查询字符串</param>
/// <param name="type">祈愿类型</param>
/// <param name="endId">终止Id</param>
public GachaLogQueryOptions(string query, GachaConfigType type, long endId = 0L)
public GachaLogQueryOptions(GachaLogQuery query, GachaConfigType type, long endId = 0L)
{
innerQuery = QueryString.Parse(query);
IsOversea = query.IsOversea;
innerQuery = QueryString.Parse(query.Query);
innerQuery.Set("lang", "zh-cn");
innerQuery.Set("gacha_type", (int)type);
@@ -47,11 +54,6 @@ public struct GachaLogQueryOptions
EndId = endId;
}
/// <summary>
/// 是否为国际服
/// </summary>
public bool IsOversea { get; set; }
/// <summary>
/// 结束Id
/// 控制API返回的分页

View File

@@ -189,30 +189,26 @@ internal class HomaClient
.GetPlayerInfoAsync(userAndUid, token)
.ConfigureAwait(false);
if (!playerInfoResponse.IsOk())
if (playerInfoResponse.IsOk())
{
return null;
Response<CharacterWrapper> charactersResponse = await gameRecordClient
.GetCharactersAsync(userAndUid, playerInfoResponse.Data, token)
.ConfigureAwait(false);
if (charactersResponse.IsOk())
{
Response<SpiralAbyss> spiralAbyssResponse = await gameRecordClient
.GetSpiralAbyssAsync(userAndUid, SpiralAbyssSchedule.Current, token)
.ConfigureAwait(false);
if (spiralAbyssResponse.IsOk())
{
return new(userAndUid.Uid.Value, charactersResponse.Data.Avatars, spiralAbyssResponse.Data);
}
}
}
Response<CharacterWrapper> charactersResponse = await gameRecordClient
.GetCharactersAsync(userAndUid, playerInfoResponse.Data, token)
.ConfigureAwait(false);
if (!charactersResponse.IsOk())
{
return null;
}
Response<SpiralAbyss> spiralAbyssResponse = await gameRecordClient
.GetSpiralAbyssAsync(userAndUid, SpiralAbyssSchedule.Current, token)
.ConfigureAwait(false);
if (!spiralAbyssResponse.IsOk())
{
return null;
}
return new(userAndUid.Uid.Value, charactersResponse.Data.Avatars, spiralAbyssResponse.Data);
return null;
}
/// <summary>

View File

@@ -32,16 +32,21 @@ internal class HomaClient2
/// <returns>任务</returns>
public async Task<string?> UploadLogAsync(Exception exception)
{
HutaoLog log = new()
{
Id = Core.CoreEnvironment.HutaoDeviceId,
Time = DateTimeOffset.Now.ToUnixTimeMilliseconds(),
Info = exception.ToString(),
};
HutaoLog log = BuildFromException(exception);
Response<string>? a = await httpClient
.TryCatchPostAsJsonAsync<HutaoLog, Response<string>>(HutaoEndpoints.HutaoLogUpload, log)
.ConfigureAwait(false);
return a?.Data;
}
private static HutaoLog BuildFromException(Exception exception)
{
return new()
{
Id = Core.CoreEnvironment.HutaoDeviceId,
Time = DateTimeOffset.Now.ToUnixTimeMilliseconds(),
Info = exception.ToString(),
};
}
}