feat: add tts commands
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
import { CommandCategoryInfo } from '../../commands';
|
||||
|
||||
const info: CommandCategoryInfo = {
|
||||
name: 'TTS',
|
||||
description: 'Text to Speech related commands'
|
||||
};
|
||||
|
||||
export default info;
|
||||
@@ -0,0 +1,22 @@
|
||||
import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js';
|
||||
import { Command } from '../../commands';
|
||||
|
||||
const builder = new SlashCommandBuilder()
|
||||
.setName('tts-clear')
|
||||
.setDescription('Clears the TTS queue');
|
||||
|
||||
const cmd: Command = {
|
||||
name: builder.name,
|
||||
builder: builder,
|
||||
requiresAdmin: true,
|
||||
execute: async (interaction: ChatInputCommandInteraction): Promise<void> => {
|
||||
if (!interaction.guildId) {
|
||||
interaction.reply('This command only works on Guilds');
|
||||
return;
|
||||
}
|
||||
|
||||
interaction.reply('Queue cleared.');
|
||||
}
|
||||
};
|
||||
|
||||
export default cmd;
|
||||
@@ -0,0 +1,97 @@
|
||||
import { Message } from 'discord.js';
|
||||
import { Command } from '../../commands';
|
||||
import { Logger } from '../../utils/log';
|
||||
import { getVoiceConnection, VoiceConnectionStatus } from '@discordjs/voice';
|
||||
import { TTSManager } from '../../modules/tts';
|
||||
import { AudioStreamManager } from '../../modules/audiostreams';
|
||||
import { Readable } from 'stream';
|
||||
import { DataTypes } from 'sequelize';
|
||||
import { config } from '../../utils/config';
|
||||
import { DatabaseManager } from '../../modules/db';
|
||||
|
||||
const URL_REGEX = /(?:https?|ftp):\/\/[\n\S]+/g;
|
||||
|
||||
class TTSListener implements Command {
|
||||
private log: Logger;
|
||||
|
||||
constructor() {
|
||||
this.log = new Logger('TTS Listener');
|
||||
}
|
||||
|
||||
user_keys = {
|
||||
tts_voice: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: config.tts_default_voice
|
||||
},
|
||||
tts_mode: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: config.tts_default_mode
|
||||
}
|
||||
};
|
||||
|
||||
guild_keys = {
|
||||
tts_channel: {
|
||||
type: DataTypes.STRING
|
||||
}
|
||||
};
|
||||
|
||||
messageListener = async (msg: Message): Promise<void> => {
|
||||
if (msg.content.length === 0) return;
|
||||
|
||||
const guild = msg.guild;
|
||||
if (!guild) return;
|
||||
|
||||
const me = guild.members.me;
|
||||
if (!me || !me.voice) return;
|
||||
|
||||
const member = msg.member;
|
||||
if (!member || !member.voice) return;
|
||||
|
||||
if (member.voice.channelId !== me.voice.channelId) return;
|
||||
|
||||
const voiceConnection = getVoiceConnection(guild.id);
|
||||
if (
|
||||
!voiceConnection ||
|
||||
voiceConnection.state.status !== VoiceConnectionStatus.Ready
|
||||
)
|
||||
return;
|
||||
|
||||
const db = await DatabaseManager.get;
|
||||
|
||||
const guildData = await db.getGuild(guild.id);
|
||||
if (msg.channelId !== guildData.get('tts_channel')) return;
|
||||
|
||||
const userData = await db.getUser(member.id);
|
||||
|
||||
const modName = userData.get('tts_mode') as string;
|
||||
const voiceName = userData.get('tts_voice') as string;
|
||||
|
||||
const processTTS = async () => {
|
||||
try {
|
||||
const ttsModule = TTSManager.get.getModule(modName);
|
||||
if (!ttsModule) return;
|
||||
|
||||
const voices = await ttsModule.getVoices();
|
||||
if (!voices) return;
|
||||
if (!voices.includes(voiceName)) return;
|
||||
|
||||
const msgFiltered = msg.content.replace(URL_REGEX, 'a link');
|
||||
|
||||
const audio = await ttsModule.generate(voiceName, msgFiltered);
|
||||
|
||||
if (audio?.data) {
|
||||
const stream =
|
||||
AudioStreamManager.get.getOrCreateStream(voiceConnection);
|
||||
const queue = stream.getQueue('TTS');
|
||||
queue.enqueue(Readable.from(audio.data));
|
||||
}
|
||||
} catch (err) {
|
||||
this.log.error('error occurred while processing TTS message (%s)', err);
|
||||
}
|
||||
};
|
||||
|
||||
processTTS();
|
||||
};
|
||||
}
|
||||
|
||||
export default new TTSListener();
|
||||
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
ChatInputCommandInteraction,
|
||||
MessageFlags,
|
||||
SlashCommandBuilder
|
||||
} from 'discord.js';
|
||||
import { Command } from '../../commands';
|
||||
import { DatabaseManager } from '../../modules/db';
|
||||
|
||||
const builder = new SlashCommandBuilder()
|
||||
.setName('tts-channel')
|
||||
.setDescription('Sets the channel where TTS messages will be read from');
|
||||
|
||||
const cmd: Command = {
|
||||
name: builder.name,
|
||||
builder: builder,
|
||||
|
||||
execute: async (interaction: ChatInputCommandInteraction): Promise<void> => {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
|
||||
if (!interaction.guild) {
|
||||
interaction.editReply('This message can only be executed on guilds');
|
||||
return;
|
||||
}
|
||||
|
||||
const guildData = await DatabaseManager.get.getGuild(interaction.guild.id);
|
||||
|
||||
await guildData.set('tts_channel', interaction.channelId);
|
||||
await guildData.save();
|
||||
|
||||
interaction.editReply('TTS channel updated.');
|
||||
}
|
||||
};
|
||||
|
||||
export default cmd;
|
||||
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
AutocompleteInteraction,
|
||||
ChatInputCommandInteraction,
|
||||
MessageFlags,
|
||||
SlashCommandBuilder
|
||||
} from 'discord.js';
|
||||
import { Command } from '../../commands';
|
||||
import { DatabaseManager } from '../../modules/db';
|
||||
import { TTSManager } from '../../modules/tts';
|
||||
|
||||
const builder = new SlashCommandBuilder()
|
||||
.setName('tts-mode')
|
||||
.setDescription('Selects a mode to use for TTS')
|
||||
.addStringOption((opt) =>
|
||||
opt
|
||||
.setName('mode')
|
||||
.setDescription('Which TTS provider to use')
|
||||
.setAutocomplete(true)
|
||||
.setRequired(true)
|
||||
);
|
||||
|
||||
const cmd: Command = {
|
||||
name: builder.name,
|
||||
builder: builder,
|
||||
|
||||
execute: async (interaction: ChatInputCommandInteraction): Promise<void> => {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
|
||||
const userData = await DatabaseManager.get.getUser(interaction.user.id);
|
||||
const modeName = interaction.options.getString('mode', true);
|
||||
const selectedMode = TTSManager.get
|
||||
.getModules()
|
||||
.find((mode) => mode.name === modeName);
|
||||
|
||||
if (!selectedMode) {
|
||||
await interaction.editReply(`Unknown mode (${modeName})`);
|
||||
return;
|
||||
}
|
||||
|
||||
await userData.set('tts_mode', modeName);
|
||||
await userData.save();
|
||||
|
||||
interaction.editReply(`TTS mode has been set to: ${modeName}.`);
|
||||
},
|
||||
|
||||
autocomplete: async (interaction: AutocompleteInteraction): Promise<void> => {
|
||||
const focused = interaction.options.getFocused(true);
|
||||
if (focused.name != 'mode') return;
|
||||
|
||||
const modes = TTSManager.get.getModules();
|
||||
|
||||
const filtered: string[] = modes
|
||||
.filter((mode) =>
|
||||
mode.name.toLowerCase().startsWith(focused.value.toLowerCase())
|
||||
)
|
||||
.map((mode) => mode.name)
|
||||
.slice(0, 25);
|
||||
|
||||
await interaction.respond(
|
||||
filtered.map((choice) => ({ name: choice, value: choice }))
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default cmd;
|
||||
@@ -0,0 +1,76 @@
|
||||
import {
|
||||
AutocompleteInteraction,
|
||||
ChatInputCommandInteraction,
|
||||
MessageFlags,
|
||||
SlashCommandBuilder
|
||||
} from 'discord.js';
|
||||
import { Command } from '../../commands';
|
||||
import { DatabaseManager } from '../../modules/db';
|
||||
import { TTSManager } from '../../modules/tts';
|
||||
|
||||
const builder = new SlashCommandBuilder()
|
||||
.setName('tts-voice')
|
||||
.setDescription('Selects a voice to use for TTS')
|
||||
.addStringOption((opt) =>
|
||||
opt
|
||||
.setName('voice')
|
||||
.setDescription('Which voice to use')
|
||||
.setAutocomplete(true)
|
||||
.setRequired(true)
|
||||
);
|
||||
|
||||
const cmd: Command = {
|
||||
name: builder.name,
|
||||
builder: builder,
|
||||
|
||||
execute: async (interaction: ChatInputCommandInteraction): Promise<void> => {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
|
||||
const userData = await DatabaseManager.get.getUser(interaction.user.id);
|
||||
const mod = TTSManager.get.getModule(userData.get('tts_mode') as string);
|
||||
if (!mod) return;
|
||||
|
||||
const voices = await mod.getVoices();
|
||||
if (!voices) {
|
||||
await interaction.editReply(
|
||||
'Unknown error occured while fetching TTS voices'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const voiceName = interaction.options.getString('voice', true);
|
||||
if (!voices.includes(voiceName)) {
|
||||
await interaction.editReply('Invalid voice');
|
||||
return;
|
||||
}
|
||||
|
||||
await userData.set('tts_voice', voiceName);
|
||||
await userData.save();
|
||||
|
||||
interaction.editReply(`TTS voice has been set to: ${voiceName}.`);
|
||||
},
|
||||
autocomplete: async (interaction: AutocompleteInteraction): Promise<void> => {
|
||||
const focused = interaction.options.getFocused(true);
|
||||
if (focused.name != 'voice') return;
|
||||
|
||||
const userData = await DatabaseManager.get.getUser(interaction.user.id);
|
||||
|
||||
const mode = TTSManager.get.getModule(userData.get('tts_mode') as string);
|
||||
if (!mode) return;
|
||||
|
||||
const voices: string[] | undefined = await mode.getVoices();
|
||||
if (!voices) return;
|
||||
|
||||
const filtered: string[] = voices
|
||||
.filter((voice) =>
|
||||
voice.toLowerCase().startsWith(focused.value.toLowerCase())
|
||||
)
|
||||
.slice(0, 25);
|
||||
|
||||
await interaction.respond(
|
||||
filtered.map((choice) => ({ name: choice, value: choice }))
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default cmd;
|
||||
Reference in New Issue
Block a user