Compare commits

..

1 Commits

Author SHA1 Message Date
Lightczx
8703c3a598 init 2024-04-16 17:28:15 +08:00
74 changed files with 665 additions and 983 deletions

View File

@@ -28,21 +28,4 @@ internal static class FrameworkElementExtension
frameworkElement.IsRightTapEnabled = false;
frameworkElement.IsTabStop = false;
}
public static void InitializeDataContext<TDataContext>(this FrameworkElement frameworkElement, IServiceProvider? serviceProvider = default)
where TDataContext : class
{
IServiceProvider service = serviceProvider ?? Ioc.Default;
try
{
frameworkElement.DataContext = service.GetRequiredService<TDataContext>();
}
catch (Exception ex)
{
ILogger? logger = service.GetRequiredService(typeof(ILogger<>).MakeGenericType([frameworkElement.GetType()])) as ILogger;
logger?.LogError(ex, "Failed to initialize DataContext");
throw;
}
}
}

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using System.Runtime.InteropServices;
using Windows.Foundation;
namespace Snap.Hutao.Control.Panel;
@@ -19,14 +18,13 @@ internal partial class HorizontalEqualPanel : Microsoft.UI.Xaml.Controls.Panel
protected override Size MeasureOverride(Size availableSize)
{
List<UIElement> visibleChildren = Children.Where(child => child.Visibility is Visibility.Visible).ToList();
foreach (ref readonly UIElement visibleChild in CollectionsMarshal.AsSpan(visibleChildren))
foreach (UIElement child in Children)
{
// ScrollViewer will always return an Infinity Size, we should use ActualWidth for this situation.
double availableWidth = double.IsInfinity(availableSize.Width) ? ActualWidth : availableSize.Width;
double childAvailableWidth = (availableWidth + Spacing) / visibleChildren.Count;
double childAvailableWidth = (availableWidth + Spacing) / Children.Count;
double childMaxAvailableWidth = Math.Max(MinItemWidth, childAvailableWidth);
visibleChild.Measure(new(childMaxAvailableWidth - Spacing, ActualHeight));
child.Measure(new(childMaxAvailableWidth - Spacing, ActualHeight));
}
return base.MeasureOverride(availableSize);
@@ -34,14 +32,14 @@ internal partial class HorizontalEqualPanel : Microsoft.UI.Xaml.Controls.Panel
protected override Size ArrangeOverride(Size finalSize)
{
List<UIElement> visibleChildren = Children.Where(child => child.Visibility is Visibility.Visible).ToList();
double availableItemWidth = (finalSize.Width - (Spacing * (visibleChildren.Count - 1))) / visibleChildren.Count;
int itemCount = Children.Count;
double availableItemWidth = (finalSize.Width - (Spacing * (itemCount - 1))) / itemCount;
double actualItemWidth = Math.Max(MinItemWidth, availableItemWidth);
double offset = 0;
foreach (ref readonly UIElement visibleChild in CollectionsMarshal.AsSpan(visibleChildren))
foreach (UIElement child in Children)
{
visibleChild.Arrange(new Rect(offset, 0, actualItemWidth, finalSize.Height));
child.Arrange(new Rect(offset, 0, actualItemWidth, finalSize.Height));
offset += actualItemWidth + Spacing;
}
@@ -51,8 +49,7 @@ internal partial class HorizontalEqualPanel : Microsoft.UI.Xaml.Controls.Panel
private static void OnLoaded(object sender, RoutedEventArgs e)
{
HorizontalEqualPanel panel = (HorizontalEqualPanel)sender;
int vivibleChildrenCount = panel.Children.Count(child => child.Visibility is Visibility.Visible);
panel.MinWidth = (panel.MinItemWidth * vivibleChildrenCount) + (panel.Spacing * (vivibleChildrenCount - 1));
panel.MinWidth = (panel.MinItemWidth * panel.Children.Count) + (panel.Spacing * (panel.Children.Count - 1));
}
private static void OnSizeChanged(object sender, SizeChangedEventArgs e)

View File

@@ -15,7 +15,7 @@ internal class ScopedPage : Page
{
private readonly RoutedEventHandler unloadEventHandler;
private readonly CancellationTokenSource viewCancellationTokenSource = new();
private readonly IServiceScope pageScope;
private readonly IServiceScope currentScope;
private bool inFrame = true;
@@ -23,7 +23,7 @@ internal class ScopedPage : Page
{
unloadEventHandler = OnUnloaded;
Unloaded += unloadEventHandler;
pageScope = Ioc.Default.GetRequiredService<IScopedPageScopeReferenceTracker>().CreateScope();
currentScope = Ioc.Default.GetRequiredService<IScopedPageScopeReferenceTracker>().CreateScope();
}
public async ValueTask NotifyRecipientAsync(INavigationData extra)
@@ -44,17 +44,9 @@ internal class ScopedPage : Page
protected void InitializeWith<TViewModel>()
where TViewModel : class, IViewModel
{
try
{
IViewModel viewModel = pageScope.ServiceProvider.GetRequiredService<TViewModel>();
viewModel.CancellationToken = viewCancellationTokenSource.Token;
DataContext = viewModel;
}
catch (Exception ex)
{
pageScope.ServiceProvider.GetRequiredService<ILogger<ScopedPage>>().LogError(ex, "Failed to initialize view model.");
throw;
}
IViewModel viewModel = currentScope.ServiceProvider.GetRequiredService<TViewModel>();
viewModel.CancellationToken = viewCancellationTokenSource.Token;
DataContext = viewModel;
}
/// <inheritdoc/>
@@ -103,7 +95,7 @@ internal class ScopedPage : Page
viewModel.IsViewDisposed = true;
// Dispose the scope
pageScope.Dispose();
currentScope.Dispose();
}
}
}

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Snap.Hutao.Control.Extension;
using Snap.Hutao.Core.Windowing;
using Snap.Hutao.ViewModel.Game;
using Snap.Hutao.Win32.UI.WindowsAndMessaging;
@@ -36,7 +35,7 @@ internal sealed partial class LaunchGameWindow : Window, IDisposable, IWindowOpt
scope = serviceProvider.CreateScope();
windowOptions = new(this, DragableGrid, new(MaxWidth, MaxHeight));
this.InitializeController(serviceProvider);
RootGrid.InitializeDataContext<LaunchGameViewModel>(scope.ServiceProvider);
RootGrid.DataContext = scope.ServiceProvider.GetRequiredService<LaunchGameViewModel>();
}
/// <inheritdoc/>

View File

@@ -19,7 +19,6 @@ internal sealed partial class SettingEntry
public const string AnnouncementRegion = "AnnouncementRegion";
public const string IsEmptyHistoryWishVisible = "IsEmptyHistoryWishVisible";
public const string IsUnobtainedWishItemVisible = "IsUnobtainedWishItemVisible";
public const string GeetestCustomCompositeUrl = "GeetestCustomCompositeUrl";

View File

@@ -11,6 +11,11 @@ namespace Snap.Hutao.Model.Metadata.Converter;
[HighQuality]
internal sealed class AvatarNameCardPicConverter : ValueConverter<Avatar.Avatar?, Uri>
{
/// <summary>
/// 从角色转换到名片
/// </summary>
/// <param name="avatar">角色</param>
/// <returns>名片</returns>
public static Uri AvatarToUri(Avatar.Avatar? avatar)
{
if (avatar is null)

View File

@@ -2702,12 +2702,6 @@
<data name="ViewPageSettingTranslateNavigate" xml:space="preserve">
<value>贡献翻译</value>
</data>
<data name="ViewPageSettingUnobtainedWishItemVisibleDescription" xml:space="preserve">
<value>在祈愿记录页面角色与武器页签显示未抽取到的祈愿物品</value>
</data>
<data name="ViewPageSettingUnobtainedWishItemVisibleHeader" xml:space="preserve">
<value>未抽取到的祈愿物品</value>
</data>
<data name="ViewPageSettingUpdateCheckAction" xml:space="preserve">
<value>前往商店</value>
</data>

View File

@@ -17,15 +17,16 @@ namespace Snap.Hutao.Service;
/// <inheritdoc/>
[HighQuality]
[ConstructorGenerated]
[Injection(InjectAs.Scoped, typeof(IAnnouncementService))]
[Injection(InjectAs.Transient, typeof(IAnnouncementService))]
internal sealed partial class AnnouncementService : IAnnouncementService
{
private static readonly string CacheKey = $"{nameof(AnnouncementService)}.Cache.{nameof(AnnouncementWrapper)}";
private readonly IServiceScopeFactory serviceScopeFactory;
private readonly AnnouncementClient announcementClient;
private readonly ITaskContext taskContext;
private readonly IMemoryCache memoryCache;
/// <inheritdoc/>
public async ValueTask<AnnouncementWrapper> GetAnnouncementWrapperAsync(string languageCode, Region region, CancellationToken cancellationToken = default)
{
// 缓存中存在记录,直接返回
@@ -35,37 +36,29 @@ internal sealed partial class AnnouncementService : IAnnouncementService
}
await taskContext.SwitchToBackgroundAsync();
Response<AnnouncementWrapper> announcementWrapperResponse = await announcementClient
.GetAnnouncementsAsync(languageCode, region, cancellationToken)
.ConfigureAwait(false);
List<AnnouncementContent>? contents;
AnnouncementWrapper wrapper;
using (IServiceScope scope = serviceScopeFactory.CreateScope())
if (!announcementWrapperResponse.IsOk())
{
AnnouncementClient announcementClient = scope.ServiceProvider.GetRequiredService<AnnouncementClient>();
Response<AnnouncementWrapper> announcementWrapperResponse = await announcementClient
.GetAnnouncementsAsync(languageCode, region, cancellationToken)
.ConfigureAwait(false);
if (!announcementWrapperResponse.IsOk())
{
return default!;
}
wrapper = announcementWrapperResponse.Data;
Response<ListWrapper<AnnouncementContent>> announcementContentResponse = await announcementClient
.GetAnnouncementContentsAsync(languageCode, region, cancellationToken)
.ConfigureAwait(false);
if (!announcementContentResponse.IsOk())
{
return default!;
}
contents = announcementContentResponse.Data.List;
return default!;
}
Dictionary<int, string> contentMap = contents.ToDictionary(id => id.AnnId, content => content.Content);
AnnouncementWrapper wrapper = announcementWrapperResponse.Data;
Response<ListWrapper<AnnouncementContent>> announcementContentResponse = await announcementClient
.GetAnnouncementContentsAsync(languageCode, region, cancellationToken)
.ConfigureAwait(false);
if (!announcementContentResponse.IsOk())
{
return default!;
}
List<AnnouncementContent> contents = announcementContentResponse.Data.List;
Dictionary<int, string> contentMap = contents
.ToDictionary(id => id.AnnId, content => content.Content);
// 将活动公告置于前方
wrapper.List.Reverse();
@@ -82,7 +75,8 @@ internal sealed partial class AnnouncementService : IAnnouncementService
{
foreach (ref readonly WebAnnouncement item in CollectionsMarshal.AsSpan(listWrapper.List))
{
item.Content = contentMap.GetValueOrDefault(item.AnnId, string.Empty);
contentMap.TryGetValue(item.AnnId, out string? rawContent);
item.Content = rawContent ?? string.Empty;
}
}

View File

@@ -16,7 +16,6 @@ namespace Snap.Hutao.Service;
internal sealed partial class AppOptions : DbStoreOptions
{
private bool? isEmptyHistoryWishVisible;
private bool? isUnobtainedWishItemVisible;
private BackdropType? backdropType;
private ElementTheme? elementTheme;
private BackgroundImageType? backgroundImageType;
@@ -25,16 +24,10 @@ internal sealed partial class AppOptions : DbStoreOptions
public bool IsEmptyHistoryWishVisible
{
get => GetOption(ref isEmptyHistoryWishVisible, SettingEntry.IsEmptyHistoryWishVisible, false);
get => GetOption(ref isEmptyHistoryWishVisible, SettingEntry.IsEmptyHistoryWishVisible);
set => SetOption(ref isEmptyHistoryWishVisible, SettingEntry.IsEmptyHistoryWishVisible, value);
}
public bool IsUnobtainedWishItemVisible
{
get => GetOption(ref isUnobtainedWishItemVisible, SettingEntry.IsUnobtainedWishItemVisible, false);
set => SetOption(ref isUnobtainedWishItemVisible, SettingEntry.IsUnobtainedWishItemVisible, value);
}
public List<NameValue<BackdropType>> BackdropTypes { get; } = CollectionsNameValue.FromEnum<BackdropType>(type => type >= 0);
public BackdropType BackdropType

View File

@@ -13,6 +13,9 @@ using EntityAvatarInfo = Snap.Hutao.Model.Entity.AvatarInfo;
namespace Snap.Hutao.Service.AvatarInfo;
/// <summary>
/// 角色信息服务
/// </summary>
[HighQuality]
[ConstructorGenerated]
[Injection(InjectAs.Scoped, typeof(IAvatarInfoService))]
@@ -20,11 +23,12 @@ internal sealed partial class AvatarInfoService : IAvatarInfoService
{
private readonly AvatarInfoDbBulkOperation avatarInfoDbBulkOperation;
private readonly IAvatarInfoDbService avatarInfoDbService;
private readonly IServiceScopeFactory serviceScopeFactory;
private readonly ILogger<AvatarInfoService> logger;
private readonly IMetadataService metadataService;
private readonly ISummaryFactory summaryFactory;
private readonly EnkaClient enkaClient;
/// <inheritdoc/>
public async ValueTask<ValueResult<RefreshResult, Summary?>> GetSummaryAsync(UserAndUid userAndUid, RefreshOption refreshOption, CancellationToken token = default)
{
if (await metadataService.InitializeAsync().ConfigureAwait(false))
@@ -88,13 +92,8 @@ internal sealed partial class AvatarInfoService : IAvatarInfoService
private async ValueTask<EnkaResponse?> GetEnkaResponseAsync(PlayerUid uid, CancellationToken token = default)
{
using (IServiceScope scope = serviceScopeFactory.CreateScope())
{
EnkaClient enkaClient = scope.ServiceProvider.GetRequiredService<EnkaClient>();
return await enkaClient.GetForwardDataAsync(uid, token).ConfigureAwait(false)
?? await enkaClient.GetDataAsync(uid, token).ConfigureAwait(false);
}
return await enkaClient.GetForwardDataAsync(uid, token).ConfigureAwait(false)
?? await enkaClient.GetDataAsync(uid, token).ConfigureAwait(false);
}
private async ValueTask<Summary> GetSummaryCoreAsync(IEnumerable<EntityAvatarInfo> avatarInfos, CancellationToken token)

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 sealed class AvatarViewBuilder : IAvatarViewBuilder
{
public ViewModel.AvatarProperty.AvatarView AvatarView { get; } = new();
}

View File

@@ -1,260 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction.Extension;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.ViewModel.AvatarProperty;
namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder;
internal static class AvatarViewBuilderExtension
{
public static TBuilder ApplyCostumeIconOrDefault<TBuilder>(this TBuilder builder, Web.Enka.Model.AvatarInfo avatarInfo, Avatar avatar)
where TBuilder : IAvatarViewBuilder
{
if (avatarInfo.CostumeId.TryGetValue(out CostumeId id))
{
Costume costume = avatar.Costumes.Single(c => c.Id == id);
// Set to costume icon
builder.AvatarView.Icon = AvatarIconConverter.IconNameToUri(costume.FrontIcon);
builder.AvatarView.SideIcon = AvatarIconConverter.IconNameToUri(costume.SideIcon);
}
else
{
builder.AvatarView.Icon = AvatarIconConverter.IconNameToUri(avatar.Icon);
builder.AvatarView.SideIcon = AvatarIconConverter.IconNameToUri(avatar.SideIcon);
}
return builder;
}
public static TBuilder SetCalculatorRefreshTimeFormat<TBuilder>(this TBuilder builder, DateTimeOffset refreshTime, Func<object?, string> format, string defaultValue)
where TBuilder : IAvatarViewBuilder
{
return builder.SetCalculatorRefreshTimeFormat(refreshTime == DateTimeOffsetExtension.DatebaseDefaultTime ? defaultValue : format(refreshTime.ToLocalTime()));
}
public static TBuilder SetCalculatorRefreshTimeFormat<TBuilder>(this TBuilder builder, string calculatorRefreshTimeFormat)
where TBuilder : IAvatarViewBuilder
{
return builder.Configure(b => b.AvatarView.CalculatorRefreshTimeFormat = calculatorRefreshTimeFormat);
}
public static TBuilder SetConstellations<TBuilder>(this TBuilder builder, List<Skill> talents, List<SkillId>? talentIds)
where TBuilder : IAvatarViewBuilder
{
return builder.SetConstellations(CreateConstellations(talents, talentIds.EmptyIfNull()));
static List<ConstellationView> CreateConstellations(List<Skill> talents, List<SkillId> talentIds)
{
// TODO: use builder here
return talents.SelectList(talent => new ViewModel.AvatarProperty.ConstellationView()
{
Name = talent.Name,
Icon = SkillIconConverter.IconNameToUri(talent.Icon),
Description = talent.Description,
IsActivated = talentIds.Contains(talent.Id),
});
}
}
public static TBuilder SetConstellations<TBuilder>(this TBuilder builder, List<ConstellationView> constellations)
where TBuilder : IAvatarViewBuilder
{
return builder.Configure(b => b.AvatarView.Constellations = constellations);
}
public static TBuilder SetCritScore<TBuilder>(this TBuilder builder, Dictionary<FightProperty, float>? fightPropMap)
where TBuilder : IAvatarViewBuilder
{
return builder.SetCritScore(ScoreCrit(fightPropMap));
static float ScoreCrit(Dictionary<FightProperty, float>? fightPropMap)
{
if (fightPropMap.IsNullOrEmpty())
{
return 0F;
}
float cr = fightPropMap[FightProperty.FIGHT_PROP_CRITICAL];
float cd = fightPropMap[FightProperty.FIGHT_PROP_CRITICAL_HURT];
return 100 * ((cr * 2) + cd);
}
}
public static TBuilder SetCritScore<TBuilder>(this TBuilder builder, float critScore)
where TBuilder : IAvatarViewBuilder
{
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)
where TBuilder : IAvatarViewBuilder
{
return builder.Configure(b => b.AvatarView.Element = element);
}
public static TBuilder SetFetterLevel<TBuilder>(this TBuilder builder, FetterLevel? level)
where TBuilder : IAvatarViewBuilder
{
if (level.TryGetValue(out FetterLevel value))
{
return builder.Configure(b => b.AvatarView.FetterLevel = value);
}
return builder;
}
public static TBuilder SetFetterLevel<TBuilder>(this TBuilder builder, uint level)
where TBuilder : IAvatarViewBuilder
{
return builder.Configure(b => b.AvatarView.FetterLevel = level);
}
public static TBuilder SetGameRecordRefreshTimeFormat<TBuilder>(this TBuilder builder, DateTimeOffset refreshTime, Func<object?, string> format, string defaultValue)
where TBuilder : IAvatarViewBuilder
{
return builder.SetGameRecordRefreshTimeFormat(refreshTime == DateTimeOffsetExtension.DatebaseDefaultTime ? defaultValue : format(refreshTime.ToLocalTime()));
}
public static TBuilder SetGameRecordRefreshTimeFormat<TBuilder>(this TBuilder builder, string gameRecordRefreshTimeFormat)
where TBuilder : IAvatarViewBuilder
{
return builder.Configure(b => b.AvatarView.GameRecordRefreshTimeFormat = gameRecordRefreshTimeFormat);
}
public static TBuilder SetId<TBuilder>(this TBuilder builder, AvatarId id)
where TBuilder : IAvatarViewBuilder
{
return builder.Configure(b => b.AvatarView.Id = id);
}
public static TBuilder SetLevelNumber<TBuilder>(this TBuilder builder, uint? levelNumber)
where TBuilder : IAvatarViewBuilder
{
if (levelNumber.TryGetValue(out uint value))
{
return builder.Configure(b => b.AvatarView.LevelNumber = value);
}
return builder;
}
public static TBuilder SetName<TBuilder>(this TBuilder builder, string name)
where TBuilder : IAvatarViewBuilder
{
return builder.Configure(b => b.AvatarView.Name = name);
}
public static TBuilder SetNameCard<TBuilder>(this TBuilder builder, Uri nameCard)
where TBuilder : IAvatarViewBuilder
{
return builder.Configure(b => b.AvatarView.NameCard = nameCard);
}
public static TBuilder SetProperties<TBuilder>(this TBuilder builder, List<AvatarProperty> properties)
where TBuilder : IAvatarViewBuilder
{
return builder.Configure(b => b.AvatarView.Properties = properties);
}
public static TBuilder SetQuality<TBuilder>(this TBuilder builder, QualityType quality)
where TBuilder : IAvatarViewBuilder
{
return builder.Configure(b => b.AvatarView.Quality = quality);
}
public static TBuilder SetReliquaries<TBuilder>(this TBuilder builder, List<ReliquaryView> reliquaries)
where TBuilder : IAvatarViewBuilder
{
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)
where TBuilder : IAvatarViewBuilder
{
return builder.SetShowcaseRefreshTimeFormat(refreshTime == DateTimeOffsetExtension.DatebaseDefaultTime ? defaultValue : format(refreshTime.ToLocalTime()));
}
public static TBuilder SetShowcaseRefreshTimeFormat<TBuilder>(this TBuilder builder, string showcaseRefreshTimeFormat)
where TBuilder : IAvatarViewBuilder
{
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)
where TBuilder : IAvatarViewBuilder
{
return builder.SetSkills(CreateSkills(skillLevelMap, proudSkillExtraLevelMap, proudSkills));
static List<SkillView> CreateSkills(Dictionary<SkillId, SkillLevel>? skillLevelMap, Dictionary<SkillGroupId, SkillLevel>? proudSkillExtraLevelMap, List<ProudableSkill> proudSkills)
{
if (skillLevelMap.IsNullOrEmpty())
{
return [];
}
Dictionary<SkillId, SkillLevel> skillExtraLeveledMap = new(skillLevelMap);
if (proudSkillExtraLevelMap is not null)
{
foreach ((SkillGroupId groupId, SkillLevel extraLevel) in proudSkillExtraLevelMap)
{
skillExtraLeveledMap.IncreaseValue(proudSkills.Single(p => p.GroupId == groupId).Id, extraLevel);
}
}
return proudSkills.SelectList(proudableSkill =>
{
SkillId skillId = proudableSkill.Id;
// TODO: use builder here
return new SkillView()
{
Name = proudableSkill.Name,
Icon = SkillIconConverter.IconNameToUri(proudableSkill.Icon),
Description = proudableSkill.Description,
GroupId = proudableSkill.GroupId,
LevelNumber = skillLevelMap[skillId],
Info = DescriptionsParametersDescriptor.Convert(proudableSkill.Proud, skillExtraLeveledMap[skillId]),
};
});
}
}
public static TBuilder SetSkills<TBuilder>(this TBuilder builder, List<SkillView> skills)
where TBuilder : IAvatarViewBuilder
{
return builder.Configure(b => b.AvatarView.Skills = skills);
}
public static TBuilder SetWeapon<TBuilder>(this TBuilder builder, WeaponView? weapon)
where TBuilder : IAvatarViewBuilder
{
return builder.Configure(b => b.AvatarView.Weapon = weapon);
}
}

View File

@@ -1,11 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction;
namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder;
internal interface IAvatarViewBuilder : IBuilder
{
ViewModel.AvatarProperty.AvatarView AvatarView { get; }
}

View File

@@ -5,8 +5,17 @@ using Snap.Hutao.ViewModel.AvatarProperty;
namespace Snap.Hutao.Service.AvatarInfo.Factory;
/// <summary>
/// 简述工厂
/// </summary>
[HighQuality]
internal interface ISummaryFactory
{
/// <summary>
/// 异步创建简述对象
/// </summary>
/// <param name="avatarInfos">角色列表</param>
/// <param name="token">取消令牌</param>
/// <returns>简述对象</returns>
ValueTask<Summary> CreateAsync(IEnumerable<Model.Entity.AvatarInfo> avatarInfos, CancellationToken token);
}

View File

@@ -5,9 +5,8 @@ using Snap.Hutao.Model;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Intrinsic.Format;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Service.AvatarInfo.Factory.Builder;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Web.Enka.Model;
using System.Runtime.InteropServices;
using EntityAvatarInfo = Snap.Hutao.Model.Entity.AvatarInfo;
using MetadataAvatar = Snap.Hutao.Model.Metadata.Avatar.Avatar;
using MetadataWeapon = Snap.Hutao.Model.Metadata.Weapon.Weapon;
@@ -18,6 +17,9 @@ using PropertyWeapon = Snap.Hutao.ViewModel.AvatarProperty.WeaponView;
namespace Snap.Hutao.Service.AvatarInfo.Factory;
/// <summary>
/// 单个角色工厂
/// </summary>
[HighQuality]
internal sealed class SummaryAvatarFactory
{
@@ -25,11 +27,16 @@ internal sealed class SummaryAvatarFactory
private readonly DateTimeOffset showcaseRefreshTime;
private readonly DateTimeOffset gameRecordRefreshTime;
private readonly DateTimeOffset calculatorRefreshTime;
private readonly SummaryFactoryMetadataContext context;
private readonly SummaryMetadataContext metadataContext;
public SummaryAvatarFactory(SummaryFactoryMetadataContext context, EntityAvatarInfo avatarInfo)
/// <summary>
/// 构造一个新的角色工厂
/// </summary>
/// <param name="metadataContext">元数据上下文</param>
/// <param name="avatarInfo">角色信息</param>
public SummaryAvatarFactory(SummaryMetadataContext metadataContext, EntityAvatarInfo avatarInfo)
{
this.context = context;
this.metadataContext = metadataContext;
this.avatarInfo = avatarInfo.Info;
showcaseRefreshTime = avatarInfo.ShowcaseRefreshTime;
@@ -37,51 +44,84 @@ internal sealed class SummaryAvatarFactory
calculatorRefreshTime = avatarInfo.CalculatorRefreshTime;
}
public static PropertyAvatar Create(SummaryFactoryMetadataContext context, EntityAvatarInfo avatarInfo)
{
return new SummaryAvatarFactory(context, avatarInfo).Create();
}
/// <summary>
/// 创建角色
/// </summary>
/// <returns>角色</returns>
public PropertyAvatar Create()
{
ReliquaryAndWeapon reliquaryAndWeapon = ProcessEquip(avatarInfo.EquipList.EmptyIfNull());
MetadataAvatar avatar = context.IdAvatarMap[avatarInfo.AvatarId];
MetadataAvatar avatar = metadataContext.IdAvatarMap[avatarInfo.AvatarId];
PropertyAvatar propertyAvatar = new AvatarViewBuilder()
.SetId(avatar.Id)
.SetName(avatar.Name)
.SetQuality(avatar.Quality)
.SetNameCard(AvatarNameCardPicConverter.AvatarToUri(avatar))
.SetElement(ElementNameIconConverter.ElementNameToElementType(avatar.FetterInfo.VisionBefore))
.SetConstellations(avatar.SkillDepot.Talents, avatarInfo.TalentIdList)
.SetSkills(avatarInfo.SkillLevelMap, avatarInfo.ProudSkillExtraLevelMap, avatar.SkillDepot.CompositeSkillsNoInherents())
.SetFetterLevel(avatarInfo.FetterInfo?.ExpLevel)
.SetProperties(SummaryAvatarProperties.Create(avatarInfo.FightPropMap))
.SetCritScore(avatarInfo.FightPropMap)
.SetLevelNumber(avatarInfo.PropMap?[PlayerProperty.PROP_LEVEL].Value)
.SetWeapon(reliquaryAndWeapon.Weapon)
.SetReliquaries(reliquaryAndWeapon.Reliquaries)
.SetScore(reliquaryAndWeapon.Reliquaries.Sum(r => r.Score))
.SetShowcaseRefreshTimeFormat(showcaseRefreshTime, SH.FormatServiceAvatarInfoSummaryShowcaseRefreshTimeFormat, SH.ServiceAvatarInfoSummaryShowcaseNotRefreshed)
.SetGameRecordRefreshTimeFormat(gameRecordRefreshTime, SH.FormatServiceAvatarInfoSummaryGameRecordRefreshTimeFormat, SH.ServiceAvatarInfoSummaryGameRecordNotRefreshed)
.SetCalculatorRefreshTimeFormat(calculatorRefreshTime, SH.FormatServiceAvatarInfoSummaryCalculatorRefreshTimeFormat, SH.ServiceAvatarInfoSummaryCalculatorNotRefreshed)
.ApplyCostumeIconOrDefault(avatarInfo, avatar)
.AvatarView;
PropertyAvatar propertyAvatar = new()
{
// metadata part
Id = avatar.Id,
Name = avatar.Name,
Quality = avatar.Quality,
NameCard = AvatarNameCardPicConverter.AvatarToUri(avatar),
Element = ElementNameIconConverter.ElementNameToElementType(avatar.FetterInfo.VisionBefore),
// webinfo & metadata mixed part
Constellations = SummaryHelper.CreateConstellations(avatar.SkillDepot.Talents, avatarInfo.TalentIdList),
Skills = SummaryHelper.CreateSkills(avatarInfo.SkillLevelMap, avatarInfo.ProudSkillExtraLevelMap, avatar.SkillDepot.CompositeSkillsNoInherents()),
// webinfo part
FetterLevel = avatarInfo.FetterInfo?.ExpLevel ?? 0U,
Properties = SummaryAvatarProperties.Create(avatarInfo.FightPropMap),
CritScore = $"{SummaryHelper.ScoreCrit(avatarInfo.FightPropMap):F2}",
LevelNumber = avatarInfo.PropMap?[PlayerProperty.PROP_LEVEL].Value ?? 0U,
// processed webinfo part
Weapon = reliquaryAndWeapon.Weapon,
Reliquaries = reliquaryAndWeapon.Reliquaries,
Score = $"{reliquaryAndWeapon.Reliquaries.Sum(r => r.Score):F2}",
// times
ShowcaseRefreshTimeFormat = showcaseRefreshTime == DateTimeOffsetExtension.DatebaseDefaultTime
? SH.ServiceAvatarInfoSummaryShowcaseNotRefreshed
: SH.FormatServiceAvatarInfoSummaryShowcaseRefreshTimeFormat(showcaseRefreshTime.ToLocalTime()),
GameRecordRefreshTimeFormat = gameRecordRefreshTime == DateTimeOffsetExtension.DatebaseDefaultTime
? SH.ServiceAvatarInfoSummaryGameRecordNotRefreshed
: SH.FormatServiceAvatarInfoSummaryGameRecordRefreshTimeFormat(gameRecordRefreshTime.ToLocalTime()),
CalculatorRefreshTimeFormat = calculatorRefreshTime == DateTimeOffsetExtension.DatebaseDefaultTime
? SH.ServiceAvatarInfoSummaryCalculatorNotRefreshed
: SH.FormatServiceAvatarInfoSummaryCalculatorRefreshTimeFormat(calculatorRefreshTime.ToLocalTime()),
};
ApplyCostumeIconOrDefault(ref propertyAvatar, avatar);
return propertyAvatar;
}
private void ApplyCostumeIconOrDefault(ref PropertyAvatar propertyAvatar, MetadataAvatar avatar)
{
if (avatarInfo.CostumeId.TryGetValue(out CostumeId id))
{
Model.Metadata.Avatar.Costume costume = avatar.Costumes.Single(c => c.Id == id);
// Set to costume icon
propertyAvatar.Icon = AvatarIconConverter.IconNameToUri(costume.FrontIcon);
propertyAvatar.SideIcon = AvatarIconConverter.IconNameToUri(costume.SideIcon);
}
else
{
propertyAvatar.Icon = AvatarIconConverter.IconNameToUri(avatar.Icon);
propertyAvatar.SideIcon = AvatarIconConverter.IconNameToUri(avatar.SideIcon);
}
}
private ReliquaryAndWeapon ProcessEquip(List<Equip> equipments)
{
List<PropertyReliquary> reliquaryList = [];
PropertyWeapon? weapon = null;
foreach (ref readonly Equip equip in CollectionsMarshal.AsSpan(equipments))
foreach (Equip equip in equipments)
{
switch (equip.Flat.ItemType)
{
case ItemType.ITEM_RELIQUARY:
reliquaryList.Add(SummaryReliquaryFactory.Create(context, avatarInfo, equip));
SummaryReliquaryFactory summaryReliquaryFactory = new(metadataContext, avatarInfo, equip);
reliquaryList.Add(summaryReliquaryFactory.CreateReliquary());
break;
case ItemType.ITEM_WEAPON:
weapon = CreateWeapon(equip);
@@ -94,7 +134,7 @@ internal sealed class SummaryAvatarFactory
private PropertyWeapon CreateWeapon(Equip equip)
{
MetadataWeapon weapon = context.IdWeaponMap[equip.ItemId];
MetadataWeapon weapon = metadataContext.IdWeaponMap[equip.ItemId];
// AffixMap can be null when it's a white weapon.
ArgumentNullException.ThrowIfNull(equip.Weapon);
@@ -128,9 +168,7 @@ internal sealed class SummaryAvatarFactory
// EquipBase
Level = $"Lv.{equip.Weapon.Level.Value}",
Quality = weapon.Quality,
MainProperty = mainStat is not null
? FightPropertyFormat.ToNameValue(mainStat.AppendPropId, mainStat.StatValue)
: NameValueDefaults.String,
MainProperty = mainStat is not null ? FightPropertyFormat.ToNameValue(mainStat.AppendPropId, mainStat.StatValue) : NameValueDefaults.String,
// Weapon
Id = weapon.Id,

View File

@@ -3,7 +3,6 @@
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.Metadata.ContextAbstraction;
using Snap.Hutao.ViewModel.AvatarProperty;
namespace Snap.Hutao.Service.AvatarInfo.Factory;
@@ -21,18 +20,23 @@ internal sealed partial class SummaryFactory : ISummaryFactory
/// <inheritdoc/>
public async ValueTask<Summary> CreateAsync(IEnumerable<Model.Entity.AvatarInfo> avatarInfos, CancellationToken token)
{
SummaryFactoryMetadataContext context = await metadataService
.GetContextAsync<SummaryFactoryMetadataContext>(token)
.ConfigureAwait(false);
SummaryMetadataContext metadataContext = new()
{
IdAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false),
IdWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false),
IdReliquaryAffixWeightMap = await metadataService.GetIdToReliquaryAffixWeightMapAsync(token).ConfigureAwait(false),
IdReliquaryMainAffixMap = await metadataService.GetIdToReliquaryMainPropertyMapAsync(token).ConfigureAwait(false),
IdReliquarySubAffixMap = await metadataService.GetIdToReliquarySubAffixMapAsync(token).ConfigureAwait(false),
ReliquaryLevels = await metadataService.GetReliquaryLevelListAsync(token).ConfigureAwait(false),
Reliquaries = await metadataService.GetReliquaryListAsync(token).ConfigureAwait(false),
};
IOrderedEnumerable<AvatarView> avatars = avatarInfos
.Where(a => !AvatarIds.IsPlayer(a.Info.AvatarId))
.Select(a => SummaryAvatarFactory.Create(context, a))
.Select(a => new SummaryAvatarFactory(metadataContext, a).Create())
.OrderByDescending(a => a.LevelNumber)
.ThenByDescending(a => a.FetterLevel)
.ThenBy(a => a.Element);
.ThenBy(a => a.Name);
// TODO: thenby weapon type
return new()
{
Avatars = [.. avatars],

View File

@@ -1,7 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.ViewModel.AvatarProperty;
namespace Snap.Hutao.Service.AvatarInfo.Factory;
@@ -11,6 +15,66 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory;
[HighQuality]
internal static class SummaryHelper
{
/// <summary>
/// 创建命之座
/// </summary>
/// <param name="talents">全部命座</param>
/// <param name="talentIds">激活的命座列表</param>
/// <returns>命之座</returns>
public static List<ConstellationView> CreateConstellations(List<Skill> talents, List<SkillId>? talentIds)
{
talentIds ??= [];
return talents.SelectList(talent => new ConstellationView()
{
Name = talent.Name,
Icon = SkillIconConverter.IconNameToUri(talent.Icon),
Description = talent.Description,
IsActivated = talentIds.Contains(talent.Id),
});
}
/// <summary>
/// 创建技能组
/// </summary>
/// <param name="skillLevelMap">技能等级映射</param>
/// <param name="proudSkillExtraLevelMap">额外提升等级映射</param>
/// <param name="proudSkills">技能列表</param>
/// <returns>技能</returns>
public static List<SkillView> CreateSkills(Dictionary<SkillId, SkillLevel>? skillLevelMap, Dictionary<SkillGroupId, SkillLevel>? proudSkillExtraLevelMap, List<ProudableSkill> proudSkills)
{
if (skillLevelMap.IsNullOrEmpty())
{
return [];
}
Dictionary<SkillId, SkillLevel> skillExtraLeveledMap = new(skillLevelMap);
if (proudSkillExtraLevelMap is not null)
{
foreach ((SkillGroupId groupId, SkillLevel extraLevel) in proudSkillExtraLevelMap)
{
skillExtraLeveledMap.IncreaseValue(proudSkills.Single(p => p.GroupId == groupId).Id, extraLevel);
}
}
return proudSkills.SelectList(proudableSkill =>
{
SkillId skillId = proudableSkill.Id;
return new SkillView()
{
Name = proudableSkill.Name,
Icon = SkillIconConverter.IconNameToUri(proudableSkill.Icon),
Description = proudableSkill.Description,
GroupId = proudableSkill.GroupId,
LevelNumber = skillLevelMap[skillId],
Info = DescriptionsParametersDescriptor.Convert(proudableSkill.Proud, skillExtraLeveledMap[skillId]),
};
});
}
/// <summary>
/// 获取副属性对应的最大属性的Id
/// </summary>
@@ -68,4 +132,22 @@ internal static class SummaryHelper
_ => throw Must.NeverHappen($"Unexpected AppendId: {appendId.Value} Delta: {delta}"),
};
}
/// <summary>
/// 获取双爆评分
/// </summary>
/// <param name="fightPropMap">属性</param>
/// <returns>评分</returns>
public static float ScoreCrit(Dictionary<FightProperty, float>? fightPropMap)
{
if (fightPropMap.IsNullOrEmpty())
{
return 0F;
}
float cr = fightPropMap[FightProperty.FIGHT_PROP_CRITICAL];
float cd = fightPropMap[FightProperty.FIGHT_PROP_CRITICAL_HURT];
return 100 * ((cr * 2) + cd);
}
}

View File

@@ -4,33 +4,51 @@
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.Metadata.ContextAbstraction;
using MetadataAvatar = Snap.Hutao.Model.Metadata.Avatar.Avatar;
using MetadataReliquary = Snap.Hutao.Model.Metadata.Reliquary.Reliquary;
using MetadataWeapon = Snap.Hutao.Model.Metadata.Weapon.Weapon;
namespace Snap.Hutao.Service.AvatarInfo.Factory;
/// <summary>
/// 简述元数据上下文
/// 包含了所有制造简述需要的元数据
/// </summary>
[HighQuality]
internal sealed class SummaryFactoryMetadataContext : IMetadataContext,
IMetadataDictionaryIdAvatarSource,
IMetadataDictionaryIdWeaponSource,
IMetadataDictionaryIdReliquaryAffixWeightSource,
IMetadataDictionaryIdReliquaryMainPropertySource,
IMetadataDictionaryIdReliquarySubAffixSource,
IMetadataListReliquaryMainAffixLevelSource
internal sealed class SummaryMetadataContext
{
/// <summary>
/// 角色映射
/// </summary>
public Dictionary<AvatarId, MetadataAvatar> IdAvatarMap { get; set; } = default!;
/// <summary>
/// 武器映射
/// </summary>
public Dictionary<WeaponId, MetadataWeapon> IdWeaponMap { get; set; } = default!;
/// <summary>
/// 权重映射
/// </summary>
public Dictionary<AvatarId, ReliquaryAffixWeight> IdReliquaryAffixWeightMap { get; set; } = default!;
public Dictionary<ReliquaryMainAffixId, FightProperty> IdReliquaryMainPropertyMap { get; set; } = default!;
/// <summary>
/// 圣遗物主属性映射
/// </summary>
public Dictionary<ReliquaryMainAffixId, FightProperty> IdReliquaryMainAffixMap { get; set; } = default!;
/// <summary>
/// 圣遗物副属性映射
/// </summary>
public Dictionary<ReliquarySubAffixId, ReliquarySubAffix> IdReliquarySubAffixMap { get; set; } = default!;
public List<ReliquaryMainAffixLevel> ReliquaryMainAffixLevels { get; set; } = default!;
/// <summary>
/// 圣遗物等级
/// </summary>
public List<ReliquaryMainAffixLevel> ReliquaryLevels { get; set; } = default!;
/// <summary>
/// 圣遗物
/// </summary>
public List<MetadataReliquary> Reliquaries { get; set; } = default!;
}

View File

@@ -19,23 +19,28 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory;
[HighQuality]
internal sealed class SummaryReliquaryFactory
{
private readonly SummaryFactoryMetadataContext metadataContext;
private readonly SummaryMetadataContext metadataContext;
private readonly ModelAvatarInfo avatarInfo;
private readonly Web.Enka.Model.Equip equip;
public SummaryReliquaryFactory(SummaryFactoryMetadataContext metadataContext, ModelAvatarInfo avatarInfo, Web.Enka.Model.Equip equip)
/// <summary>
/// 构造一个新的圣遗物工厂
/// </summary>
/// <param name="metadataContext">元数据上下文</param>
/// <param name="avatarInfo">角色信息</param>
/// <param name="equip">圣遗物</param>
public SummaryReliquaryFactory(SummaryMetadataContext metadataContext, ModelAvatarInfo avatarInfo, Web.Enka.Model.Equip equip)
{
this.metadataContext = metadataContext;
this.avatarInfo = avatarInfo;
this.equip = equip;
}
public static ReliquaryView Create(SummaryFactoryMetadataContext metadataContext, ModelAvatarInfo avatarInfo, Web.Enka.Model.Equip equip)
{
return new SummaryReliquaryFactory(metadataContext, avatarInfo, equip).Create();
}
public ReliquaryView Create()
/// <summary>
/// 构造圣遗物
/// </summary>
/// <returns>圣遗物</returns>
public ReliquaryView CreateReliquary()
{
MetadataReliquary reliquary = metadataContext.Reliquaries.Single(r => r.Ids.Contains(equip.ItemId));
@@ -64,8 +69,8 @@ internal sealed class SummaryReliquaryFactory
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);
FightProperty property = metadataContext.IdReliquaryMainPropertyMap[equip.Reliquary.MainPropId];
ReliquaryMainAffixLevel relicLevel = metadataContext.ReliquaryLevels.Single(r => r.Level == equip.Reliquary.Level && r.Rank == reliquary.RankLevel);
FightProperty property = metadataContext.IdReliquaryMainAffixMap[equip.Reliquary.MainPropId];
result.MainProperty = FightPropertyFormat.ToNameValue(property, relicLevel.PropertyMap[property]);
result.Score = ScoreReliquary(property, reliquary, relicLevel, subProperty);
@@ -141,7 +146,7 @@ internal sealed class SummaryReliquaryFactory
// 从喵插件抓取的圣遗物评分权重
// 部分复杂的角色暂时使用了默认值
ReliquaryAffixWeight affixWeight = metadataContext.IdReliquaryAffixWeightMap.GetValueOrDefault(avatarInfo.AvatarId, ReliquaryAffixWeight.Default);
ReliquaryMainAffixLevel? maxRelicLevel = metadataContext.ReliquaryMainAffixLevels.Where(r => r.Rank == reliquary.RankLevel).MaxBy(r => r.Level);
ReliquaryMainAffixLevel? maxRelicLevel = metadataContext.ReliquaryLevels.Where(r => r.Rank == reliquary.RankLevel).MaxBy(r => r.Level);
ArgumentNullException.ThrowIfNull(maxRelicLevel);
float percent = relicLevel.PropertyMap[property] / maxRelicLevel.PropertyMap[property];

View File

@@ -46,17 +46,11 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient<U
{
DailyNoteEntry newEntry = DailyNoteEntry.From(userAndUid);
Web.Response.Response<WebDailyNote> dailyNoteResponse;
using (IServiceScope scope = serviceProvider.CreateScope())
{
IGameRecordClient gameRecordClient = scope.ServiceProvider
.GetRequiredService<IOverseaSupportFactory<IGameRecordClient>>()
.Create(PlayerUid.IsOversea(roleUid));
dailyNoteResponse = await gameRecordClient
.GetDailyNoteAsync(userAndUid)
.ConfigureAwait(false);
}
Web.Response.Response<WebDailyNote> dailyNoteResponse = await serviceProvider
.GetRequiredService<IOverseaSupportFactory<IGameRecordClient>>()
.Create(PlayerUid.IsOversea(roleUid))
.GetDailyNoteAsync(userAndUid)
.ConfigureAwait(false);
if (dailyNoteResponse.IsOk())
{
@@ -123,17 +117,11 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient<U
continue;
}
Web.Response.Response<WebDailyNote> dailyNoteResponse;
using (IServiceScope scope = serviceProvider.CreateScope())
{
IGameRecordClient gameRecordClient = serviceProvider
.GetRequiredService<IOverseaSupportFactory<IGameRecordClient>>()
.Create(PlayerUid.IsOversea(entry.Uid));
dailyNoteResponse = await gameRecordClient
.GetDailyNoteAsync(new(entry.User, entry.Uid))
.ConfigureAwait(false);
}
Web.Response.Response<WebDailyNote> dailyNoteResponse = await serviceProvider
.GetRequiredService<IOverseaSupportFactory<IGameRecordClient>>()
.Create(PlayerUid.IsOversea(entry.Uid))
.GetDailyNoteAsync(new(entry.User, entry.Uid))
.ConfigureAwait(false);
if (dailyNoteResponse.IsOk())
{

View File

@@ -3,7 +3,6 @@
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Service.Metadata;
@@ -11,7 +10,6 @@ using Snap.Hutao.Service.Metadata.ContextAbstraction;
using Snap.Hutao.ViewModel.GachaLog;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using Snap.Hutao.Web.Hutao.GachaLog;
using System.Collections.Frozen;
using System.Runtime.InteropServices;
namespace Snap.Hutao.Service.GachaLog.Factory;
@@ -24,24 +22,7 @@ namespace Snap.Hutao.Service.GachaLog.Factory;
[Injection(InjectAs.Scoped, typeof(IGachaStatisticsFactory))]
internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
{
private static readonly FrozenSet<uint> BlueStandardWeaponIdsSet = FrozenSet.ToFrozenSet(
[
11301U, 11302U, 11306U,
12301U, 12302U, 12305U,
13303U,
14301U, 14302U, 14304U,
15301U, 15302U, 15304U
]);
private static readonly FrozenSet<uint> PurpleStandardWeaponIdsSet = FrozenSet.ToFrozenSet(
[
11401U, 11402U, 11403U, 11405U,
12401U, 12402U, 12403U, 12405U,
13401U, 13407U,
14401U, 14402U, 14403U, 14409U,
15401U, 15402U, 15403U, 15405U
]);
private readonly IMetadataService metadataService;
private readonly HomaGachaLogClient homaGachaLogClient;
private readonly ITaskContext taskContext;
private readonly AppOptions options;
@@ -52,7 +33,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
await taskContext.SwitchToBackgroundAsync();
List<HistoryWishBuilder> historyWishBuilders = context.GachaEvents.SelectList(gachaEvent => new HistoryWishBuilder(gachaEvent, context));
return CreateCore(taskContext, homaGachaLogClient, items, historyWishBuilders, context, options);
return CreateCore(taskContext, homaGachaLogClient, items, historyWishBuilders, context, options.IsEmptyHistoryWishVisible);
}
private static GachaStatistics CreateCore(
@@ -61,7 +42,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
List<Model.Entity.GachaItem> items,
List<HistoryWishBuilder> historyWishBuilders,
in GachaLogServiceMetadataContext context,
AppOptions appOptions)
bool isEmptyHistoryWishVisible)
{
TypedWishSummaryBuilderContext standardContext = TypedWishSummaryBuilderContext.StandardWish(taskContext, gachaLogClient);
TypedWishSummaryBuilder standardWishBuilder = new(standardContext);
@@ -81,45 +62,6 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
Dictionary<Weapon, int> purpleWeaponCounter = [];
Dictionary<Weapon, int> blueWeaponCounter = [];
if (appOptions.IsUnobtainedWishItemVisible)
{
orangeAvatarCounter = context.IdAvatarMap.Values
.Where(avatar => avatar.Quality == QualityType.QUALITY_ORANGE)
.ToDictionary(avatar => avatar, _ => 0);
purpleAvatarCounter = context.IdAvatarMap.Values
.Where(avatar => avatar.Quality == QualityType.QUALITY_PURPLE)
.ToDictionary(avatar => avatar, _ => 0);
orangeWeaponCounter = context.IdWeaponMap.Values
.Where(weapon => weapon.Quality == QualityType.QUALITY_ORANGE)
.ToDictionary(weapon => weapon, _ => 0);
HashSet<Weapon> purpleWeapons = [];
foreach (uint weaponId in PurpleStandardWeaponIdsSet)
{
purpleWeapons.Add(context.IdWeaponMap[weaponId]);
}
foreach (GachaEvent gachaEvent in context.GachaEvents)
{
if (gachaEvent.Type is GachaType.ActivityWeapon)
{
foreach (uint weaponId in gachaEvent.UpPurpleList)
{
purpleWeapons.Add(context.IdWeaponMap[weaponId]);
}
}
}
HashSet<Weapon> blueWeapons = [];
foreach (uint weaponId in BlueStandardWeaponIdsSet)
{
blueWeapons.Add(context.IdWeaponMap[weaponId]);
}
purpleWeaponCounter = purpleWeapons.ToDictionary(weapon => weapon, _ => 0);
blueWeaponCounter = blueWeapons.ToDictionary(weapon => weapon, _ => 0);
}
// Pre group builders
Dictionary<GachaType, List<HistoryWishBuilder>> historyWishBuilderMap = historyWishBuilders
.GroupBy(b => b.ConfigType)
@@ -206,7 +148,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
{
// history
HistoryWishes = historyWishBuilders
.Where(b => appOptions.IsEmptyHistoryWishVisible || (!b.IsEmpty))
.Where(b => isEmptyHistoryWishVisible || (!b.IsEmpty))
.OrderByDescending(builder => builder.From)
.ThenBy(builder => builder.ConfigType, GachaTypeComparer.Shared)
.Select(builder => builder.ToHistoryWish())

View File

@@ -19,7 +19,7 @@ internal sealed class HutaoStatisticsFactory
private readonly GachaEvent avatarEvent;
private readonly GachaEvent avatarEvent2;
private readonly GachaEvent weaponEvent;
private readonly GachaEvent? chronicledEvent;
private readonly GachaEvent chronicledEvent;
public HutaoStatisticsFactory(in HutaoStatisticsFactoryMetadataContext context)
{
@@ -32,7 +32,7 @@ internal sealed class HutaoStatisticsFactory
avatarEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaType.ActivityAvatar);
avatarEvent2 = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaType.SpecialActivityAvatar);
weaponEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaType.ActivityWeapon);
chronicledEvent = context.GachaEvents.SingleOrDefault(g => g.From < now && g.To > now && g.Type == GachaType.ActivityCity);
chronicledEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaType.ActivityCity);
}
public HutaoStatistics Create(GachaEventStatistics raw)
@@ -42,7 +42,7 @@ internal sealed class HutaoStatisticsFactory
AvatarEvent = CreateWishSummary(avatarEvent, raw.AvatarEvent),
AvatarEvent2 = CreateWishSummary(avatarEvent2, raw.AvatarEvent2),
WeaponEvent = CreateWishSummary(weaponEvent, raw.WeaponEvent),
Chronicled = chronicledEvent is null ? null : CreateWishSummary(chronicledEvent, raw.Chronicled),
Chronicled = CreateWishSummary(chronicledEvent, raw.Chronicled),
};
}

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 IMetadataDictionaryIdReliquaryAffixWeightSource
{
public Dictionary<AvatarId, ReliquaryAffixWeight> IdReliquaryAffixWeightMap { get; set; }
}

View File

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

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 IMetadataDictionaryIdReliquarySubAffixSource
{
public Dictionary<ReliquarySubAffixId, ReliquarySubAffix> IdReliquarySubAffixMap { get; set; }
}

View File

@@ -1,11 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Metadata.Reliquary;
namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataListReliquaryMainAffixLevelSource
{
public List<ReliquaryMainAffixLevel> ReliquaryMainAffixLevels { get; set; }
}

View File

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

View File

@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Item;
using Snap.Hutao.Model.Metadata.Weapon;
@@ -33,25 +32,10 @@ internal static class MetadataServiceContextExtension
{
listMaterialSource.Materials = await metadataService.GetMaterialListAsync(token).ConfigureAwait(false);
}
if (context is IMetadataListReliquaryMainAffixLevelSource listReliquaryMainAffixLevelSource)
{
listReliquaryMainAffixLevelSource.ReliquaryMainAffixLevels = await metadataService.GetReliquaryMainAffixLevelListAsync(token).ConfigureAwait(false);
}
if (context is IMetadataListReliquarySource listReliquarySource)
{
listReliquarySource.Reliquaries = await metadataService.GetReliquaryListAsync(token).ConfigureAwait(false);
}
}
// Dictionary
{
if (context is IMetadataDictionaryIdAchievementSource dictionaryIdAchievementSource)
{
dictionaryIdAchievementSource.IdAchievementMap = await metadataService.GetIdToAchievementMapAsync(token).ConfigureAwait(false);
}
if (context is IMetadataDictionaryIdAvatarSource dictionaryIdAvatarSource)
{
dictionaryIdAvatarSource.IdAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false);
@@ -62,21 +46,6 @@ internal static class MetadataServiceContextExtension
dictionaryIdMaterialSource.IdMaterialMap = await metadataService.GetIdToMaterialMapAsync(token).ConfigureAwait(false);
}
if (context is IMetadataDictionaryIdReliquaryAffixWeightSource dictionaryIdReliquaryAffixWeightSource)
{
dictionaryIdReliquaryAffixWeightSource.IdReliquaryAffixWeightMap = await metadataService.GetIdToReliquaryAffixWeightMapAsync(token).ConfigureAwait(false);
}
if (context is IMetadataDictionaryIdReliquaryMainPropertySource dictionaryIdReliquaryMainPropertySource)
{
dictionaryIdReliquaryMainPropertySource.IdReliquaryMainPropertyMap = await metadataService.GetIdToReliquaryMainPropertyMapAsync(token).ConfigureAwait(false);
}
if (context is IMetadataDictionaryIdReliquarySubAffixSource dictionaryIdReliquarySubAffixSource)
{
dictionaryIdReliquarySubAffixSource.IdReliquarySubAffixMap = await metadataService.GetIdToReliquarySubAffixMapAsync(token).ConfigureAwait(false);
}
if (context is IMetadataDictionaryIdWeaponSource dictionaryIdWeaponSource)
{
dictionaryIdWeaponSource.IdWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false);

View File

@@ -80,7 +80,7 @@ internal static class MetadataServiceListExtension
return metadataService.FromCacheOrFileAsync<List<ReliquaryMainAffix>>(FileNameReliquaryMainAffix, token);
}
public static ValueTask<List<ReliquaryMainAffixLevel>> GetReliquaryMainAffixLevelListAsync(this IMetadataService metadataService, CancellationToken token = default)
public static ValueTask<List<ReliquaryMainAffixLevel>> GetReliquaryLevelListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<ReliquaryMainAffixLevel>>(FileNameReliquaryMainAffixLevel, token);
}

View File

@@ -16,22 +16,31 @@ internal sealed partial class SignInService : ISignInService
public async ValueTask<ValueResult<bool, string>> ClaimRewardAsync(UserAndUid userAndUid, CancellationToken token = default)
{
using (IServiceScope scope = serviceProvider.CreateScope())
ISignInClient signInClient = serviceProvider
.GetRequiredService<IOverseaSupportFactory<ISignInClient>>()
.Create(userAndUid.User.IsOversea);
Response<Reward> rewardResponse = await signInClient.GetRewardAsync(userAndUid.User, token).ConfigureAwait(false);
if (rewardResponse.IsOk())
{
ISignInClient signInClient = scope.ServiceProvider
.GetRequiredService<IOverseaSupportFactory<ISignInClient>>()
.Create(userAndUid.User.IsOversea);
Response<Reward> rewardResponse = await signInClient.GetRewardAsync(userAndUid.User, token).ConfigureAwait(false);
if (!rewardResponse.IsOk())
{
return new(false, SH.ServiceSignInRewardListRequestFailed);
}
Response<SignInResult> resultResponse = await signInClient.SignAsync(userAndUid, token).ConfigureAwait(false);
if (!resultResponse.IsOk(showInfoBar: false))
if (resultResponse.IsOk(showInfoBar: false))
{
Response<SignInRewardInfo> infoResponse = await signInClient.GetInfoAsync(userAndUid, token).ConfigureAwait(false);
if (infoResponse.IsOk())
{
int index = infoResponse.Data.TotalSignDay - 1;
Award award = rewardResponse.Data.Awards[index];
return new(true, SH.FormatServiceSignInSuccessRewardFormat(award.Name, award.Count));
}
else
{
return new(false, SH.ServiceSignInInfoRequestFailed);
}
}
else
{
string message = resultResponse.Message;
@@ -47,16 +56,10 @@ internal sealed partial class SignInService : ISignInService
return new(false, SH.FormatServiceSignInClaimRewardFailedFormat(message));
}
Response<SignInRewardInfo> infoResponse = await signInClient.GetInfoAsync(userAndUid, token).ConfigureAwait(false);
if (!infoResponse.IsOk())
{
return new(false, SH.ServiceSignInInfoRequestFailed);
}
int index = infoResponse.Data.TotalSignDay - 1;
Award award = rewardResponse.Data.Awards[index];
return new(true, SH.FormatServiceSignInSuccessRewardFormat(award.Name, award.Count));
}
else
{
return new(false, SH.ServiceSignInRewardListRequestFailed);
}
}
}

View File

@@ -21,9 +21,8 @@ namespace Snap.Hutao.Service.SpiralAbyss;
[Injection(InjectAs.Scoped, typeof(ISpiralAbyssRecordService))]
internal sealed partial class SpiralAbyssRecordService : ISpiralAbyssRecordService
{
//private readonly IOverseaSupportFactory<IGameRecordClient> gameRecordClientFactory;
private readonly IOverseaSupportFactory<IGameRecordClient> gameRecordClientFactory;
private readonly ISpiralAbyssRecordDbService spiralAbyssRecordDbService;
private readonly IServiceScopeFactory serviceScopeFactory;
private readonly IMetadataService metadataService;
private readonly ITaskContext taskContext;
@@ -77,69 +76,54 @@ internal sealed partial class SpiralAbyssRecordService : ISpiralAbyssRecordServi
/// <inheritdoc/>
public async ValueTask RefreshSpiralAbyssAsync(UserAndUid userAndUid)
{
using (IServiceScope scope = serviceScopeFactory.CreateScope())
{
IOverseaSupportFactory<IGameRecordClient> gameRecordClientFactory = scope.ServiceProvider.GetRequiredService<IOverseaSupportFactory<IGameRecordClient>>();
// request the index first
await gameRecordClientFactory
.Create(userAndUid.User.IsOversea)
.GetPlayerInfoAsync(userAndUid)
.ConfigureAwait(false);
// request the index first
await gameRecordClientFactory
.Create(userAndUid.User.IsOversea)
.GetPlayerInfoAsync(userAndUid)
.ConfigureAwait(false);
await RefreshSpiralAbyssCoreAsync(userAndUid, SpiralAbyssSchedule.Last).ConfigureAwait(false);
await RefreshSpiralAbyssCoreAsync(userAndUid, SpiralAbyssSchedule.Current).ConfigureAwait(false);
}
await RefreshSpiralAbyssCoreAsync(userAndUid, SpiralAbyssSchedule.Last).ConfigureAwait(false);
await RefreshSpiralAbyssCoreAsync(userAndUid, SpiralAbyssSchedule.Current).ConfigureAwait(false);
}
private async ValueTask RefreshSpiralAbyssCoreAsync(UserAndUid userAndUid, SpiralAbyssSchedule schedule)
{
Response<Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss> response;
using (IServiceScope scope = serviceScopeFactory.CreateScope())
Response<Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss> response = await gameRecordClientFactory
.Create(userAndUid.User.IsOversea)
.GetSpiralAbyssAsync(userAndUid, schedule)
.ConfigureAwait(false);
if (response.IsOk())
{
IOverseaSupportFactory<IGameRecordClient> gameRecordClientFactory = scope.ServiceProvider.GetRequiredService<IOverseaSupportFactory<IGameRecordClient>>();
Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss webSpiralAbyss = response.Data;
response = await gameRecordClientFactory
.Create(userAndUid.User.IsOversea)
.GetSpiralAbyssAsync(userAndUid, schedule)
.ConfigureAwait(false);
ArgumentNullException.ThrowIfNull(spiralAbysses);
ArgumentNullException.ThrowIfNull(metadataContext);
int index = spiralAbysses.FirstIndexOf(s => s.ScheduleId == webSpiralAbyss.ScheduleId);
if (index >= 0)
{
await taskContext.SwitchToBackgroundAsync();
SpiralAbyssView view = spiralAbysses[index];
SpiralAbyssEntry targetEntry;
if (view.Entity is not null)
{
view.Entity.SpiralAbyss = webSpiralAbyss;
await spiralAbyssRecordDbService.UpdateSpiralAbyssEntryAsync(view.Entity).ConfigureAwait(false);
targetEntry = view.Entity;
}
else
{
SpiralAbyssEntry newEntry = SpiralAbyssEntry.From(userAndUid.Uid.Value, webSpiralAbyss);
await spiralAbyssRecordDbService.AddSpiralAbyssEntryAsync(newEntry).ConfigureAwait(false);
targetEntry = newEntry;
}
await taskContext.SwitchToMainThreadAsync();
spiralAbysses.RemoveAt(index);
spiralAbysses.Insert(index, SpiralAbyssView.From(targetEntry, metadataContext));
}
}
if (!response.IsOk())
{
return;
}
Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss webSpiralAbyss = response.Data;
ArgumentNullException.ThrowIfNull(spiralAbysses);
ArgumentNullException.ThrowIfNull(metadataContext);
int index = spiralAbysses.FirstIndexOf(s => s.ScheduleId == webSpiralAbyss.ScheduleId);
if (index < 0)
{
return;
}
await taskContext.SwitchToBackgroundAsync();
SpiralAbyssView view = spiralAbysses[index];
SpiralAbyssEntry targetEntry;
if (view.Entity is not null)
{
view.Entity.SpiralAbyss = webSpiralAbyss;
await spiralAbyssRecordDbService.UpdateSpiralAbyssEntryAsync(view.Entity).ConfigureAwait(false);
targetEntry = view.Entity;
}
else
{
SpiralAbyssEntry newEntry = SpiralAbyssEntry.From(userAndUid.Uid.Value, webSpiralAbyss);
await spiralAbyssRecordDbService.AddSpiralAbyssEntryAsync(newEntry).ConfigureAwait(false);
targetEntry = newEntry;
}
await taskContext.SwitchToMainThreadAsync();
spiralAbysses.RemoveAt(index);
spiralAbysses.Insert(index, SpiralAbyssView.From(targetEntry, metadataContext));
}
}

View File

@@ -68,7 +68,6 @@ internal sealed partial class UserInitializationService : IUserInitializationSer
return false;
}
// TODO: sharing scope
if (!await TrySetUserLTokenAsync(user, token).ConfigureAwait(false))
{
return false;
@@ -101,17 +100,11 @@ internal sealed partial class UserInitializationService : IUserInitializationSer
return true;
}
Response<LTokenWrapper> lTokenResponse;
using (IServiceScope scope = serviceProvider.CreateScope())
{
IPassportClient passportClient = scope.ServiceProvider
.GetRequiredService<IOverseaSupportFactory<IPassportClient>>()
.Create(user.IsOversea);
lTokenResponse = await passportClient
.GetLTokenBySTokenAsync(user.Entity, token)
.ConfigureAwait(false);
}
Response<LTokenWrapper> lTokenResponse = await serviceProvider
.GetRequiredService<IOverseaSupportFactory<IPassportClient>>()
.Create(user.IsOversea)
.GetLTokenBySTokenAsync(user.Entity, token)
.ConfigureAwait(false);
if (lTokenResponse.IsOk())
{
@@ -138,17 +131,11 @@ internal sealed partial class UserInitializationService : IUserInitializationSer
}
}
Response<UidCookieToken> cookieTokenResponse;
using (IServiceScope scope = serviceProvider.CreateScope())
{
IPassportClient passportClient = scope.ServiceProvider
.GetRequiredService<IOverseaSupportFactory<IPassportClient>>()
.Create(user.IsOversea);
cookieTokenResponse = await passportClient
.GetCookieAccountInfoBySTokenAsync(user.Entity, token)
.ConfigureAwait(false);
}
Response<UidCookieToken> cookieTokenResponse = await serviceProvider
.GetRequiredService<IOverseaSupportFactory<IPassportClient>>()
.Create(user.IsOversea)
.GetCookieAccountInfoBySTokenAsync(user.Entity, token)
.ConfigureAwait(false);
if (cookieTokenResponse.IsOk())
{
@@ -170,17 +157,11 @@ internal sealed partial class UserInitializationService : IUserInitializationSer
private async ValueTask<bool> TrySetUserUserInfoAsync(ViewModel.User.User user, CancellationToken token)
{
Response<UserFullInfoWrapper> response;
using (IServiceScope scope = serviceProvider.CreateScope())
{
IUserClient userClient = scope.ServiceProvider
.GetRequiredService<IOverseaSupportFactory<IUserClient>>()
.Create(user.IsOversea);
response = await userClient
.GetUserFullInfoAsync(user.Entity, token)
.ConfigureAwait(false);
}
Response<UserFullInfoWrapper> response = await serviceProvider
.GetRequiredService<IOverseaSupportFactory<IUserClient>>()
.Create(user.IsOversea)
.GetUserFullInfoAsync(user.Entity, token)
.ConfigureAwait(false);
if (response.IsOk())
{
@@ -195,16 +176,10 @@ internal sealed partial class UserInitializationService : IUserInitializationSer
private async ValueTask<bool> TrySetUserUserGameRolesAsync(ViewModel.User.User user, CancellationToken token)
{
Response<ListWrapper<UserGameRole>> userGameRolesResponse;
using (IServiceScope scope = serviceProvider.CreateScope())
{
BindingClient bindingClient = scope.ServiceProvider
.GetRequiredService<BindingClient>();
userGameRolesResponse = await bindingClient
.GetUserGameRolesOverseaAwareAsync(user.Entity, token)
.ConfigureAwait(false);
}
Response<ListWrapper<UserGameRole>> userGameRolesResponse = await serviceProvider
.GetRequiredService<BindingClient>()
.GetUserGameRolesOverseaAwareAsync(user.Entity, token)
.ConfigureAwait(false);
if (userGameRolesResponse.IsOk())
{

View File

@@ -93,17 +93,11 @@ internal sealed partial class UserService : IUserService, IUserServiceUnsafe
public async ValueTask<bool> RefreshCookieTokenAsync(Model.Entity.User user)
{
// TODO: 提醒其他组件此用户的Cookie已更改
Response<UidCookieToken> cookieTokenResponse;
using (IServiceScope scope = serviceProvider.CreateScope())
{
IPassportClient passportClient = serviceProvider
.GetRequiredService<IOverseaSupportFactory<IPassportClient>>()
.Create(user.IsOversea);
cookieTokenResponse = await passportClient
.GetCookieAccountInfoBySTokenAsync(user)
.ConfigureAwait(false);
}
Response<UidCookieToken> cookieTokenResponse = await serviceProvider
.GetRequiredService<IOverseaSupportFactory<IPassportClient>>()
.Create(user.IsOversea)
.GetCookieAccountInfoBySTokenAsync(user)
.ConfigureAwait(false);
if (!cookieTokenResponse.IsOk())
{

View File

@@ -35,11 +35,11 @@
<Configurations>Debug;Release</Configurations>
<!--
Required for .NET 8 MSIX packaging
10.2.4.1 Security - Software Dependencies
Products may depend on non-integrated software (such as another product or module)
to deliver primary functionality only as long as the additional required software
is disclosed within the first two lines of the description in the Store.
is disclosed within the first two lines of the description in the Store.
-->
<SelfContained>true</SelfContained>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
@@ -52,7 +52,7 @@
<AppxManifest Include="Package.appxmanifest" Condition="'$(ConfigurationName)'!='Debug'" />
<AppxManifest Include="Package.development.appxmanifest" Condition="'$(ConfigurationName)'=='Debug'" />
</ItemGroup>
<!-- Included Files -->
<ItemGroup>
<None Remove="Assets\LargeTile.scale-100.png" />
@@ -205,7 +205,7 @@
<None Remove="View\TitleView.xaml" />
<None Remove="View\UserView.xaml" />
</ItemGroup>
<!-- Analyzer Files -->
<ItemGroup>
<AdditionalFiles Include="CodeMetricsConfig.txt" />
@@ -220,7 +220,7 @@
<AdditionalFiles Include="Resource\Localization\SH.ru.resx" />
<AdditionalFiles Include="Resource\Localization\SH.zh-Hant.resx" />
</ItemGroup>
<!-- Assets Files -->
<ItemGroup>
<Content Update="Assets\Logo.ico" />
@@ -389,37 +389,37 @@
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Control\Theme\ScrollViewer.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Control\Theme\FlyoutStyle.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="View\Dialog\HutaoPassportUnregisterDialog.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="View\Dialog\HutaoPassportResetPasswordDialog.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="View\Dialog\HutaoPassportRegisterDialog.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="View\Dialog\HutaoPassportLoginDialog.xaml">
<Generator>MSBuild:Compile</Generator>
@@ -569,13 +569,13 @@
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="IdentifyMonitorWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="View\Control\HutaoStatisticsCard.xaml">
<Generator>MSBuild:Compile</Generator>
@@ -617,7 +617,7 @@
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<!-- Pages -->
<ItemGroup>
<Page Update="View\Dialog\LaunchGamePackageConvertDialog.xaml">

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control.Extension;
namespace Snap.Hutao.View.Card;
@@ -16,7 +15,7 @@ internal sealed partial class AchievementCard : Button
/// </summary>
public AchievementCard()
{
this.InitializeDataContext<ViewModel.Achievement.AchievementViewModelSlim>();
DataContext = Ioc.Default.GetRequiredService<ViewModel.Achievement.AchievementViewModelSlim>();
InitializeComponent();
}
}

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control.Extension;
namespace Snap.Hutao.View.Card;
@@ -16,7 +15,7 @@ internal sealed partial class DailyNoteCard : Button
/// </summary>
public DailyNoteCard()
{
this.InitializeDataContext<ViewModel.DailyNote.DailyNoteViewModelSlim>();
DataContext = Ioc.Default.GetRequiredService<ViewModel.DailyNote.DailyNoteViewModelSlim>();
InitializeComponent();
}
}

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control.Extension;
namespace Snap.Hutao.View.Card;
@@ -16,7 +15,7 @@ internal sealed partial class GachaStatisticsCard : Button
/// </summary>
public GachaStatisticsCard()
{
this.InitializeDataContext<ViewModel.GachaLog.GachaLogViewModelSlim>();
DataContext = Ioc.Default.GetRequiredService<ViewModel.GachaLog.GachaLogViewModelSlim>();
InitializeComponent();
}
}

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control.Extension;
namespace Snap.Hutao.View.Card;
@@ -16,7 +15,7 @@ internal sealed partial class LaunchGameCard : Button
/// </summary>
public LaunchGameCard()
{
this.InitializeDataContext<ViewModel.Game.LaunchGameViewModelSlim>();
DataContext = Ioc.Default.GetRequiredService<ViewModel.Game.LaunchGameViewModelSlim>();
InitializeComponent();
}
}

View File

@@ -14,7 +14,7 @@ internal sealed partial class GuideView : UserControl
{
public GuideView()
{
this.InitializeDataContext<GuideViewModel>();
InitializeComponent();
DataContext = this.ServiceProvider().GetRequiredService<GuideViewModel>();
}
}

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control.Extension;
using Snap.Hutao.Service.Navigation;
using Snap.Hutao.View.Page;
using Snap.Hutao.ViewModel;
@@ -24,11 +23,12 @@ internal sealed partial class MainView : UserControl
{
IServiceProvider serviceProvider = Ioc.Default;
this.InitializeDataContext<MainViewModel>(serviceProvider);
MainViewModel mainViewModel = serviceProvider.GetRequiredService<MainViewModel>();
DataContext = mainViewModel;
InitializeComponent();
(DataContext as MainViewModel)?.Initialize(new BackgroundImagePresenterAccessor(BackgroundImagePresenter));
mainViewModel.Initialize(new BackgroundImagePresenterAccessor(BackgroundImagePresenter));
navigationService = serviceProvider.GetRequiredService<INavigationService>();
if (navigationService is INavigationInitialization navigationInitialization)

View File

@@ -3,8 +3,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cw="using:CommunityToolkit.WinUI"
xmlns:cwcont="using:CommunityToolkit.WinUI.Controls"
xmlns:cwconv="using:CommunityToolkit.WinUI.Converters"
xmlns:cwc="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
@@ -25,11 +24,6 @@
</mxi:Interaction.Behaviors>
<Page.Resources>
<cwconv:DoubleToObjectConverter x:Key="DoubleToOpacityConverter" GreaterThan="0">
<cwconv:DoubleToObjectConverter.TrueValue>1.0</cwconv:DoubleToObjectConverter.TrueValue>
<cwconv:DoubleToObjectConverter.FalseValue>0.4</cwconv:DoubleToObjectConverter.FalseValue>
</cwconv:DoubleToObjectConverter>
<Flyout x:Key="HutaoCloudFlyout">
<Grid>
<mxi:Interaction.Behaviors>
@@ -126,12 +120,12 @@
Style="{StaticResource SubtitleTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewPageGachaLogHutaoCloudNotAllowed}"
TextAlignment="Center"/>
<cwcont:SettingsCard
<cwc:SettingsCard
Command="{Binding HutaoCloudViewModel.NavigateToSpiralAbyssRecordCommand}"
Description="{shcm:ResourceString Name=ViewPageGachaLogHutaoCloudSpiralAbyssActivityDescription}"
Header="{shcm:ResourceString Name=ViewPageGachaLogHutaoCloudSpiralAbyssActivityHeader}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
<cwc:SettingsCard
Command="{Binding HutaoCloudViewModel.NavigateToAfdianSkuCommand}"
Description="{shcm:ResourceString Name=ViewPageGachaLogHutaoCloudAfdianPurchaseDescription}"
Header="{shcm:ResourceString Name=ViewPageGachaLogHutaoCloudAfdianPurchaseHeader}"
@@ -216,7 +210,6 @@
<shvc:ItemIcon
Badge="{Binding Badge}"
Icon="{Binding Icon}"
Opacity="{Binding Count, Converter={StaticResource DoubleToOpacityConverter}}"
Quality="{Binding Quality}"/>
<Border
HorizontalAlignment="Right"
@@ -335,14 +328,14 @@
HorizontalAlignment="Left"
cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
<Grid HorizontalAlignment="Center" Style="{StaticResource GridCardStyle}">
<cwcont:ConstrainedBox AspectRatio="1080:533">
<cwc:ConstrainedBox AspectRatio="1080:533">
<shci:CachedImage
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="{ThemeResource ControlCornerRadius}"
Source="{Binding SelectedHistoryWish.BannerImage}"
Stretch="UniformToFill"/>
</cwcont:ConstrainedBox>
</cwc:ConstrainedBox>
<Border Grid.ColumnSpan="2" Background="{ThemeResource DarkOnlyOverlayMaskColorBrush}"/>
</Grid>
</Border>
@@ -475,18 +468,24 @@
Margin="16"
CornerRadius="{ThemeResource ControlCornerRadius}"
IsLoading="{Binding HutaoCloudStatisticsViewModel.IsInitialized, Converter={StaticResource BoolNegationConverter}}"/>
<shcp:HorizontalEqualPanel
<Grid
Margin="16"
Spacing="16"
ColumnSpacing="16"
Visibility="{Binding HutaoCloudStatisticsViewModel.IsInitialized, Converter={StaticResource BoolToVisibilityConverter}}">
<mxi:Interaction.Behaviors>
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding HutaoCloudStatisticsViewModel.OpenUICommand}"/>
</mxi:Interaction.Behaviors>
<shvc:HutaoStatisticsCard DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.AvatarEvent}"/>
<shvc:HutaoStatisticsCard DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.AvatarEvent2}"/>
<shvc:HutaoStatisticsCard DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.WeaponEvent}"/>
<shvc:HutaoStatisticsCard DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.Chronicled}" Visibility="{Binding Converter={StaticResource EmptyObjectToVisibilityConverter}, FallbackValue=Collapsed}"/>
</shcp:HorizontalEqualPanel>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<shvc:HutaoStatisticsCard Grid.Column="0" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.AvatarEvent}"/>
<shvc:HutaoStatisticsCard Grid.Column="1" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.AvatarEvent2}"/>
<shvc:HutaoStatisticsCard Grid.Column="2" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.WeaponEvent}"/>
<shvc:HutaoStatisticsCard Grid.Column="3" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.Chronicled}"/>
</Grid>
</Grid>
</PivotItem>
</Pivot>
@@ -508,35 +507,35 @@
Style="{StaticResource SubtitleTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewPageGachaLogHint}"/>
<StackPanel Margin="0,24,0,0" Spacing="{StaticResource SettingsCardSpacing}">
<cwcont:SettingsCard
<cwc:SettingsCard
ActionIconToolTip="{shcm:ResourceString Name=ViewPageGachaLogRefreshAction}"
Command="{Binding RefreshBySTokenCommand}"
Description="{shcm:ResourceString Name=ViewPageGachaLogRefreshBySTokenDescription}"
Header="{shcm:ResourceString Name=ViewPageGachaLogRefreshBySToken}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE192;}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
<cwc:SettingsCard
ActionIconToolTip="{shcm:ResourceString Name=ViewPageGachaLogRefreshAction}"
Command="{Binding RefreshByWebCacheCommand}"
Description="{shcm:ResourceString Name=ViewPageGachaLogRefreshByWebCacheDescription}"
Header="{shcm:ResourceString Name=ViewPageGachaLogRefreshByWebCache}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE81E;}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
<cwc:SettingsCard
ActionIconToolTip="{shcm:ResourceString Name=ViewPageGachaLogInputAction}"
Command="{Binding RefreshByManualInputCommand}"
Description="{shcm:ResourceString Name=ViewPageGachaLogRefreshByManualInputDescription}"
Header="{shcm:ResourceString Name=ViewPageGachaLogRefreshByManualInput}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE765;}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
<cwc:SettingsCard
ActionIconToolTip="{shcm:ResourceString Name=ViewPageGachaLogImportAction}"
Command="{Binding ImportFromUIGFJsonCommand}"
Description="{shcm:ResourceString Name=ViewPageGachaLogImportDescription}"
Header="{shcm:ResourceString Name=ViewPageGachaLogImportHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE8B5;}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
<cwc:SettingsCard
Description="{shcm:ResourceString Name=ViewPageGachaLogRecoverFromHutaoCloudDescription}"
FlyoutBase.AttachedFlyout="{StaticResource HutaoCloudFlyout}"
Header="{shcm:ResourceString Name=ViewPageGachaLogHutaoCloud}"
@@ -547,7 +546,7 @@
<shcb:ShowAttachedFlyoutAction/>
</mxic:EventTriggerBehavior>
</mxi:Interaction.Behaviors>
</cwcont:SettingsCard>
</cwc:SettingsCard>
</StackPanel>
</StackPanel>
</Border>

View File

@@ -559,15 +559,6 @@
OffContent="{shcm:ResourceString Name=ViewPageSettingEmptyHistoryVisibleOff}"
OnContent="{shcm:ResourceString Name=ViewPageSettingEmptyHistoryVisibleOn}"/>
</cwc:SettingsCard>
<cwc:SettingsCard
Description="{shcm:ResourceString Name=ViewPageSettingUnobtainedWishItemVisibleDescription}"
Header="{shcm:ResourceString Name=ViewPageSettingUnobtainedWishItemVisibleHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE890;}">
<ToggleSwitch
IsOn="{Binding AppOptions.IsUnobtainedWishItemVisible, Mode=TwoWay}"
OffContent="{shcm:ResourceString Name=ViewPageSettingEmptyHistoryVisibleOff}"
OnContent="{shcm:ResourceString Name=ViewPageSettingEmptyHistoryVisibleOn}"/>
</cwc:SettingsCard>
</StackPanel>
</Border>
</Border>

View File

@@ -3,7 +3,6 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control.Extension;
using Snap.Hutao.ViewModel;
namespace Snap.Hutao.View;
@@ -16,7 +15,7 @@ internal sealed partial class TitleView : UserControl
{
public TitleView()
{
this.InitializeDataContext<TitleViewModel>();
DataContext = Ioc.Default.GetRequiredService<TitleViewModel>();
InitializeComponent();
}

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control.Extension;
using Snap.Hutao.ViewModel.User;
namespace Snap.Hutao.View;
@@ -18,7 +17,7 @@ internal sealed partial class UserView : UserControl
/// </summary>
public UserView()
{
this.InitializeDataContext<UserViewModel>();
InitializeComponent();
DataContext = Ioc.Default.GetRequiredService<UserViewModel>();
}
}

View File

@@ -1,6 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.ViewModel.Abstraction;
internal interface IPageScoped;

View File

@@ -7,7 +7,7 @@ namespace Snap.Hutao.ViewModel.Abstraction;
/// 视图模型接口
/// </summary>
[HighQuality]
internal interface IViewModel : IPageScoped
internal interface IViewModel
{
/// <summary>
/// 用于通知页面卸载的取消令牌

View File

@@ -7,16 +7,29 @@ using Snap.Hutao.Service.Navigation;
namespace Snap.Hutao.ViewModel.Abstraction;
/// <summary>
/// 简化的视图模型抽象类
/// </summary>
[ConstructorGenerated]
internal abstract partial class ViewModelSlim : ObservableObject
{
private readonly IServiceProvider serviceProvider;
private bool isInitialized;
/// <summary>
/// 是否初始化完成
/// </summary>
public bool IsInitialized { get => isInitialized; set => SetProperty(ref isInitialized, value); }
/// <summary>
/// 服务提供器
/// </summary>
protected IServiceProvider ServiceProvider { get => serviceProvider; }
/// <summary>
/// 打开界面执行
/// </summary>
/// <returns>任务</returns>
[Command("OpenUICommand")]
protected virtual Task OpenUIAsync()
{
@@ -24,10 +37,18 @@ internal abstract partial class ViewModelSlim : ObservableObject
}
}
/// <summary>
/// 简化的视图模型抽象类
/// 可导航
/// </summary>
/// <typeparam name="TPage">要导航到的页面类型</typeparam>
[ConstructorGenerated(CallBaseConstructor = true)]
internal abstract partial class ViewModelSlim<TPage> : ViewModelSlim
where TPage : Page
{
/// <summary>
/// 导航到指定的页面类型
/// </summary>
[Command("NavigateCommand")]
protected virtual void Navigate()
{

View File

@@ -7,9 +7,16 @@ using System.Runtime.InteropServices;
namespace Snap.Hutao.ViewModel.Achievement;
/// <summary>
/// 成就完成进度
/// </summary>
[HighQuality]
internal static class AchievementFinishPercent
{
/// <summary>
/// 更新完成进度
/// </summary>
/// <param name="viewModel">视图模型</param>
public static void Update(AchievementViewModel viewModel)
{
int totalFinished = 0;

View File

@@ -26,5 +26,5 @@ internal sealed class HutaoStatistics
/// <summary>
/// 集录祈愿
/// </summary>
public HutaoWishSummary? Chronicled { get; set; }
public HutaoWishSummary Chronicled { get; set; } = default!;
}

View File

@@ -8,7 +8,6 @@ using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.ViewModel.SpiralAbyss;
// TODO: replace this
internal sealed class SpiralAbyssMetadataContext
{
public Dictionary<TowerScheduleId, TowerSchedule> IdScheduleMap { get; set; } = default!;

View File

@@ -259,35 +259,31 @@ internal class MiHoYoJSBridge
protected virtual async ValueTask<JsResult<Dictionary<string, object>>> GetUserInfoAsync(JsParam param)
{
Response<UserFullInfoWrapper> response;
using (IServiceScope scope = serviceProvider.CreateScope())
Response<UserFullInfoWrapper> response = await serviceProvider
.GetRequiredService<IOverseaSupportFactory<IUserClient>>()
.Create(userAndUid.User.IsOversea)
.GetUserFullInfoAsync(userAndUid.User)
.ConfigureAwait(false);
if (response.IsOk())
{
IUserClient userClient = scope.ServiceProvider
.GetRequiredService<IOverseaSupportFactory<IUserClient>>()
.Create(userAndUid.User.IsOversea);
response = await userClient
.GetUserFullInfoAsync(userAndUid.User)
.ConfigureAwait(false);
UserInfo info = response.Data.UserInfo;
return new()
{
Data = new()
{
["id"] = info.Uid,
["gender"] = info.Gender,
["nickname"] = info.Nickname,
["introduce"] = info.Introduce,
["avatar_url"] = info.AvatarUrl,
},
};
}
if (!response.IsOk())
else
{
return new();
}
UserInfo info = response.Data.UserInfo;
return new()
{
Data = new()
{
["id"] = info.Uid,
["gender"] = info.Gender,
["nickname"] = info.Nickname,
["introduce"] = info.Introduce,
["avatar_url"] = info.AvatarUrl,
},
};
}
protected virtual async ValueTask<IJsBridgeResult?> PushPageAsync(JsParam<PushPagePayload> param)

View File

@@ -23,8 +23,8 @@ internal sealed partial class EnkaClient
private const string EnkaAPI = "https://enka.network/api/uid/{0}";
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly IHttpClientFactory httpClientFactory;
private readonly JsonSerializerOptions options;
private readonly HttpClient httpClient;
public ValueTask<EnkaResponse?> GetForwardDataAsync(in PlayerUid playerUid, CancellationToken token = default)
{
@@ -44,34 +44,37 @@ internal sealed partial class EnkaClient
.SetRequestUri(url)
.Get();
using (HttpResponseMessage response = await httpClient.SendAsync(builder.HttpRequestMessage, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false))
using (HttpClient httpClient = httpClientFactory.CreateClient(nameof(EnkaClient)))
{
if (response.IsSuccessStatusCode)
using (HttpResponseMessage response = await httpClient.SendAsync(builder.HttpRequestMessage, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false))
{
return await response.Content.ReadFromJsonAsync<EnkaResponse>(options, token).ConfigureAwait(false);
}
else
{
// https://github.com/yoimiya-kokomi/miao-plugin/pull/441
// Additionally, HTTP codes for UID requests:
// 400 = wrong UID format
// 404 = player does not exist(MHY server told that)
// 429 = rate - limit
// 424 = game maintenance / everything is broken after the update
// 500 = general server error
// 503 = I screwed up massively
string message = response.StatusCode switch
if (response.IsSuccessStatusCode)
{
HttpStatusCode.BadRequest => SH.WebEnkaResponseStatusCode400,
HttpStatusCode.NotFound => SH.WebEnkaResponseStatusCode404,
HttpStatusCode.FailedDependency => SH.WebEnkaResponseStatusCode424,
HttpStatusCode.TooManyRequests => SH.WebEnkaResponseStatusCode429,
HttpStatusCode.InternalServerError => SH.WebEnkaResponseStatusCode500,
HttpStatusCode.ServiceUnavailable => SH.WebEnkaResponseStatusCode503,
_ => SH.WebEnkaResponseStatusCodeUnknown,
};
return await response.Content.ReadFromJsonAsync<EnkaResponse>(options, token).ConfigureAwait(false);
}
else
{
// https://github.com/yoimiya-kokomi/miao-plugin/pull/441
// Additionally, HTTP codes for UID requests:
// 400 = wrong UID format
// 404 = player does not exist(MHY server told that)
// 429 = rate - limit
// 424 = game maintenance / everything is broken after the update
// 500 = general server error
// 503 = I screwed up massively
string message = response.StatusCode switch
{
HttpStatusCode.BadRequest => SH.WebEnkaResponseStatusCode400,
HttpStatusCode.NotFound => SH.WebEnkaResponseStatusCode404,
HttpStatusCode.FailedDependency => SH.WebEnkaResponseStatusCode424,
HttpStatusCode.TooManyRequests => SH.WebEnkaResponseStatusCode429,
HttpStatusCode.InternalServerError => SH.WebEnkaResponseStatusCode500,
HttpStatusCode.ServiceUnavailable => SH.WebEnkaResponseStatusCode503,
_ => SH.WebEnkaResponseStatusCodeUnknown,
};
return new() { Message = message, };
return new() { Message = message, };
}
}
}
}

View File

@@ -0,0 +1,25 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Web.Hoyolab.DataSigning;
namespace Snap.Hutao.Web.Hoyolab.Annotation;
/// <summary>
/// API 信息
/// 指示此API 已经经过验证,且明确其调用
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
[Obsolete("不再使用此特性")]
internal sealed class ApiInformationAttribute : Attribute
{
/// <summary>
/// Cookie类型
/// </summary>
public CookieType Cookie { get; set; }
/// <summary>
/// SALT
/// </summary>
public SaltType Salt { get; set; }
}

View File

@@ -3,6 +3,7 @@
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Web.Hoyolab.Annotation;
using Snap.Hutao.Web.Hoyolab.DataSigning;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
using Snap.Hutao.Web.Request.Builder;
@@ -18,8 +19,8 @@ namespace Snap.Hutao.Web.Hoyolab.App.Account;
internal sealed partial class AccountClient
{
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly IHttpClientFactory httpClientFactory;
private readonly ILogger<AccountClient> logger;
private readonly HttpClient httpClient;
public async ValueTask<Response<GameAuthKey>> GenerateAuthenticationKeyAsync(User user, GenAuthKeyData data, CancellationToken token = default)
{
@@ -32,7 +33,7 @@ internal sealed partial class AccountClient
await builder.SignDataAsync(DataSignAlgorithmVersion.Gen1, SaltType.K2, false).ConfigureAwait(false);
Response<GameAuthKey>? resp = await builder
.TryCatchSendAsync<Response<GameAuthKey>>(httpClient, logger, token)
.TryCatchSendAsync<Response<GameAuthKey>>(httpClientFactory.CreateClient(nameof(AccountClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);

View File

@@ -15,8 +15,8 @@ namespace Snap.Hutao.Web.Hoyolab.Bbs.User;
internal sealed partial class UserClient : IUserClient
{
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly IHttpClientFactory httpClientFactory;
private readonly ILogger<UserClient> logger;
private readonly HttpClient httpClient;
public async ValueTask<Response<UserFullInfoWrapper>> GetUserFullInfoAsync(Model.Entity.User user, CancellationToken token = default)
{
@@ -28,7 +28,7 @@ internal sealed partial class UserClient : IUserClient
.Get();
Response<UserFullInfoWrapper>? resp = await builder
.TryCatchSendAsync<Response<UserFullInfoWrapper>>(httpClient, logger, token)
.TryCatchSendAsync<Response<UserFullInfoWrapper>>(httpClientFactory.CreateClient(nameof(UserClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Web.Hoyolab.Annotation;
using Snap.Hutao.Web.Request.Builder;
using Snap.Hutao.Web.Request.Builder.Abstraction;
using Snap.Hutao.Web.Response;

View File

@@ -9,24 +9,14 @@ using System.Net.Http;
namespace Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
/// <summary>
/// 公告客户端
/// </summary>
[ConstructorGenerated(ResolveHttpClient = true)]
[HttpClient(HttpClientConfiguration.Default)]
internal sealed partial class AnnouncementClient
{
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly IHttpClientFactory httpClientFactory;
private readonly ILogger<AnnouncementClient> logger;
private readonly HttpClient httpClient;
/// <summary>
/// 异步获取公告列表
/// </summary>
/// <param name="languageCode">语言代码</param>
/// <param name="region">服务器</param>
/// <param name="token">取消令牌</param>
/// <returns>公告列表</returns>
public async ValueTask<Response<AnnouncementWrapper>> GetAnnouncementsAsync(string languageCode, Region region, CancellationToken token = default)
{
string annListUrl = region.IsOversea()
@@ -38,19 +28,12 @@ internal sealed partial class AnnouncementClient
.Get();
Response<AnnouncementWrapper>? resp = await builder
.TryCatchSendAsync<Response<AnnouncementWrapper>>(httpClient, logger, token)
.TryCatchSendAsync<Response<AnnouncementWrapper>>(httpClientFactory.CreateClient(nameof(AnnouncementClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);
}
/// <summary>
/// 异步获取公告内容列表
/// </summary>
/// <param name="languageCode">语言代码</param>
/// <param name="region">服务器</param>
/// <param name="token">取消令牌</param>
/// <returns>公告内容列表</returns>
public async ValueTask<Response<ListWrapper<AnnouncementContent>>> GetAnnouncementContentsAsync(string languageCode, Region region, CancellationToken token = default)
{
string annContentUrl = region.IsOversea()
@@ -62,7 +45,7 @@ internal sealed partial class AnnouncementClient
.Get();
Response<ListWrapper<AnnouncementContent>>? resp = await builder
.TryCatchSendAsync<Response<ListWrapper<AnnouncementContent>>>(httpClient, logger, token)
.TryCatchSendAsync<Response<ListWrapper<AnnouncementContent>>>(httpClientFactory.CreateClient(nameof(AnnouncementClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);

View File

@@ -18,8 +18,8 @@ namespace Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
internal sealed partial class GachaInfoClient
{
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly IHttpClientFactory httpClientFactory;
private readonly ILogger<GachaInfoClient> logger;
private readonly HttpClient httpClient;
/// <summary>
/// 获取记录页面
@@ -40,7 +40,7 @@ internal sealed partial class GachaInfoClient
.Get();
Response<GachaLogPage>? resp = await builder
.TryCatchSendAsync<Response<GachaLogPage>>(httpClient, logger, token)
.TryCatchSendAsync<Response<GachaLogPage>>(httpClientFactory.CreateClient(nameof(GachaInfoClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);

View File

@@ -14,19 +14,21 @@ namespace Snap.Hutao.Web.Hoyolab.Hk4e.Sdk.Combo;
internal sealed partial class PandaClient
{
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly IHttpClientFactory httpClientFactory;
private readonly ILogger<PandaClient> logger;
private readonly HttpClient httpClient;
public async ValueTask<Response<UrlWrapper>> QRCodeFetchAsync(CancellationToken token = default)
{
GameLoginRequest options = GameLoginRequest.Create(4, HoyolabOptions.DeviceId40);
// Use 12 (zzz) instead of 4 (gi) temporarily to get legacy game token
GameLoginRequest options = GameLoginRequest.Create(12, HoyolabOptions.DeviceId40);
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(ApiEndpoints.QrCodeFetch)
.SetHeader("x-rpc-device_id", HoyolabOptions.DeviceId40)
.PostJson(options);
Response<UrlWrapper>? resp = await builder
.TryCatchSendAsync<Response<UrlWrapper>>(httpClient, logger, token)
.TryCatchSendAsync<Response<UrlWrapper>>(httpClientFactory.CreateClient(nameof(PandaClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);
@@ -34,14 +36,15 @@ internal sealed partial class PandaClient
public async ValueTask<Response<GameLoginResult>> QRCodeQueryAsync(string ticket, CancellationToken token = default)
{
GameLoginRequest options = GameLoginRequest.Create(4, HoyolabOptions.DeviceId40, ticket);
GameLoginRequest options = GameLoginRequest.Create(12, HoyolabOptions.DeviceId40, ticket);
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(ApiEndpoints.QrCodeQuery)
.SetHeader("x-rpc-device_id", HoyolabOptions.DeviceId40)
.PostJson(options);
Response<GameLoginResult>? resp = await builder
.TryCatchSendAsync<Response<GameLoginResult>>(httpClient, logger, token)
.TryCatchSendAsync<Response<GameLoginResult>>(httpClientFactory.CreateClient(nameof(PandaClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);

View File

@@ -3,6 +3,7 @@
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Web.Hoyolab.Annotation;
using Snap.Hutao.Web.Hoyolab.DataSigning;
using Snap.Hutao.Web.Request.Builder;
using Snap.Hutao.Web.Request.Builder.Abstraction;
@@ -17,8 +18,8 @@ namespace Snap.Hutao.Web.Hoyolab.Passport;
internal sealed partial class PassportClient : IPassportClient
{
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly IHttpClientFactory httpClientFactory;
private readonly ILogger<PassportClient2> logger;
private readonly HttpClient httpClient;
public async ValueTask<Response<UidCookieToken>> GetCookieAccountInfoBySTokenAsync(User user, CancellationToken token = default)
{
@@ -30,7 +31,7 @@ internal sealed partial class PassportClient : IPassportClient
await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.PROD, true).ConfigureAwait(false);
Response<UidCookieToken>? resp = await builder
.TryCatchSendAsync<Response<UidCookieToken>>(httpClient, logger, token)
.TryCatchSendAsync<Response<UidCookieToken>>(httpClientFactory.CreateClient(nameof(PassportClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);
@@ -46,7 +47,7 @@ internal sealed partial class PassportClient : IPassportClient
await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.PROD, true).ConfigureAwait(false);
Response<LTokenWrapper>? resp = await builder
.TryCatchSendAsync<Response<LTokenWrapper>>(httpClient, logger, token)
.TryCatchSendAsync<Response<LTokenWrapper>>(httpClientFactory.CreateClient(nameof(PassportClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);

View File

@@ -3,6 +3,7 @@
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Web.Hoyolab.Annotation;
using Snap.Hutao.Web.Hoyolab.DataSigning;
using Snap.Hutao.Web.Request.Builder;
using Snap.Hutao.Web.Request.Builder.Abstraction;
@@ -18,8 +19,8 @@ namespace Snap.Hutao.Web.Hoyolab.Passport;
internal sealed partial class PassportClient2
{
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly IHttpClientFactory httpClientFactory;
private readonly ILogger<PassportClient2> logger;
private readonly HttpClient httpClient;
public async ValueTask<Response<UserInfoWrapper>> VerifyLtokenAsync(User user, CancellationToken token)
{
@@ -29,7 +30,7 @@ internal sealed partial class PassportClient2
.PostJson(new Timestamp());
Response<UserInfoWrapper>? resp = await builder
.TryCatchSendAsync<Response<UserInfoWrapper>>(httpClient, logger, token)
.TryCatchSendAsync<Response<UserInfoWrapper>>(httpClientFactory.CreateClient(nameof(PassportClient2)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);
@@ -45,7 +46,7 @@ internal sealed partial class PassportClient2
await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.PROD, true).ConfigureAwait(false);
Response<LoginResult>? resp = await builder
.TryCatchSendAsync<Response<LoginResult>>(httpClient, logger, token)
.TryCatchSendAsync<Response<LoginResult>>(httpClientFactory.CreateClient(nameof(PassportClient2)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);
@@ -61,10 +62,11 @@ internal sealed partial class PassportClient2
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(ApiEndpoints.AccountGetSTokenByGameToken)
.SetHeader("x-rpc-device_id", HoyolabOptions.DeviceId40)
.PostJson(data);
Response<LoginResult>? resp = await builder
.TryCatchSendAsync<Response<LoginResult>>(httpClient, logger, token)
.TryCatchSendAsync<Response<LoginResult>>(httpClientFactory.CreateClient(nameof(PassportClient2)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);

View File

@@ -3,6 +3,7 @@
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Web.Hoyolab.Annotation;
using Snap.Hutao.Web.Request.Builder;
using Snap.Hutao.Web.Request.Builder.Abstraction;
using Snap.Hutao.Web.Response;
@@ -16,7 +17,7 @@ internal sealed partial class PassportClientOversea : IPassportClient
{
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly ILogger<PassportClientOversea> logger;
private readonly HttpClient httpClient;
private readonly IHttpClientFactory httpClientFactory;
public async ValueTask<Response<UidCookieToken>> GetCookieAccountInfoBySTokenAsync(User user, CancellationToken token = default)
{
@@ -31,7 +32,7 @@ internal sealed partial class PassportClientOversea : IPassportClient
.PostJson(data);
Response<UidCookieToken>? resp = await builder
.TryCatchSendAsync<Response<UidCookieToken>>(httpClient, logger, token)
.TryCatchSendAsync<Response<UidCookieToken>>(httpClientFactory.CreateClient(nameof(PassportClientOversea)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);
@@ -50,7 +51,7 @@ internal sealed partial class PassportClientOversea : IPassportClient
.PostJson(data);
Response<LTokenWrapper>? resp = await builder
.TryCatchSendAsync<Response<LTokenWrapper>>(httpClient, logger, token)
.TryCatchSendAsync<Response<LTokenWrapper>>(httpClientFactory.CreateClient(nameof(PassportClientOversea)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);

View File

@@ -14,7 +14,7 @@ namespace Snap.Hutao.Web.Hoyolab.PublicData.DeviceFp;
internal sealed partial class DeviceFpClient
{
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly HttpClient httpClient;
private readonly IHttpClientFactory httpClientFactory;
private readonly ILogger<DeviceFpClient> logger;
public async ValueTask<Response<DeviceFpWrapper>> GetFingerprintAsync(DeviceFpData data, CancellationToken token)
@@ -24,7 +24,7 @@ internal sealed partial class DeviceFpClient
.PostJson(data);
Response<DeviceFpWrapper>? resp = await builder
.TryCatchSendAsync<Response<DeviceFpWrapper>>(httpClient, logger, token)
.TryCatchSendAsync<Response<DeviceFpWrapper>>(httpClientFactory.CreateClient(nameof(DeviceFpClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);

View File

@@ -8,30 +8,19 @@ using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher.Resource;
using Snap.Hutao.Web.Request.Builder;
using Snap.Hutao.Web.Request.Builder.Abstraction;
using Snap.Hutao.Web.Response;
using System.IO;
using System.Net.Http;
using System.Text;
namespace Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
/// <summary>
/// 游戏资源客户端
/// </summary>
[HighQuality]
[ConstructorGenerated(ResolveHttpClient = true)]
[HttpClient(HttpClientConfiguration.Default)]
internal sealed partial class ResourceClient
{
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly HttpClient httpClient;
private readonly IHttpClientFactory httpClientFactory;
private readonly ILogger<ResourceClient> logger;
/// <summary>
/// 异步获取游戏资源
/// </summary>
/// <param name="scheme">方案</param>
/// <param name="token">取消令牌</param>
/// <returns>游戏资源</returns>
public async ValueTask<Response<GameResource>> GetResourceAsync(LaunchScheme scheme, CancellationToken token = default)
{
string url = scheme.IsOversea
@@ -43,7 +32,7 @@ internal sealed partial class ResourceClient
.Get();
Response<GameResource>? resp = await builder
.TryCatchSendAsync<Response<GameResource>>(httpClient, logger, token)
.TryCatchSendAsync<Response<GameResource>>(httpClientFactory.CreateClient(nameof(ResourceClient)), logger, token)
.ConfigureAwait(false);
// 最新版完整包
@@ -72,7 +61,7 @@ internal sealed partial class ResourceClient
.Get();
Response<GameContent>? resp = await builder
.TryCatchSendAsync<Response<GameContent>>(httpClient, logger, token)
.TryCatchSendAsync<Response<GameContent>>(httpClientFactory.CreateClient(nameof(ResourceClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);

View File

@@ -3,6 +3,7 @@
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Web.Hoyolab.Annotation;
using Snap.Hutao.Web.Hoyolab.DataSigning;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
using Snap.Hutao.Web.Request.Builder;
@@ -18,8 +19,8 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.Auth;
internal sealed partial class AuthClient
{
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly IHttpClientFactory httpClientFactory;
private readonly ILogger<BindingClient> logger;
private readonly HttpClient httpClient;
public async ValueTask<Response<ActionTicketWrapper>> GetActionTicketBySTokenAsync(string action, User user, CancellationToken token = default)
{
@@ -34,7 +35,7 @@ internal sealed partial class AuthClient
await builder.SignDataAsync(DataSignAlgorithmVersion.Gen1, SaltType.K2, true).ConfigureAwait(false);
Response<ActionTicketWrapper>? resp = await builder
.TryCatchSendAsync<Response<ActionTicketWrapper>>(httpClient, logger, token)
.TryCatchSendAsync<Response<ActionTicketWrapper>>(httpClientFactory.CreateClient(nameof(AuthClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);
@@ -57,7 +58,7 @@ internal sealed partial class AuthClient
.Get();
resp = await builder
.TryCatchSendAsync<Response<ListWrapper<NameToken>>>(httpClient, logger, token)
.TryCatchSendAsync<Response<ListWrapper<NameToken>>>(httpClientFactory.CreateClient(nameof(AuthClient)), logger, token)
.ConfigureAwait(false);
}

View File

@@ -1,8 +1,10 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Extensions.Http;
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Web.Hoyolab.Annotation;
using Snap.Hutao.Web.Hoyolab.Takumi.Auth;
using Snap.Hutao.Web.Request.Builder;
using Snap.Hutao.Web.Request.Builder.Abstraction;
@@ -17,9 +19,9 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.Binding;
internal sealed partial class BindingClient
{
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly IHttpClientFactory httpClientFactory;
private readonly IServiceProvider serviceProvider;
private readonly ILogger<BindingClient> logger;
private readonly HttpClient httpClient;
public async ValueTask<Response<ListWrapper<UserGameRole>>> GetUserGameRolesOverseaAwareAsync(User user, CancellationToken token = default)
{
@@ -52,7 +54,7 @@ internal sealed partial class BindingClient
.Get();
Response<ListWrapper<UserGameRole>>? resp = await builder
.TryCatchSendAsync<Response<ListWrapper<UserGameRole>>>(httpClient, logger, token)
.TryCatchSendAsync<Response<ListWrapper<UserGameRole>>>(httpClientFactory.CreateClient(nameof(BindingClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);
@@ -66,7 +68,7 @@ internal sealed partial class BindingClient
.Get();
Response<ListWrapper<UserGameRole>>? resp = await builder
.TryCatchSendAsync<Response<ListWrapper<UserGameRole>>>(httpClient, logger, token)
.TryCatchSendAsync<Response<ListWrapper<UserGameRole>>>(httpClientFactory.CreateClient(nameof(BindingClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);

View File

@@ -3,6 +3,7 @@
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Web.Hoyolab.Annotation;
using Snap.Hutao.Web.Hoyolab.DataSigning;
using Snap.Hutao.Web.Request.Builder;
using Snap.Hutao.Web.Request.Builder.Abstraction;
@@ -18,8 +19,8 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.Binding;
internal sealed partial class BindingClient2
{
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly IHttpClientFactory httpClientFactory;
private readonly ILogger<BindingClient2> logger;
private readonly HttpClient httpClient;
public async ValueTask<Response<ListWrapper<UserGameRole>>> GetUserGameRolesBySTokenAsync(User user, CancellationToken token = default)
{
@@ -32,7 +33,7 @@ internal sealed partial class BindingClient2
await builder.SignDataAsync(DataSignAlgorithmVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false);
Response<ListWrapper<UserGameRole>>? resp = await builder
.TryCatchSendAsync<Response<ListWrapper<UserGameRole>>>(httpClient, logger, token)
.TryCatchSendAsync<Response<ListWrapper<UserGameRole>>>(httpClientFactory.CreateClient(nameof(BindingClient2)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);
@@ -49,7 +50,7 @@ internal sealed partial class BindingClient2
await builder.SignDataAsync(DataSignAlgorithmVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false);
Response<GameAuthKey>? resp = await builder
.TryCatchSendAsync<Response<GameAuthKey>>(httpClient, logger, token)
.TryCatchSendAsync<Response<GameAuthKey>>(httpClientFactory.CreateClient(nameof(BindingClient2)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);

View File

@@ -13,19 +13,16 @@ using System.Net.Http;
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
/// <summary>
/// 签到客户端
/// </summary>
[ConstructorGenerated(ResolveHttpClient = true)]
[HttpClient(HttpClientConfiguration.XRpc)]
[PrimaryHttpMessageHandler(UseCookies = false)]
internal sealed partial class SignInClient : ISignInClient
{
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly IHttpClientFactory httpClientFactory;
private readonly HomaGeetestClient homaGeetestClient;
private readonly CultureOptions cultureOptions;
private readonly ILogger<SignInClient> logger;
private readonly HttpClient httpClient;
public async ValueTask<Response<ExtraAwardInfo>> GetExtraAwardInfoAsync(UserAndUid userAndUid, CancellationToken token = default)
{
@@ -38,7 +35,7 @@ internal sealed partial class SignInClient : ISignInClient
await builder.SignDataAsync(DataSignAlgorithmVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false);
Response<ExtraAwardInfo>? resp = await builder
.TryCatchSendAsync<Response<ExtraAwardInfo>>(httpClient, logger, token)
.TryCatchSendAsync<Response<ExtraAwardInfo>>(httpClientFactory.CreateClient(nameof(SignInClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);
@@ -55,7 +52,7 @@ internal sealed partial class SignInClient : ISignInClient
await builder.SignDataAsync(DataSignAlgorithmVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false);
Response<SignInRewardInfo>? resp = await builder
.TryCatchSendAsync<Response<SignInRewardInfo>>(httpClient, logger, token)
.TryCatchSendAsync<Response<SignInRewardInfo>>(httpClientFactory.CreateClient(nameof(SignInClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);
@@ -72,7 +69,7 @@ internal sealed partial class SignInClient : ISignInClient
await builder.SignDataAsync(DataSignAlgorithmVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false);
Response<SignInRewardReSignInfo>? resp = await builder
.TryCatchSendAsync<Response<SignInRewardReSignInfo>>(httpClient, logger, token)
.TryCatchSendAsync<Response<SignInRewardReSignInfo>>(httpClientFactory.CreateClient(nameof(SignInClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);
@@ -87,7 +84,7 @@ internal sealed partial class SignInClient : ISignInClient
.Get();
Response<Reward>? resp = await builder
.TryCatchSendAsync<Response<Reward>>(httpClient, logger, token)
.TryCatchSendAsync<Response<Reward>>(httpClientFactory.CreateClient(nameof(SignInClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);
@@ -104,7 +101,7 @@ internal sealed partial class SignInClient : ISignInClient
await builder.SignDataAsync(DataSignAlgorithmVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false);
Response<SignInResult>? resp = await builder
.TryCatchSendAsync<Response<SignInResult>>(httpClient, logger, token)
.TryCatchSendAsync<Response<SignInResult>>(httpClientFactory.CreateClient(nameof(SignInClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);
@@ -121,7 +118,7 @@ internal sealed partial class SignInClient : ISignInClient
await builder.SignDataAsync(DataSignAlgorithmVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false);
Response<SignInResult>? resp = await builder
.TryCatchSendAsync<Response<SignInResult>>(httpClient, logger, token)
.TryCatchSendAsync<Response<SignInResult>>(httpClientFactory.CreateClient(nameof(SignInClient)), logger, token)
.ConfigureAwait(false);
if (resp is { Data: { Success: 1, Gt: string gt, Challenge: string originChallenge } })
@@ -140,7 +137,7 @@ internal sealed partial class SignInClient : ISignInClient
await verifiedBuilder.SignDataAsync(DataSignAlgorithmVersion.Gen1, SaltType.LK2, true).ConfigureAwait(false);
resp = await verifiedBuilder
.TryCatchSendAsync<Response<SignInResult>>(httpClient, logger, token)
.TryCatchSendAsync<Response<SignInResult>>(httpClientFactory.CreateClient(nameof(SignInClient)), logger, token)
.ConfigureAwait(false);
}
else

View File

@@ -11,18 +11,15 @@ using System.Net.Http;
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
/// <summary>
/// Global签到客户端
/// </summary>
[ConstructorGenerated(ResolveHttpClient = true)]
[HttpClient(HttpClientConfiguration.Default)]
[PrimaryHttpMessageHandler(UseCookies = false)]
internal sealed partial class SignInClientOversea : ISignInClient
{
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly IHttpClientFactory httpClientFactory;
private readonly HomaGeetestClient homaGeetestClient;
private readonly ILogger<SignInClient> logger;
private readonly HttpClient httpClient;
public async ValueTask<Response<SignInRewardInfo>> GetInfoAsync(UserAndUid userAndUid, CancellationToken token = default(CancellationToken))
{
@@ -32,7 +29,7 @@ internal sealed partial class SignInClientOversea : ISignInClient
.Get();
Response<SignInRewardInfo>? resp = await builder
.TryCatchSendAsync<Response<SignInRewardInfo>>(httpClient, logger, token)
.TryCatchSendAsync<Response<SignInRewardInfo>>(httpClientFactory.CreateClient(nameof(SignInClientOversea)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);
@@ -46,7 +43,7 @@ internal sealed partial class SignInClientOversea : ISignInClient
.Get();
Response<Reward>? resp = await builder
.TryCatchSendAsync<Response<Reward>>(httpClient, logger, token)
.TryCatchSendAsync<Response<Reward>>(httpClientFactory.CreateClient(nameof(SignInClientOversea)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);
@@ -60,7 +57,7 @@ internal sealed partial class SignInClientOversea : ISignInClient
.PostJson(new SignInData(userAndUid.Uid, true));
Response<SignInResult>? resp = await builder
.TryCatchSendAsync<Response<SignInResult>>(httpClient, logger, token)
.TryCatchSendAsync<Response<SignInResult>>(httpClientFactory.CreateClient(nameof(SignInClientOversea)), logger, token)
.ConfigureAwait(false);
if (resp is { Data: { Success: 1, Gt: string gt, Challenge: string originChallenge } })
@@ -76,7 +73,7 @@ internal sealed partial class SignInClientOversea : ISignInClient
.PostJson(new SignInData(userAndUid.Uid, true));
resp = await verifiedBuilder
.TryCatchSendAsync<Response<SignInResult>>(httpClient, logger, token)
.TryCatchSendAsync<Response<SignInResult>>(httpClientFactory.CreateClient(nameof(SignInClientOversea)), logger, token)
.ConfigureAwait(false);
}
else

View File

@@ -3,6 +3,7 @@
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.ViewModel.User;
using Snap.Hutao.Web.Hoyolab.Annotation;
using Snap.Hutao.Web.Request.Builder;
using Snap.Hutao.Web.Request.Builder.Abstraction;
using Snap.Hutao.Web.Response;
@@ -16,8 +17,8 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
internal sealed partial class CalculateClient
{
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly IHttpClientFactory httpClientFactory;
private readonly ILogger<CalculateClient> logger;
private readonly HttpClient httpClient;
public async ValueTask<Response<Consumption>> ComputeAsync(Model.Entity.User user, AvatarPromotionDelta delta, CancellationToken token = default)
{
@@ -28,7 +29,7 @@ internal sealed partial class CalculateClient
.PostJson(delta);
Response<Consumption>? resp = await builder
.TryCatchSendAsync<Response<Consumption>>(httpClient, logger, token)
.TryCatchSendAsync<Response<Consumption>>(httpClientFactory.CreateClient(nameof(CalculateClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);
@@ -53,7 +54,7 @@ internal sealed partial class CalculateClient
.PostJson(filter);
resp = await builder
.TryCatchSendAsync<Response<ListWrapper<Avatar>>>(httpClient, logger, token)
.TryCatchSendAsync<Response<ListWrapper<Avatar>>>(httpClientFactory.CreateClient(nameof(CalculateClient)), logger, token)
.ConfigureAwait(false);
if (resp is not null && resp.IsOk())
@@ -86,7 +87,7 @@ internal sealed partial class CalculateClient
.Get();
Response<AvatarDetail>? resp = await builder
.TryCatchSendAsync<Response<AvatarDetail>>(httpClient, logger, token)
.TryCatchSendAsync<Response<AvatarDetail>>(httpClientFactory.CreateClient(nameof(CalculateClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);
@@ -101,7 +102,7 @@ internal sealed partial class CalculateClient
.Get();
Response<FurnitureListWrapper>? resp = await builder
.TryCatchSendAsync<Response<FurnitureListWrapper>>(httpClient, logger, token)
.TryCatchSendAsync<Response<FurnitureListWrapper>>(httpClientFactory.CreateClient(nameof(CalculateClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);
@@ -118,7 +119,7 @@ internal sealed partial class CalculateClient
.PostJson(data);
Response<ListWrapper<Item>>? resp = await builder
.TryCatchSendAsync<Response<ListWrapper<Item>>>(httpClient, logger, token)
.TryCatchSendAsync<Response<ListWrapper<Item>>>(httpClientFactory.CreateClient(nameof(CalculateClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);

View File

@@ -3,6 +3,7 @@
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Web.Hoyolab.Annotation;
using Snap.Hutao.Web.Hoyolab.DataSigning;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Verification;
using Snap.Hutao.Web.Request.Builder;
@@ -12,6 +13,9 @@ using System.Net.Http;
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
/// <summary>
/// 卡片客户端
/// </summary>
[HighQuality]
[ConstructorGenerated(ResolveHttpClient = true)]
[HttpClient(HttpClientConfiguration.XRpc)]
@@ -25,7 +29,7 @@ internal sealed partial class CardClient
{
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(ApiEndpoints.CardCreateVerification(true))
.SetUserCookieAndFpHeader(user, CookieType.Cookie)
.SetUserCookieAndFpHeader(user, CookieType.LToken)
.SetHeader("x-rpc-challenge_game", $"{headers.ChallengeGame}")
.SetHeader("x-rpc-challenge_path", headers.ChallengePath)
.Get();
@@ -39,11 +43,10 @@ internal sealed partial class CardClient
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()
.SetRequestUri(ApiEndpoints.CardVerifyVerification)
.SetUserCookieAndFpHeader(user, CookieType.Cookie)
.SetHeader("x-rpc-challenge_game", $"{headers.ChallengeGame}")
.SetHeader("x-rpc-challenge_path", headers.ChallengePath)
.PostJson(new VerificationData(challenge, validate));
@@ -57,6 +60,13 @@ internal sealed partial class CardClient
return Response.Response.DefaultIfNull(resp);
}
/// <summary>
/// 异步获取桌面小组件数据
/// </summary>
/// <param name="user">用户</param>
/// <param name="token">取消令牌</param>
/// <returns>桌面小组件数据</returns>
[ApiInformation(Cookie = CookieType.SToken, Salt = SaltType.X6)]
public async ValueTask<Response<DailyNote.WidgetDailyNote>> GetWidgetDataAsync(User user, CancellationToken token)
{
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()

View File

@@ -3,6 +3,7 @@
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.ViewModel.User;
using Snap.Hutao.Web.Hoyolab.Annotation;
using Snap.Hutao.Web.Hoyolab.DataSigning;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Verification;
@@ -20,9 +21,9 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
internal sealed partial class GameRecordClient : IGameRecordClient
{
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly IHttpClientFactory httpClientFactory;
private readonly IServiceProvider serviceProvider;
private readonly ILogger<GameRecordClient> logger;
private readonly HttpClient httpClient;
public async ValueTask<Response<DailyNote.DailyNote>> GetDailyNoteAsync(UserAndUid userAndUid, CancellationToken token = default)
{
@@ -35,7 +36,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient
await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false);
Response<DailyNote.DailyNote>? resp = await builder
.TryCatchSendAsync<Response<DailyNote.DailyNote>>(httpClient, logger, token)
.TryCatchSendAsync<Response<DailyNote.DailyNote>>(httpClientFactory.CreateClient(nameof(GameRecordClient)), logger, token)
.ConfigureAwait(false);
// We have a verification procedure to handle
@@ -59,7 +60,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient
await verifiedbuilder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false);
resp = await verifiedbuilder
.TryCatchSendAsync<Response<DailyNote.DailyNote>>(httpClient, logger, token)
.TryCatchSendAsync<Response<DailyNote.DailyNote>>(httpClientFactory.CreateClient(nameof(GameRecordClient)), logger, token)
.ConfigureAwait(false);
}
}
@@ -78,7 +79,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient
await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false);
Response<PlayerInfo>? resp = await builder
.TryCatchSendAsync<Response<PlayerInfo>>(httpClient, logger, token)
.TryCatchSendAsync<Response<PlayerInfo>>(httpClientFactory.CreateClient(nameof(GameRecordClient)), logger, token)
.ConfigureAwait(false);
// We have a verification procedure to handle
@@ -102,7 +103,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient
await verifiedbuilder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false);
resp = await verifiedbuilder
.TryCatchSendAsync<Response<PlayerInfo>>(httpClient, logger, token)
.TryCatchSendAsync<Response<PlayerInfo>>(httpClientFactory.CreateClient(nameof(GameRecordClient)), logger, token)
.ConfigureAwait(false);
}
}
@@ -121,7 +122,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient
await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false);
Response<SpiralAbyss.SpiralAbyss>? resp = await builder
.TryCatchSendAsync<Response<SpiralAbyss.SpiralAbyss>>(httpClient, logger, token)
.TryCatchSendAsync<Response<SpiralAbyss.SpiralAbyss>>(httpClientFactory.CreateClient(nameof(GameRecordClient)), logger, token)
.ConfigureAwait(false);
// We have a verification procedure to handle
@@ -145,7 +146,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient
await verifiedbuilder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false);
resp = await verifiedbuilder
.TryCatchSendAsync<Response<SpiralAbyss.SpiralAbyss>>(httpClient, logger, token)
.TryCatchSendAsync<Response<SpiralAbyss.SpiralAbyss>>(httpClientFactory.CreateClient(nameof(GameRecordClient)), logger, token)
.ConfigureAwait(false);
}
}
@@ -164,7 +165,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient
await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false);
Response<BasicRoleInfo>? resp = await builder
.TryCatchSendAsync<Response<BasicRoleInfo>>(httpClient, logger, token)
.TryCatchSendAsync<Response<BasicRoleInfo>>(httpClientFactory.CreateClient(nameof(GameRecordClient)), logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);
@@ -181,7 +182,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient
await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false);
Response<CharacterWrapper>? resp = await builder
.TryCatchSendAsync<Response<CharacterWrapper>>(httpClient, logger, token)
.TryCatchSendAsync<Response<CharacterWrapper>>(httpClientFactory.CreateClient(nameof(GameRecordClient)), logger, token)
.ConfigureAwait(false);
// We have a verification procedure to handle
@@ -205,7 +206,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient
await verifiedBuilder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false);
resp = await verifiedBuilder
.TryCatchSendAsync<Response<CharacterWrapper>>(httpClient, logger, token)
.TryCatchSendAsync<Response<CharacterWrapper>>(httpClientFactory.CreateClient(nameof(GameRecordClient)), logger, token)
.ConfigureAwait(false);
}
}

View File

@@ -3,6 +3,7 @@
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.ViewModel.User;
using Snap.Hutao.Web.Hoyolab.Annotation;
using Snap.Hutao.Web.Hoyolab.DataSigning;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
using Snap.Hutao.Web.Request.Builder;
@@ -12,6 +13,9 @@ using System.Net.Http;
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
/// <summary>
/// Hoyoverse game record provider
/// </summary>
[ConstructorGenerated(ResolveHttpClient = true)]
[HttpClient(HttpClientConfiguration.XRpc3)]
[PrimaryHttpMessageHandler(UseCookies = false)]
@@ -21,6 +25,13 @@ internal sealed partial class GameRecordClientOversea : IGameRecordClient
private readonly ILogger<GameRecordClient> logger;
private readonly HttpClient httpClient;
/// <summary>
/// 异步获取实时便笺
/// </summary>
/// <param name="userAndUid">用户与角色</param>
/// <param name="token">取消令牌</param>
/// <returns>实时便笺</returns>
[ApiInformation(Cookie = CookieType.Cookie, Salt = SaltType.OSX4)]
public async ValueTask<Response<DailyNote.DailyNote>> GetDailyNoteAsync(UserAndUid userAndUid, CancellationToken token = default)
{
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
@@ -37,6 +48,13 @@ internal sealed partial class GameRecordClientOversea : IGameRecordClient
return Response.Response.DefaultIfNull(resp);
}
/// <summary>
/// 获取玩家基础信息
/// </summary>
/// <param name="userAndUid">用户与角色</param>
/// <param name="token">取消令牌</param>
/// <returns>玩家的基础信息</returns>
[ApiInformation(Cookie = CookieType.LToken, Salt = SaltType.OSX4)]
public async ValueTask<Response<PlayerInfo>> GetPlayerInfoAsync(UserAndUid userAndUid, CancellationToken token = default)
{
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
@@ -53,6 +71,14 @@ internal sealed partial class GameRecordClientOversea : IGameRecordClient
return Response.Response.DefaultIfNull(resp);
}
/// <summary>
/// 获取玩家深渊信息
/// </summary>
/// <param name="userAndUid">用户</param>
/// <param name="schedule">1当期2上期</param>
/// <param name="token">取消令牌</param>
/// <returns>深渊信息</returns>
[ApiInformation(Cookie = CookieType.Cookie, Salt = SaltType.OSX4)]
public async ValueTask<Response<SpiralAbyss.SpiralAbyss>> GetSpiralAbyssAsync(UserAndUid userAndUid, SpiralAbyssSchedule schedule, CancellationToken token = default)
{
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
@@ -69,6 +95,14 @@ internal sealed partial class GameRecordClientOversea : IGameRecordClient
return Response.Response.DefaultIfNull(resp);
}
/// <summary>
/// 获取玩家角色详细信息
/// </summary>
/// <param name="userAndUid">用户与角色</param>
/// <param name="playerInfo">玩家的基础信息</param>
/// <param name="token">取消令牌</param>
/// <returns>角色列表</returns>
[ApiInformation(Cookie = CookieType.LToken, Salt = SaltType.OSX4)]
public async ValueTask<Response<CharacterWrapper>> GetCharactersAsync(UserAndUid userAndUid, PlayerInfo playerInfo, CancellationToken token = default)
{
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()

View File

@@ -24,7 +24,7 @@ internal sealed partial class HomaGeetestCardVerifier : IGeetestCardVerifier
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())
{
VerificationResult result = verifyResponse.Data;