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
- 2. Caramelo, o bot padrão
- 3. Criar um bot
- 4. Autenticação
- 5. Colocar o bot num servidor
- 6. API REST
- 7. Gateway (eventos em tempo real)
- 8. Exemplo completo
- 9. Regras, limites e boas práticas
- 10. O que ainda não existe
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:
| Tipo | Onde roda | Como é criado |
|---|---|---|
| Padrão (Caramelo e Zeca) | Dentro do próprio servidor do Samba | Nasce com o servidor e entra automaticamente em todos os servidores, os novos e os já existentes |
| De desenvolvedor | No computador ou na hospedagem de quem o criou | Pela 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.
!csozinho 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).
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:
| Comando | O 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 continuar | Pausa e retoma. Esquecer pausado por 10 minutos faz ele sair da call |
!m pular (ou !skip) | Vota para pular: a música pula quando metade de quem está na call pede |
!m anterior | Volta para a música anterior. Se a atual já tocou mais de 5 segundos, recomeça ela |
!m parar | Para tudo, limpa a fila e sai da call |
!m fila | Mostra 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 chamamPOST /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.
Algumas músicas podem não tocar: quando isso acontece, o Zeca avisa no chat.
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#
Qualquer conta cria bots em Configurações → Meus bots: é lá que você pega o token, troca a foto, deixa o bot público e copia o link para colocar num servidor. Cada pessoa pode ter até 10 bots.
Pela API, 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ção | Chamada |
|---|---|
| Listar meus bots (e se posso criar) | GET /api/bots → { "canCreate": true, "bots": [{ "id", "username", "public" }] } |
| Gerar um novo token | POST /api/bots/{botId}/token → o bot com o campo token |
| Apagar o bot | DELETE /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#
Abra o link do bot (https://sambachat.app/app?bot=ID) e escolha o servidor. Quem adiciona precisa de Gerenciar servidor
(você só pode usar um bot seu ou um bot público). Pela API:
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 com Gerenciar servidor; 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 caminho | Para quê |
|---|---|
GET /api/bot/me | Quem 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}/messages | Enviar 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}/reaction | Reagir: { "emoji": "👍" } (um emoji por pessoa por mensagem) |
DELETE /api/channels/{id}/messages/{messageId}/reaction | Tirar a própria reação |
POST /api/channels/{id}/typing | Mostrar "digitando…" (dura alguns segundos) |
POST /api/channels/{id}/attachments | Enviar 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:
- Depois do
hello, envie oidentifyem até 10 segundos, ou a conexão é fechada. - Envie um
heartbeata cadaheartbeatInterval(30 s). Se o servidor ficar ~60 s sem receber, ele derruba a conexão. O camposeqé osdo último evento que você recebeu (ounull). - Token inválido →
{"op":"invalid_session","d":{"reason":"invalid token"}}e a conexão fecha. Não adianta reconectar com o mesmo token. - 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.
- 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):
| Evento | Quando | Dados |
|---|---|---|
READY | Logo após o identify | { user, guilds: [{ guild, channels, members }], presences, voiceStates, ... } |
GUILD_CREATE | O bot foi adicionado a um servidor | O servidor completo |
GUILD_MEMBER_ADD / GUILD_MEMBER_REMOVE | Alguém (ou um bot) entrou/saiu do servidor | { guildId, user } / { guildId, userId } |
CHANNEL_CREATE | Canal novo | O canal |
MESSAGE_CREATE | Mensagem nova | A mensagem completa |
MESSAGE_UPDATE | Mensagem editada | A mensagem completa, já editada |
MESSAGE_DELETE | Mensagem apagada | { id, channelId } |
MESSAGE_REACTIONS | Reações mudaram | { channelId, messageId, reactions } (a lista inteira) |
TYPING_START | Alguém começou a digitar | { channelId, userId, at } |
PRESENCE_UPDATE | Alguém ficou online/offline | { userId, status } |
VOICE_STATE_UPDATE | Alguém entrou/saiu/mudou na voz | Veja 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#
| Limite | Valor |
|---|---|
| Mensagens por segundo por bot | 5 (acima disso: 429 slow down) |
| Tamanho da mensagem | 2000 caracteres |
| Anexos por mensagem | 4 (imagens de até 8 MB) |
| Bots por pessoa | 10 |
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, 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.
- O bot obedece às permissões como qualquer membro: as do @todos, as dos canais e as do cargo dele. Só adicione bots de quem confia.
Permissões do bot#
Em Meus bots, marque o que o bot pede. Quem adiciona vê a lista, pode recusar itens e não pode dar o que não tem. O bot recebe
um cargo com o nome dele, no fim da lista: para agir sobre alguém, esse cargo precisa estar acima da pessoa. O cargo só serve a
esse bot e some quando ele sai. Pela API: PATCH /api/bots/{botId} com { "permissions": ["kick_members"] }, e
POST /api/guilds/{id}/bots com { "botId": "...", "permissions": [...] }.
Com a permissão, o bot também pode usar:
| Método e caminho | Para quê | Permissão |
|---|---|---|
DELETE /api/guilds/{id}/members/{userId} | Expulsar | kick_members |
PUT e DELETE /api/guilds/{id}/bans/{userId}, GET /api/guilds/{id}/bans | Banir, desbanir, listar | ban_members |
PUT /api/guilds/{id}/members/{userId}/timeout | Castigo | moderate_members |
PUT /api/guilds/{id}/members/{userId}/roles | Dar e tirar cargos abaixo do dele | manage_roles |
PUT e DELETE /api/channels/{id}/pins/{messageId} | Fixar | manage_messages |
Webhooks#
Para só postar, sem manter um bot conectado: em Configurações do servidor → Webhooks (precisa de Gerenciar webhooks) escolha o bate-papo e copie o endereço. Ele aparece uma vez só; se vazar, gere outro.
curl -X POST "$ENDERECO_DO_WEBHOOK" -H "Content-Type: application/json" -d '{"content": "A live começou"}'
content: até 2000 caracteres.embedstambém é aceito (até 10) e vira texto: autor, título, link, descrição, campos e rodapé.usernameeavatar_urlsão ignorados: a mensagem sai com o nome do webhook, com o selo BOT.@todose cargos nunca avisam ninguém.- Limite: 5 mensagens a cada 2 segundos e 30 por minuto, por endereço (acima disso:
429).
10. O que ainda não existe#
- Comandos com
/(registrados pelo bot, com sugestões) e interações com botões. - Conversa direta com bots (por enquanto só nos canais dos servidores).
- Bots de desenvolvedores na voz: o Zeca participa da chamada por dentro do servidor; bots externos ainda não têm API de voz.