Skip to content

Gateway ​

O gateway é um WebSocket por onde o Zynko avisa o que acontece: mensagens novas, gente entrando, canais mudando. Um bot fica conectado nele o tempo todo e usa as rotas HTTP para agir.

wss://zynko.org/api/gateway

Frames ​

Tudo o que passa pelo gateway é texto JSON, um objeto por frame (o que o bot manda pode ter no máximo 64 KB):

json
{ "op": 0, "t": "MESSAGE_CREATE", "d": { "...": "..." } }
campodescrição
opO opcode (abaixo).
tNome do evento. Só em op 0.
dOs dados. Pode faltar em alguns opcodes.

Não há número de sequência (s).

Opcodes ​

opnomedireçãoo que é
0DISPATCHservidor → botUm evento. t diz qual.
1HEARTBEATbot → servidor"Ainda estou aqui." d é ignorado.
2IDENTIFYbot → servidorAutentica a conexão.
4VOICE_STATEbot → servidorEntra, sai ou muda o estado numa sala de voz.
10HELLOservidor → botPrimeira mensagem, com o intervalo de heartbeat.
11HEARTBEAT_ACKservidor → botResposta a cada heartbeat.

Conectando ​

  1. Abra o WebSocket. O servidor manda HELLO:

    json
    { "op": 10, "d": { "heartbeat_interval": 30000 } }
  2. Mande IDENTIFY em até 15 segundos:

    json
    { "op": 2, "d": { "token": "bot_3f9a...e1", "intents": 9 } }
    campodescrição
    tokenO token do bot, puro (sem Bot na frente).
    intentsOpcional. Os tipos de evento que o bot quer, somados (veja Intents). Sem esse campo, chegam todos.
  3. O servidor responde com o evento READY (op 0, t: "READY"), com tudo o que a conta precisa saber (veja READY). A partir daí, chegam os eventos.

  4. Mande HEARTBEAT ({ "op": 1 }) a cada heartbeat_interval milissegundos. O servidor responde { "op": 11 }.

Se o servidor passar 60 segundos (duas vezes o intervalo) sem receber nada da conexão, ele a fecha. Antes do IDENTIFY, um HEARTBEAT também é respondido; qualquer outro opcode fecha a conexão.

Intents ​

Os intents dizem que eventos a conexão quer receber. Um bot que só responde a comandos não precisa receber cada presença e cada "digitando..." de todos os servidores em que está.

intentbitdecimaleventos
GUILDS1 << 01GUILD_UPDATE, GUILD_ROLE_CREATE, GUILD_ROLE_UPDATE, GUILD_ROLE_DELETE, GUILD_EMOJIS_UPDATE, CHANNEL_UPDATE, CHANNEL_DELETE
GUILD_MEMBERS1 << 12GUILD_MEMBER_ADD, GUILD_MEMBER_UPDATE, GUILD_MEMBER_REMOVE
PRESENCES1 << 24PRESENCE_UPDATE
MESSAGES1 << 38MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, MESSAGE_ACK (em servidores e DMs)
REACTIONS1 << 416MESSAGE_REACTION_ADD, MESSAGE_REACTION_REMOVE
TYPING1 << 532TYPING_START
VOICE_STATES1 << 664VOICE_STATE_UPDATE

Todos juntos: 127. Sem o campo intents, a conexão recebe todos (é o que os apps fazem); com "intents": 0, recebe só os que sempre chegam.

Sempre chegam, com qualquer intent: READY, GUILD_CREATE, GUILD_DELETE, CHANNEL_CREATE (uma DM nova é um canal novo), USER_UPDATE, INTERACTION_CREATE, INTERACTION_RESPONSE, RELATIONSHIP_ADD e RELATIONSHIP_REMOVE.

Que intents usar

Quase todo bot quer GUILDS (1): sem ele, a lista de canais e cargos que veio no READY fica desatualizada. Some MESSAGES (8) para ler mensagens: GUILDS | MESSAGES = 9.

Os intents só filtram o que chega. Eles não dão acesso a nada: o bot só recebe eventos dos canais que pode ver.

READY ​

O primeiro evento depois do IDENTIFY traz o estado inteiro da conta:

campotipodescrição
userPublicUserA própria conta (o bot).
guildsGuildFull[]Os servidores, com canais visíveis, membros, cargos, emojis e quem está online.
dm_channelsChannel[]As DMs, com recipients, das mais recentes para as mais antigas.
read_statesReadState[]Até onde a conta leu cada canal.
dm_unreadobjeto{ "<channel_id>": n }: quantas mensagens não lidas há em cada DM.
presencesstring[]Ids das contas relacionadas (servidores em comum, DMs, amigos) que estão online.
relationshipsRelationship[]Amigos, pedidos e bloqueios.
voice_statesVoiceState[]Quem está nas salas de voz que a conta vê, e em chamadas nas DMs dela.
session_idstringId desta conexão. Aparece em VoiceState.session_id.

Reconectando ​

Não existe RESUME. Se a conexão cair, abra outra, mande IDENTIFY de novo e use o READY novo como o estado atual: ele traz tudo, então nada se perde além dos eventos do intervalo (busque mensagens que faltaram com GET /channels/{id}/messages?after=...).

Uma conta pode ter várias conexões abertas ao mesmo tempo; cada uma recebe os eventos que pediu. A conta aparece online enquanto houver pelo menos uma.

Se a conexão não der conta de ler os eventos (mais de 2048 na fila), o servidor a fecha com 4000; reconecte.

Códigos de fechamento ​

códigomotivoo que fazer
4000Sessão encerrada: o token foi trocado ou o bot apagado, ou a conexão ficou para trás nos eventos.Reconectar. Se o IDENTIFY falhar com 4004, o token não vale mais.
4003Não autenticado: IDENTIFY não chegou em 15 s, frame inválido, ou outro opcode antes do IDENTIFY.Corrigir e reconectar.
4004Falha de autenticação: token inválido.Não reconectar com o mesmo token.
1011Erro interno ao montar o READY.Reconectar depois de alguns segundos.

Quando o servidor fica 60 s sem ouvir a conexão, ele simplesmente fecha o socket.

Estado de voz (op 4) ​

Depois de pegar um token em POST /channels/{id}/voice, avise pelo gateway que a conta está na sala:

json
{
  "op": 4,
  "d": { "channel_id": "1234567890123456789", "self_mute": false, "self_deaf": false, "self_stream": false, "self_video": false }
}
campotipodescrição
channel_idid?A sala de voz (ou DM). null (ou ausente) = sair da voz.
self_mutebooleanMicrofone desligado. Padrão false.
self_deafbooleanSom desligado. Padrão false.
self_streambooleanCompartilhando a tela. Padrão false.
self_videobooleanCâmera ligada. Padrão false.

Mande de novo sempre que algo mudar (o estado inteiro, não só o que mudou). Quem vê a sala recebe VOICE_STATE_UPDATE. Uma conta fica em uma sala por vez: entrar em outra tira da anterior. Se não puder entrar (sem Entrar em salas, canal errado), o pedido é ignorado em silêncio.

O lugar na voz pertence à conexão do gateway que mandou o op 4. Se ela cair, a conta continua na sala por 15 segundos; se uma nova conexão não reclamar o lugar nesse tempo, a conta sai.

Exemplo ​

Um bot mínimo em JavaScript, sem bibliotecas (Node.js 22 ou Bun, que têm WebSocket global): conecta, se identifica, mantém o heartbeat e mostra as mensagens novas.

js
// gateway.js — ZYNKO_TOKEN=bot_... node gateway.js
const TOKEN = process.env.ZYNKO_TOKEN;
const GUILDS = 1 << 0;
const MESSAGES = 1 << 3;

function connect() {
  const ws = new WebSocket('wss://zynko.org/api/gateway');
  let heartbeat;
  let me;

  ws.addEventListener('message', (ev) => {
    const { op, t, d } = JSON.parse(ev.data);

    if (op === 10) {
      // HELLO: identifique-se e comece o heartbeat.
      ws.send(JSON.stringify({ op: 2, d: { token: TOKEN, intents: GUILDS | MESSAGES } }));
      heartbeat = setInterval(() => ws.send(JSON.stringify({ op: 1 })), d.heartbeat_interval);
      return;
    }
    if (op !== 0) return; // 11 = HEARTBEAT_ACK

    if (t === 'READY') {
      me = d.user;
      console.log(`Conectado como ${me.username}, em ${d.guilds.length} servidores`);
    } else if (t === 'MESSAGE_CREATE') {
      if (d.author.id === me.id) return; // as mensagens do próprio bot também chegam
      console.log(`#${d.channel_id} ${d.author.display_name}: ${d.content}`);
    }
  });

  ws.addEventListener('close', (ev) => {
    clearInterval(heartbeat);
    if (ev.code === 4004) {
      console.error('Token inválido.');
      return;
    }
    console.log(`Conexão fechada (${ev.code}); reconectando em 5 s`);
    setTimeout(connect, 5000);
  });
}

connect();

Para responder, chame a API com o mesmo token; veja Seu primeiro bot.

Zynko · zynko.org