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/Migrations/20240613144942_UserGameRoleProfilePicture.Designer.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/20240613144942_UserGameRoleProfilePicture.Designer.cs new file mode 100644 index 00000000..3e6db24b --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Migrations/20240613144942_UserGameRoleProfilePicture.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("20240613144942_UserGameRoleProfilePicture")] + partial class UserGameRoleProfilePicture + { + /// + 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.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.UserGameRoleProfilePicture", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AvatarId") + .HasColumnType("INTEGER"); + + b.Property("CostumeId") + .HasColumnType("INTEGER"); + + b.Property("LastUpdateTime") + .HasColumnType("TEXT"); + + b.Property("ProfilePictureId") + .HasColumnType("INTEGER"); + + b.Property("Uid") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.ToTable("profile_pictures"); + }); + + 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/20240613144942_UserGameRoleProfilePicture.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/20240613144942_UserGameRoleProfilePicture.cs new file mode 100644 index 00000000..84e5271f --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Migrations/20240613144942_UserGameRoleProfilePicture.cs @@ -0,0 +1,38 @@ +// +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Snap.Hutao.Migrations +{ + /// + public partial class UserGameRoleProfilePicture : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "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), + LastUpdateTime = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_profile_pictures", x => x.InnerId); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "profile_pictures"); + } + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs index fd2b7d28..3be71058 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 => { @@ -515,6 +515,33 @@ namespace Snap.Hutao.Migrations b.ToTable("users"); }); + modelBuilder.Entity("Snap.Hutao.Model.Entity.UserGameRoleProfilePicture", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AvatarId") + .HasColumnType("INTEGER"); + + b.Property("CostumeId") + .HasColumnType("INTEGER"); + + b.Property("LastUpdateTime") + .HasColumnType("TEXT"); + + b.Property("ProfilePictureId") + .HasColumnType("INTEGER"); + + b.Property("Uid") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.ToTable("profile_pictures"); + }); + modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b => { b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive") 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..8e6f5a16 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 UserGameRoleProfilePictures { 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/UserGameRoleProfilePicture.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/UserGameRoleProfilePicture.cs new file mode 100644 index 00000000..21bc9372 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/UserGameRoleProfilePicture.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("profile_pictures")] +[SuppressMessage("", "SH002")] +internal sealed class UserGameRoleProfilePicture : 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 LastUpdateTime { get; set; } + + public static UserGameRoleProfilePicture From(PlayerUid uid, ProfilePicture profilePicture) + { + return new() + { + Uid = uid.ToString(), + ProfilePictureId = profilePicture.ProfilePictureId, + AvatarId = profilePicture.AvatarId, + CostumeId = profilePicture.CostumeId, + LastUpdateTime = 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 ee3b3ade..9e3db05b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/ProfilePicture.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/ProfilePicture.cs @@ -12,4 +12,8 @@ internal sealed class ProfilePicture public string Icon { get; set; } = default!; public string Name { get; set; } = default!; + + public AvatarId AvatarId { get; set; } + + public CostumeId CostumeId { 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/IMetadataDictionaryAvatarIdProfilePictureSource.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryAvatarIdProfilePictureSource.cs new file mode 100644 index 00000000..fe755b48 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryAvatarIdProfilePictureSource.cs @@ -0,0 +1,11 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Primitive; + +namespace Snap.Hutao.Service.Metadata.ContextAbstraction; + +internal interface IMetadataDictionaryAvatarIdProfilePictureSource +{ + public Dictionary AvatarIdProfilePictureMap { get; set; } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryCostumeIdProfilePictureSource.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryCostumeIdProfilePictureSource.cs new file mode 100644 index 00000000..655d2cd6 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryCostumeIdProfilePictureSource.cs @@ -0,0 +1,11 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Primitive; + +namespace Snap.Hutao.Service.Metadata.ContextAbstraction; + +internal interface IMetadataDictionaryCostumeIdProfilePictureSource +{ + public Dictionary CostumeIdProfilePictureMap { get; set; } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryIdProfilePictureSource.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryIdProfilePictureSource.cs new file mode 100644 index 00000000..66605c17 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryIdProfilePictureSource.cs @@ -0,0 +1,11 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Primitive; + +namespace Snap.Hutao.Service.Metadata.ContextAbstraction; + +internal interface IMetadataDictionaryIdProfilePictureSource +{ + public Dictionary IdProfilePictureMap { 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..53a5d504 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/MetadataServiceContextExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/MetadataServiceContextExtension.cs @@ -66,6 +66,21 @@ internal static class MetadataServiceContextExtension dictionaryIdMaterialSource.IdMaterialMap = await metadataService.GetIdToMaterialMapAsync(token).ConfigureAwait(false); } + if (context is IMetadataDictionaryIdProfilePictureSource dictionaryIdProfilePictureSource) + { + dictionaryIdProfilePictureSource.IdProfilePictureMap = await metadataService.GetIdToProfilePictureMapAsync(token).ConfigureAwait(false); + } + + if (context is IMetadataDictionaryAvatarIdProfilePictureSource dictionaryAvatarIdProfilePictureSource) + { + dictionaryAvatarIdProfilePictureSource.AvatarIdProfilePictureMap = await metadataService.GetAvatarIdToProfilePictureMapAsync(token).ConfigureAwait(false); + } + + if (context is IMetadataDictionaryCostumeIdProfilePictureSource dictionaryCostumeIdProfilePictureSource) + { + dictionaryCostumeIdProfilePictureSource.CostumeIdProfilePictureMap = await metadataService.GetCostumeIdToProfilePictureMapAsync(token).ConfigureAwait(false); + } + if (context is IMetadataDictionaryIdReliquarySource dictionaryIdReliquarySource) { dictionaryIdReliquarySource.IdReliquaryMap = await metadataService.GetIdToReliquaryMapAsync(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/MetadataServiceDictionaryExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceDictionaryExtension.cs index 364808a5..3313145a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceDictionaryExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceDictionaryExtension.cs @@ -71,6 +71,21 @@ internal static class MetadataServiceDictionaryExtension return metadataService.FromCacheAsDictionaryAsync(FileNameMaterial, a => a.Id, token); } + public static ValueTask> GetIdToProfilePictureMapAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheAsDictionaryAsync(FileNameProfilePicture, p => p.Id, token); + } + + public static ValueTask> GetAvatarIdToProfilePictureMapAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheAsDictionaryAsync(FileNameProfilePicture, p => p.AvatarId, token); + } + + public static ValueTask> GetCostumeIdToProfilePictureMapAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheAsDictionaryAsync(FileNameProfilePicture, p => p.CostumeId, token); + } + public static ValueTask> GetIdToReliquaryMapAsync(this IMetadataService metadataService, CancellationToken token = default) { return metadataService.FromCacheAsDictionaryAsync(FileNameReliquary, (List list) => list.SelectMany(r => r.Ids, (r, i) => (Index: i, Reliquary: r)), token); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserGameRoleDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserGameRoleDbService.cs new file mode 100644 index 00000000..217af9a2 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserGameRoleDbService.cs @@ -0,0 +1,18 @@ +// 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 IUserGameRoleDbService : IAppDbService +{ + ValueTask ContainsUidAsync(string uid, CancellationToken token = default); + + ValueTask GetUserGameRoleProfilePictureByUidAsync(string uid, CancellationToken token = default); + + ValueTask UpdateUserGameRoleProfilePictureAsync(UserGameRoleProfilePicture profilePicture, CancellationToken token = default); + + ValueTask DeleteUserGameRoleProfilePictureByUidAsync(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..5509609e 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 @@ -8,4 +10,6 @@ internal interface IUserInitializationService ValueTask CreateUserFromInputCookieOrDefaultAsync(InputCookie inputCookie, CancellationToken token = default(CancellationToken)); ValueTask ResumeUserAsync(Model.Entity.User inner, CancellationToken token = default(CancellationToken)); + + ValueTask RefreshUserGameRolesProfilePictureAsync(UserGameRole userGameRole, CancellationToken token = default); } \ No newline at end of file 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..4b64ae97 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserMetadataContext.cs @@ -0,0 +1,11 @@ +// 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, + IMetadataDictionaryIdProfilePictureSource, + IMetadataDictionaryAvatarIdProfilePictureSource, + IMetadataDictionaryCostumeIdProfilePictureSource; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs index 011b0f40..08e1fd87 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 RefreshUserGameRoleProfilePictureAsync(UserGameRole userGameRole); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserGameRoleDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserGameRoleDbService.cs new file mode 100644 index 00000000..09ef97f4 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserGameRoleDbService.cs @@ -0,0 +1,37 @@ +// 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(IUserGameRoleDbService))] +internal sealed partial class UserGameRoleDbService : IUserGameRoleDbService +{ + private readonly IServiceProvider serviceProvider; + + public IServiceProvider ServiceProvider { get => serviceProvider; } + + public ValueTask ContainsUidAsync(string uid, CancellationToken token = default) + { + return this.QueryAsync(query => query.AnyAsync(n => n.Uid == uid)); + } + + public ValueTask GetUserGameRoleProfilePictureByUidAsync(string uid, CancellationToken token = default) + { + return this.QueryAsync(query => query.FirstAsync(n => n.Uid == uid)); + } + + public async ValueTask UpdateUserGameRoleProfilePictureAsync(UserGameRoleProfilePicture profilePicture, CancellationToken token = default) + { + await this.UpdateAsync(profilePicture, token).ConfigureAwait(false); + } + + public async ValueTask DeleteUserGameRoleProfilePictureByUidAsync(string uid, CancellationToken token = default) + { + await this.DeleteAsync(profilePicture => profilePicture.Uid == uid, token).ConfigureAwait(false); + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs index 18af3acd..e0bbe03a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs @@ -2,12 +2,18 @@ // Licensed under the MIT license. using Snap.Hutao.Core.DependencyInjection.Abstraction; +using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity.Extension; +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; using Snap.Hutao.Web.Hoyolab.Bbs.User; using Snap.Hutao.Web.Hoyolab.Passport; using Snap.Hutao.Web.Hoyolab.Takumi.Binding; using Snap.Hutao.Web.Response; +using MetadataProfilePicture = Snap.Hutao.Model.Metadata.Avatar.ProfilePicture; namespace Snap.Hutao.Service.User; @@ -16,6 +22,7 @@ namespace Snap.Hutao.Service.User; internal sealed partial class UserInitializationService : IUserInitializationService { private readonly IUserFingerprintService userFingerprintService; + private readonly IUserGameRoleDbService userGameRoleDbService; private readonly IServiceProvider serviceProvider; public async ValueTask ResumeUserAsync(Model.Entity.User inner, CancellationToken token = default) @@ -55,6 +62,30 @@ internal sealed partial class UserInitializationService : IUserInitializationSer } } + public async ValueTask RefreshUserGameRolesProfilePictureAsync(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); + } + + if (enkaResponse is { PlayerInfo: { } playerInfo }) + { + UserGameRoleProfilePicture profilePicture = UserGameRoleProfilePicture.From(userGameRole, playerInfo.ProfilePicture); + + await userGameRoleDbService.DeleteUserGameRoleProfilePictureByUidAsync(userGameRole.GameUid, token).ConfigureAwait(false); + await userGameRoleDbService.UpdateUserGameRoleProfilePictureAsync(profilePicture, token).ConfigureAwait(false); + + await SetUserGameRolesProfilePictureCoreAsync(userGameRole, profilePicture, token).ConfigureAwait(false); + } + } + private async ValueTask InitializeUserAsync(ViewModel.User.User user, CancellationToken token = default) { if (user.IsInitialized) @@ -89,6 +120,8 @@ internal sealed partial class UserInitializationService : IUserInitializationSer return false; } + TrySetUserUserGameRolesProfilePictureAsync(user, token).SafeForget(); + await userFingerprintService.TryInitializeAsync(user, token).ConfigureAwait(false); return user.IsInitialized = true; @@ -216,4 +249,69 @@ internal sealed partial class UserInitializationService : IUserInitializationSer return false; } } + + private async ValueTask TrySetUserUserGameRolesProfilePictureAsync(ViewModel.User.User user, CancellationToken token = default) + { + foreach (UserGameRole userGameRole in user.UserGameRoles) + { + if (await userGameRoleDbService.ContainsUidAsync(userGameRole.GameUid, token).ConfigureAwait(false)) + { + UserGameRoleProfilePicture savedProfilePicture = await userGameRoleDbService + .GetUserGameRoleProfilePictureByUidAsync(userGameRole.GameUid, token) + .ConfigureAwait(false); + + if (await SetUserGameRolesProfilePictureCoreAsync(userGameRole, savedProfilePicture, token).ConfigureAwait(false)) + { + continue; + } + } + + await RefreshUserGameRolesProfilePictureAsync(userGameRole, token).ConfigureAwait(false); + } + } + + private async ValueTask SetUserGameRolesProfilePictureCoreAsync(UserGameRole userGameRole, UserGameRoleProfilePicture profilePicture, CancellationToken token = default) + { + if (profilePicture.LastUpdateTime.AddDays(15) < DateTimeOffset.Now) + { + return false; + } + + UserMetadataContext context; + using (IServiceScope scope = serviceProvider.CreateScope()) + { + IMetadataService metadataService = scope.ServiceProvider + .GetRequiredService(); + + if (!await metadataService.InitializeAsync().ConfigureAwait(false)) + { + return false; + } + + context = await scope.ServiceProvider + .GetRequiredService() + .GetContextAsync(token) + .ConfigureAwait(false); + } + + if (context.IdProfilePictureMap.TryGetValue(profilePicture.ProfilePictureId, out MetadataProfilePicture? metadataProfilePicture)) + { + userGameRole.ProfilePictureIcon = metadataProfilePicture.Icon; + return true; + } + + if (context.CostumeIdProfilePictureMap.TryGetValue(profilePicture.CostumeId, out metadataProfilePicture)) + { + userGameRole.ProfilePictureIcon = metadataProfilePicture.Icon; + return true; + } + + if (context.AvatarIdProfilePictureMap.TryGetValue(profilePicture.AvatarId, out metadataProfilePicture)) + { + userGameRole.ProfilePictureIcon = metadataProfilePicture.Icon; + return true; + } + + return false; + } } \ No newline at end of file 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..03b60b6e --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserMetadataContext.cs @@ -0,0 +1,16 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Metadata.Avatar; +using Snap.Hutao.Model.Primitive; + +namespace Snap.Hutao.Service.User; + +internal class UserMetadataContext : IUserMetadataContext +{ + public Dictionary IdProfilePictureMap { get; set; } = default!; + + public Dictionary AvatarIdProfilePictureMap { get; set; } = default!; + + public Dictionary CostumeIdProfilePictureMap { 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..6766ad1e 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 IUserInitializationService userInitializationService; 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 RefreshUserGameRoleProfilePictureAsync(UserGameRole userGameRole) + { + await userInitializationService.RefreshUserGameRolesProfilePictureAsync(userGameRole).ConfigureAwait(false); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj index 8c589fbd..1c22d37a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj +++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj @@ -314,8 +314,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml index e659bf97..022e4bfe 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml @@ -19,14 +19,73 @@ - - - - + + + + + + + + + + + + diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs index 3c3847ae..fc8d7b59 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; @@ -297,4 +298,10 @@ internal sealed partial class UserViewModel : ObservableObject FlyoutBase.ShowAttachedFlyout(appBarButton); infoBarService.Warning(message); } + + [Command("RefreshUserGameRoleProfilePictureCommand")] + private void RefreshUserGameRoleProfilePicture(UserGameRole userGameRole) + { + userService.RefreshUserGameRoleProfilePictureAsync(userGameRole).SafeForget(); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Enka/EnkaClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Enka/EnkaClient.cs index c02c196c..77e8814b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Enka/EnkaClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Enka/EnkaClient.cs @@ -21,11 +21,23 @@ 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) + { + // TODO + return TryGetEnkaResponseCoreAsync($"https://enka-api.hut.ao/{playerUid}?info", token); + } + + public ValueTask GetPlayerInfoAsync(in PlayerUid playerUid, CancellationToken token = default) + { + return TryGetEnkaResponseCoreAsync(string.Format(CultureInfo.CurrentCulture, EnkaInfoAPI, playerUid), token); + } + public ValueTask GetForwardDataAsync(in PlayerUid playerUid, CancellationToken token = default) { return TryGetEnkaResponseCoreAsync(HutaoEndpoints.Enka(playerUid), token); 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..2bf0aafc 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Enka/Model/ProfilePicture.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Enka/Model/ProfilePicture.cs @@ -12,5 +12,11 @@ namespace Snap.Hutao.Web.Enka.Model; internal sealed class ProfilePicture { [JsonPropertyName("id")] - public ProfilePictureId Id { get; set; } + public ProfilePictureId ProfilePictureId { 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..1ad7a81b 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 @@ -57,6 +57,8 @@ internal sealed class UserGameRole [JsonPropertyName("is_official")] public bool IsOfficial { get; set; } = default!; + public string ProfilePictureIcon { get; set; } = default!; + /// /// 玩家服务器与等级简述 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Web/HutaoEndpoints.cs b/src/Snap.Hutao/Snap.Hutao/Web/HutaoEndpoints.cs index 093c38e8..b2d2dc9c 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 @@ -292,4 +297,5 @@ internal static class HutaoEndpoints private const string ApiSnapGenshinStaticZip = $"{ApiSnapGenshin}/static/zip"; private const string ApiSnapGenshinEnka = $"{ApiSnapGenshin}/enka"; private const string HomaSnapGenshin = "https://homa.snapgenshin.com"; + private const string EnkaHutao = "https://enka-api.hut.ao"; } \ No newline at end of file