yaejunyang/packages/bot/tts.ts
2026-05-19 18:08:13 +00:00

123 lines
4 KiB
TypeScript

import { AudioPlayer, AudioPlayerStatus, AudioResource, createAudioPlayer, VoiceConnection } from "@discordjs/voice";
import { Voice } from "../db/generated/prisma/enums";
import TTSTypecastModel from "../tts/typecast";
import TTSPapagoModel from "../tts/papago";
import { Guild } from "discord.js";
import { getOrCreateVoiceConnection } from "./util";
import TTSModelBase from "../tts";
import { DiscordUserProfile } from "../db/generated/prisma/client";
import { nyaize } from "../utils/nyaize";
import { OutputHandler } from "../utils/outputHandler";
import TTSSupertonicModel from "../tts/supertonic";
class VoiceQueue {
private connection: VoiceConnection;
private list: AudioResource[];
private currentPlayer?: AudioPlayer;
constructor(connection: VoiceConnection) {
this.connection = connection;
this.list = [];
}
public static fromConnection(connection: VoiceConnection): VoiceQueue {
return (connection as any).queue ??= new VoiceQueue(connection);
}
private play() {
if (!this.list[0]) return;
const player = this.currentPlayer = createAudioPlayer();
this.connection.subscribe(player);
player.once(AudioPlayerStatus.Idle, this.next.bind(this));
player.play(this.list[0]);
}
public enqueue(resource: AudioResource) {
this.list.push(resource);
if (this.list.length == 1) {
this.play();
return;
}
}
public next() {
this.currentPlayer?.removeAllListeners(AudioPlayerStatus.Idle);
this.list.shift();
this.play();
}
public hasNext(): boolean {
return !!this.list[0];
}
}
export type PlayVoiceOptions = {
supertonicStyleId?: string,
};
export async function playVoice(
guild: Guild,
profile: DiscordUserProfile,
voice: Voice,
text: string,
options?: PlayVoiceOptions,
) {
if (profile.nya)
text = nyaize(text);
try {
let connection = await getOrCreateVoiceConnection(guild);
if (!connection)
throw new Error("Yaeju is not joined VoiceChat");
if (voice == "TypeCast" && !profile.canTypecast) {
throw new Error(`the user ${profile.userId} is can't use typecast voice`);
}
let voiceBuffer: Buffer
if (voice == "TypeCast") {
const content = TTSTypecastModel.instance.ttsify(text);
if (!content.length)
throw new Error("Empty content");
voiceBuffer = await TTSTypecastModel.instance.getMemcachedVoice(
TTSTypecastModel.instance.createRequestId(content)
);
} else if (voice == "Supertonic") {
const content = TTSSupertonicModel.instance.ttsify(text);
if (!content.length)
throw new Error("Empty content");
voiceBuffer = await TTSSupertonicModel.instance.getMemcachedVoice(
TTSSupertonicModel.instance.createRequestId(content, options?.supertonicStyleId)
);
} else if (voice == "Papago") {
const content = TTSPapagoModel.instance.ttsify(text);
if (!content.length)
throw new Error("Empty content");
voiceBuffer = await TTSPapagoModel.instance.getMemcachedVoice(
TTSPapagoModel.instance.createRequestId(content)
);
} else {
throw new Error(`Unknown voice type: ${voice}`);
}
VoiceQueue.fromConnection(connection).enqueue(
TTSModelBase.bufferToAudioResource(voiceBuffer)
);
} catch(err) {
OutputHandler.errorLog("[PlayVoice Error]", err);
throw new Error(err as any);
}
}
export async function skipCurrentVoice(guild: Guild): Promise<boolean> {
let connection = await getOrCreateVoiceConnection(guild);
if (!connection)
throw new Error("Yaeju is not joined VoiceChat");
const vqueue = VoiceQueue.fromConnection(connection);
if (vqueue.hasNext()) {
vqueue.next();
return true;
}
return false;
}