A discord.js TTS bot that streams AI voices into a voice channel
A text to speech bot is one of the most popular Discord bots: it joins the voice channel and reads what muted members type. Building your own lets you pick the voice: a narrator, an announcer, a game or anime character, or your server’s own voice.
This guide builds one with discord.js and @discordjs/voice. The audio comes from the SuperClonica speech API, which streams speech from about 300 ms in our measurements, so the bot starts talking almost as soon as a message arrives.
What you need
Node.js 18 or newer, a bot application in the Discord Developer Portal with the Message Content intent turned on, and an invite with the Connect and Speak permissions. Then install:
npm install discord.js @discordjs/voice @discordjs/opus ffmpeg-static
Per the @discordjs/voice README, it needs an Opus library such as @discordjs/opus and FFmpeg, which ffmpeg-static provides; its DAVE end-to-end encryption library comes pre-installed. Create a SuperClonica API key on the speech API page; it is free and needs no card.
The bot
Set DISCORD_TOKEN, TTS_CHANNEL_ID (the text channel to read), SUPERCLONICA_KEY and VOICE_ID, the ID shown on the page of the catalog voice you picked, then run the file with 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);Each message becomes one request with "stream": true. The API answers with an Ogg stream that @discordjs/voice transcodes through FFmpeg and plays as it arrives; messages that come in while the bot is talking wait in a queue.
Voices and plans
Every voice in the SuperClonica catalog works through the API by its ID, on any plan and without buying it; you can also train a voice from your own recording and give the bot that. The free key gives a minute of audio a day. Starter, $5 a month, gives up to 3 minutes of speech in every 10-minute window, and the larger plans 8, 20 or 65 minutes. When a window is used up, the API answers 429 with Retry-After and nothing is charged.
To run without any API, use the free SuperClonica engine on your own machine; the guide to a Python TTS bot shows the offline version.