Como criar um bot para chat da Twitch e YouTube com Node.js

Um bot para chat da Twitch (e do YouTube) resolve o que mais consome a atenção de quem transmite: responder as mesmas perguntas, cortar spam e manter o chat vivo enquanto você está concentrado no código. Com Node.js, dá para ter uma primeira versão funcionando em uma noite, usando a conexão IRC da Twitch e a API de chat ao vivo do YouTube.

Este tutorial monta um bot do zero, em etapas: o que ele pode fazer, como conectar nas duas plataformas, como estruturar comandos com cooldown e permissões, como moderar automaticamente, quando faz sentido responder com IA e como manter o bot no ar depois do deploy. O código é o mesmo para os dois chats; só muda o adaptador de entrada e saída.

O que um bot de chat pode fazer (comandos, moderação, avisos)

Antes de escrever código, vale separar as funções de um bot em três grupos, porque cada grupo tem exigências técnicas diferentes:

  • Comandos: respostas prontas disparadas por !projeto, !stack, !github, !uptime. Só precisam ler e escrever no chat.
  • Moderação: apagar mensagens com links suspeitos, silenciar spam e dar timeout. Precisam de permissão de moderador e, na Twitch, de chamadas à API Helix.
  • Avisos e automações: lembrar o link do repositório de tempos em tempos, agradecer inscrições, avisar quando a live começa. Precisam de timers e, às vezes, de eventos da plataforma.

Um bot bem feito não responde a tudo. Ele absorve o repetitivo e deixa para o humano o que importa. O guia do Live Copiloto sobre como responder comentários na live sem perder o ritmo traz a visão de quem está do outro lado da câmera: o que vale resposta ao vivo, o que vale resposta automática e o que pode esperar o fim da transmissão.

Twitch: conectando via IRC com tmi.js

O chat da Twitch é, por baixo, um servidor IRC. A biblioteca tmi.js encapsula a conexão, a autenticação e o parse das mensagens, e continua sendo o caminho mais rápido para um bot para chat da Twitch em Node.js. A Twitch também oferece leitura e envio de mensagens via EventSub e API Helix; vale conhecer, mas para começar o IRC é mais simples.

Você vai precisar de uma conta para o bot (de preferência separada da sua), de um token OAuth com os escopos chat:read e chat:edit, e do nome do canal. Guarde tudo em variáveis de ambiente:

npm init -y
npm install tmi.js dotenv

# .env (nunca versionar)
TWITCH_BOT_USERNAME=meubot
TWITCH_OAUTH_TOKEN=xxxxxxxx      # sem o prefixo oauth:
TWITCH_CHANNEL=seucanal
// twitch.js
import 'dotenv/config';
import tmi from 'tmi.js';

export function conectarTwitch(onMessage) {
  const client = new tmi.Client({
    options: { debug: false },
    connection: { reconnect: true, secure: true },
    identity: {
      username: process.env.TWITCH_BOT_USERNAME,
      password: `oauth:${process.env.TWITCH_OAUTH_TOKEN}`,
    },
    channels: [process.env.TWITCH_CHANNEL],
  });

  client.on('message', (channel, tags, text, self) => {
    if (self) return; // ignora as mensagens do próprio bot
    onMessage({
      platform: 'twitch',
      author: tags['display-name'] ?? tags.username,
      roles: [tags.mod && 'mod', tags.badges?.broadcaster && 'owner', tags.subscriber && 'sub'].filter(Boolean),
      text,
      reply: (msg) => client.say(channel, msg),
    });
  });

  return client.connect();
}

O objeto passado para onMessage é a peça central do projeto: uma mensagem normalizada, com autor, papéis (mod, owner, sub), texto e uma função reply. Todo o resto do bot trabalha com esse formato e não sabe de qual plataforma a mensagem veio.

YouTube: lendo o chat ao vivo pela API

O YouTube não tem IRC. O chat ao vivo é lido pela YouTube Data API v3, em dois passos: descobrir o activeLiveChatId da transmissão (a partir do ID do vídeo) e consultar liveChatMessages.list em intervalos. A própria resposta informa quanto tempo esperar antes da próxima chamada (pollingIntervalMillis) e o token da próxima página.

Para ler o chat de uma transmissão pública basta uma chave de API criada no Google Cloud. Para o bot escrever no chat (liveChatMessages.insert) é preciso OAuth 2.0 com o escopo do YouTube, em nome da conta que vai aparecer como autora. Cada chamada consome unidades da cota diária do projeto, então respeite o intervalo sugerido e não faça polling fora da live.

// youtube.js
const API = 'https://www.googleapis.com/youtube/v3';
const KEY = process.env.YOUTUBE_API_KEY;

export async function obterLiveChatId(videoId) {
  const url = `${API}/videos?part=liveStreamingDetails&id=${videoId}&key=${KEY}`;
  const json = await (await fetch(url)).json();
  return json.items?.[0]?.liveStreamingDetails?.activeLiveChatId ?? null;
}

export async function lerChatYouTube(liveChatId, onMessage, pageToken) {
  const params = new URLSearchParams({ liveChatId, part: 'snippet,authorDetails', key: KEY });
  if (pageToken) params.set('pageToken', pageToken);

  const json = await (await fetch(`${API}/liveChat/messages?${params}`)).json();

  for (const item of json.items ?? []) {
    const a = item.authorDetails;
    onMessage({
      platform: 'youtube',
      author: a.displayName,
      roles: [a.isChatModerator && 'mod', a.isChatOwner && 'owner', a.isChatSponsor && 'sub'].filter(Boolean),
      text: item.snippet.displayMessage,
      reply: (msg) => enviarNoYouTube(liveChatId, msg), // precisa de OAuth
    });
  }

  // respeita o intervalo que a API pede
  setTimeout(() => lerChatYouTube(liveChatId, onMessage, json.nextPageToken), json.pollingIntervalMillis ?? 5000);
}

A função enviarNoYouTube faz um POST em liveChat/messages?part=snippet com o token OAuth no cabeçalho Authorization: Bearer e um corpo com snippet.liveChatId, snippet.type igual a textMessageEvent e o texto em snippet.textMessageDetails.messageText. A biblioteca oficial googleapis cuida do fluxo OAuth e da renovação do token, e é o que recomendo para não reinventar essa parte.

Infográfico com a anatomia de um bot de chat: conexão via IRC ou API, leitura de mensagens em tempo real, comandos com cooldown e permissões, moderação de palavras, links e spam, e respostas com IA quando faz sentido

Comandos, cooldown e permissões

Com as mensagens normalizadas, a lógica de comandos fica em um único módulo. Três regras evitam dor de cabeça: todo comando tem cooldown (para o bot não virar o spammer do próprio chat), comandos sensíveis exigem papel (mod ou owner) e o bot só reage a mensagens que começam com o prefixo.

// comandos.js
const comandos = new Map([
  ['projeto', { cooldownMs: 30_000, run: () => 'Hoje: API de login com JWT. Repo: github.com/seu-usuario/api-login' }],
  ['stack',   { cooldownMs: 30_000, run: () => 'Node 22, Fastify, Postgres e Docker.' }],
  ['uptime',  { cooldownMs: 10_000, run: () => `Live no ar há ${Math.round(process.uptime() / 60)} min.` }],
  ['aviso',   { cooldownMs: 0, roles: ['mod', 'owner'], run: (msg, args) => args.join(' ') }],
]);

const ultimoUso = new Map();

export function tratarComando(msg) {
  if (!msg.text.startsWith('!')) return null;
  const [nome, ...args] = msg.text.slice(1).trim().split(/\s+/);
  const cmd = comandos.get(nome.toLowerCase());
  if (!cmd) return null;

  if (cmd.roles && !cmd.roles.some((r) => msg.roles.includes(r))) return null;

  const agora = Date.now();
  if (agora - (ultimoUso.get(nome) ?? 0) < cmd.cooldownMs) return null;
  ultimoUso.set(nome, agora);

  return cmd.run(msg, args);
}

O arquivo principal junta tudo. Repare que ele não sabe nada de IRC ou de API do YouTube: só recebe mensagens, passa pelo tratador de comandos e, se houver resposta, chama reply.

// index.js
import { conectarTwitch } from './twitch.js';
import { obterLiveChatId, lerChatYouTube } from './youtube.js';
import { tratarComando } from './comandos.js';
import { moderar } from './moderacao.js';

async function aoReceber(msg) {
  const acao = moderar(msg);
  if (acao) return acao(); // avisa, apaga ou dá timeout

  const resposta = tratarComando(msg);
  if (resposta) await msg.reply(resposta);
}

await conectarTwitch(aoReceber);

const liveChatId = await obterLiveChatId(process.env.YOUTUBE_VIDEO_ID);
if (liveChatId) lerChatYouTube(liveChatId, aoReceber);

Para comandos que dependem de estado (contadores, fila de perguntas, enquetes), guarde os dados em um arquivo JSON ou em um banco leve como SQLite. Memória de processo some a cada reinício, e reinício no meio da live acontece.

Moderação automática e respostas com IA

Regras simples resolvem a maior parte

A moderação automática mais útil é a mais previsível: uma lista de termos proibidos, um detector de links de quem não é inscrito e um limite de mensagens repetidas. Isso pega a maior parte do spam sem falsos positivos constrangedores.

// moderacao.js
const proibidas = (process.env.PALAVRAS_PROIBIDAS ?? '').split(',').filter(Boolean);
const temLink = /https?:\/\/|www\./i;
const historico = new Map(); // autor -> últimas mensagens

export function moderar(msg) {
  if (msg.roles.includes('mod') || msg.roles.includes('owner')) return null;

  const texto = msg.text.toLowerCase();
  if (proibidas.some((p) => texto.includes(p))) {
    return () => msg.reply(`@${msg.author}, esse termo não é permitido aqui.`);
  }
  if (temLink.test(texto) && !msg.roles.includes('sub')) {
    return () => msg.reply(`@${msg.author}, links só para inscritos.`);
  }

  const lista = [...(historico.get(msg.author) ?? []), texto].slice(-5);
  historico.set(msg.author, lista);
  if (lista.length >= 3 && lista.slice(-3).every((t) => t === texto)) {
    return () => msg.reply(`@${msg.author}, sem repetir a mesma mensagem.`);
  }
  return null;
}

Para ações reais de moderação (apagar mensagem, timeout, banimento), cada plataforma tem o próprio caminho. A Twitch retirou os comandos de moderação do IRC; hoje eles passam pela API Helix, com um token de usuário que tenha papel de moderador no canal. No YouTube, a API tem os recursos liveChatMessages.delete e liveChatBans, também via OAuth. Em ambos os casos, comece avisando no chat e só depois automatize punições.

Quando usar IA (e quando não)

Responder com um modelo de linguagem faz sentido para perguntas abertas que se repetem (“qual a diferença entre esse ORM e aquele?”, “por que você usou Docker?”). Não faz sentido para tudo: um bot que responde cada mensagem vira ruído e custa dinheiro por chamada. O padrão que funciona é classificar antes de responder, do mesmo jeito que times de atendimento usam análise de conversas com IA para separar o que é dúvida, elogio, reclamação ou spam.

// ia.js — classifica e só responde o que é pergunta técnica
export async function responderComIA(msg, chamarModelo) {
  if (!msg.text.includes('?') || msg.text.length < 15) return null;

  const classe = await chamarModelo(
    `Classifique em uma palavra (pergunta_tecnica, conversa, spam): "${msg.text}"`
  );
  if (!classe.trim().startsWith('pergunta_tecnica')) return null;

  const resposta = await chamarModelo(
    `Você é o assistente do chat de uma live de programação. Responda em até 2 frases, em português: ${msg.text}`
  );
  return `@${msg.author} ${resposta.trim()}`;
}

A função chamarModelo é um adaptador para o SDK do provedor que você escolher; mantenha a chave em process.env e coloque um limite de chamadas por minuto. Deixe claro no chat que a resposta veio do bot e trate o conteúdo das mensagens sempre como texto do usuário, nunca como instrução para o modelo.

Deploy e manutenção do bot para chat da Twitch e do YouTube

O bot é um processo de longa duração, então precisa de um lugar que não durma: uma VPS pequena, um container em um serviço de hospedagem ou até um computador em casa. Um Dockerfile simples resolve o empacotamento:

FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
CMD ["node", "index.js"]

Se Docker ainda é novidade para você, o guia prático de Docker cobre a instalação, a criação da imagem e a execução do container. Na manutenção, três pontos pedem atenção:

  • Tokens expiram. O token OAuth da Twitch e o do YouTube têm validade. Implemente a renovação com refresh token ou monitore erros de autenticação e gere um novo token.
  • Reconexão. O tmi.js reconecta sozinho com reconnect: true; no YouTube, trate erros de rede no polling com uma nova tentativa após alguns segundos.
  • Logs. Registre comandos executados e ações de moderação com data e autor. É o que permite revisar um falso positivo e ajustar as regras.

Use um gerenciador de processo (pm2 ou a política de restart do Docker) para que o bot volte sozinho depois de uma falha, e guarde os segredos em variáveis de ambiente do host, nunca em arquivos dentro da imagem. O artigo sobre segurança de tokens com variáveis de ambiente e cofres de senhas detalha as opções.

Perguntas frequentes

Preciso de conta separada para um bot para chat da Twitch?

Não é obrigatório, mas é o recomendado. Com uma conta própria, o bot aparece com nome e avatar diferentes do seu, você pode dar a ele papel de moderador e, se o token vazar, a conta principal não é afetada.

O bot consegue escrever no chat do YouTube?

Consegue, desde que use OAuth 2.0 em nome de uma conta do Google e chame liveChatMessages.insert. Só a leitura funciona com chave de API. Lembre que cada chamada consome cota diária do projeto no Google Cloud.

Posso usar o mesmo código para Twitch e YouTube?

Sim, e é a abordagem deste tutorial: cada plataforma tem um adaptador que converte a mensagem em um formato comum (autor, papéis, texto, função de resposta). Comandos, moderação e IA trabalham só com esse formato.

O bot pode ser bloqueado por enviar mensagens demais?

Pode. As plataformas limitam a quantidade de mensagens por período, e um bot sem cooldown atinge esse limite rápido. Use cooldown por comando, agrupe avisos e evite responder a cada mensagem do chat.

Quanto custa manter um bot de chat?

A conexão IRC da Twitch e a YouTube Data API têm uso gratuito dentro das cotas. O custo real vem da hospedagem (um container pequeno basta) e, se usar IA, das chamadas ao modelo, cobradas por uso. Classificar antes de responder é o que mantém essa conta baixa.

Próximos passos

Comece pelo !projeto e pelo cooldown; rode uma live inteira só com isso e anote o que o chat mais perguntou. Depois acrescente a moderação de links e, só então, a IA para perguntas técnicas. Se você ainda não faz lives, o guia de como fazer live de programação mostra o setup mínimo para começar.