GUIDE

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.