feat(bot): load custom avatar on instance startup

Thread AvatarStore from index.ts → BotManager → BotInstance so every
BotInstance reads the persisted custom avatar from disk at construction
time and hands it to BotProfileManager via setCustomAvatar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
saopig1andClaude Sonnet 4.6 committed 2026-05-07 20:07:47 +08:00
1 parent ffa27d7224
commit 2dd6a9e20d
3 files changed
+25 -4

No files matched your search

+10
View File
@@ -16,6 +16,7 @@ import type { Logger } from "../logger.js";
import type { BotDatabase, ProfileConfig } from "../data/database.js"; import type { BotDatabase, ProfileConfig } from "../data/database.js";
import type { BotConfig } from "../data/config.js"; import type { BotConfig } from "../data/config.js";
import { BotProfileManager } from "./profile.js"; import { BotProfileManager } from "./profile.js";
import type { AvatarStore } from "../data/avatars.js";
export interface BotInstanceOptions { export interface BotInstanceOptions {
id: string; id: string;
@@ -28,6 +29,7 @@ export interface BotInstanceOptions {
database: BotDatabase; database: BotDatabase;
config: BotConfig; config: BotConfig;
logger: Logger; logger: Logger;
avatarStore: AvatarStore;
} }
export interface BotStatus { export interface BotStatus {
@@ -57,6 +59,7 @@ export class BotInstance extends EventEmitter {
private database: BotDatabase; private database: BotDatabase;
private config: BotConfig; private config: BotConfig;
private logger: Logger; private logger: Logger;
private avatarStore: AvatarStore;
private connected = false; private connected = false;
private disconnectEmitted = false; private disconnectEmitted = false;
private voteSkipUsers = new Set<string>(); private voteSkipUsers = new Set<string>();
@@ -77,6 +80,7 @@ export class BotInstance extends EventEmitter {
this.database = options.database; this.database = options.database;
this.config = options.config; this.config = options.config;
this.logger = options.logger.child({ botId: this.id }); this.logger = options.logger.child({ botId: this.id });
this.avatarStore = options.avatarStore;
this.tsClient = new TS3Client(options.tsOptions, this.logger); this.tsClient = new TS3Client(options.tsOptions, this.logger);
this.player = new AudioPlayer(this.logger); this.player = new AudioPlayer(this.logger);
@@ -90,6 +94,12 @@ export class BotInstance extends EventEmitter {
options.tsOptions.nickname, options.tsOptions.nickname,
); );
const relPath = this.database.getCustomAvatarPath(this.id);
if (relPath) {
const buf = this.avatarStore.read(relPath);
if (buf) this.profileManager.setCustomAvatar(buf);
}
this.setupPlayerEvents(); this.setupPlayerEvents();
this.setupTsEvents(); this.setupTsEvents();
} }
+10 -3
View File
@@ -11,6 +11,7 @@ import type { BotConfig } from "../data/config.js";
import type { Logger } from "../logger.js"; import type { Logger } from "../logger.js";
import type { ServerProtocol } from "../ts-protocol/client.js"; import type { ServerProtocol } from "../ts-protocol/client.js";
import type { AvatarStore } from "../data/avatars.js";
/** /**
* Run bot.connect() with a hard deadline. If the handshake hangs (e.g. the * Run bot.connect() with a hard deadline. If the handshake hangs (e.g. the
@@ -74,6 +75,7 @@ export class BotManager extends EventEmitter {
private database: BotDatabase; private database: BotDatabase;
private config: BotConfig; private config: BotConfig;
private logger: Logger; private logger: Logger;
private avatarStore: AvatarStore;
constructor( constructor(
neteaseProvider: MusicProvider, neteaseProvider: MusicProvider,
@@ -81,7 +83,8 @@ export class BotManager extends EventEmitter {
bilibiliProvider: MusicProvider, bilibiliProvider: MusicProvider,
database: BotDatabase, database: BotDatabase,
config: BotConfig, config: BotConfig,
logger: Logger logger: Logger,
avatarStore: AvatarStore
) { ) {
super(); super();
this.neteaseProvider = neteaseProvider; this.neteaseProvider = neteaseProvider;
@@ -91,6 +94,7 @@ export class BotManager extends EventEmitter {
this.database = database; this.database = database;
this.config = config; this.config = config;
this.logger = logger; this.logger = logger;
this.avatarStore = avatarStore;
} }
async createBot(params: CreateBotParams): Promise<BotInstance> { async createBot(params: CreateBotParams): Promise<BotInstance> {
@@ -117,6 +121,7 @@ export class BotManager extends EventEmitter {
database: this.database, database: this.database,
config: this.config, config: this.config,
logger: this.logger, logger: this.logger,
avatarStore: this.avatarStore,
}); });
this.bots.set(id, bot); this.bots.set(id, bot);
@@ -229,10 +234,11 @@ export class BotManager extends EventEmitter {
neteaseProvider: this.neteaseProvider, neteaseProvider: this.neteaseProvider,
qqProvider: this.qqProvider, qqProvider: this.qqProvider,
bilibiliProvider: this.bilibiliProvider, bilibiliProvider: this.bilibiliProvider,
youtubeProvider: this.youtubeProvider, youtubeProvider: this.youtubeProvider,
database: this.database, database: this.database,
config: this.config, config: this.config,
logger: this.logger, logger: this.logger,
avatarStore: this.avatarStore,
}); });
this.bots.set(id, bot); this.bots.set(id, bot);
this.emit("botInstance", bot); this.emit("botInstance", bot);
@@ -279,10 +285,11 @@ export class BotManager extends EventEmitter {
neteaseProvider: this.neteaseProvider, neteaseProvider: this.neteaseProvider,
qqProvider: this.qqProvider, qqProvider: this.qqProvider,
bilibiliProvider: this.bilibiliProvider, bilibiliProvider: this.bilibiliProvider,
youtubeProvider: this.youtubeProvider, youtubeProvider: this.youtubeProvider,
database: this.database, database: this.database,
config: this.config, config: this.config,
logger: this.logger, logger: this.logger,
avatarStore: this.avatarStore,
}); });
this.bots.set(saved.id, bot); this.bots.set(saved.id, bot);
+5 -1
View File
@@ -8,6 +8,7 @@ import { NeteaseProvider } from "./music/netease.js";
import { QQMusicProvider } from "./music/qq.js"; import { QQMusicProvider } from "./music/qq.js";
import { BiliBiliProvider } from "./music/bilibili.js"; import { BiliBiliProvider } from "./music/bilibili.js";
import { createCookieStore } from "./music/auth.js"; import { createCookieStore } from "./music/auth.js";
import { createAvatarStore } from "./data/avatars.js";
import { BotManager } from "./bot/manager.js"; import { BotManager } from "./bot/manager.js";
import { createWebServer } from "./web/server.js"; import { createWebServer } from "./web/server.js";
@@ -18,6 +19,7 @@ const CONFIG_PATH = path.join(ROOT_DIR, "config.json");
const DB_PATH = path.join(DATA_DIR, "tsmusicbot.db"); const DB_PATH = path.join(DATA_DIR, "tsmusicbot.db");
const LOG_DIR = path.join(DATA_DIR, "logs"); const LOG_DIR = path.join(DATA_DIR, "logs");
const COOKIE_DIR = path.join(DATA_DIR, "cookies"); const COOKIE_DIR = path.join(DATA_DIR, "cookies");
const AVATAR_DIR = path.join(DATA_DIR, "avatars");
const STATIC_DIR = path.join(ROOT_DIR, "web", "dist"); const STATIC_DIR = path.join(ROOT_DIR, "web", "dist");
async function main() { async function main() {
@@ -46,6 +48,7 @@ async function main() {
const bilibiliProvider = new BiliBiliProvider(); const bilibiliProvider = new BiliBiliProvider();
const cookieStore = createCookieStore(COOKIE_DIR); const cookieStore = createCookieStore(COOKIE_DIR);
const avatarStore = createAvatarStore(AVATAR_DIR);
const neteaseCookie = cookieStore.load("netease"); const neteaseCookie = cookieStore.load("netease");
if (neteaseCookie) neteaseProvider.setCookie(neteaseCookie); if (neteaseCookie) neteaseProvider.setCookie(neteaseCookie);
const qqCookie = cookieStore.load("qq"); const qqCookie = cookieStore.load("qq");
@@ -59,7 +62,8 @@ async function main() {
bilibiliProvider, bilibiliProvider,
db, db,
config, config,
logger logger,
avatarStore
); );
await botManager.loadSavedBots(); await botManager.loadSavedBots();