diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Theme/Converter.xaml b/src/Snap.Hutao/Snap.Hutao/Control/Theme/Converter.xaml index beef99ac..d9b8a21e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Theme/Converter.xaml +++ b/src/Snap.Hutao/Snap.Hutao/Control/Theme/Converter.xaml @@ -10,6 +10,7 @@ + diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Theme/CornerRadius.xaml b/src/Snap.Hutao/Snap.Hutao/Control/Theme/CornerRadius.xaml index 4dcfd7d4..741bf85e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Theme/CornerRadius.xaml +++ b/src/Snap.Hutao/Snap.Hutao/Control/Theme/CornerRadius.xaml @@ -2,4 +2,5 @@ 4,4,0,0 0,0,4,4 0,4,0,4 + 16 diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/20240616104646_UidProfilePicture.Designer.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/20240616104646_UidProfilePicture.Designer.cs new file mode 100644 index 00000000..26aa2963 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Migrations/20240616104646_UidProfilePicture.Designer.cs @@ -0,0 +1,654 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Snap.Hutao.Model.Entity.Database; + +#nullable disable + +namespace Snap.Hutao.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20240616104646_UidProfilePicture")] + partial class UidProfilePicture + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ArchiveId") + .HasColumnType("TEXT"); + + b.Property("Current") + .HasColumnType("INTEGER"); + + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Time") + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.HasIndex("ArchiveId"); + + b.ToTable("achievements"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsSelected") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.ToTable("achievement_archives"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CalculatorRefreshTime") + .HasColumnType("TEXT"); + + b.Property("GameRecordRefreshTime") + .HasColumnType("TEXT"); + + b.Property("Info") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ShowcaseRefreshTime") + .HasColumnType("TEXT"); + + b.Property("Uid") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.ToTable("avatar_infos"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("InnerId"); + + b.HasIndex("ProjectId"); + + b.ToTable("cultivate_entries"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AvatarLevelFrom") + .HasColumnType("INTEGER"); + + b.Property("AvatarLevelTo") + .HasColumnType("INTEGER"); + + b.Property("EntryId") + .HasColumnType("TEXT"); + + b.Property("SkillALevelFrom") + .HasColumnType("INTEGER"); + + b.Property("SkillALevelTo") + .HasColumnType("INTEGER"); + + b.Property("SkillELevelFrom") + .HasColumnType("INTEGER"); + + b.Property("SkillELevelTo") + .HasColumnType("INTEGER"); + + b.Property("SkillQLevelFrom") + .HasColumnType("INTEGER"); + + b.Property("SkillQLevelTo") + .HasColumnType("INTEGER"); + + b.Property("WeaponLevelFrom") + .HasColumnType("INTEGER"); + + b.Property("WeaponLevelTo") + .HasColumnType("INTEGER"); + + b.HasKey("InnerId"); + + b.HasIndex("EntryId") + .IsUnique(); + + b.ToTable("cultivate_entry_level_informations"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.Property("EntryId") + .HasColumnType("TEXT"); + + b.Property("IsFinished") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("INTEGER"); + + b.HasKey("InnerId"); + + b.HasIndex("EntryId"); + + b.ToTable("cultivate_items"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateProject", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AttachedUid") + .HasColumnType("TEXT"); + + b.Property("IsSelected") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.ToTable("cultivate_projects"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("DailyNote") + .HasColumnType("TEXT"); + + b.Property("DailyTaskNotify") + .HasColumnType("INTEGER"); + + b.Property("DailyTaskNotifySuppressed") + .HasColumnType("INTEGER"); + + b.Property("ExpeditionNotify") + .HasColumnType("INTEGER"); + + b.Property("ExpeditionNotifySuppressed") + .HasColumnType("INTEGER"); + + b.Property("HomeCoinNotifySuppressed") + .HasColumnType("INTEGER"); + + b.Property("HomeCoinNotifyThreshold") + .HasColumnType("INTEGER"); + + b.Property("RefreshTime") + .HasColumnType("TEXT"); + + b.Property("ResinNotifySuppressed") + .HasColumnType("INTEGER"); + + b.Property("ResinNotifyThreshold") + .HasColumnType("INTEGER"); + + b.Property("TransformerNotify") + .HasColumnType("INTEGER"); + + b.Property("TransformerNotifySuppressed") + .HasColumnType("INTEGER"); + + b.Property("Uid") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.HasIndex("UserId"); + + b.ToTable("daily_notes"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsSelected") + .HasColumnType("INTEGER"); + + b.Property("Uid") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.ToTable("gacha_archives"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ArchiveId") + .HasColumnType("TEXT"); + + b.Property("GachaType") + .HasColumnType("INTEGER"); + + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("INTEGER"); + + b.Property("QueryType") + .HasColumnType("INTEGER"); + + b.Property("Time") + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.HasIndex("ArchiveId"); + + b.ToTable("gacha_items"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AttachUid") + .HasColumnType("TEXT"); + + b.Property("Index") + .HasColumnType("INTEGER"); + + b.Property("MihoyoSDK") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("InnerId"); + + b.ToTable("game_accounts"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("INTEGER"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.HasIndex("ProjectId"); + + b.ToTable("inventory_items"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryReliquary", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppendPropIdList") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasColumnType("INTEGER"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("MainPropId") + .HasColumnType("INTEGER"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.HasIndex("ProjectId"); + + b.ToTable("inventory_reliquaries"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryWeapon", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasColumnType("INTEGER"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("PromoteLevel") + .HasColumnType("INTEGER"); + + b.HasKey("InnerId"); + + b.HasIndex("ProjectId"); + + b.ToTable("inventory_weapons"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.ObjectCacheEntry", b => + { + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("ExpireTime") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Key"); + + b.ToTable("object_cache"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b => + { + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Key"); + + b.ToTable("settings"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.SpiralAbyssEntry", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ScheduleId") + .HasColumnType("INTEGER"); + + b.Property("SpiralAbyss") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Uid") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.ToTable("spiral_abysses"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.UidProfilePicture", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AvatarId") + .HasColumnType("INTEGER"); + + b.Property("CostumeId") + .HasColumnType("INTEGER"); + + b.Property("ProfilePictureId") + .HasColumnType("INTEGER"); + + b.Property("RefreshTime") + .HasColumnType("TEXT"); + + b.Property("Uid") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.ToTable("uid_profile_pictures"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Aid") + .HasColumnType("TEXT"); + + b.Property("CookieToken") + .HasColumnType("TEXT"); + + b.Property("CookieTokenLastUpdateTime") + .HasColumnType("TEXT"); + + b.Property("Fingerprint") + .HasColumnType("TEXT"); + + b.Property("FingerprintLastUpdateTime") + .HasColumnType("TEXT"); + + b.Property("Index") + .HasColumnType("INTEGER"); + + b.Property("IsOversea") + .HasColumnType("INTEGER"); + + b.Property("IsSelected") + .HasColumnType("INTEGER"); + + b.Property("LToken") + .HasColumnType("TEXT") + .HasColumnName("Ltoken"); + + b.Property("Mid") + .HasColumnType("TEXT"); + + b.Property("PreferredUid") + .HasColumnType("TEXT"); + + b.Property("SToken") + .HasColumnType("TEXT") + .HasColumnName("Stoken"); + + b.HasKey("InnerId"); + + b.ToTable("users"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b => + { + b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive") + .WithMany() + .HasForeignKey("ArchiveId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Archive"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b => + { + b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", b => + { + b.HasOne("Snap.Hutao.Model.Entity.CultivateEntry", "Entry") + .WithOne("LevelInformation") + .HasForeignKey("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", "EntryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Entry"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b => + { + b.HasOne("Snap.Hutao.Model.Entity.CultivateEntry", "Entry") + .WithMany() + .HasForeignKey("EntryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Entry"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b => + { + b.HasOne("Snap.Hutao.Model.Entity.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b => + { + b.HasOne("Snap.Hutao.Model.Entity.GachaArchive", "Archive") + .WithMany() + .HasForeignKey("ArchiveId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Archive"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b => + { + b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryReliquary", b => + { + b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryWeapon", b => + { + b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b => + { + b.Navigation("LevelInformation"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/20240616104646_UidProfilePicture.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/20240616104646_UidProfilePicture.cs new file mode 100644 index 00000000..0c323d59 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Migrations/20240616104646_UidProfilePicture.cs @@ -0,0 +1,39 @@ +// +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Snap.Hutao.Migrations +{ + /// + public partial class UidProfilePicture : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "uid_profile_pictures", + columns: table => new + { + InnerId = table.Column(type: "TEXT", nullable: false), + Uid = table.Column(type: "TEXT", nullable: false), + ProfilePictureId = table.Column(type: "INTEGER", nullable: false), + AvatarId = table.Column(type: "INTEGER", nullable: false), + CostumeId = table.Column(type: "INTEGER", nullable: false), + RefreshTime = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_uid_profile_pictures", x => x.InnerId); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "uid_profile_pictures"); + } + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs index fd2b7d28..03413381 100644 --- a/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs +++ b/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs @@ -15,7 +15,7 @@ namespace Snap.Hutao.Migrations protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.2"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b => { @@ -466,6 +466,33 @@ namespace Snap.Hutao.Migrations b.ToTable("spiral_abysses"); }); + modelBuilder.Entity("Snap.Hutao.Model.Entity.UidProfilePicture", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AvatarId") + .HasColumnType("INTEGER"); + + b.Property("CostumeId") + .HasColumnType("INTEGER"); + + b.Property("ProfilePictureId") + .HasColumnType("INTEGER"); + + b.Property("RefreshTime") + .HasColumnType("TEXT"); + + b.Property("Uid") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.ToTable("uid_profile_pictures"); + }); + modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b => { b.Property("InnerId") diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/Database/AppDbContext.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Database/AppDbContext.cs index d03ac2d8..44be285d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/Database/AppDbContext.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Database/AppDbContext.cs @@ -65,6 +65,8 @@ internal sealed class AppDbContext : DbContext public DbSet SpiralAbysses { get; set; } = default!; + public DbSet UidProfilePictures { get; set; } = default!; + public static AppDbContext Create(IServiceProvider serviceProvider, string sqlConnectionString) { DbContextOptions options = new DbContextOptionsBuilder() diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/UidProfilePicture.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/UidProfilePicture.cs new file mode 100644 index 00000000..c19bcb59 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/UidProfilePicture.cs @@ -0,0 +1,41 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core.Abstraction; +using Snap.Hutao.Web.Enka.Model; +using Snap.Hutao.Web.Hoyolab; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Snap.Hutao.Model.Entity; + +[Table("uid_profile_pictures")] +internal sealed class UidProfilePicture : IMappingFrom +{ + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid InnerId { get; set; } + + public string Uid { get; set; } = default!; + + public uint ProfilePictureId { get; set; } + + public uint AvatarId { get; set; } + + public uint CostumeId { get; set; } + + public DateTimeOffset RefreshTime { get; set; } + + [SuppressMessage("", "SH002")] + public static UidProfilePicture From(PlayerUid uid, ProfilePicture profilePicture) + { + return new() + { + Uid = uid.ToString(), + ProfilePictureId = profilePicture.Id, + AvatarId = profilePicture.AvatarId, + CostumeId = profilePicture.CostumeId, + RefreshTime = DateTimeOffset.Now, + }; + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/ProfilePicture.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/ProfilePicture.cs index 7aad4ef0..0231bdfb 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/ProfilePicture.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/ProfilePicture.cs @@ -26,4 +26,4 @@ internal sealed class ProfilePicture /// -> /// public uint UnlockParameter { get; set; } -} \ No newline at end of file +} diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/AvatarIconCircleConverter.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/AvatarIconCircleConverter.cs new file mode 100644 index 00000000..97a04a65 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/AvatarIconCircleConverter.cs @@ -0,0 +1,29 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Control; + +namespace Snap.Hutao.Model.Metadata.Converter; + +/// +/// 玩家头像转换器 +/// +[HighQuality] +internal sealed class AvatarIconCircleConverter : ValueConverter +{ + /// + /// 名称转Uri + /// + /// 名称 + /// 链接 + public static Uri IconNameToUri(string name) + { + return Web.HutaoEndpoints.StaticRaw("AvatarIconCircle", $"{name}.png").ToUri(); + } + + /// + public override Uri Convert(string from) + { + return IconNameToUri(from); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataListProfilePictureSource.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataListProfilePictureSource.cs new file mode 100644 index 00000000..34536889 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataListProfilePictureSource.cs @@ -0,0 +1,11 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Metadata.Avatar; + +namespace Snap.Hutao.Service.Metadata.ContextAbstraction; + +internal interface IMetadataListProfilePictureSource +{ + public List ProfilePictures { get; set; } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/MetadataServiceContextExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/MetadataServiceContextExtension.cs index fd17f81d..11fe4b5b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/MetadataServiceContextExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/MetadataServiceContextExtension.cs @@ -38,6 +38,11 @@ internal static class MetadataServiceContextExtension listMaterialSource.Materials = await metadataService.GetMaterialListAsync(token).ConfigureAwait(false); } + if (context is IMetadataListProfilePictureSource dictionaryIdProfilePictureSource) + { + dictionaryIdProfilePictureSource.ProfilePictures = await metadataService.GetProfilePictureListAsync(token).ConfigureAwait(false); + } + if (context is IMetadataListReliquaryMainAffixLevelSource listReliquaryMainAffixLevelSource) { listReliquaryMainAffixLevelSource.ReliquaryMainAffixLevels = await metadataService.GetReliquaryMainAffixLevelListAsync(token).ConfigureAwait(false); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataFileNames.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataFileNames.cs index 6a2e2b5c..0080a6cd 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataFileNames.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataFileNames.cs @@ -16,6 +16,7 @@ internal static class MetadataFileNames public const string FileNameMaterial = "Material"; public const string FileNameMonster = "Monster"; public const string FileNameMonsterCurve = "MonsterCurve"; + public const string FileNameProfilePicture = "ProfilePicture"; public const string FileNameReliquary = "Reliquary"; public const string FileNameReliquaryAffixWeight = "ReliquaryAffixWeight"; public const string FileNameReliquaryMainAffix = "ReliquaryMainAffix"; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceListExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceListExtension.cs index 1532e245..720147cd 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceListExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceListExtension.cs @@ -70,6 +70,11 @@ internal static class MetadataServiceListExtension return metadataService.FromCacheOrFileAsync>(FileNameMonsterCurve, token); } + public static ValueTask> GetProfilePictureListAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheOrFileAsync>(FileNameProfilePicture, token); + } + public static ValueTask> GetReliquaryListAsync(this IMetadataService metadataService, CancellationToken token = default) { return metadataService.FromCacheOrFileAsync>(FileNameReliquary, token); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/IProfilePictureService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/IProfilePictureService.cs new file mode 100644 index 00000000..a3a85146 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/IProfilePictureService.cs @@ -0,0 +1,14 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Entity; +using Snap.Hutao.Web.Hoyolab.Takumi.Binding; + +namespace Snap.Hutao.Service.User; + +internal interface IProfilePictureService +{ + ValueTask TryInitializeAsync(ViewModel.User.User user, CancellationToken token = default(CancellationToken)); + + ValueTask RefreshUserGameRoleAsync(UserGameRole userGameRole, CancellationToken token = default(CancellationToken)); +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/IUidProfilePictureDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/IUidProfilePictureDbService.cs new file mode 100644 index 00000000..a516d4cf --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/IUidProfilePictureDbService.cs @@ -0,0 +1,16 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Entity; +using Snap.Hutao.Service.Abstraction; + +namespace Snap.Hutao.Service.User; + +internal interface IUidProfilePictureDbService : IAppDbService +{ + ValueTask SingleUidProfilePictureOrDefaultByUidAsync(string uid, CancellationToken token = default); + + ValueTask UpdateUidProfilePictureAsync(UidProfilePicture profilePicture, CancellationToken token = default); + + ValueTask DeleteUidProfilePictureByUidAsync(string uid, CancellationToken token = default); +} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserInitializationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserInitializationService.cs index 442bd68b..ebca47cb 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserInitializationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserInitializationService.cs @@ -1,6 +1,8 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Web.Hoyolab.Takumi.Binding; + namespace Snap.Hutao.Service.User; internal interface IUserInitializationService diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserMetadataContext.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserMetadataContext.cs new file mode 100644 index 00000000..ac828068 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserMetadataContext.cs @@ -0,0 +1,9 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Service.Metadata.ContextAbstraction; + +namespace Snap.Hutao.Service.User; + +internal interface IUserMetadataContext : IMetadataContext, + IMetadataListProfilePictureSource; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs index 011b0f40..8c9fe4d9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs @@ -53,4 +53,6 @@ internal interface IUserService /// 待移除的用户 /// 任务 ValueTask RemoveUserAsync(BindingUser user); + + ValueTask RefreshProfilePictureAsync(UserGameRole userGameRole); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/ProfilePictureService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/ProfilePictureService.cs new file mode 100644 index 00000000..2d6438ef --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/ProfilePictureService.cs @@ -0,0 +1,109 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Entity; +using Snap.Hutao.Model.Intrinsic; +using Snap.Hutao.Service.Metadata; +using Snap.Hutao.Service.Metadata.ContextAbstraction; +using Snap.Hutao.Web.Enka; +using Snap.Hutao.Web.Enka.Model; +using Snap.Hutao.Web.Hoyolab.Takumi.Binding; + +namespace Snap.Hutao.Service.User; + +[ConstructorGenerated] +[Injection(InjectAs.Singleton, typeof(IProfilePictureService))] +internal sealed partial class ProfilePictureService : IProfilePictureService +{ + private readonly IUidProfilePictureDbService uidProfilePictureDbService; + private readonly IMetadataService metadataService; + private readonly IServiceProvider serviceProvider; + private readonly ITaskContext taskContext; + + public async ValueTask TryInitializeAsync(ViewModel.User.User user, CancellationToken token = default) + { + foreach (UserGameRole userGameRole in user.UserGameRoles) + { + if (await uidProfilePictureDbService.SingleUidProfilePictureOrDefaultByUidAsync(userGameRole.GameUid, token).ConfigureAwait(false) is { } profilePicture) + { + if (await TryUpdateUserGameRoleAsync(userGameRole, profilePicture, token).ConfigureAwait(false)) + { + continue; + } + } + + // Force update + await RefreshUserGameRoleAsync(userGameRole, token: token).ConfigureAwait(false); + } + } + + public async ValueTask RefreshUserGameRoleAsync(UserGameRole userGameRole, CancellationToken token = default) + { + EnkaResponse? enkaResponse; + using (IServiceScope scope = serviceProvider.CreateScope()) + { + EnkaClient enkaClient = scope.ServiceProvider + .GetRequiredService(); + + enkaResponse = await enkaClient.GetForwardPlayerInfoAsync(userGameRole, token).ConfigureAwait(false) + ?? await enkaClient.GetPlayerInfoAsync(userGameRole, token).ConfigureAwait(false); + } + + if (enkaResponse is { PlayerInfo: { } playerInfo }) + { + UidProfilePicture profilePicture = UidProfilePicture.From(userGameRole, playerInfo.ProfilePicture); + + await uidProfilePictureDbService.DeleteUidProfilePictureByUidAsync(userGameRole.GameUid, token).ConfigureAwait(false); + await uidProfilePictureDbService.UpdateUidProfilePictureAsync(profilePicture, token).ConfigureAwait(false); + + await TryUpdateUserGameRoleAsync(userGameRole, profilePicture, token).ConfigureAwait(false); + } + } + + private async ValueTask TryUpdateUserGameRoleAsync(UserGameRole userGameRole, UidProfilePicture cache, CancellationToken token = default) + { + if (cache.RefreshTime.AddDays(15) < DateTimeOffset.Now) + { + return false; + } + + if (!await metadataService.InitializeAsync().ConfigureAwait(false)) + { + return false; + } + + UserMetadataContext context = await metadataService.GetContextAsync(token).ConfigureAwait(false); + + await taskContext.SwitchToMainThreadAsync(); + + // Most common to most rare + if (cache.ProfilePictureId is not 0U) + { + userGameRole.ProfilePictureIcon = context.ProfilePictures + .Single(p => p.Id == cache.ProfilePictureId) + .Icon; + + return true; + } + + if (cache.AvatarId is not 0U) + { + userGameRole.ProfilePictureIcon = context.ProfilePictures + .Single(p => p.UnlockType is ProfilePictureUnlockType.Avatar && p.UnlockParameter == cache.AvatarId) + .Icon; + + return true; + } + + if (cache.CostumeId is not 0U) + { + userGameRole.ProfilePictureIcon = context.ProfilePictures + .Single(p => p.UnlockType is ProfilePictureUnlockType.Costume && p.UnlockParameter == cache.CostumeId) + .Icon; + + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UidProfilePictureDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UidProfilePictureDbService.cs new file mode 100644 index 00000000..da6887f2 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UidProfilePictureDbService.cs @@ -0,0 +1,32 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.EntityFrameworkCore; +using Snap.Hutao.Model.Entity; +using Snap.Hutao.Service.Abstraction; + +namespace Snap.Hutao.Service.User; + +[ConstructorGenerated] +[Injection(InjectAs.Singleton, typeof(IUidProfilePictureDbService))] +internal sealed partial class UidProfilePictureDbService : IUidProfilePictureDbService +{ + private readonly IServiceProvider serviceProvider; + + public IServiceProvider ServiceProvider { get => serviceProvider; } + + public ValueTask SingleUidProfilePictureOrDefaultByUidAsync(string uid, CancellationToken token = default) + { + return this.QueryAsync(query => query.SingleOrDefaultAsync(n => n.Uid == uid)); + } + + public async ValueTask UpdateUidProfilePictureAsync(UidProfilePicture profilePicture, CancellationToken token = default) + { + await this.UpdateAsync(profilePicture, token).ConfigureAwait(false); + } + + public async ValueTask DeleteUidProfilePictureByUidAsync(string uid, CancellationToken token = default) + { + await this.DeleteAsync(profilePicture => profilePicture.Uid == uid, token).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs index 18af3acd..a310752c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs @@ -16,6 +16,7 @@ namespace Snap.Hutao.Service.User; internal sealed partial class UserInitializationService : IUserInitializationService { private readonly IUserFingerprintService userFingerprintService; + private readonly IProfilePictureService profilePictureService; private readonly IServiceProvider serviceProvider; public async ValueTask ResumeUserAsync(Model.Entity.User inner, CancellationToken token = default) @@ -90,6 +91,7 @@ internal sealed partial class UserInitializationService : IUserInitializationSer } await userFingerprintService.TryInitializeAsync(user, token).ConfigureAwait(false); + await profilePictureService.TryInitializeAsync(user, token).ConfigureAwait(false); return user.IsInitialized = true; } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserMetadataContext.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserMetadataContext.cs new file mode 100644 index 00000000..977f5547 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserMetadataContext.cs @@ -0,0 +1,11 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Metadata.Avatar; + +namespace Snap.Hutao.Service.User; + +internal class UserMetadataContext : IUserMetadataContext +{ + public List ProfilePictures { get; set; } = default!; +} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs index 24b46f0c..eaf87c88 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs @@ -21,6 +21,7 @@ namespace Snap.Hutao.Service.User; [Injection(InjectAs.Singleton, typeof(IUserService))] internal sealed partial class UserService : IUserService, IUserServiceUnsafe { + private readonly IProfilePictureService profilePictureService; private readonly IUserCollectionService userCollectionService; private readonly IServiceProvider serviceProvider; private readonly IUserDbService userDbService; @@ -121,4 +122,9 @@ internal sealed partial class UserService : IUserService, IUserServiceUnsafe return true; } + + public async ValueTask RefreshProfilePictureAsync(UserGameRole userGameRole) + { + await profilePictureService.RefreshUserGameRoleAsync(userGameRole).ConfigureAwait(false); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/Guide/GuideView.xaml b/src/Snap.Hutao/Snap.Hutao/View/Guide/GuideView.xaml index b6e79770..8f28ee39 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Guide/GuideView.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Guide/GuideView.xaml @@ -301,7 +301,7 @@ - - - - + + + + + + + + + + + + diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/StaticResource.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/StaticResource.cs index dec258d7..10fd435e 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/StaticResource.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/StaticResource.cs @@ -20,6 +20,7 @@ internal static class StaticResource { "AchievementIcon", 0 }, { "AvatarCard", 0 }, { "AvatarIcon", 0 }, + { "AvatarIconCircle", 0 }, { "Bg", 0 }, { "ChapterIcon", 0 }, { "CodexMonster", 0 }, @@ -50,6 +51,7 @@ internal static class StaticResource { "AchievementIcon", 2 }, { "AvatarCard", 2 }, { "AvatarIcon", 5 }, + { "AvatarIconCircle", 1 }, { "Bg", 3 }, { "ChapterIcon", 3 }, { "CodexMonster", 0 }, diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs index 3c3847ae..0ca85f10 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs @@ -17,6 +17,7 @@ using Snap.Hutao.View.Dialog; using Snap.Hutao.View.Page; using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab.Passport; +using Snap.Hutao.Web.Hoyolab.Takumi.Binding; using Snap.Hutao.Web.Response; using System.Text; using EntityUser = Snap.Hutao.Model.Entity.User; diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Enka/EnkaClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Enka/EnkaClient.cs index c02c196c..f6b994e1 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Enka/EnkaClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Enka/EnkaClient.cs @@ -21,22 +21,33 @@ namespace Snap.Hutao.Web.Enka; internal sealed partial class EnkaClient { private const string EnkaAPI = "https://enka.network/api/uid/{0}"; + private const string EnkaInfoAPI = "https://enka.network/api/uid/{0}?info"; private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory; private readonly JsonSerializerOptions options; private readonly HttpClient httpClient; + public ValueTask GetForwardPlayerInfoAsync(in PlayerUid playerUid, CancellationToken token = default) + { + return TryGetEnkaResponseCoreAsync(HutaoEndpoints.EnkaPlayerInfo(playerUid), true, token); + } + + public ValueTask GetPlayerInfoAsync(in PlayerUid playerUid, CancellationToken token = default) + { + return TryGetEnkaResponseCoreAsync(string.Format(CultureInfo.CurrentCulture, EnkaInfoAPI, playerUid), false, token); + } + public ValueTask GetForwardDataAsync(in PlayerUid playerUid, CancellationToken token = default) { - return TryGetEnkaResponseCoreAsync(HutaoEndpoints.Enka(playerUid), token); + return TryGetEnkaResponseCoreAsync(HutaoEndpoints.Enka(playerUid), true, token); } public ValueTask GetDataAsync(in PlayerUid playerUid, CancellationToken token = default) { - return TryGetEnkaResponseCoreAsync(string.Format(CultureInfo.CurrentCulture, EnkaAPI, playerUid), token); + return TryGetEnkaResponseCoreAsync(string.Format(CultureInfo.CurrentCulture, EnkaAPI, playerUid), false, token); } - private async ValueTask TryGetEnkaResponseCoreAsync(string url, CancellationToken token = default) + private async ValueTask TryGetEnkaResponseCoreAsync(string url, bool isForward, CancellationToken token = default) { try { @@ -52,6 +63,16 @@ internal sealed partial class EnkaClient } else { + // We want to fallback to original API and retry when requesting our forward api + if (isForward) + { + string content = await response.Content.ReadAsStringAsync(token).ConfigureAwait(false); + if (content.Contains("nginx", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + } + // https://github.com/yoimiya-kokomi/miao-plugin/pull/441 // Additionally, HTTP codes for UID requests: // 400 = wrong UID format diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Enka/Model/ProfilePicture.cs b/src/Snap.Hutao/Snap.Hutao/Web/Enka/Model/ProfilePicture.cs index e927d380..88760563 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Enka/Model/ProfilePicture.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Enka/Model/ProfilePicture.cs @@ -13,4 +13,10 @@ internal sealed class ProfilePicture { [JsonPropertyName("id")] public ProfilePictureId Id { get; set; } + + [JsonPropertyName("avatarId")] + public AvatarId AvatarId { get; set; } + + [JsonPropertyName("costumeId")] + public CostumeId CostumeId { get; set; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRole.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRole.cs index e3322d57..e08f24cd 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRole.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Binding/UserGameRole.cs @@ -1,14 +1,21 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Snap.Hutao.Service.User; + namespace Snap.Hutao.Web.Hoyolab.Takumi.Binding; /// /// 用户游戏角色 /// [HighQuality] -internal sealed class UserGameRole +internal sealed class UserGameRole : ObservableObject { + private string? profilePictureIcon; + private ICommand? refreshProfilePictureCommand; + /// /// hk4e_cn for Genshin Impact /// @@ -65,6 +72,18 @@ internal sealed class UserGameRole get => $"{RegionName} | Lv.{Level}"; } + [JsonIgnore] + public string? ProfilePictureIcon + { + get => profilePictureIcon; + set => SetProperty(ref profilePictureIcon, value); + } + + public ICommand RefreshProfilePictureCommand + { + get => refreshProfilePictureCommand ??= new AsyncRelayCommand(RefreshProfilePictureAsync); + } + public static implicit operator PlayerUid(UserGameRole userGameRole) { return new PlayerUid(userGameRole.GameUid, userGameRole.Region); @@ -75,4 +94,10 @@ internal sealed class UserGameRole { return $"{Nickname} | {RegionName} | Lv.{Level}"; } + + [SuppressMessage("", "SH003")] + private async Task RefreshProfilePictureAsync() + { + await Ioc.Default.GetRequiredService().RefreshProfilePictureAsync(this).ConfigureAwait(false); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/HutaoEndpoints.cs b/src/Snap.Hutao/Snap.Hutao/Web/HutaoEndpoints.cs index 093c38e8..c314c07d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/HutaoEndpoints.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/HutaoEndpoints.cs @@ -212,6 +212,11 @@ internal static class HutaoEndpoints return $"{ApiSnapGenshinEnka}/{uid}"; } + public static string EnkaPlayerInfo(in PlayerUid uid) + { + return $"{ApiSnapGenshinEnka}/{uid}/info"; + } + public const string Ip = $"{ApiSnapGenshin}/ip"; #region Metadata