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