Un bot TTS con discord.js que transmite voces IA a un canal de voz
Un bot de texto a voz es de los bots de Discord más populares: entra al canal de voz y lee lo que escriben los miembros sin micrófono. Con uno propio eliges la voz: un narrador, un locutor, un personaje de juego o de anime o la voz propia de tu servidor.
Esta guía crea uno con discord.js y @discordjs/voice. El audio lo da la API de voz de SuperClonica, que según nuestras mediciones transmite voz desde unos 300 ms, así que el bot habla casi en cuanto llega un mensaje.
Qué necesitas
Node.js 18 o posterior, una aplicación de bot en el Discord Developer Portal con el intent de Message Content activado y una invitación con los permisos Conectar y Hablar. Después instala:
npm install discord.js @discordjs/voice @discordjs/opus ffmpeg-static
Según el README de @discordjs/voice, necesita una biblioteca Opus como @discordjs/opus y FFmpeg, que aporta ffmpeg-static; la biblioteca de cifrado de extremo a extremo DAVE viene preinstalada. Crea una clave de API de SuperClonica en la página de la API de voz; es gratis y no pide tarjeta.
El bot
Define DISCORD_TOKEN, TTS_CHANNEL_ID (el canal de texto que se leerá), SUPERCLONICA_KEY y VOICE_ID, el ID que aparece en la página de la voz del catálogo elegida, y ejecuta el archivo con node:
import { Client, GatewayIntentBits } from "discord.js";
import { joinVoiceChannel, createAudioPlayer, createAudioResource, AudioPlayerStatus, getVoiceConnection } from "@discordjs/voice";
import { Readable } from "node:stream";
const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent, GatewayIntentBits.GuildVoiceStates] });
const player = createAudioPlayer();
const queue = [];
async function speak(text) {
const answer = await fetch("https://superclonica.com/api/v1/speech", {
method: "POST",
headers: { Authorization: "Bearer " + process.env.SUPERCLONICA_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ text, voice: process.env.VOICE_ID, language: "en", stream: true }),
});
if (!answer.ok) throw new Error(await answer.text());
return createAudioResource(Readable.fromWeb(answer.body));
}
player.on(AudioPlayerStatus.Idle, async () => {
const next = queue.shift();
if (next) player.play(await next());
});
client.on("messageCreate", async (message) => {
if (message.author.bot || message.channelId !== process.env.TTS_CHANNEL_ID) return;
const channel = message.member?.voice.channel;
if (!channel) return;
const connection = getVoiceConnection(message.guildId) ?? joinVoiceChannel({
channelId: channel.id, guildId: message.guildId, adapterCreator: message.guild.voiceAdapterCreator });
connection.subscribe(player);
const job = () => speak(message.member.displayName + " says: " + message.cleanContent.slice(0, 300));
if (player.state.status === AudioPlayerStatus.Idle && queue.length === 0) player.play(await job());
else queue.push(job);
});
client.login(process.env.DISCORD_TOKEN);Cada mensaje es una petición con "stream": true. La API responde con un flujo Ogg que @discordjs/voice convierte con FFmpeg y reproduce a medida que llega; los mensajes que entran mientras el bot habla esperan en una cola.
Voces y planes
Todas las voces del catálogo de SuperClonica funcionan por la API con su ID, en cualquier plan y sin comprarlas; también puedes entrenar una voz con tu propia grabación y dársela al bot. La clave gratuita da un minuto de audio al día. Inicio, 5 $ al mes, da hasta 3 minutos de voz en cada ventana de 10 minutos, y los planes mayores 8, 20 o 65 minutos. Cuando se agota una ventana, la API responde 429 con Retry-After y no se cobra nada.
Para prescindir de la API, usa el motor gratuito de SuperClonica en tu equipo; la guía del bot TTS en Python muestra la versión sin conexión.