Samba Entrar

Bots no Samba

Este documento explica como os bots funcionam, como criar o seu e como o servidor é configurado para eles. Tudo aqui foi testado contra o servidor real; o exemplo da seção 8 roda como está.

1. Visão geral#

Um bot é uma conta do Samba operada por um programa em vez de uma pessoa. Ele aparece na lista de membros e nas mensagens com o selo BOT, e usa a mesma API que o aplicativo usa: a REST para agir (enviar mensagem, reagir...) e o Gateway (WebSocket) para ouvir o que acontece nos servidores onde está.

Existem dois tipos:

TipoOnde rodaComo é criado
Padrão (Caramelo e Zeca)Dentro do próprio servidor do SambaNasce com o servidor e entra automaticamente em todos os servidores, os novos e os já existentes
De desenvolvedorNo computador ou na hospedagem de quem o criouPela API POST /api/bots (seção 3) e adicionado a cada servidor pelo dono dele

O que um bot pode: ler e enviar mensagens nos canais de texto dos servidores onde foi adicionado, editar e apagar as próprias mensagens, reagir, mostrar "digitando…", enviar imagens e receber os eventos desses servidores.

O que um bot não pode: mandar pedido de amizade, abrir ou receber conversas diretas, criar servidores, entrar em servidor por convite, gerenciar outros bots, nem usar a conta como pessoa (não há login por senha nem pelo Google).

2. Caramelo, o bot padrão#

O Caramelo responde perguntas usando a API do Google Gemini. Em qualquer canal de texto de um servidor:

!c Que dia é hoje?

Ele mostra "digitando…" e responde citando a pergunta. Detalhes:

  • Memória do canal: ele lê as últimas mensagens do canal e entende continuações. Depois de !c Quanto é 5+5?, você pode perguntar !c E o dobro disso?. Só conta a conversa com o Caramelo; o resto do canal ele ignora.
  • Data e hora: ele sabe a data e a hora de Brasília, então responde "que dia é hoje" corretamente.
  • Limites: 5 perguntas por minuto por pessoa e 40 por minuto no servidor inteiro (para proteger seus créditos). Perguntas de até 1500 caracteres. Respostas grandes são divididas em até 3 mensagens.
  • Só responde a pessoas. Mensagens de outros bots são ignoradas, para dois bots não entrarem em loop.
  • !c sozinho mostra um exemplo de uso. Sem a chave do Gemini configurada, ele avisa que ainda não foi configurado.
  • Não dá para removê-lo de um servidor (ele é um bot padrão).

Configuração no servidor (seção 10): SAMBACORD_GEMINI_API_KEY e, opcionalmente, SAMBACORD_GEMINI_MODEL.

Zeca, o bot de música#

O Zeca toca o áudio de vídeos do YouTube dentro da chamada de voz de quem pedir. Em qualquer canal de texto do servidor, estando em uma call de voz desse mesmo servidor:

!m https://www.youtube.com/watch?v=dQw4w9WgXcQ

O Zeca entra na sua call (se ainda não estiver nela), anuncia a música no canal e toca. Detalhes:

ComandoO que faz
!m <link do YouTube>Toca a música, ou coloca na fila se já tiver uma tocando (até 20). Vale youtube.com/watch, youtu.be, shorts, music.youtube.com. Link de playlist toca só o vídeo do link
!m <nome da música>Procura no YouTube e toca o primeiro resultado (ele diz qual achou). Ex.: !m costa gold 155. Para ter certeza de qual versão toca, use o link
!m pausar / !m continuarPausa e retoma. Esquecer pausado por 10 minutos faz ele sair da call
!m pularPula para a próxima da fila
!m anteriorVolta para a música anterior. Se a atual já tocou mais de 5 segundos, recomeça ela
!m pararPara tudo, limpa a fila e sai da call
!m filaMostra o que está tocando e a fila
  • Só quem está numa call do servidor pode pedir (e só essa pessoa e quem está na mesma call manda pular ou parar).
  • Miniplayer: enquanto o Zeca toca na sua call, aparece um player acima de "Conectado" com voltar, pausar e avançar. Clicar nele (ou no Zeca) abre o player grande com a fila e o botão de parar. O chat avisa quem apertou (gus pausou ⏸️). Os botões chamam POST /api/music/<pause|resume|skip|previous|stop>.
  • Uma call por vez. O Zeca toca em um canal de voz de cada vez em todo o Samba. Se alguém de outra call pedir enquanto ele toca, ele avisa que está ocupado.
  • Sai sozinho: quando a fila acaba (30 s depois) ou quando a última pessoa sai da call (8 s depois).
  • Limites: vídeos de até 60 minutos (SAMBACORD_MUSIC_MAX_MINUTES), sem transmissões ao vivo, 6 pedidos por minuto por pessoa.
  • Cada pessoa pode ajustar o volume dele (ou silenciá-lo só para si) pelo perfil, como com qualquer participante.
  • O som é mono, 48 kHz, o suficiente para música de call, não para ouvir com qualidade de estúdio.

Como funciona por dentro. O servidor participa da chamada como se fosse mais um navegador (mesma negociação WebRTC de malha completa do aplicativo, com o módulo nativo @roamhq/wrtc). O yt-dlp baixa o áudio, o ffmpeg converte para PCM e um relógio de 10 ms entrega os quadros ao WebRTC, que codifica em Opus. O código está em server/bots/music/: youtube.ts (links e yt-dlp), player.ts (download, decodificação e ritmo), bot-voice.ts (a conexão com a call), zeca.ts (comandos e fila).

Aviso: é um recurso frágil por natureza.

  • Termos do YouTube: baixar áudio fora do player do YouTube vai contra os termos de uso deles. Para um servidor de amigos o risco prático é baixo, mas é uma decisão consciente do dono do Samba.
  • O YouTube bloqueia servidores. Rodando no Railway (IP de datacenter) o YouTube costuma responder "confirme que você não é um robô". O Zeca avisa isso no chat. A saída são os cookies de uma conta do YouTube — veja o passo a passo abaixo.
  • Algumas músicas funcionam e outras não ("Não consegui pegar essa música no YouTube"): costuma ser o YouTube recusando o download a partir do IP do servidor, e depende do vídeo. O Zeca tenta uma segunda vez com outros "clientes" do YouTube antes de desistir, e o log do servidor (Railway → Logs) mostra o que o yt-dlp respondeu, em linhas que começam com [zeca]. Os cookies (acima) resolvem a maioria dos casos.
  • O YouTube muda o tempo todo. O yt-dlp é atualizado sozinho a cada início do servidor em produção. Se mesmo assim parar de funcionar, reinicie o serviço (ele atualiza de novo) ou veja o log do Railway.

Passo a passo dos cookies (conta secundária do YouTube).

  1. Use uma conta Google só para isso, não a sua principal: o Google pode estranhar uma conta usada de um servidor.
  2. Abra uma janela anônima/privada, entre em youtube.com com essa conta e, na mesma aba, abra https://www.youtube.com/robots.txt (só para a aba ficar parada numa página sem vídeo).
  3. Com uma extensão como "Get cookies.txt LOCALLY", exporte os cookies de youtube.com para um arquivo cookies.txt (formato Netscape). Depois feche a janela anônima. Isso importa: se a sessão continuar aberta, o YouTube troca os cookies e os exportados deixam de valer em poucas horas.
  4. Transforme o arquivo em uma linha de texto (base64). No PowerShell: [Convert]::ToBase64String([IO.File]::ReadAllBytes("cookies.txt")) | Set-Clipboard
  5. No Railway, em Variables, crie SAMBACORD_YTDLP_COOKIES_B64 e cole o texto. O servidor grava o arquivo no volume (youtube-cookies.txt, só ele lê) ao iniciar e o log mostra YouTube cookies loaded. (Sem Railway: ponha o cookies.txt onde quiser e use SAMBACORD_YTDLP_COOKIES=/caminho/cookies.txt.)

Os cookies vencem de tempos em tempos (semanas ou meses). Quando o Zeca voltar a dizer que o YouTube bloqueou, repita os passos com cookies novos. Nunca publique o arquivo nem a variável: quem tem os cookies controla a conta.

Variáveis do servidor (todas opcionais):

VariávelPadrãoPara quê
SAMBACORD_YTDLPyt-dlpCaminho do executável do yt-dlp
SAMBACORD_FFMPEGffmpegCaminho do executável do ffmpeg
SAMBACORD_YTDLP_COOKIES(nenhum)Caminho de um arquivo cookies.txt do YouTube (veja acima)
SAMBACORD_YTDLP_COOKIES_B64(nenhum)O conteúdo do cookies.txt em base64; o servidor grava o arquivo sozinho (o jeito mais fácil no Railway)
SAMBACORD_YTDLP_JS_RUNTIMEnodeRuntime JavaScript que o yt-dlp usa para resolver os desafios do YouTube (vazio = padrão do yt-dlp)
SAMBACORD_MUSIC_MAX_MINUTES60Duração máxima de cada música

A imagem Docker já instala ffmpeg e yt-dlp. Para rodar o servidor na sua máquina, instale os dois e deixe-os no PATH (ou aponte as variáveis acima). Sem eles o Zeca continua no servidor, mas responde que não consegue tocar.

Perfil dos bots padrão. Ao clicar num bot padrão (lista de membros ou mensagem) o cartão mostra uma descrição e a lista de comandos: passe o mouse sobre um comando (ou toque nele, no celular) para ver o que ele faz. Esse texto vem de src/shared/bots.ts; para um bot padrão novo, descreva-o ali.

3. Criar um bot#

Quem pode criar bots é definido por quem administra o servidor do Samba (variável SAMBACORD_BOT_DEVELOPERS, seção 10). Por padrão, ninguém. Cada pessoa pode ter até 10 bots.

Com a sua conta (o token de sessão vem do login, POST /api/auth/login):

curl -X POST $URL/api/bots \
  -H "Authorization: Bearer $TOKEN_DA_SUA_SESSAO" \
  -H "Content-Type: application/json" \
  -d '{"name": "MeuBot", "public": false}'

Resposta:

{ "id": "1234567890123456", "username": "MeuBot", "public": false, "token": "1234567890123456.AbC...xyz" }
  • name: de 2 a 32 caracteres, só letras, números, _, . e - (sem espaços; isso impede que alguém se passe pelos bots padrão, que têm espaço no nome). O nome é único em todo o Samba.
  • public: false (padrão) = só você coloca o bot em servidores seus. true = o dono de qualquer servidor pode adicioná-lo.
  • token: aparece uma única vez. O servidor guarda só um hash, então ninguém consegue recuperá-lo depois. Se perder ou vazar, gere outro (abaixo); o antigo deixa de funcionar na hora.

Outras operações de gerenciamento (sempre com a sessão da pessoa dona, não com o token do bot):

OperaçãoChamada
Listar meus bots (e se posso criar)GET /api/bots → { "canCreate": true, "bots": [{ "id", "username", "public" }] }
Gerar um novo tokenPOST /api/bots/{botId}/token → o bot com o campo token
Apagar o botDELETE /api/bots/{botId}. Remove o bot de todos os servidores e apaga as mensagens dele

4. Autenticação#

Toda chamada do bot usa o token no cabeçalho Authorization, com o prefixo Bot (não Bearer):

Authorization: Bot 1234567890123456.AbC...xyz

No Gateway, o prefixo também vale: {"op":"identify","d":{"token":"Bot 1234567890123456.AbC...xyz"}}.

Trate o token como uma senha. Quem o tem controla o bot. Guarde-o em variável de ambiente, nunca no código nem no Git. Se vazou, gere outro com POST /api/bots/{botId}/token.

Um token válido de bot chamando um endpoint só para pessoas recebe 403; um token inválido ou de um bot apagado, 401.

5. Colocar o bot num servidor#

O dono do servidor adiciona o bot (você só pode usar um bot seu ou um bot público):

curl -X POST $URL/api/guilds/$ID_DO_SERVIDOR/bots \
  -H "Authorization: Bearer $TOKEN_DO_DONO" -H "Content-Type: application/json" \
  -d '{"botId": "1234567890123456"}'

Os membros recebem o evento GUILD_MEMBER_ADD e o bot recebe GUILD_CREATE com o servidor completo (canais e membros) pelo Gateway. Para remover: DELETE /api/guilds/{guildId}/bots/{botId} (também só o dono; os bots padrão não podem ser removidos).

Para saber em quais servidores o bot está sem usar o Gateway: GET /api/bot/me → { "user": {...}, "guilds": [{ "guild", "channels", "members" }] }.

6. API REST#

Base: a URL do servidor (por exemplo https://sambachat.app). Corpo e resposta em JSON; erros vêm como { "error": "mensagem" } com o código HTTP correspondente. Os IDs são strings numéricas (snowflakes); os mais novos têm IDs maiores, então ordenar por ID ordena por data.

Endpoints que bots podem usar:

Método e caminhoPara quê
GET /api/bot/meQuem sou eu e meus servidores (com canais e membros)
GET /api/channels/{id}/messages?limit=50&before={messageId}Histórico (1 a 100, do mais antigo ao mais novo)
POST /api/channels/{id}/messagesEnviar mensagem
PATCH /api/channels/{id}/messages/{messageId}Editar a própria mensagem: { "content": "..." }
DELETE /api/channels/{id}/messages/{messageId}Apagar a própria mensagem
PUT /api/channels/{id}/messages/{messageId}/reactionReagir: { "emoji": "👍" } (um emoji por pessoa por mensagem)
DELETE /api/channels/{id}/messages/{messageId}/reactionTirar a própria reação
POST /api/channels/{id}/typingMostrar "digitando…" (dura alguns segundos)
POST /api/channels/{id}/attachmentsEnviar uma imagem (corpo binário, cabeçalho X-Filename); use o id devolvido em attachmentIds

Enviar uma mensagem:

curl -X POST $URL/api/channels/$ID_DO_CANAL/messages \
  -H "Authorization: Bot $TOKEN_DO_BOT" -H "Content-Type: application/json" \
  -d '{"content": "Olá, pessoal!", "replyTo": "ID_DE_OUTRA_MENSAGEM"}'

Campos do envio: content (até 2000 caracteres), attachmentIds (até 4), replyTo (opcional, mensagem do mesmo canal). Uma mensagem precisa ter texto ou anexo.

Menções. No texto, uma pessoa é <@idDaPessoa> e um cargo é <@&idDoCargo> (o & diferencia). O servidor só considera (e só avisa) o que o autor pode mencionar: cargos "mencionáveis" por qualquer um, ou qualquer cargo e @todos/@aqui para quem tem a permissão Mencionar todos. Na mensagem recebida, mentions lista as pessoas, mentionRoles os cargos e mentionEveryone indica @todos. O bot só enxerga os canais dos servidores onde está; qualquer outro canal responde 404.

O formato de cada objeto (User, Message, Channel, GuildSnapshot...) está em src/shared/protocol.ts, o mesmo arquivo que o servidor e o aplicativo usam. Contas de bot têm "bot": true no User.

7. Gateway (eventos em tempo real)#

Para reagir ao que acontece (por exemplo, responder quando alguém escreve !ping), o bot mantém uma conexão WebSocket em wss://SERVIDOR/gateway. É a mesma conexão que o aplicativo usa.

bot                                servidor
 │ ── abre o WebSocket ───────────►│
 │ ◄── {"op":"hello","d":{"heartbeatInterval":30000}}
 │ ── {"op":"identify","d":{"token":"Bot ..."}} ──►│
 │ ◄── {"op":"dispatch","t":"READY","s":1,"d":{...}}      (quem sou eu, servidores, canais, membros)
 │ ── {"op":"heartbeat","d":{"seq":1}} ──► (a cada heartbeatInterval)
 │ ◄── {"op":"heartbeat_ack"}
 │ ◄── {"op":"dispatch","t":"MESSAGE_CREATE","s":2,"d":{...mensagem...}}

Regras:

  1. Depois do hello, envie o identify em até 10 segundos, ou a conexão é fechada.
  2. Envie um heartbeat a cada heartbeatInterval (30 s). Se o servidor ficar ~60 s sem receber, ele derruba a conexão. O campo seq é o s do último evento que você recebeu (ou null).
  3. Token inválido → {"op":"invalid_session","d":{"reason":"invalid token"}} e a conexão fecha. Não adianta reconectar com o mesmo token.
  4. Se a conexão cair, reconecte com espera crescente (1 s, 2 s, 4 s... até 30 s) e identifique de novo. Os eventos perdidos durante a queda não são reenviados: leia o histórico pela REST se precisar.
  5. Frames de no máximo 64 KB, só texto (JSON).

Eventos que um bot recebe (campo t dos frames dispatch; os de pessoas, como amizades e DMs, não chegam):

EventoQuandoDados
READYLogo após o identify{ user, guilds: [{ guild, channels, members }], presences, voiceStates, ... }
GUILD_CREATEO bot foi adicionado a um servidorO servidor completo
GUILD_MEMBER_ADD / GUILD_MEMBER_REMOVEAlguém (ou um bot) entrou/saiu do servidor{ guildId, user } / { guildId, userId }
CHANNEL_CREATECanal novoO canal
MESSAGE_CREATEMensagem novaA mensagem completa
MESSAGE_UPDATEMensagem editadaA mensagem completa, já editada
MESSAGE_DELETEMensagem apagada{ id, channelId }
MESSAGE_REACTIONSReações mudaram{ channelId, messageId, reactions } (a lista inteira)
TYPING_STARTAlguém começou a digitar{ channelId, userId, at }
PRESENCE_UPDATEAlguém ficou online/offline{ userId, status }
VOICE_STATE_UPDATEAlguém entrou/saiu/mudou na vozVeja protocol.ts

Os bots recebem os eventos de todos os servidores onde estão; confira o channelId/guildId do evento. (O bot também recebe as próprias mensagens: ignore as suas.)

8. Exemplo completo#

docs/bots/exemplos/ping-bot.mjs é um bot de ~70 linhas, sem dependências (usa o fetch e o WebSocket do Node 22+), que responde !ping com "pong" e !eco texto repetindo o texto. Ele conecta, faz o heartbeat, reconecta sozinho e ignora outros bots.

SAMBACORD_URL=https://sambachat.app \
SAMBACORD_BOT_TOKEN=1234567890123456.AbC...xyz \
node docs/bots/exemplos/ping-bot.mjs

Passo a passo para vê-lo funcionando: crie o bot (seção 3), adicione-o a um servidor seu (seção 5), rode o comando acima e escreva !ping num canal desse servidor.

O núcleo do bot é a função que trata cada mensagem; todo o resto é infraestrutura reaproveitável:

async function onMessage(message, me) {
  if (message.author.bot) return;                 // nunca responda bots
  if (message.content.trim() === '!ping') await reply(message, 'pong 🏓');
}

9. Regras, limites e boas práticas#

LimiteValor
Mensagens por segundo por bot5 (acima disso: 429 slow down)
Tamanho da mensagem2000 caracteres
Anexos por mensagem4 (imagens de até 8 MB)
Bots por pessoa10

Boas práticas:

  • Não responda a bots (message.author.bot) e nunca a si mesmo. Dois bots que respondem um ao outro geram um loop.
  • Use prefixo de comando (!ping, !c...) em vez de responder a toda mensagem.
  • Não faça spam. Quem cuida do servidor pode tirar a permissão de criar bots (seção 10), e o dono de cada servidor pode remover o seu bot de lá.
  • Mostre typing (POST .../typing) em respostas que demoram, e responda citando a mensagem (replyTo).
  • Não guarde dados pessoais além do necessário; mensagens de um servidor são das pessoas dele.
  • Permissões por cargo ainda não existem (seção 11): por enquanto o bot pode tudo que a lista da seção 6 permite, nos servidores onde o dono o colocou. Só adicione bots em quem confia.

10. Para quem cuida do servidor#

Variáveis de ambiente (no Railway, em Variables):

VariávelPadrãoPara quê
SAMBACORD_GEMINI_API_KEY(vazia)Chave da API do Gemini (Google AI Studio). Sem ela o Caramelo responde que não foi configurado
SAMBACORD_GEMINI_MODELgemini-3.8-flashModelo usado pelo Caramelo. Os nomes mudam com o tempo: se o Google aposentar o modelo, troque aqui sem alterar código
SAMBACORD_BOT_DEVELOPERS(vazia = ninguém)Nomes de usuário que podem criar bots, separados por vírgula (gustavo,lucas), ou * para todos

Os bots padrão são criados sozinhos na inicialização e colocados em todos os servidores que ainda não os têm. A migração do banco (colunas users.is_bot, bot_owner, bot_public e a tabela bot_tokens) é automática e aditiva.

Para o Caramelo, acompanhe o custo no painel do Google AI Studio. O limite por pessoa e por servidor (seção 2) é o freio contra abuso; se precisar de outro, ele está em server/bots/samba-chat.ts.

Como é por dentro#

  • server/db.ts: contas de bot, token (guardado como SHA-256; formato <idDoBot>.<segredo>) e bots padrão.
  • server/rest.ts: autenticação Bot, a lista de rotas permitidas a bots (bot: true) e as rotas /api/bots.
  • server/gateway.ts: identify com Bot <token> e os bots internos, que recebem eventos direto na memória.
  • server/bots/runtime.ts: o que um bot padrão pode fazer (BotHost) e a inicialização.
  • server/bots/samba-chat.ts: o Caramelo. Um bot padrão novo é um arquivo assim, registrado em startBuiltinBots.
  • Testes: test/bots.test.ts.

11. O que ainda não existe#

  • Comandos com / (registrados pelo bot, com sugestões) e interações com botões.
  • Permissões e cargos por bot e por canal (etapa 5 do roadmap).
  • Conversa direta com bots (por enquanto só nos canais dos servidores).
  • Painel "Meus bots" dentro do aplicativo: hoje a criação é pela API.
  • Bots de desenvolvedores na voz: o Zeca participa da chamada por dentro do servidor; bots externos ainda não têm API de voz.
  • Webhooks (enviar mensagens por uma URL, sem manter um bot conectado).