feat(voice): per-user TTS voice selection via /목소리
Until now every TTS message was synthesized with VoiceType.가람
regardless of who spoke. This adds a user-scoped voice preference
persisted in SQLite so each member can pick their own voice and
keep it across bot restarts.
Changes
- db/schema.sql: add nullable `voice_type` column to `users`.
- src/utils/Database.ts:
- run a PRAGMA-driven ALTER TABLE migration so existing DBs gain
the column without dropping data.
- add `DB.user.setVoice(guildId, userId, name, voiceType)` that
upserts the row.
- src/classes/TTSClient.ts:
- export `DEFAULT_VOICE` (= 가람).
- resolve the speaking member's stored voice in `tts()` and
thread it through `getSource()` instead of hardcoding 가람.
- validate stored slug against `VoiceType` so stale/unknown
values silently fall back to the default.
- src/commands/voice.ts (new):
- `/목소리` slash command shows the user's current voice and a
StringSelectMenu of all `VoiceType` entries (현재 + 이전 보이스
모두). Selection writes to `users.voice_type` and confirms
ephemerally.
- defensively creates a guild row if `/tts channel register`
hasn't run yet, to satisfy the ON DELETE CASCADE FK.
Deploy
Run `npm run prod` after pulling so Discord sees the new
`/목소리` command. No env or config changes required.
This commit is contained in:
@@ -13,6 +13,15 @@ const schemaPath = join(process.cwd(), "db/schema.sql");
|
||||
const schema = readFileSync(schemaPath, "utf-8");
|
||||
database.exec(schema);
|
||||
|
||||
// 마이그레이션: 기존 DB 에 voice_type 컬럼이 없으면 추가
|
||||
// (CREATE TABLE IF NOT EXISTS 는 이미 존재하는 테이블에 컬럼을 추가하지 않으므로
|
||||
// PRAGMA 로 직접 확인한다.)
|
||||
const userCols = database.prepare("PRAGMA table_info(users)").all() as { name: string }[];
|
||||
if (!userCols.some(c => c.name === "voice_type")) {
|
||||
database.exec("ALTER TABLE users ADD COLUMN voice_type TEXT");
|
||||
Logger.ready("DB 마이그레이션: users.voice_type 컬럼 추가");
|
||||
}
|
||||
|
||||
Logger.ready("DB 활성화!");
|
||||
|
||||
const stmt = {
|
||||
@@ -108,5 +117,21 @@ export const DB = {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
// 유저 행이 없으면 insert, 있으면 update.
|
||||
// (sqlite UPSERT 대신 명시적 분기 — 기존 set/update 패턴 유지)
|
||||
setVoice(guildId: string, id: string, name: string, voiceType: string) {
|
||||
try {
|
||||
const existing = stmt.user.get.get(guildId, id) as UserType | undefined;
|
||||
if (existing) {
|
||||
stmt.user.update({ guild_id: guildId, id, name, voice_type: voiceType });
|
||||
} else {
|
||||
stmt.user.insert({ guild_id: guildId, id, name, voice_type: voiceType });
|
||||
}
|
||||
return true;
|
||||
} catch (err) {
|
||||
Logger.error(String(err));
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user