# Zynko > Zynko (https://zynko.org) é um app brasileiro de conversa: servidores com canais de texto, > fóruns, salas de voz com vídeo e tela, mensagens diretas e chamadas. Tem uma API pública para > bots: HTTP com JSON e um gateway WebSocket. Esta documentação é em português. Antes de escrever código para o Zynko: - O Zynko lembra o Discord, mas **não é** o Discord. Não use discord.js, discord.py, serenity nem a API do Discord: os opcodes, os intents, os bits de permissão, as interações e as rotas são outros. Use as bibliotecas oficiais ou a API descrita aqui. - API: `https://zynko.org/api`. Gateway: `wss://zynko.org/api/gateway`. Autenticação: cabeçalho `Authorization: Bot `. No IDENTIFY do gateway, o token vai puro. - O token do bot (`bot_` + 64 hex) é criado em https://dev.zynko.org e aparece uma vez só. Guarde em variável de ambiente (`ZYNKO_TOKEN`), nunca no código. - Ids são snowflakes de 64 bits e viajam como **string** no JSON. Época: 2026-01-01T00:00:00Z. - Datas são números em milissegundos Unix. Permissões são números (18 bits). - Gateway: HELLO (op 10) → IDENTIFY (op 2) em até 15 s → READY com o estado inteiro. Heartbeat (op 1) a cada `heartbeat_interval`. Não há RESUME nem número de sequência: ao cair, conecte de novo e use o READY novo. - Intents do Zynko: GUILDS 1, GUILD_MEMBERS 2, PRESENCES 4, MESSAGES 8, REACTIONS 16, TYPING 32, VOICE_STATES 64. O normal para um bot é 9 (GUILDS | MESSAGES). - Não existe rota que liste os servidores do bot: eles chegam no READY e em GUILD_CREATE. - Comandos com "/": `PUT /users/@me/commands` troca a lista inteira; o uso chega como INTERACTION_CREATE e a resposta vai em `POST /interactions/{id}/{token}/callback`, com `type` "message" ou "update" (strings, não números), até 15 minutos depois. - Limite comum: 10 mensagens a cada 10 s por bot. Um 429 traz `retry_after_ms`. Bibliotecas oficiais (todas com reconexão, cache, comandos, cartões, botões e voz): JavaScript/TypeScript `npm install zynko` (Node 22+), Python `pip install zynko` (3.10+), Go `go get zynko.org/go`, Rust `cargo add zynko`. Servidor MCP para assistentes (documentação e testes com o token do bot): `npx -y zynko-mcp`. Veja https://docs.zynko.org/bots/ia.md. Este arquivo traz todas as páginas de https://docs.zynko.org, uma depois da outra. Cada página está entre e . O índice curto está em https://docs.zynko.org/llms.txt. # Bots Um bot é uma conta do Zynko controlada por um programa seu. Ele aparece com a marca **BOT**, entra em servidores, lê e manda mensagens, reage, cria canais e entra em salas de voz, dentro do que os cargos dele permitem. Usa a mesma API e o mesmo gateway do app. ## Como funciona 1. **Crie o bot** em [dev.zynko.org](https://dev.zynko.org), entrando com a sua conta do Zynko. Cada pessoa pode ter até 10 bots. 2. **Guarde o token.** Ele aparece uma vez só, quando o bot é criado. Quem tem o token controla o bot, então guarde fora do código, numa variável de ambiente. Perdeu ou vazou? Gere outro no portal: o antigo para de funcionar na hora. 3. **Conecte no gateway** com o token e receba os eventos em tempo real: mensagens, pessoas entrando, reações. Veja [Seu primeiro bot](https://docs.zynko.org/bots/primeiro-bot.md). 4. **Coloque o bot num servidor** pelo link de convite. Veja [Convite e permissões](https://docs.zynko.org/bots/convite.md). ## O que um bot pode e não pode Dentro de um servidor, um bot é um membro como qualquer outro: os cargos e as permissões de cada canal decidem o que ele faz. Fora disso: - Bots não criam servidores, não fazem amizades, não criam outros bots e não usam links de convite de servidor (eles entram pelo [convite de bot](https://docs.zynko.org/bots/convite.md)). - Bots não têm senha nem e-mail. A conta só funciona com o token. - Os limites de uso são os mesmos de uma pessoa: por exemplo, 10 mensagens a cada 10 segundos. Veja [Visão geral da API](https://docs.zynko.org/api/index.md#limites). ## Endereços | | | |---|---| | API | `https://zynko.org/api` | | Gateway | `wss://zynko.org/api/gateway` | | Autenticação | cabeçalho `Authorization: Bot ` | | Portal | [dev.zynko.org](https://dev.zynko.org) | ## Bibliotecas - [JavaScript e TypeScript](https://docs.zynko.org/bots/javascript.md): `zynko`, com reconexão automática, cache e voz (tocar e ouvir áudio nas salas). Exemplo pronto: o [bot de música](https://docs.zynko.org/bots/musica.md). - [Python](https://docs.zynko.org/bots/python.md): `zynko`, com asyncio; voz com `zynko[voice]`. - [Go](https://docs.zynko.org/bots/go.md): `zynko.org/go`, sem cgo; voz no pacote `zynko.org/go/voice`. - [Rust](https://docs.zynko.org/bots/rust.md): `zynko`, com tokio; voz com a feature `voice`. Todas fazem o mesmo: gateway com reconexão, cache, a API inteira, comandos com "/", cartões e botões, permissões e voz (tocar e ouvir). Para fazer o bot com o Claude ou outro assistente, veja [Bots com IA](https://docs.zynko.org/bots/ia.md): o `llms.txt` desta documentação e o servidor MCP `zynko-mcp`. A API e o gateway também são simples de usar direto, sem biblioteca, como no [primeiro bot](https://docs.zynko.org/bots/primeiro-bot.md). # Seu primeiro bot Um bot que responde `pong` quando alguém manda `!ping`, em JavaScript, sem nenhuma biblioteca, para ver como o gateway funciona por dentro. Para um bot de verdade, a [biblioteca](https://docs.zynko.org/bots/javascript.md) faz isso tudo (e reconecta, guarda o estado e toca áudio) em poucas linhas. Roda em [Bun](https://bun.sh) ou Node.js 22 ou mais novo, que já têm `WebSocket` e `fetch`. ## 1. Crie o bot e coloque num servidor Em [dev.zynko.org](https://dev.zynko.org), crie o bot e copie o token. Na seção **Convite**, deixe marcado **Ver canais**, **Enviar mensagens** e **Ler o histórico**, copie o link, abra e escolha um servidor que você gerencia. ## 2. O código ```js // bot.js — bun bot.js (ou: node bot.js) const TOKEN = process.env.ZYNKO_TOKEN; const API = 'https://zynko.org/api'; // Uma chamada à API com o token do bot. async function api(method, path, body) { const res = await fetch(API + path, { method, headers: { Authorization: `Bot ${TOKEN}`, 'Content-Type': 'application/json' }, body: body && JSON.stringify(body), }); if (!res.ok) throw new Error(`${res.status} ${await res.text()}`); return res.json(); } function connect() { const ws = new WebSocket('wss://zynko.org/api/gateway'); let beat; let me; ws.onmessage = async ({ data }) => { const { op, t, d } = JSON.parse(data); if (op === 10) { // HELLO: identifique-se e mande um heartbeat no intervalo pedido. // intents 8 = só mensagens; veja /api/gateway#intents ws.send(JSON.stringify({ op: 2, d: { token: TOKEN, intents: 8 } })); beat = setInterval(() => ws.send(JSON.stringify({ op: 1 })), d.heartbeat_interval); } else 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; // não responder a si mesmo if (d.content === '!ping') { await api('POST', `/channels/${d.channel_id}/messages`, { content: 'pong' }); } } }; // Caiu, ou nem chegou a abrir: conecte de novo, uma vez só por conexão. // O READY traz o estado inteiro outra vez. let ended = false; const retry = (code) => { if (ended) return; ended = true; clearInterval(beat); if (code === 4004) return console.error('Token inválido'); setTimeout(connect, 5000); }; ws.onclose = (e) => retry(e.code); // No Node.js, a conexão que não abre avisa só com `error`, sem `close`. ws.onerror = () => retry(); } connect(); ``` ## 3. Rode ```sh ZYNKO_TOKEN=bot_... bun bot.js ``` Mande `!ping` num canal onde o bot está. Ele responde `pong`. ## Próximos passos - [Gateway](https://docs.zynko.org/api/gateway.md): os opcodes, os intents e quando a conexão fecha. - [Eventos](https://docs.zynko.org/api/eventos.md): tudo o que o bot pode receber. - [Rotas](https://docs.zynko.org/api/rotas.md): o que o bot pode fazer pela API. # Convite e permissões Um bot entra num servidor pelo **link de convite** dele: ``` https://zynko.org/bot/?permissions= ``` Quem abre o link vê o bot, quem o fez e a lista do que ele pede, escolhe um dos servidores que gerencia e confirma. O portal [dev.zynko.org](https://dev.zynko.org) monta o link: é só marcar as permissões e copiar. ## Público ou privado - **Privado** (o padrão): só você adiciona o bot, e só aos servidores que você gerencia. - **Público**: qualquer pessoa que gerencie um servidor pode adicionar o bot pelo link. Tornar um bot privado de novo não tira ele dos servidores onde já está. ## As permissões do link `permissions` é a soma das permissões que o bot precisa, como número. Ao adicionar, o bot ganha um cargo com o nome dele e essas permissões, que o servidor pode mudar depois. Quem adiciona só consegue dar permissões que tem. Algumas combinações comuns: | Bot | Permissões | `permissions` | |---|---|---| | Só conversa | Ver canais, Enviar mensagens, Ler o histórico | `82432` | | Moderação | + Gerenciar mensagens, Remover pessoas, Banir pessoas | `82544` | | Música | Ver canais, Enviar mensagens, Ler o histórico, Entrar em salas, Falar | `88576` | A lista completa, com o valor de cada uma, está em [Permissões](https://docs.zynko.org/api/permissoes.md). > **Peça só o que usa** > > Um bot que pede pouco é mais fácil de aceitar. **Administrador** dá acesso a tudo e assusta quem > está adicionando; só quem já é administrador no servidor consegue dar. ## Tirar um bot Um bot sai de um servidor como qualquer membro: quem pode remover pessoas remove o bot pela lista de membros. Apagar o bot no portal tira ele de todos os servidores. # Comandos, cartões e botões Além de mensagens de texto, um bot pode: - ter **comandos com "/"**: quem digita `/` no app vê a lista dos comandos dos bots do canal, com descrição e campos para preencher; - mandar **cartões**: título, texto, campos lado a lado, imagens e uma borda colorida; - pôr **botões** nas mensagens, que avisam o bot quando alguém clica; - responder **só para a pessoa** que usou o comando (a resposta não fica no canal). Funciona no app do computador, na web e no Android. ## Comandos O bot cadastra a lista inteira de comandos de uma vez, normalmente ao conectar: ```ts await client.setCommands([ { name: 'dado', description: 'Rola um dado', options: [{ name: 'lados', description: 'Quantos lados', type: 'integer', min_value: 2, max_value: 100 }], }, { name: 'ping', description: 'Responde pong' }, ]); ``` Os comandos aparecem em todos os canais que o bot vê, em todos os servidores dele. Cadastrar de novo troca a lista inteira. | campo | | |---|---| | `name` | 1 a 32 letras minúsculas, números, `_` ou `-`. | | `description` | 1 a 100 caracteres. | | `options` | Até 25 campos (abaixo). Os obrigatórios vêm antes dos opcionais. | Cada opção: | campo | | |---|---| | `name`, `description` | Como no comando. | | `type` | `string`, `integer`, `number`, `boolean`, `user` (alguém do servidor), `channel` (um canal de texto) ou `role` (um cargo). | | `required` | Se precisa ser preenchida. | | `choices` | Até 25 `{ name, value }`: o app mostra uma lista em vez de um campo livre. Para `string`, `integer` e `number`. | | `min_value`, `max_value` | Limites para `integer` e `number`. | O servidor confere o que a pessoa preencheu antes de avisar o bot: tipo, obrigatórios, limites e escolhas. Um bot pode ter até 100 comandos. ## Quando alguém usa um comando O bot recebe o evento `INTERACTION_CREATE`, e só ele recebe. Na biblioteca, é o evento `interaction`: ```ts client.on('interaction', async (i) => { if (i.command === 'dado') { const lados = i.option('lados') ?? 6; await i.reply(`🎲 ${1 + Math.floor(Math.random() * lados)}`); } }); ``` | | | |---|---| | `i.type` | `'command'` ou `'button'`. | | `i.command` | O nome do comando. | | `i.option(nome)` | O valor de uma opção; `undefined` se ficou vazia. `user`, `channel` e `role` chegam como ids. | | `i.user`, `i.member` | Quem usou, e como membro do servidor. | | `i.channelId`, `i.guildId` | Onde. | | `i.reply(...)` | Responde com uma mensagem. | | `i.update(...)` | Só em cliques: muda a mensagem do botão. | O app mostra "Bot está pensando…" até a resposta chegar, e "não respondeu" depois de 15 segundos sem nada. O bot tem até **15 minutos** para responder, e pode responder mais de uma vez (as respostas seguintes são mensagens novas). A resposta aparece no canal com "Fulano usou /dado" em cima. Se o bot estiver offline, o app avisa quem tentou usar o comando, e nada é enviado. ### Resposta só para a pessoa ```ts await i.reply({ content: 'Você não tem permissão para isso.', ephemeral: true }); ``` Só quem usou o comando vê, com "Só você vê esta mensagem · Dispensar". Ela não fica guardada: some ao recarregar o app. Não leva botões. ## Cartões ```ts await client.send(channelId, { cards: [ { author: { name: 'Previsão do tempo' }, title: 'São Paulo', url: 'https://exemplo.com/sp', description: 'Nublado, com **chuva** à tarde.', color: 0xa594f9, fields: [ { name: 'Máxima', value: '27 °C', inline: true }, { name: 'Mínima', value: '18 °C', inline: true }, ], thumbnail: { url: 'https://exemplo.com/nuvem.png' }, footer: { text: 'Atualizado' }, timestamp: Date.now(), }, ], }); ``` | campo | limite | | |---|---|---| | `title` | 256 | Em negrito; vira link se houver `url`. | | `description` | 4096 | Markdown, como uma mensagem. | | `url` | | Link do título (`http(s)`). | | `color` | | A cor da borda, `0xRRGGBB`. | | `author` | nome 256 | `{ name, url?, icon_url? }`, uma linha pequena em cima. | | `fields` | 25 campos; nome 256, valor 1024 | `{ name, value, inline? }`. Os `inline` ficam lado a lado. | | `image` | | `{ url }`: imagem grande embaixo. | | `thumbnail` | | `{ url }`: imagem pequena ao lado do texto. | | `footer` | 2048 | `{ text, icon_url? }`. | | `timestamp` | | Em ms; aparece no rodapé. | Até 5 cartões por mensagem, com até 6000 caracteres somando todos. Um cartão precisa de pelo menos título, texto, campo, imagem ou autor. As imagens vão por links `http(s)` (ou por imagens enviadas ao Zynko, `/api/media/...`). O servidor busca as imagens pelas pessoas, como faz com as prévias de links, então os sites não ficam sabendo quem viu. Só bots mandam cartões e botões; as mensagens de pessoas não levam. ## Botões ```ts await client.send(channelId, { content: 'Votação: pizza ou hambúrguer?', components: [ [ { style: 'primary', emoji: '🍕', label: 'Pizza', custom_id: 'voto:pizza' }, { style: 'secondary', emoji: '🍔', label: 'Hambúrguer', custom_id: 'voto:burger' }, { style: 'link', label: 'Cardápio', url: 'https://exemplo.com/cardapio' }, ], ], }); ``` `components` são fileiras de botões: até 5 fileiras com até 5 botões cada. | campo | | |---|---| | `style` | `primary` (lilás), `secondary` (cinza), `success` (verde), `danger` (vermelho) ou `link`. | | `label` | Até 80 caracteres. Precisa de `label` ou `emoji`. | | `emoji` | Um emoji antes do texto. | | `custom_id` | Até 100 caracteres, diferente em cada botão. Volta para o bot no clique. Todos menos `link`. | | `url` | Só no `link`: abre o endereço, sem avisar o bot. | | `disabled` | Aparece apagado e não clica. | Um clique chega como uma interação do tipo `button`: ```ts client.on('interaction', async (i) => { if (i.type !== 'button') return; if (i.customId?.startsWith('voto:')) { votos.set(i.user.id, i.customId.slice(5)); await i.update({ content: `Votação: pizza ou hambúrguer? (${votos.size} votos)` }); } }); ``` `i.update(...)` muda o texto, os cartões ou os botões da mensagem que foi clicada (o que você mandar; o resto fica). Para tirar os botões, mande `components: []`. Também dá para responder com `i.reply(...)`, como num comando. ## Editar depois O bot edita as próprias mensagens com cartões e botões: ```ts const msg = await client.send(channelId, { content: 'Contando…' }); await msg.edit({ content: 'Pronto!', cards: [{ title: 'Resultado', description: '42' }], components: [] }); ``` ## Pela API, sem biblioteca | ação | rota | |---|---| | Cadastrar os comandos | `PUT /users/@me/commands` com a lista | | Ver os comandos | `GET /users/@me/commands` | | Responder | `POST /interactions/{id}/{token}/callback` | O `INTERACTION_CREATE` traz `id` e `token`. A resposta leva `type` (`"message"`, o padrão, ou `"update"` para mudar a mensagem do botão), `content`, `cards`, `components` e `ephemeral`. Os detalhes estão em [Rotas](https://docs.zynko.org/api/rotas.md#interacoes) e [Eventos](https://docs.zynko.org/api/eventos.md#interaction-create). # Bots com IA O Claude (e outros assistentes) escreve um bot do Zynko inteiro, desde que leia a documentação do Zynko. Sem ela, ele vai pelo que conhece de parecido, o Discord, e o código não funciona: os opcodes, os intents, as permissões e as interações do Zynko são outros. Esta página mostra três jeitos de dar a documentação a ele, do mais simples ao mais completo. ## 1. Numa conversa: llms.txt Toda a documentação existe também em texto puro, feita para assistentes: | Endereço | O que tem | |---|---| | [`docs.zynko.org/llms.txt`](https://docs.zynko.org/llms.txt) | O resumo: o que não confundir com o Discord, e cada página com uma linha de descrição. | | [`docs.zynko.org/llms-full.txt`](https://docs.zynko.org/llms-full.txt) | Todas as páginas num arquivo só. | | `docs.zynko.org/.md` | Uma página em Markdown, como [`/bots/comandos.md`](https://docs.zynko.org/bots/comandos.md) ou [`/api/index.md`](https://docs.zynko.org/api/index.md). | No [claude.ai](https://claude.ai), comece a conversa assim: ``` Leia https://docs.zynko.org/llms-full.txt e me ajude a fazer um bot do Zynko em Python que dá as boas-vindas a quem entra no servidor e tem um comando /regras. ``` Se o assistente não consegue abrir links, baixe o `llms-full.txt` e anexe na conversa. Em cada página daqui, o botão **Copiar para IA**, no topo, copia aquela página em Markdown. ## 2. No projeto: CLAUDE.md No [Claude Code](https://claude.com/claude-code) (e em editores com IA, como o Cursor), um arquivo na raiz do projeto diz ao assistente o que ele precisa saber em toda conversa. Crie um `CLAUDE.md` (ou `AGENTS.md`) no projeto do bot: ````md # Bot do Zynko Este projeto é um bot para o Zynko (https://zynko.org). O Zynko lembra o Discord, mas não é: não use discord.js, discord.py nem a API do Discord, e não deduza nada a partir deles. - Documentação: https://docs.zynko.org/llms.txt (índice, com o link de cada página em Markdown). Antes de usar uma rota, evento ou função da biblioteca, confira na página dela. - Biblioteca: `zynko` (npm). Guia: https://docs.zynko.org/bots/javascript.md - O token fica em `ZYNKO_TOKEN`, no `.env` (fora do git). Nunca escreva um token no código, em logs ou em exemplos. - Intents: só os que o bot usa (`Intents.GUILDS | Intents.MESSAGES` lê mensagens). - Comandos com "/" são cadastrados ao conectar, com `client.setCommands([...])`, que troca a lista inteira. - Limite: 10 mensagens a cada 10 s por bot. Não mande mensagens em laço. ```` Troque a linha da biblioteca se o bot for em [Python](https://docs.zynko.org/bots/python.md), [Go](https://docs.zynko.org/bots/go.md) ou [Rust](https://docs.zynko.org/bots/rust.md). ## 3. Com ferramentas: o servidor MCP O [MCP](https://modelcontextprotocol.io) deixa o assistente usar ferramentas. O `zynko-mcp` dá a ele a documentação para consultar quando precisar e, com o token de um bot, a API do Zynko: ver em que servidores o bot está, ler e mandar mensagens, ouvir os eventos do gateway e trocar os comandos. Assim ele escreve o bot e também testa. Precisa do [Node.js 22](https://nodejs.org) ou mais novo. ### Claude Code Só a documentação: ```sh claude mcp add zynko -- npx -y zynko-mcp ``` Com o token do bot, para o Claude testar: ```sh claude mcp add zynko --env ZYNKO_TOKEN=bot_... -- npx -y zynko-mcp ``` Para deixar configurado no projeto, sem o token no arquivo, crie um `.mcp.json` na raiz. O Claude Code troca `${ZYNKO_TOKEN}` pelo valor da variável de ambiente: ```json { "mcpServers": { "zynko": { "command": "npx", "args": ["-y", "zynko-mcp"], "env": { "ZYNKO_TOKEN": "${ZYNKO_TOKEN}" } } } } ``` ### Claude Desktop Em **Configurações › Desenvolvedor › Editar configuração**, no `claude_desktop_config.json`: ```json { "mcpServers": { "zynko": { "command": "npx", "args": ["-y", "zynko-mcp"], "env": { "ZYNKO_TOKEN": "bot_..." } } } } ``` Reinicie o Claude Desktop. No Windows, se o servidor não iniciar, use `"command": "cmd"` e `"args": ["/c", "npx", "-y", "zynko-mcp"]`. Outros clientes MCP (Cursor, VS Code, Windsurf) usam o mesmo comando, `npx -y zynko-mcp`, com a variável `ZYNKO_TOKEN`. ### As ferramentas | Ferramenta | Token | O que faz | |---|---|---| | `docs_search` | não | Busca na documentação e devolve as seções que combinam. | | `docs_read` | não | Lê uma página (ou uma seção dela). Sem argumentos, lista as páginas. | | `invite_link` | não* | Monta o link de convite com as permissões escolhidas. | | `bot_overview` | sim | O bot, os servidores e canais em que está, o que pode fazer em cada canal, os comandos e o link de convite. | | `read_messages` | sim | Lê as mensagens de um canal. | | `send_message` | sim | Manda uma mensagem, com cartões e botões, como o bot. | | `watch_events` | sim | Ouve o gateway por alguns segundos e mostra os eventos que chegaram, com o JSON. | | `commands` | sim | Mostra ou troca os comandos com "/". | | `api_request` | sim | Qualquer rota da [API](https://docs.zynko.org/api/rotas.md), como o bot. | \* Sem token, `invite_link` pede o id do bot. No Claude Code, o MCP também traz o comando `/mcp__zynko__novo_bot`, que monta um projeto de bot do zero a partir da sua ideia. > **O MCP age como o bot, de verdade** > > Com o token, o que o assistente faz pelo MCP acontece nos servidores do bot: mensagens > enviadas todos veem, e `commands` troca os comandos de verdade. Teste com um bot de testes num > servidor seu, e leia o que o assistente vai fazer antes de aprovar cada ferramenta. > > Enquanto `bot_overview` e `watch_events` rodam, o bot aparece online por alguns segundos. ## O token - Não cole o token numa conversa. Ele vai na configuração do MCP ou no `.env`, que o assistente usa sem precisar ver. - Colou, ou foi parar num commit? Gere outro em [dev.zynko.org](https://dev.zynko.org): o antigo para de funcionar na hora. - Use um bot só para testes enquanto desenvolve, e o token do bot de verdade só onde ele roda. # Biblioteca para JavaScript e TypeScript `zynko` é a biblioteca oficial para bots do Zynko em JavaScript e TypeScript. Ela conecta no gateway e se reconecta sozinha, mantém servidores, canais e membros atualizados, transforma os eventos em `client.on(...)` e cobre a API. A parte de voz entra em salas, toca áudio e ouve quem fala. Precisa de **Node.js 22** ou mais novo. ## Instalação ```sh npm install zynko # para voz, também: npm install @livekit/rtc-node ``` Para tocar arquivos e links, o bot usa o [ffmpeg](https://ffmpeg.org), que precisa estar instalado na máquina (ou indicado em `FFMPEG_PATH`). ## Primeiro bot ```ts import { Client, Intents } from 'zynko'; const client = new Client({ token: process.env.ZYNKO_TOKEN!, intents: Intents.GUILDS | Intents.MESSAGES, }); client.on('messageCreate', async (msg) => { if (msg.mine) return; if (msg.content === '!ping') await msg.reply('pong'); }); const me = await client.login(); console.log(`${me.display_name} conectado em ${client.guilds.size} servidores`); ``` ```sh ZYNKO_TOKEN=bot_... node bot.ts ``` O Node 22 roda TypeScript direto (sem `enum` nem outros recursos que precisam de compilação). Em JavaScript é igual, sem as anotações de tipo. ## O cliente ```ts new Client({ token, // o token do bot (dev.zynko.org) intents, // o que receber; padrão: GUILDS | MESSAGES api, // outro servidor; padrão https://zynko.org/api }); ``` | | | |---|---| | `client.login()` | Conecta. Resolve com a conta do bot quando o `READY` chega. | | `client.destroy()` | Sai da voz e fecha a conexão. | | `client.user` | A conta do bot. | | `client.guilds` | `Map` dos servidores, cada um com `channels`, `members`, `roles` (todos `Map`), `emojis` e `online`. | | `client.channels` | `Map` de todos os canais que o bot vê, DMs incluídas. | | `client.users` | `Map` das contas que o bot já viu. | | `client.voiceStates` | `Map` de quem está em voz, por id da conta (precisa de `VOICE_STATES`). | | `client.rest` | A API inteira (veja [API](#api)). | Os mapas se atualizam sozinhos com os eventos. Se a conexão cair, o cliente reconecta (esperando 1 s, 2 s, 4 s... até 30 s) e reconstrói tudo com o `READY` novo. ### Intents ```ts import { Intents } from 'zynko'; Intents.GUILDS | Intents.MESSAGES | Intents.VOICE_STATES ``` `GUILDS`, `GUILD_MEMBERS`, `PRESENCES`, `MESSAGES`, `REACTIONS`, `TYPING`, `VOICE_STATES` e `ALL`. Peça só o que o bot usa. Detalhes em [Gateway](https://docs.zynko.org/api/gateway.md#intents). ## Eventos ```ts client.on('messageCreate', (msg) => { /* ... */ }); ``` | evento | argumentos | quando | |---|---|---| | `ready` | `user` | Conectou (de novo). | | `messageCreate` | `Message` | Mensagem nova. | | `messageUpdate` | `{ id, channel_id, content?, edited_timestamp?, embeds?, cards?, components? }` | Mensagem editada (ou prévia de link pronta). Só traz o que mudou, sem o autor. | | `messageDelete` | `{ id, channel_id }` | Mensagem apagada. | | `reactionAdd`, `reactionRemove` | `{ message_id, channel_id, user_id, emoji }` | Reação. | | `typingStart` | `{ channel_id, user_id, timestamp }` | Alguém digitando. | | `guildCreate` | servidor | Bot adicionado a um servidor (e cada servidor do `READY`). | | `guildUpdate` | servidor | Nome, ícone ou dono mudou. | | `guildDelete` | `guildId` | Bot saiu ou o servidor foi apagado. | | `channelCreate`, `channelDelete` | `Channel` | Canal novo ou apagado (uma DM nova é um canal novo). | | `channelUpdate` | `Channel`, anterior | Canal mudou. | | `memberAdd` | `Member` | Alguém entrou num servidor. | | `memberUpdate` | `Member`, anterior | Apelido ou cargos mudaram. | | `memberRemove` | `{ guild_id, user_id }` | Alguém saiu. | | `presenceUpdate` | `{ user_id, status }` | Ficou online ou offline. | | `voiceStateUpdate` | `VoiceState`, anterior | Alguém entrou, saiu ou mudou na voz. | | `interaction` | `Interaction` | Alguém usou um comando do bot ou clicou num botão dele. | | `raw` | `type`, `data` | Todo evento do gateway, como chegou. | | `reconnecting` | `{ code, reason, wait }` | A conexão caiu e vai voltar. | | `error` | `Error` | Erro num listener, ou o token foi recusado. | Um listener que lança erro (ou uma promessa rejeitada) vira um evento `error`, sem derrubar o bot. `client.wait('messageCreate', filtro, ms)` espera o próximo evento que passar no filtro: ```ts await msg.reply('Qual o seu nome?'); const [answer] = await client.wait('messageCreate', (m) => m.author.id === msg.author.id, 30_000); ``` ## Mensagens `messageCreate` entrega um `Message`: | | | |---|---| | `id`, `channelId`, `author`, `content`, `timestamp`, `attachments`, `reactions`, `embeds`, `replyTo` | Os dados da mensagem. | | `guildId` | O servidor, ou `null` numa DM. | | `channel` | O `Channel`, se o bot já o viu. | | `member` | O autor como membro do servidor (apelido, cargos). | | `mine` | Se foi o próprio bot que mandou. | | `reply(texto ou opções)` | Responde no mesmo canal, como resposta a ela. | | `react(emoji)`, `unreact(emoji)` | Reage ou tira a reação. | | `edit(texto)`, `delete()` | O bot edita as próprias mensagens; apaga as próprias ou, com **Gerenciar mensagens**, as dos outros. | Para mandar em qualquer canal, ou com arquivos: ```ts await client.send(channelId, 'Olá!'); await client.send(channelId, { content: 'O relatório de hoje', files: [{ name: 'relatorio.pdf', data: await readFile('relatorio.pdf') }], }); await client.sendDm(userId, 'Oi! Isso é uma mensagem direta.'); ``` ## Comandos, cartões e botões ```ts await client.setCommands([{ name: 'ping', description: 'Responde pong' }]); client.on('interaction', async (i) => { if (i.command === 'ping') await i.reply({ content: 'pong', ephemeral: true }); }); await client.send(channelId, { cards: [{ title: 'Placar', fields: [{ name: 'Ana', value: '3', inline: true }] }], components: [[{ style: 'primary', label: 'Mais um', custom_id: 'mais' }]], }); ``` Tudo sobre isso em [Comandos, cartões e botões](https://docs.zynko.org/bots/comandos.md). ## Permissões ```ts import { Permissions } from 'zynko'; if (!client.can(msg.channelId, msg.author.id, Permissions.KICK_MEMBERS)) { return msg.reply('Você não pode remover pessoas.'); } ``` `client.permissionsIn(channelId, userId)` devolve todos os bits, calculados como o servidor calcula: `@everyone`, os cargos, as permissões do canal, ADM e dono. A lista dos bits está em [Permissões](https://docs.zynko.org/api/permissoes.md). ## API `client.rest` tem um método para cada ação comum, e `request` para o resto: ```ts await client.rest.kick(guildId, userId); await client.rest.addRole(guildId, userId, roleId); const msgs = await client.rest.messages(channelId, { limit: 50 }); await client.rest.request('PATCH', `/guilds/${guildId}/members/${userId}`, { nick: 'Novato' }); ``` `send`, `upload`, `editMessage`, `deleteMessage`, `messages`, `react`, `unreact`, `typing`, `me`, `profile`, `dm`, `members`, `kick`, `ban`, `unban`, `addRole`, `removeRole`, `createRole`, `createChannel`, `editChannel`, `deleteChannel`, `createInvite` e `voiceToken`. Todas as rotas estão em [Rotas](https://docs.zynko.org/api/rotas.md). Erros da API viram `ZynkoError`, com `status`, `code` e `message`. Quando um limite de requisições estoura (`429`), a biblioteca espera o tempo pedido e tenta de novo, até 3 vezes. ## Voz ```ts const voice = await client.joinVoice(channelId); voice.player.enqueue('https://exemplo.com/musica.mp3'); ``` O bot precisa de **Entrar em salas** e **Falar** na sala, e do intent `VOICE_STATES` para saber onde as pessoas estão. Ele fica em uma sala por vez, como qualquer conta. ```ts client.joinVoice(channelId, { receive: false, // ouvir a sala (eventos 'audio') relay: false, // a conexão alternativa, pelo relay do servidor deaf: false, // aparecer com o som desligado ytdlp: false, // passar links pelo yt-dlp antes (true, ou o caminho dele) ffmpeg: 'ffmpeg', // caminho do ffmpeg }); ``` | | | |---|---| | `voice.player` | O tocador (abaixo). | | `voice.leave()` | Sai da sala. | | `voice.channelId`, `voice.server` | A sala e o servidor de voz dela (Brasil 1, Brasil 2...). | | `voice.canSpeak` | Se os cargos deixam o bot falar ali. | | `voice.on('ready')` | Conectou (de novo, depois de uma troca de servidor). | | `voice.on('speaking', ids)` | Quem está falando agora. | | `voice.on('audio', (userId, samples, channels))` | Com `receive: true`: o som de cada pessoa. | | `voice.on('disconnect', motivo)` | Saiu de vez: pediu, foi removido, a sala sumiu. | Se alguém trocar o servidor da sala (Brasil 1 ↔ Brasil 2), o bot vai junto sozinho e a música continua de onde estava. ### Tocador ```ts const p = voice.player; p.enqueue('https://radio.exemplo.com/stream', { title: 'Rádio' }); // fila p.play('https://exemplo.com/agora.mp3'); // toca já p.skip(); p.pause(); p.resume(); p.stop(); p.volume = 0.5; // 0 a 2 p.current; p.queue; p.on('start', (track) => {}); p.on('finish', (track) => {}); p.on('error', (err, track) => {}); p.on('idle', () => {}); ``` O que dá para tocar: | entrada | | |---|---| | `'https://...'` ou um caminho | Aberto pelo ffmpeg: mp3, ogg, wav, flac, rádios por stream... | | `{ stream }` | Áudio codificado vindo de um `Readable` (um download, um pipe); o ffmpeg decodifica. | | `{ pcm }` | PCM cru: 48 kHz, estéreo, 16 bits little-endian. Não passa pelo ffmpeg. | | `{ pcm: tone(440, 3) }` | Um tom de teste. | O segundo argumento de `play` e `enqueue` é o que você quiser (título, quem pediu) e volta em `track.meta` nos eventos. > **Caminhos de arquivo** > > O tocador abre o que receber. Num bot público, aceite só links (`https://`) do chat, nunca > caminhos: senão qualquer pessoa toca arquivos da máquina do bot. ### Ouvir a sala ```ts const voice = await client.joinVoice(channelId, { receive: true }); voice.on('audio', (userId, samples) => { // samples: Int16Array, 48 kHz, mono, em pedaços de 10 ms }); ``` Para gravar, transcrever ou medir volume. Avise as pessoas da sala: elas não sabem que um bot está ouvindo. ## Exemplo completo O [bot de música](https://docs.zynko.org/bots/musica.md) usa tudo isso: comandos, fila, volume e saída automática quando a sala esvazia. # Biblioteca para Python `zynko` é a biblioteca oficial para bots do Zynko em Python, feita com asyncio. Ela conecta no gateway e se reconecta sozinha, mantém servidores, canais e membros atualizados, transforma os eventos em funções suas e cobre a API. A parte de voz entra em salas, toca áudio e ouve quem fala. Precisa de **Python 3.10** ou mais novo. ## Instalação ```sh pip install zynko # com voz: pip install "zynko[voice]" ``` A voz usa o pacote `livekit`, que o extra `voice` instala. Para tocar arquivos e links, o bot usa o [ffmpeg](https://ffmpeg.org), que precisa estar instalado na máquina (ou indicado em `FFMPEG_PATH`). ## Primeiro bot ```python import os import zynko client = zynko.Client(intents=zynko.Intents.GUILDS | zynko.Intents.MESSAGES) @client.on("message_create") async def ping(msg: zynko.Message): if msg.mine: return if msg.content == "!ping": await msg.reply("pong") @client.event async def on_ready(user: zynko.User): print(f"{user.display_name} conectado em {len(client.guilds)} servidores") client.run(os.environ["ZYNKO_TOKEN"]) ``` ```sh ZYNKO_TOKEN=bot_... python bot.py ``` `client.run(token)` conecta e fica rodando até o Ctrl+C. Dentro de um programa que já usa asyncio, use `await client.start(token)`, que roda até `client.close()`, ou `await client.login(token)`, que volta assim que o bot conecta e deixa a conexão rodando por trás. ## O cliente ```python zynko.Client( token, # o token do bot (dev.zynko.org); ou passe em run(), start() ou login() intents=..., # o que receber; padrão: GUILDS | MESSAGES api=..., # outro servidor; padrão https://zynko.org/api ) ``` | | | |---|---| | `client.run(token)` | Conecta e bloqueia até o Ctrl+C. | | `await client.start(token)` | Conecta e roda até `close()`. | | `await client.login(token)` | Conecta. Devolve a conta do bot quando o `READY` chega. | | `await client.close()` | Sai da voz e fecha a conexão. | | `client.user` | A conta do bot. | | `client.guilds` | `dict` dos servidores, cada um com `channels`, `members`, `roles` (todos `dict`), `emojis` e `online`. | | `client.channels` | `dict` de todos os canais que o bot vê, DMs incluídas. | | `client.users` | `dict` das contas que o bot já viu. | | `client.voice_states` | `dict` de quem está em voz, por id da conta (precisa de `VOICE_STATES`). | | `client.rest` | A API inteira (veja [API](#api)). | Os dicionários se atualizam sozinhos com os eventos. Se a conexão cair, o cliente reconecta (esperando 1 s, 2 s, 4 s... até 30 s) e reconstrói tudo com o `READY` novo. Os objetos (`User`, `Channel`, `Member`, `Role`, `VoiceState`...) são dataclasses com os campos da [API](https://docs.zynko.org/api/objetos.md), em snake_case como no JSON. Cada um guarda o JSON original em `raw`, para campos que a biblioteca ainda não conhece. ### Intents ```python from zynko import Intents Intents.GUILDS | Intents.MESSAGES | Intents.VOICE_STATES ``` `GUILDS`, `GUILD_MEMBERS`, `PRESENCES`, `MESSAGES`, `REACTIONS`, `TYPING`, `VOICE_STATES` e `ALL`. Peça só o que o bot usa. Detalhes em [Gateway](https://docs.zynko.org/api/gateway.md#intents). ## Eventos Registre uma função com o decorador `@client.on("evento")`, ou dê a ela o nome `on_evento` e use `@client.event`: ```python @client.on("message_create") async def nova(msg: zynko.Message): ... @client.event async def on_message_create(msg: zynko.Message): ... ``` As funções podem ser `async` ou comuns. Cada `async` roda na sua própria tarefa, então uma função demorada não segura os próximos eventos. | evento | argumentos | quando | |---|---|---| | `ready` | `user` | Conectou (de novo). | | `message_create` | `Message` | Mensagem nova. | | `message_update` | `Message` | Mensagem editada (ou prévia de link pronta). Chega parcial: veja abaixo. | | `message_delete` | `{id, channel_id}` | Mensagem apagada. | | `reaction_add`, `reaction_remove` | `{message_id, channel_id, user_id, emoji}` | Reação. | | `typing_start` | `{channel_id, user_id, timestamp}` | Alguém digitando. | | `guild_create` | `Guild` | Bot adicionado a um servidor (e cada servidor do `READY`). | | `guild_update` | `Guild` | Nome, ícone ou dono mudou. | | `guild_delete` | `guild_id` | Bot saiu ou o servidor foi apagado. | | `channel_create`, `channel_delete` | `Channel` | Canal novo ou apagado (uma DM nova é um canal novo). | | `channel_update` | `Channel`, anterior | Canal mudou. | | `member_add` | `Member` | Alguém entrou num servidor. | | `member_update` | `Member`, anterior | Apelido ou cargos mudaram. | | `member_remove` | `{guild_id, user_id}` | Alguém saiu. | | `presence_update` | `{user_id, status}` | Ficou online ou offline. | | `voice_state_update` | `VoiceState`, anterior | Alguém entrou, saiu ou mudou na voz. | | `interaction` | `Interaction` | Alguém usou um comando do bot ou clicou num botão dele. | | `raw` | `type`, `data` | Todo evento do gateway, como chegou. | | `reconnecting` | `{code, reason, wait}` | A conexão caiu e vai voltar. | | `error` | `Exception` | Erro numa função de evento, ou o token foi recusado. | Uma função que lança erro vira um evento `error`, sem derrubar o bot (sem ninguém ouvindo `error`, o erro vai para o `logging`). `client.wait_for("evento", filtro, timeout)` espera o próximo evento que passar no filtro: ```python await msg.reply("Qual o seu nome?") answer = await client.wait_for("message_create", lambda m: m.author.id == msg.author.id, timeout=30) ``` Depois do tempo, levanta `asyncio.TimeoutError`. Num evento com mais de um argumento, devolve uma tupla. ## Mensagens `message_create` e `message_update` entregam um `Message`: | | | |---|---| | `id`, `channel_id`, `author`, `content`, `timestamp`, `attachments`, `reactions`, `embeds`, `reply_to` | Os dados da mensagem. | | `guild_id` | O servidor, ou `None` numa DM. | | `channel` | O `Channel`, se o bot já o viu. | | `member` | O autor como membro do servidor (apelido, cargos). | | `mine` | Se foi o próprio bot que mandou. | | `raw` | O JSON da mensagem. | | `await reply(texto, ...)` | Responde no mesmo canal, como resposta a ela. | | `await react(emoji)`, `await unreact(emoji)` | Reage ou tira a reação. | | `await edit(texto, ...)`, `await delete()` | O bot edita as próprias mensagens; apaga as próprias ou, com **Gerenciar mensagens**, as dos outros. | O `message_update` traz só o que mudou (`id`, `channel_id`, `content`, `edited_timestamp`, `embeds`): nele, `msg.partial` é `True` e `msg.author` vem vazio. Para mandar em qualquer canal, ou com arquivos: ```python await client.send(channel_id, "Olá!") await client.send( channel_id, "O relatório de hoje", files=["relatorio.pdf"], # um caminho, ou zynko.File("nome.pdf", dados) ) await client.send_dm(user_id, "Oi! Isso é uma mensagem direta.") ``` ## Comandos, cartões e botões ```python await client.set_commands([{"name": "ping", "description": "Responde pong"}]) @client.on("interaction") async def comandos(i: zynko.Interaction): if i.command == "ping": await i.reply("pong", ephemeral=True) await client.send( channel_id, cards=[{"title": "Placar", "fields": [{"name": "Ana", "value": "3", "inline": True}]}], components=[[{"style": "primary", "label": "Mais um", "custom_id": "mais"}]], ) ``` | | | |---|---| | `i.type` | `"command"` ou `"button"`. | | `i.command` | O nome do comando. | | `i.option(nome, padrão)` | O valor de uma opção; `padrão` (ou `None`) se ficou vazia. | | `i.custom_id` | O `custom_id` do botão clicado. | | `await i.reply(texto, cards=, components=, ephemeral=)` | Responde com uma mensagem. | | `await i.update(texto, cards=, components=)` | Só em cliques: muda a mensagem do botão. | Cartões, botões e comandos são dicionários, com os mesmos campos da API (`zynko.Card`, `zynko.Button` e `zynko.Command` dão os tipos ao editor). Um exemplo completo, um dado com botão "De novo", está em `examples/dado.py`. Tudo sobre isso em [Comandos, cartões e botões](https://docs.zynko.org/bots/comandos.md). ## Permissões ```python from zynko import Permissions if not client.can(msg.channel_id, msg.author.id, Permissions.KICK_MEMBERS): return await msg.reply("Você não pode remover pessoas.") ``` `client.permissions_in(channel_id, user_id)` devolve todos os bits, calculados como o servidor calcula: `@everyone`, os cargos, as permissões do canal, ADM e dono. A lista dos bits está em [Permissões](https://docs.zynko.org/api/permissoes.md). ## API `client.rest` tem um método para cada ação comum, e `request` para o resto: ```python await client.rest.kick(guild_id, user_id) await client.rest.add_role(guild_id, user_id, role_id) msgs = await client.rest.messages(channel_id, limit=50) await client.rest.request("PATCH", f"/guilds/{guild_id}/members/{user_id}", {"nick": "Novato"}) ``` `send`, `upload`, `edit_message`, `delete_message`, `messages`, `react`, `unreact`, `typing`, `me`, `profile`, `dm`, `members`, `kick`, `ban`, `unban`, `add_role`, `remove_role`, `create_role`, `create_channel`, `edit_channel`, `delete_channel`, `create_invite`, `set_commands`, `commands` e `voice_token`. Eles devolvem o JSON da API (dicionários e listas). Todas as rotas estão em [Rotas](https://docs.zynko.org/api/rotas.md). Erros da API viram `zynko.ZynkoError`, com `status`, `code` e `message`. Quando um limite de requisições estoura (`429`), a biblioteca espera o tempo pedido e tenta de novo, até 3 vezes. ## Voz ```python voice = await client.join_voice(channel_id) voice.player.enqueue("https://exemplo.com/musica.mp3") ``` Precisa do extra `voice` (`pip install "zynko[voice]"`). O bot precisa de **Entrar em salas** e **Falar** na sala, e do intent `VOICE_STATES` para saber onde as pessoas estão. Ele fica em uma sala por vez, como qualquer conta. ```python await client.join_voice( channel_id, receive=False, # ouvir a sala (eventos "audio") relay=False, # a conexão alternativa, pelo relay do servidor deaf=False, # aparecer com o som desligado ytdlp=False, # passar links pelo yt-dlp antes (True, ou o caminho dele) ffmpeg="ffmpeg", # caminho do ffmpeg ) ``` | | | |---|---| | `voice.player` | O tocador (abaixo). | | `await voice.leave()` | Sai da sala. | | `voice.channel_id`, `voice.server` | A sala e o servidor de voz dela (Brasil 1, Brasil 2...). | | `voice.can_speak` | Se os cargos deixam o bot falar ali. | | `voice.on("ready")` | Conectou (de novo, depois de uma troca de servidor). | | `voice.on("speaking")` | Quem está falando agora: uma lista de ids. | | `voice.on("audio")` | Com `receive=True`: o som de cada pessoa, `(user_id, samples, channels)`. | | `voice.on("disconnect")` | Saiu de vez: pediu, foi removido, a sala sumiu. Recebe o motivo. | Se alguém trocar o servidor da sala (Brasil 1 ↔ Brasil 2), o bot vai junto sozinho e a música continua de onde estava. ### Tocador ```python p = voice.player p.enqueue("https://radio.exemplo.com/stream", {"title": "Rádio"}) # fila p.play("https://exemplo.com/agora.mp3") # toca já p.skip(); p.pause(); p.resume(); p.stop() p.volume = 0.5 # 0 a 2 p.current; p.queue @p.on("start") def comecou(track: zynko.Track): ... # também "finish" (track), "error" (erro, track) e "idle" (a fila acabou) ``` O que dá para tocar: | entrada | | |---|---| | `"https://..."` ou um caminho | Aberto pelo ffmpeg: mp3, ogg, wav, flac, rádios por stream... | | `zynko.Stream(fonte)` | Áudio codificado (um download, um pipe); o ffmpeg decodifica. | | `zynko.PCM(fonte)` | PCM cru: 48 kHz, estéreo, 16 bits little-endian. Não passa pelo ffmpeg. | | `zynko.tone(440, 3)` | Um tom de teste. | A `fonte` pode ser `bytes`, um arquivo aberto em modo binário, um `asyncio.StreamReader` ou qualquer iterável (ou iterável assíncrono) de `bytes`. O segundo argumento de `play` e `enqueue` é o que você quiser (título, quem pediu) e volta em `track.meta` nos eventos. > **Caminhos de arquivo** > > O tocador abre o que receber. Num bot público, aceite só links (`https://`) do chat, nunca > caminhos: senão qualquer pessoa toca arquivos da máquina do bot. ### Ouvir a sala ```python voice = await client.join_voice(channel_id, receive=True) @voice.on("audio") def ouviu(user_id: str, samples, channels: int): # samples: array('h'), 48 kHz, mono, em pedaços de 10 ms ... ``` Para gravar, transcrever ou medir volume. Avise as pessoas da sala: elas não sabem que um bot está ouvindo. ## Exemplos Na pasta `examples` do código-fonte do pacote (o `.tar.gz` em [pypi.org/project/zynko](https://pypi.org/project/zynko/#files)): - `ping.py`: o bot mínimo, que responde `!ping`; - `dado.py`: comandos com "/", um cartão e botões; - `musica.py`: um bot de música com `/tocar `, fila, volume e os botões Pausar, Pular e Parar no cartão "Tocando agora" (os comandos também funcionam com `!`). É a versão em Python do [bot de música](https://docs.zynko.org/bots/musica.md). # Biblioteca para Go `zynko.org/go` é a biblioteca oficial para bots do Zynko em Go. Ela conecta no gateway e se reconecta sozinha, mantém servidores, canais e membros atualizados, entrega os eventos para funções tipadas e cobre a API. O pacote `zynko.org/go/voice` entra em salas, toca áudio e ouve quem fala. Precisa de **Go 1.25** ou mais novo. Tudo funciona sem cgo (`CGO_ENABLED=0`), inclusive a voz. ## Instalação ```sh go get zynko.org/go ``` A voz vem no mesmo módulo, no pacote `zynko.org/go/voice`: um bot que não importa esse pacote não leva o LiveKit para o binário. Para tocar e ouvir, o bot usa o [ffmpeg](https://ffmpeg.org), que precisa estar instalado na máquina (ou indicado em `FFMPEG_PATH`). ## Primeiro bot ```go package main import ( "context" "log" "os" "os/signal" "zynko.org/go" ) func main() { ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() client, err := zynko.New(zynko.Options{ Token: os.Getenv("ZYNKO_TOKEN"), Intents: zynko.IntentGuilds | zynko.IntentMessages, }) if err != nil { log.Fatal(err) } client.OnMessageCreate(func(ctx context.Context, m *zynko.Message) error { if m.Mine() || m.Content != "!ping" { return nil } _, err := m.Reply(ctx, "pong") return err }) me, err := client.Login(ctx) if err != nil { log.Fatal(err) } log.Printf("%s conectado em %d servidores", me.DisplayName, len(client.Guilds())) <-ctx.Done() client.Close() } ``` ```sh ZYNKO_TOKEN=bot_... go run . ``` ## O cliente ```go zynko.New(zynko.Options{ Token: token, // o token do bot (dev.zynko.org) Intents: intents, // o que receber; padrão: IntentGuilds | IntentMessages API: "", // outro servidor; padrão https://zynko.org/api Retries: 0, // vezes que um 429 é esperado e repetido; padrão 3, -1 nunca SyncEvents: false, // rodar os handlers em ordem, na goroutine do gateway }) ``` | | | |---|---| | `client.Login(ctx)` | Conecta. Volta com a conta do bot quando o `READY` chega. Se o `ctx` acabar antes, ou o token for recusado, o cliente fecha. | | `client.Close()` | Sai da voz e fecha a conexão. | | `client.Done()` | Fecha quando o cliente para de vez (`Close`, ou token recusado). | | `client.User()`, `client.UserID()` | A conta do bot. | | `client.Guilds()`, `client.Guild(id)` | Os servidores. Cada `*CachedGuild` tem `Channels()`, `Members()`, `Roles()`, `Emojis()`, `OnlineIDs()` e as versões por id (`Channel(id)`, `Member(id)`, `Role(id)`, `Online(id)`). | | `client.Channels()`, `client.Channel(id)` | Todos os canais que o bot vê, DMs incluídas. | | `client.Users()`, `client.CachedUser(id)` | As contas que o bot já viu. | | `client.Member(guildID, userID)` | Alguém como membro de um servidor. | | `client.VoiceStates()`, `client.VoiceState(userID)` | Quem está em voz (precisa de `IntentVoiceStates`). | | `client.Rest` | A API inteira (veja [API](#api)). | Os caches se atualizam sozinhos com os eventos e podem ser lidos de qualquer goroutine: os métodos devolvem cópias. Se a conexão cair, o cliente reconecta (esperando 1 s, 2 s, 4 s... até 30 s) e reconstrói tudo com o `READY` novo. ### Intents ```go zynko.IntentGuilds | zynko.IntentMessages | zynko.IntentVoiceStates ``` `IntentGuilds`, `IntentGuildMembers`, `IntentPresences`, `IntentMessages`, `IntentReactions`, `IntentTyping`, `IntentVoiceStates` e `IntentsAll`. Peça só o que o bot usa. Detalhes em [Gateway](https://docs.zynko.org/api/gateway.md#intents). ## Eventos Cada evento é um tipo. `zynko.On` registra uma função para um deles, escolhido pelo tipo do argumento: ```go zynko.On(client, func(ctx context.Context, e *zynko.MemberAdd) error { log.Printf("%s entrou", e.Member.User.DisplayName) return nil }) ``` Os mais comuns têm atalhos: `client.OnReady`, `client.OnMessageCreate`, `client.OnInteraction` e `client.OnError`. Todos devolvem uma função que remove o handler. | evento | campos | quando | |---|---|---| | `*Ready` | `User` | Conectou (de novo). | | `*MessageCreate` | `*Message` | Mensagem nova. | | `*MessageUpdate` | `*Message` | Mensagem editada (ou prévia de link pronta). | | `*MessageDelete` | `ID`, `ChannelID` | Mensagem apagada. | | `*ReactionAdd`, `*ReactionRemove` | `MessageID`, `ChannelID`, `UserID`, `Emoji` | Reação. | | `*TypingStart` | `ChannelID`, `UserID`, `Timestamp` | Alguém digitando. | | `*GuildCreate` | `Guild` | Bot adicionado a um servidor (e cada servidor do `READY`). | | `*GuildUpdate` | `Guild` | Nome, ícone ou dono mudou. | | `*GuildDelete` | `GuildID` | Bot saiu ou o servidor foi apagado. | | `*ChannelCreate`, `*ChannelDelete` | `Channel` | Canal novo ou apagado (uma DM nova é um canal novo). | | `*ChannelUpdate` | `Channel`, `Previous` | Canal mudou. | | `*MemberAdd` | `Member` | Alguém entrou num servidor. | | `*MemberUpdate` | `Member`, `Previous` | Apelido ou cargos mudaram. | | `*MemberRemove` | `GuildID`, `UserID` | Alguém saiu. | | `*PresenceUpdate` | `UserID`, `Status` | Ficou online ou offline. | | `*VoiceStateUpdate` | `State`, `Previous` | Alguém entrou, saiu ou mudou na voz. | | `*InteractionCreate` | `*Interaction` | Alguém usou um comando do bot ou clicou num botão dele. | | `*Raw` | `Type`, `Data` | Todo evento do gateway, como chegou. | | `*Reconnecting` | `Code`, `Reason`, `Wait` | A conexão caiu e vai voltar. | | `*ErrorEvent` | `Err` | Erro num handler, ou o token foi recusado. | `Previous` é `nil` quando o cliente ainda não conhecia o canal, membro ou estado. Um handler que devolve erro, ou entra em pânico, vira um `*ErrorEvent`, sem derrubar o bot. Cada evento roda na sua própria goroutine (os handlers de um mesmo evento, em ordem); com `SyncEvents: true`, tudo roda em ordem na goroutine do gateway, e um handler lento segura os outros eventos. O `ctx` que os handlers recebem acaba quando o cliente fecha. `zynko.Wait` espera o próximo evento de um tipo que passar no filtro. O prazo vem do `ctx`: ```go _, err := m.Reply(ctx, "Qual o seu nome?") wctx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() answer, err := zynko.Wait(wctx, client, func(e *zynko.MessageCreate) bool { return e.Author.ID == m.Author.ID }) ``` ## Mensagens `*MessageCreate` e `*MessageUpdate` trazem um `*Message`: | | | |---|---| | `ID`, `ChannelID`, `Author`, `Content`, `Timestamp`, `Attachments`, `Reactions`, `Embeds`, `ReplyTo`, `Cards`, `Components` | Os dados da mensagem. | | `GuildID()` | O servidor, ou `""` numa DM. | | `Channel()` | O `Channel`, se o bot já o viu. | | `Member()` | O autor como membro do servidor (apelido, cargos). | | `Mine()` | Se foi o próprio bot que mandou. | | `Reply(ctx, texto)`, `ReplyWith(ctx, SendOptions)` | Responde no mesmo canal, como resposta a ela. | | `React(ctx, emoji)`, `Unreact(ctx, emoji)` | Reage ou tira a reação. | | `Edit(ctx, texto)`, `EditWith(ctx, EditOptions)`, `Delete(ctx)` | O bot edita as próprias mensagens; apaga as próprias ou, com **Gerenciar mensagens**, as dos outros. | Para mandar em qualquer canal, ou com arquivos: ```go client.Send(ctx, channelID, "Olá!") relatorio, err := zynko.FileFromPath("relatorio.pdf") client.SendWith(ctx, channelID, zynko.SendOptions{ Content: "O relatório de hoje", Files: []zynko.File{relatorio}, }) client.SendDM(ctx, userID, zynko.SendOptions{Content: "Oi! Isso é uma mensagem direta."}) ``` Um `zynko.File` também pode vir da memória: `zynko.File{Name: "dados.csv", Data: bytes}`. ## Comandos, cartões e botões ```go client.SetCommands(ctx, []zynko.Command{{Name: "ping", Description: "Responde pong"}}) client.OnInteraction(func(ctx context.Context, i *zynko.Interaction) error { if i.Command == "ping" { _, err := i.ReplyEphemeral(ctx, "pong") return err } return nil }) client.SendWith(ctx, channelID, zynko.SendOptions{ Cards: []zynko.Card{{ Title: "Placar", Fields: []zynko.CardField{{Name: "Ana", Value: "3", Inline: true}}, }}, Components: zynko.Components{{ {Style: zynko.ButtonPrimary, Label: "Mais um", CustomID: "mais"}, }}, }) ``` A interação: | | | |---|---| | `i.Type`, `i.IsCommand()`, `i.IsButton()` | `"command"` ou `"button"`. | | `i.Command` | O nome do comando. | | `i.String(nome)`, `i.Int(nome)`, `i.Float(nome)`, `i.Bool(nome)` | O valor de uma opção, e se ela veio. `user`, `channel` e `role` chegam como ids (use `String`). | | `i.CustomID`, `i.Message` | No clique: o `custom_id` do botão e a mensagem dele. | | `i.User`, `i.Member` | Quem usou, e como membro do servidor (`nil` numa DM). | | `i.ChannelID`, `i.GuildID` | Onde. | | `i.Reply(ctx, texto)`, `i.ReplyEphemeral(ctx, texto)`, `i.ReplyWith(ctx, ReplyOptions)` | Responde com uma mensagem; `Ephemeral: true` só para a pessoa. | | `i.Update(ctx, texto)`, `i.UpdateWith(ctx, EditOptions)` | Só em cliques: muda a mensagem do botão. | Em `EditOptions`, o que fica vazio não muda. Para tirar os botões, mande `Components: zynko.Components{}`. Nas opções de comando, `zynko.Limit(2)` preenche `MinValue` e `MaxValue`. Tudo sobre isso em [Comandos, cartões e botões](https://docs.zynko.org/bots/comandos.md). ## Permissões ```go if !client.Can(m.ChannelID, m.Author.ID, zynko.PermKickMembers) { _, err := m.Reply(ctx, "Você não pode remover pessoas.") return err } ``` `client.PermissionsIn(channelID, userID)` devolve todos os bits, calculados como o servidor calcula: `@everyone`, os cargos, as permissões do canal, ADM e dono. `zynko.ComputePermissions` faz a mesma conta com dados que você já tem. A lista dos bits está em [Permissões](https://docs.zynko.org/api/permissoes.md); em Go, `PermAdministrator`, `PermSendMessages`, `PermConnect`... ## API `client.Rest` tem um método para cada ação comum, e `Request` para o resto: ```go client.Rest.Kick(ctx, guildID, userID) client.Rest.AddRole(ctx, guildID, userID, roleID) msgs, err := client.Rest.Messages(ctx, channelID, zynko.MessagesQuery{Limit: 50}) client.Rest.Request(ctx, "PATCH", "/guilds/"+guildID+"/members/"+userID, map[string]any{"nick": "Novato"}, nil) ``` `Send`, `Upload`, `EditMessage`, `DeleteMessage`, `Messages`, `React`, `Unreact`, `Typing`, `Me`, `Profile`, `DM`, `Members`, `Kick`, `Ban`, `Unban`, `AddRole`, `RemoveRole`, `CreateRole`, `CreateChannel`, `EditChannel`, `DeleteChannel`, `CreateInvite`, `SetCommands`, `Commands`, `InteractionCallback` e `VoiceToken`. Todas as rotas estão em [Rotas](https://docs.zynko.org/api/rotas.md). Para usar só a API, sem gateway: `zynko.NewRest(zynko.RestOptions{Token: token})`. Erros da API viram `*zynko.Error`, com `Status`, `Code` e `Message`: ```go var zerr *zynko.Error if errors.As(err, &zerr) && zerr.Code == "forbidden" { /* ... */ } ``` Quando um limite de requisições estoura (`429`), a biblioteca espera o tempo pedido e tenta de novo, até 3 vezes. ## Voz ```go import "zynko.org/go/voice" conn, err := voice.Join(ctx, client, channelID, voice.Options{}) conn.Player.Enqueue(voice.URL("https://exemplo.com/musica.mp3"), nil) ``` O bot precisa de **Entrar em salas** e **Falar** na sala, e do intent `IntentVoiceStates` para saber onde as pessoas estão. Ele fica em uma sala por vez, como qualquer conta: `voice.Join` noutra sala sai da anterior, e `voice.Of(client)` é a conexão atual (ou `nil`). ```go voice.Join(ctx, client, channelID, voice.Options{ Receive: false, // ouvir a sala (OnOpus e OnAudio) Relay: false, // a conexão alternativa, pelo relay do servidor Deaf: false, // aparecer com o som desligado YTDLP: "", // passar links pelo yt-dlp antes ("yt-dlp", ou o caminho dele) FFmpeg: "ffmpeg", // caminho do ffmpeg Bitrate: 128000, // bits por segundo do Opus }) ``` | | | |---|---| | `conn.Player` | O tocador (abaixo). | | `conn.Leave(ctx)` | Sai da sala. | | `conn.ChannelID()`, `conn.Server()` | A sala e o servidor de voz dela (Brasil 1, Brasil 2...). | | `conn.CanSpeak()` | Se os cargos deixam o bot falar ali. | | `conn.OnReady(func())` | Conectou (de novo, depois de uma troca de servidor). | | `conn.OnSpeaking(func(ids []string))` | Quem está falando agora (muda quando alguém começa ou para). | | `conn.OnOpus(func(userID, pacote))` | Com `Receive`: cada pacote Opus de cada pessoa. | | `conn.OnAudio(func(userID, samples))` | Com `Receive`: o som de cada pessoa, decodificado. | | `conn.OnDisconnect(func(motivo))` | Saiu de vez: pediu, foi removido, a sala sumiu. | | `conn.Done()` | Fecha quando a conexão acaba. | Se alguém trocar o servidor da sala (Brasil 1 ↔ Brasil 2), o bot vai junto sozinho e a música continua de onde estava. > **Como o áudio sai, sem cgo** > > O tocador decodifica a entrada para PCM (48 kHz, estéreo, 16 bits) e manda quadros de 20 ms > no ritmo real. Um ffmpeg por conexão transforma esses quadros em Opus, e os pacotes vão para > o LiveKit pelo SDK em Go puro. Quando nada está tocando, ou está pausado, a conexão manda > silêncio. A espera entre o tocador e a sala fica em torno de 150 ms. ### Tocador ```go p := conn.Player p.Enqueue(voice.URL("https://radio.exemplo.com/stream"), "Rádio") // fila p.Play(voice.URL("https://exemplo.com/agora.mp3"), nil) // toca já p.Skip(); p.Pause(); p.Resume(); p.Stop() p.SetVolume(0.5) // 0 a 2 p.Current(); p.Queue(); p.Paused(); p.Volume() p.OnStart(func(t *voice.Track) {}) p.OnFinish(func(t *voice.Track) {}) p.OnError(func(err error, t *voice.Track) {}) p.OnIdle(func() {}) ``` O que dá para tocar: | entrada | | |---|---| | `voice.URL("https://...")` ou `voice.URL(caminho)` | Aberto pelo ffmpeg: mp3, ogg, wav, flac, rádios por stream... | | `voice.Stream(r)` | Áudio codificado vindo de um `io.Reader` (um download, um pipe); o ffmpeg decodifica. | | `voice.PCM(r)` | PCM cru: 48 kHz, estéreo, 16 bits little-endian. Não passa pelo ffmpeg. | | `voice.Tone(440, 3)` | Um tom de teste. | O segundo argumento de `Play` e `Enqueue` é o que você quiser (título, quem pediu) e volta em `track.Meta` nos eventos. Os eventos do tocador rodam em ordem, numa goroutine dele. > **Caminhos de arquivo** > > O tocador abre o que receber. Num bot público, aceite só links (`https://`) do chat, nunca > caminhos: senão qualquer pessoa toca arquivos da máquina do bot. ### Ouvir a sala ```go conn, err := voice.Join(ctx, client, channelID, voice.Options{Receive: true}) conn.OnAudio(func(userID string, samples []int16) { // 48 kHz, mono, em pedaços de 20 ms (960 amostras) }) ``` Com `Receive`, cada pessoa chega de dois jeitos: - `OnOpus`: os pacotes Opus como vêm do LiveKit (20 ms cada), sem custo extra. Bom para gravar (num arquivo Ogg, por exemplo) ou repassar. - `OnAudio`: o som já decodificado em PCM. Como o Go não tem decodificador Opus sem cgo, a biblioteca usa um ffmpeg por pessoa que fala. Registre o `OnAudio` antes de as pessoas falarem. Para gravar, transcrever ou medir volume. Avise as pessoas da sala: elas não sabem que um bot está ouvindo. ## Exemplos Na pasta [`examples`](https://github.com/ZynkoOrg/zynko-go/tree/main/examples) do repositório [ZynkoOrg/zynko-go](https://github.com/ZynkoOrg/zynko-go): - `ping`: responde `!ping` com `pong`; - `dado`: o comando `/dado` com um cartão e o botão "De novo"; - `musica`: um bot de música pequeno, com `/tocar ` (ou `!tocar`), fila e os botões Pausar, Pular e Parar. ```sh ZYNKO_TOKEN=bot_... go run ./examples/musica ``` O [bot de música](https://docs.zynko.org/bots/musica.md) completo, em JavaScript, mostra mais ideias: volume, ajuda e saída automática quando a sala esvazia. # Biblioteca para Rust `zynko` é a biblioteca oficial para bots do Zynko em Rust. Ela conecta no gateway e se reconecta sozinha, mantém servidores, canais e membros atualizados, transforma os eventos em chamadas de um `EventHandler` e cobre a API. Com a feature `voice`, o bot entra em salas, toca áudio e ouve quem fala. Usa [tokio](https://tokio.rs) e o Rust estável (testada com o 1.98). ## Instalação ```sh cargo add zynko cargo add tokio --features full # para voz, em vez da primeira linha: cargo add zynko --features voice ``` Para tocar arquivos e links, o bot usa o [ffmpeg](https://ffmpeg.org), que precisa estar instalado na máquina (ou indicado em `FFMPEG_PATH`). A voz usa o [SDK oficial do LiveKit](https://github.com/livekit/rust-sdks), que baixa uma libwebrtc pronta na primeira compilação (uns 500 MB descompactados, alguns minutos). Sem a feature `voice`, nada disso entra. > **Voz no Windows** > > A libwebrtc do LiveKit usa o runtime estático do C. No Windows, crie `.cargo/config.toml` no > seu projeto com: > > ```toml > [target.x86_64-pc-windows-msvc] > rustflags = ["-C", "target-feature=+crt-static"] > ``` > > Sem isso, a compilação para no link com o erro `LNK2038` (`RuntimeLibrary`). ## Primeiro bot ```rust use zynko::{async_trait, Client, EventHandler, HandlerResult, Intents, Message, User}; struct Bot; #[async_trait] impl EventHandler for Bot { async fn ready(&self, ctx: Client, me: User) -> HandlerResult { println!("{} conectado em {} servidores", me.display_name, ctx.cache().guilds.len()); Ok(()) } async fn message_create(&self, _ctx: Client, msg: Message) -> HandlerResult { if msg.mine() { return Ok(()); } if msg.content == "!ping" { msg.reply("pong").await?; } Ok(()) } } #[tokio::main] async fn main() -> zynko::Result<()> { let client = Client::builder(std::env::var("ZYNKO_TOKEN").expect("ZYNKO_TOKEN")) .intents(Intents::GUILDS | Intents::MESSAGES) .event_handler(Bot) .build()?; client.start().await } ``` ```sh ZYNKO_TOKEN=bot_... cargo run ``` `start()` conecta e roda até `destroy()` (ou até o token ser recusado). Para fazer algo depois de conectar, use `login()`, que devolve a conta do bot quando o `READY` chega, e depois `closed()`, que espera o cliente parar. ## O cliente ```rust Client::builder(token) // o token do bot (dev.zynko.org) .intents(intents) // o que receber; padrão: GUILDS | MESSAGES .api("https://...") // outro servidor; padrão https://zynko.org/api .event_handler(Bot) // quem trata os eventos .build()? ``` | | | |---|---| | `client.login().await` | Conecta. Devolve a conta do bot quando o `READY` chega. | | `client.start().await` | `login` e depois espera até o cliente parar. | | `client.destroy().await` | Sai da voz e fecha a conexão. | | `client.user()` | A conta do bot. | | `client.guild(id)`, `client.channel(id)` | Um servidor ou canal, como estão agora. | | `client.cache()` | Tudo o que o cliente sabe: `guilds`, `channels`, `users`, `voice_states`. | | `client.rest()` | A API inteira (veja [API](#api)). | `client.cache()` devolve uma trava de leitura com `HashMap`s: `guilds` (cada servidor com `channels`, `members`, `roles`, `emojis` e `online`), `channels` (todos os canais que o bot vê, DMs incluídas), `users` (as contas que o bot já viu) e `voice_states` (quem está em voz, por id da conta; precisa de `VOICE_STATES`). Leia e solte: não segure a trava através de um `.await`. ```rust let nomes: Vec = client.cache().guilds.values().map(|g| g.name.clone()).collect(); ``` Os mapas se atualizam sozinhos com os eventos. Se a conexão cair, o cliente reconecta (esperando 1 s, 2 s, 4 s... até 30 s) e reconstrói tudo com o `READY` novo. O `Client` é barato de clonar: todos os clones são o mesmo bot. ### Intents ```rust use zynko::Intents; Intents::GUILDS | Intents::MESSAGES | Intents::VOICE_STATES ``` `GUILDS`, `GUILD_MEMBERS`, `PRESENCES`, `MESSAGES`, `REACTIONS`, `TYPING`, `VOICE_STATES` e `ALL`. Peça só o que o bot usa. Detalhes em [Gateway](https://docs.zynko.org/api/gateway.md#intents). ## Eventos Implemente no seu `EventHandler` só os métodos que o bot usa; os outros não fazem nada. Todos recebem o `Client` (`ctx`) primeiro. | método | argumentos | quando | |---|---|---| | `ready` | `User` | Conectou (de novo). | | `message_create` | `Message` | Mensagem nova. | | `message_update` | `Message` | Mensagem editada (ou prévia de link pronta). Só vêm os campos que mudaram. | | `message_delete` | `MessageDeleteEvent { id, channel_id }` | Mensagem apagada. | | `reaction_add`, `reaction_remove` | `ReactionEvent { message_id, channel_id, user_id, emoji }` | Reação. | | `typing_start` | `TypingEvent { channel_id, user_id, timestamp }` | Alguém digitando. | | `guild_create` | `CachedGuild` | Bot adicionado a um servidor (e cada servidor do `READY`). | | `guild_update` | `CachedGuild` | Nome, ícone ou dono mudou. | | `guild_delete` | id | Bot saiu ou o servidor foi apagado. | | `channel_create`, `channel_delete` | `Channel` | Canal novo ou apagado (uma DM nova é um canal novo). | | `channel_update` | `Channel`, anterior | Canal mudou. | | `member_add` | `Member` | Alguém entrou num servidor. | | `member_update` | `Member`, anterior | Apelido ou cargos mudaram. | | `member_remove` | `MemberRemoveEvent { guild_id, user_id }` | Alguém saiu. | | `presence_update` | `PresenceEvent { user_id, status }` | Ficou online ou offline. | | `voice_state_update` | `VoiceState`, anterior | Alguém entrou, saiu ou mudou na voz. | | `interaction` | `Interaction` | Alguém usou um comando do bot ou clicou num botão dele. | | `raw` | tipo, dados (`serde_json::Value`) | Todo evento do gateway, como chegou. | | `reconnecting` | `ReconnectInfo { code, reason, wait }` | A conexão caiu e vai voltar. | | `error` | `Arc` | Erro em outro método, ou o token foi recusado. Por padrão, vai para o stderr. | Cada chamada roda na sua própria tarefa. Os métodos devolvem `HandlerResult`, então `?` funciona com qualquer erro: um `Err` (ou um `panic!`) vira uma chamada a `error`, sem derrubar o bot. Também dá para receber os eventos num laço, sem `EventHandler`, com `client.subscribe()` e o enum `Event`. E `client.wait_for(tempo, filtro)` espera o próximo evento que passar no filtro: ```rust use std::time::Duration; msg.reply("Qual o seu nome?").await?; let autor = msg.author.id.clone(); let resposta = ctx .wait_for_message(Duration::from_secs(30), |m| m.author.id == autor) .await?; ``` Se o tempo acabar, vem `Err(Error::Timeout(..))`. ## Mensagens `message_create` e `message_update` entregam um `Message`: | | | |---|---| | `id`, `channel_id`, `author`, `content`, `timestamp`, `attachments`, `reactions`, `embeds`, `reply_to` | Os dados da mensagem. | | `guild_id()` | O servidor, ou `None` numa DM. | | `channel()` | O `Channel`, se o bot já o viu. | | `member()` | O autor como membro do servidor (apelido, cargos). | | `mine()` | Se foi o próprio bot que mandou. | | `reply(texto ou SendOptions)` | Responde no mesmo canal, como resposta a ela. | | `react(emoji)`, `unreact(emoji)` | Reage ou tira a reação. | | `edit(texto ou EditOptions)`, `delete()` | O bot edita as próprias mensagens; apaga as próprias ou, com **Gerenciar mensagens**, as dos outros. | Para mandar em qualquer canal, ou com arquivos: ```rust use zynko::{FileInput, SendOptions}; client.send(&channel_id, "Olá!").await?; client .send( &channel_id, SendOptions::new("O relatório de hoje").file(FileInput::from_path("relatorio.pdf").await?), ) .await?; client.send_dm(&user_id, "Oi! Isso é uma mensagem direta.").await?; ``` Onde a biblioteca pede `impl Into`, um `&str` ou `String` serve. `FileInput::new(nome, bytes)` manda bytes que já estão na memória. ## Comandos, cartões e botões ```rust use zynko::{Button, Card, Command, ReplyOptions, SendOptions}; client.set_commands(&[Command::new("ping", "Responde pong")]).await?; // no EventHandler: async fn interaction(&self, _ctx: Client, i: Interaction) -> HandlerResult { if i.command.as_deref() == Some("ping") { i.reply(ReplyOptions::new("pong").ephemeral()).await?; } Ok(()) } client .send( &channel_id, SendOptions::default() .card(Card::new().title("Placar").field("Ana", "3", true)) .row(vec![Button::primary("Mais um", "mais")]), ) .await?; ``` Na interação, `i.option_str("nome")`, `i.option_i64(..)`, `i.option_f64(..)` e `i.option_bool(..)` leem as opções (`None` se ficou vazia; `user`, `channel` e `role` chegam como ids, em `option_str`). Num clique, `i.is_button()` é verdadeiro, `i.custom_id` traz o botão e `i.update(EditOptions::new().content(..).components(vec![]))` muda a mensagem clicada. Tudo sobre isso em [Comandos, cartões e botões](https://docs.zynko.org/bots/comandos.md). Os exemplos de lá estão em TypeScript; em Rust os campos têm os mesmos nomes, e `Command`, `CommandOption`, `Card` e `Button` têm construtores (`Command::new(..).option(..)`, `Card::new().title(..)`, `Button::primary(rótulo, custom_id)`, `Button::link(rótulo, url)`). O exemplo [`dado`](https://docs.rs/crate/zynko/latest/source/examples/dado.rs) faz o `/dado` daquela página. ## Permissões ```rust use zynko::Permissions; if !ctx.can(&msg.channel_id, &msg.author.id, Permissions::KICK_MEMBERS) { msg.reply("Você não pode remover pessoas.").await?; return Ok(()); } ``` `client.permissions_in(channel_id, user_id)` devolve todos os bits (`u64`), calculados como o servidor calcula: `@everyone`, os cargos, as permissões do canal, ADM e dono. A lista dos bits está em [Permissões](https://docs.zynko.org/api/permissoes.md). ## API `client.rest()` tem um método para cada ação comum, e `request` para o resto: ```rust use serde_json::{json, Value}; use zynko::{Method, MessagesQuery}; let rest = client.rest(); rest.kick(&guild_id, &user_id).await?; rest.add_role(&guild_id, &user_id, &role_id).await?; let msgs = rest.messages(&channel_id, MessagesQuery { limit: Some(50), ..Default::default() }).await?; rest.request::(Method::PATCH, &format!("/guilds/{guild_id}/members/{user_id}"), Some(json!({ "nick": "Novato" }))) .await?; ``` `send`, `upload`, `edit_message`, `delete_message`, `messages`, `react`, `unreact`, `typing`, `me`, `profile`, `dm`, `members`, `kick`, `ban`, `unban`, `add_role`, `remove_role`, `create_role`, `create_channel`, `edit_channel`, `delete_channel`, `create_invite`, `set_commands`, `commands`, `interaction_callback` e `voice_token`. Todas as rotas estão em [Rotas](https://docs.zynko.org/api/rotas.md). Erros da API viram `Error::Api { status, code, message }` (também em `err.status()` e `err.code()`). Quando um limite de requisições estoura (`429`), a biblioteca espera o tempo pedido e tenta de novo, até 3 vezes (`.retries(n)` no builder muda isso). ## Voz Precisa da feature `voice`. ```rust use zynko::voice::JoinVoiceOptions; let voice = client.join_voice(&channel_id, JoinVoiceOptions::default()).await?; voice.player().enqueue("https://exemplo.com/musica.mp3", serde_json::Value::Null); ``` O bot precisa de **Entrar em salas** e **Falar** na sala, e do intent `VOICE_STATES` para saber onde as pessoas estão. Ele fica em uma sala por vez, como qualquer conta. ```rust JoinVoiceOptions { receive: false, // ouvir a sala (eventos Audio) relay: false, // a conexão alternativa, pelo relay do servidor deaf: false, // aparecer com o som desligado player: PlayerOptions { ffmpeg: None, // caminho do ffmpeg; padrão FFMPEG_PATH ou "ffmpeg" ytdlp: None, // Some("yt-dlp"): passar links pelo yt-dlp antes }, } ``` | | | |---|---| | `voice.player()` | O tocador (abaixo). | | `voice.leave().await` | Sai da sala. | | `voice.channel_id()`, `voice.server()` | A sala e o servidor de voz dela (Brasil 1, Brasil 2...). | | `voice.can_speak()` | Se os cargos deixam o bot falar ali. | | `voice.status()` | `Connecting`, `Connected`, `Reconnecting` ou `Closed`. | | `voice.events()` | Os eventos: `Ready`, `Speaking(ids)`, `Audio { .. }`, `Disconnect(motivo)`, `Error`. | | `client.voice()` | A conexão de voz atual, se houver. | ```rust use zynko::VoiceEvent; let mut eventos = voice.events(); tokio::spawn(async move { while let Ok(ev) = eventos.recv().await { match ev { VoiceEvent::Speaking(ids) => println!("falando: {ids:?}"), VoiceEvent::Disconnect(motivo) => println!("saiu: {motivo}"), _ => {} } } }); ``` Se alguém trocar o servidor da sala (Brasil 1 ↔ Brasil 2), o bot vai junto sozinho e a música continua de onde estava. ### Tocador ```rust use serde_json::json; use zynko::{tone, PlayerEvent}; let p = voice.player(); p.enqueue("https://radio.exemplo.com/stream", json!({ "title": "Rádio" })); // fila p.play("https://exemplo.com/agora.mp3", json!(null)); // toca já p.skip(); p.pause(); p.resume(); p.stop(); p.set_volume(0.5); // 0 a 2 p.current(); p.queue(); p.volume(); p.is_paused(); let mut eventos = p.events(); while let Ok(ev) = eventos.recv().await { match ev { PlayerEvent::Start(track) => println!("tocando {}", track.meta["title"]), PlayerEvent::Finish(track) => {} PlayerEvent::Error { error, track } => eprintln!("{error}"), PlayerEvent::Idle => {} // a fila acabou } } ``` O que dá para tocar (`AudioInput`): | entrada | | |---|---| | `"https://..."` ou um caminho | Aberto pelo ffmpeg: mp3, ogg, wav, flac, rádios por stream... | | `AudioInput::Stream(leitor)` | Áudio codificado vindo de qualquer `AsyncRead` (um download, um pipe); o ffmpeg decodifica. | | `AudioInput::Pcm(leitor)` | PCM cru: 48 kHz, estéreo, 16 bits little-endian. Não passa pelo ffmpeg. | | `tone(440.0, 3.0)` | Um tom de teste (frequência, segundos). | O segundo argumento de `play` e `enqueue` é um `serde_json::Value` com o que você quiser (título, quem pediu) e volta em `track.meta` nos eventos. O áudio sai em quadros de 20 ms, no ritmo real. > **Caminhos de arquivo** > > O tocador abre o que receber. Num bot público, aceite só links (`https://`) do chat, nunca > caminhos: senão qualquer pessoa toca arquivos da máquina do bot. ### Ouvir a sala ```rust let voice = client .join_voice(&channel_id, JoinVoiceOptions { receive: true, ..Default::default() }) .await?; let mut eventos = voice.events(); while let Ok(ev) = eventos.recv().await { if let VoiceEvent::Audio { user_id, samples, channels } = ev { // samples: Arc<[i16]>, 48 kHz, mono, em pedaços de 10 ms } } ``` Para gravar, transcrever ou medir volume. Avise as pessoas da sala: elas não sabem que um bot está ouvindo. ## Exemplo completo O exemplo [`musica`](https://docs.rs/crate/zynko/latest/source/examples/musica.rs) é um bot de música pequeno: `/tocar ` (ou `!tocar `), fila, e um cartão "Tocando agora" com os botões Pausar, Pular e Parar. ```sh ZYNKO_TOKEN=bot_... cargo run --example musica --features voice ``` O [bot de música](https://docs.zynko.org/bots/musica.md) completo, em TypeScript, mostra o resto: volume e saída automática quando a sala esvazia. # Bot de música Um bot que entra na sala de voz de quem chamou e toca links, com fila. O código completo está no repositório do Zynko, em `bots/musica`, feito com a [biblioteca para JavaScript](https://docs.zynko.org/bots/javascript.md). ## Comandos Os comandos aparecem ao digitar `/` no app (`/tocar`, `/fila`...) e também funcionam com `!` no lugar da barra. Ao começar cada música, o bot manda um cartão "Tocando agora" com o título, quem pediu, o volume, as próximas da fila e os botões **Pausar**, **Pular** e **Parar**; o cartão se atualiza a cada clique. As respostas a comandos com `/` aparecem só para quem usou. | comando | | |---|---| | `/tocar link` ou `!tocar ` | Toca, ou põe na fila se já estiver tocando. | | `!pular` | Vai para a próxima da fila. | | `!parar` | Para e esvazia a fila. | | `!pausar`, `!continuar` | | | `!fila` | O que está tocando e as próximas. | | `!tocando` | O que está tocando. | | `!volume <0-200>` | Volume em porcentagem; sem número, mostra o atual. | | `!sair` | Sai da sala. | | `!teste` | Toca um tom de 3 segundos. | | `!ajuda` | A lista. | O bot entra na sala de quem mandou o comando e sai sozinho quando fica um minuto sem ninguém. ## Que links ele toca Qualquer coisa que o ffmpeg abra: links diretos de mp3, ogg, wav, flac e rádios por stream. Com `YTDLP=1`, os links passam antes pelo [yt-dlp](https://github.com/yt-dlp/yt-dlp), que entende páginas de muitos sites, e o título vem do próprio site. Respeite os termos de uso dos sites e os direitos de quem fez a música. ## Rodar 1. Crie o bot em [dev.zynko.org](https://dev.zynko.org). No convite, marque **Ver canais**, **Enviar mensagens**, **Ler o histórico**, **Adicionar reações**, **Entrar em salas** e **Falar**, e adicione ao seu servidor. 2. Instale o Node.js 22 e o ffmpeg. 3. No `bots/musica`: ```sh npm install ZYNKO_TOKEN=bot_... npm start ``` | variável | | |---|---| | `ZYNKO_TOKEN` | O token do bot. | | `PREFIX` | O começo dos comandos. Padrão: `!`. | | `YTDLP` | `1` para usar o yt-dlp do PATH, ou o caminho dele. | | `FFMPEG_PATH` | O ffmpeg, se não estiver no PATH. | ### Com Docker O `Dockerfile` da pasta já traz o Node e o ffmpeg: ```sh docker build -t zynko-musica -f bots/musica/Dockerfile . docker run -d --restart unless-stopped -e ZYNKO_TOKEN=bot_... zynko-musica ``` O build parte da raiz do repositório, porque usa a biblioteca de `sdk/js`. # Visão geral da API A API do Zynko é a mesma que os apps usam: HTTP com JSON para ler e mudar as coisas, e um [gateway](https://docs.zynko.org/api/gateway.md) WebSocket para receber o que acontece em tempo real. Um bot usa as duas com o token dele. | | | |---|---| | **Base da API** | `https://zynko.org/api` | | **Gateway** | `wss://zynko.org/api/gateway` | | **Formato** | JSON (UTF-8), exceto envio de arquivos (`multipart/form-data`) | Todas as rotas desta referência são relativas à base. `GET /users/@me` quer dizer `GET https://zynko.org/api/users/@me`. ## Autenticação Mande o token no cabeçalho `Authorization`: ```http GET /api/users/@me HTTP/1.1 Host: zynko.org Authorization: Bot bot_3f9a...e1 ``` O servidor aceita os prefixos `Bot ` e `Bearer ` do mesmo jeito. Tokens de bot começam com `bot_` seguido de 64 dígitos hexadecimais e **não expiram**: valem até o dono gerar um novo (`POST /users/@me/bots/{bot_id}/token`) ou apagar o bot. Quando isso acontece, as conexões abertas no gateway com o token antigo caem com o código `4000`. Sem token, ou com um token inválido, as rotas respondem `401 unauthorized`. Algumas coisas são só para pessoas (criar servidor, entrar por convite, criar bots, fazer amizade); nessas um bot recebe `403` ou `400`. A [lista de rotas](https://docs.zynko.org/api/rotas.md) marca cada uma. ## IDs Todo id (usuário, servidor, canal, mensagem, cargo, emoji, anexo) é um *snowflake* de 64 bits e **viaja como string** no JSON, porque não cabe num `number` do JavaScript sem perder precisão: ```json { "id": "1234567890123456789", "channel_id": "1234567890123456000" } ``` No corpo das requisições o servidor aceita ids como string ou como número, mas mande string. Nos caminhos (`/channels/{channel_id}`) eles vão como número decimal. Um snowflake guarda o momento em que foi criado: os 42 bits de cima são milissegundos desde **2026-01-01T00:00:00Z** (`1767225600000` em Unix ms), e os 22 de baixo são uma sequência. Ordenar por id é ordenar por criação. ```js const EPOCH = 1767225600000n; const criadoEm = (id) => new Date(Number((BigInt(id) >> 22n) + EPOCH)); criadoEm(msg.id); // Date de quando a mensagem foi enviada ``` O contrário também funciona: `(BigInt(ms) - EPOCH) << 22n` é o menor id possível naquele instante, útil para paginar mensagens a partir de uma data (`?after=`). ## Datas e outros valores - Datas são **números**: milissegundos desde a época Unix (`timestamp`, `edited_timestamp`, `joined_at`, `expires_at`, `since`, `created_at`). - Permissões são **números** (bits; cabem em 18 bits). Veja [Permissões](https://docs.zynko.org/api/permissoes.md). - Cores de cargo são inteiros `0xRRGGBB` ou `null`; a cor de destaque do perfil é uma string `"#rrggbb"`. - Rotas que só confirmam algo respondem `{ "ok": true }`. ## Erros Os erros da API têm sempre o mesmo formato: ```json { "code": "forbidden", "message": "Você não tem permissão para isso" } ``` `message` é uma frase em português pronta para mostrar a uma pessoa; programe contra `code` e o status HTTP. | Status | `code` | Quando | |---|---|---| | 400 | `bad_request` | Dado inválido ou ação impossível (nome longo demais, mensagem vazia, canal errado...). | | 401 | `unauthorized` | Sem token, ou token inválido. | | 403 | `forbidden` | Falta permissão, ou a rota é só para pessoas. | | 403 | `email_unverified` | Login de uma conta com e-mail não confirmado. Não acontece com bots. | | 404 | `not_found` | Não existe **ou você não tem acesso**. | | 409 | `conflict` | Nome já em uso (por exemplo o `username` de um bot novo). | | 413 | `too_large` | Arquivo grande demais. | | 429 | `rate_limited` | Limite de requisições estourado. | | 429 | `slow_mode` | O modo lento do canal ainda não deixou mandar de novo. | | 500 | `internal` | Erro do servidor. | | 503 | `unavailable` | Recurso não configurado neste servidor (arquivos, voz). | > **404 em vez de 403** > > Para que não dê para sondar ids, um servidor de que você não é membro, ou um canal que você > não pode ver, responde `404`, como se não existisse. `403` aparece quando você **vê** a > coisa mas não pode fazer aquilo com ela. Erros que o servidor nem chega a processar não seguem esse formato: um JSON malformado, um corpo sem `Content-Type: application/json` ou um id inválido no caminho voltam com `400`, `415` ou `422` e uma mensagem em texto puro. ### Limites de requisição (429) Quando um limite estoura, a resposta é `429` com o tempo de espera em milissegundos no corpo e em segundos no cabeçalho `Retry-After`: ```http HTTP/1.1 429 Too Many Requests Retry-After: 4 { "code": "rate_limited", "message": "Calma! Você está indo rápido demais", "retry_after_ms": 3712 } ``` O modo lento de um canal responde do mesmo jeito, com `code: "slow_mode"`. Os limites são contados por conta (o bot inteiro, não por canal), em janelas fixas. Os que importam para um bot: | Ação | Rota | Limite | |---|---|---| | Enviar mensagem | `POST /channels/{id}/messages` | 10 a cada 10 s | | Editar mensagem | `PATCH /channels/{id}/messages/{id}` | 10 a cada 10 s | | Reagir | `PUT .../reactions/{emoji}` | 20 a cada 10 s | | Digitando | `POST /channels/{id}/typing` | 10 a cada 10 s | | Enviar anexo | `POST /channels/{id}/attachments` | 30 por minuto | | Enviar imagem (avatar, ícone, emoji) | `POST /media` | 10 por minuto | | Criar postagem em fórum | `POST /channels/{id}/posts` | 10 por minuto | | Editar perfil | `PATCH /users/@me` | 20 por minuto | | Criar convite | `POST /guilds/{id}/invites` | 20 por hora | | Entrar numa sala de voz | `POST /channels/{id}/voice` | 20 por minuto | | Trocar o servidor de voz da sala | `PUT /channels/{id}/voice/server` | 10 por minuto | Apagar mensagens, tirar reações e as rotas de gerenciamento não têm limite próprio. Além disso, cada canal pode ter **modo lento** (`rate_limit_per_user`, em segundos): depois de mandar uma mensagem (ou abrir uma postagem), a mesma conta espera esse tempo antes da próxima. Quem tem **Gerenciar mensagens** ou **Gerenciar canais** no canal não espera. ## Imagens e arquivos Os campos de imagem (`avatar`, `banner`, `icon`, `url` de emoji) guardam um destes: - um caminho **relativo** do próprio Zynko, `/api/media/{id}/{segredo}`: público, sem autenticação, com cache longo. Para usar, junte com a origem: `https://zynko.org/api/media/...`; - uma URL **absoluta** de GIF do KLIPY (`https://static.klipy.com/...`), usada como está. ```js const urlDaImagem = (v) => (v?.startsWith('/') ? 'https://zynko.org' + v : v); ``` Para trocar uma imagem, envie o arquivo em `POST /media` (PNG, JPEG, GIF, WebP ou AVIF, até 16 MB), pegue o `id` que volta e mande-o como `avatar_id`, `banner_id`, `icon_id` ou `image_id` na rota que usa a imagem. Uploads que não forem usados somem em um dia. **Anexos de mensagens** são outra coisa. Cada canal tem sua chave e os arquivos ficam cifrados; o `url` de um anexo é `/api/attachments/{id}` e só quem pode ver o canal consegue baixar, mandando o token. Qualquer outra pessoa recebe `404`. Os downloads aceitam `Range`. Tipos que o navegador pode exibir (imagens, vídeo e áudio comuns) vêm com o `Content-Type` original; o resto vem como `application/octet-stream`. - Vídeos podem ter uma miniatura em `/api/attachments/{id}/thumb` (campo `thumbnail`). - Emojis do servidor também estão em `/api/emojis/{id}`, que redireciona para a imagem, sem autenticação. - Imagens de prévias de links (`embeds[].image.url`) passam por `/api/embeds/image?...`. Veja [Arquivos](https://docs.zynko.org/api/rotas.md#arquivos) para os detalhes do envio. ## Texto das mensagens O conteúdo é texto com marcação no estilo do Discord. Menções e emojis do servidor ficam dentro do texto: | Escreva | Para | |---|---| | `<@ID_DO_USUARIO>` | mencionar alguém (a pessoa recebe notificação) | | `<:nome:ID_DO_EMOJI>` | um emoji do servidor | | `` | um emoji animado do servidor | Não há lista separada de menções na mensagem: procure `<@id>` no `content`. ## Próximos passos - [Gateway](https://docs.zynko.org/api/gateway.md): conectar e receber eventos. - [Eventos](https://docs.zynko.org/api/eventos.md): tudo o que chega pelo gateway. - [Rotas](https://docs.zynko.org/api/rotas.md): todas as rotas que um bot pode usar. - [Objetos](https://docs.zynko.org/api/objetos.md): os formatos JSON. - [Permissões](https://docs.zynko.org/api/permissoes.md): os bits, os cargos e os canais. # 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](https://docs.zynko.org/api/rotas.md) 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": { "...": "..." } } ``` | campo | descrição | |---|---| | `op` | O opcode (abaixo). | | `t` | Nome do evento. Só em `op` 0. | | `d` | Os dados. Pode faltar em alguns opcodes. | Não há número de sequência (`s`). ## Opcodes | op | nome | direção | o que é | |---|---|---|---| | `0` | DISPATCH | servidor → bot | Um [evento](https://docs.zynko.org/api/eventos.md). `t` diz qual. | | `1` | HEARTBEAT | bot → servidor | "Ainda estou aqui." `d` é ignorado. | | `2` | IDENTIFY | bot → servidor | Autentica a conexão. | | `4` | VOICE_STATE | bot → servidor | Entra, sai ou muda o estado numa sala de voz. | | `10` | HELLO | servidor → bot | Primeira mensagem, com o intervalo de heartbeat. | | `11` | HEARTBEAT_ACK | servidor → bot | Resposta 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 } } ``` | campo | descrição | |---|---| | `token` | O token do bot, **puro** (sem `Bot ` na frente). | | `intents` | Opcional. Os tipos de evento que o bot quer, somados (veja [Intents](#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](#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á. | intent | bit | decimal | eventos | |---|---|---|---| | `GUILDS` | `1 << 0` | 1 | `GUILD_UPDATE`, `GUILD_ROLE_CREATE`, `GUILD_ROLE_UPDATE`, `GUILD_ROLE_DELETE`, `GUILD_EMOJIS_UPDATE`, `CHANNEL_UPDATE`, `CHANNEL_DELETE` | | `GUILD_MEMBERS` | `1 << 1` | 2 | `GUILD_MEMBER_ADD`, `GUILD_MEMBER_UPDATE`, `GUILD_MEMBER_REMOVE` | | `PRESENCES` | `1 << 2` | 4 | `PRESENCE_UPDATE` | | `MESSAGES` | `1 << 3` | 8 | `MESSAGE_CREATE`, `MESSAGE_UPDATE`, `MESSAGE_DELETE`, `MESSAGE_ACK` (em servidores e DMs) | | `REACTIONS` | `1 << 4` | 16 | `MESSAGE_REACTION_ADD`, `MESSAGE_REACTION_REMOVE` | | `TYPING` | `1 << 5` | 32 | `TYPING_START` | | `VOICE_STATES` | `1 << 6` | 64 | `VOICE_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: | campo | tipo | descrição | |---|---|---| | `user` | [PublicUser](https://docs.zynko.org/api/objetos.md#usuario-publicuser) | A própria conta (o bot). | | `guilds` | [GuildFull](https://docs.zynko.org/api/objetos.md#servidor-completo-guildfull)[] | Os servidores, com canais visíveis, membros, cargos, emojis e quem está online. | | `dm_channels` | [Channel](https://docs.zynko.org/api/objetos.md#canal-channel)[] | As DMs, com `recipients`, das mais recentes para as mais antigas. | | `read_states` | [ReadState](https://docs.zynko.org/api/objetos.md#estado-de-leitura-readstate)[] | Até onde a conta leu cada canal. | | `dm_unread` | objeto | `{ "": n }`: quantas mensagens não lidas há em cada DM. | | `presences` | string[] | Ids das contas relacionadas (servidores em comum, DMs, amigos) que estão online. | | `relationships` | [Relationship](https://docs.zynko.org/api/objetos.md#relacao-relationship)[] | Amigos, pedidos e bloqueios. | | `voice_states` | [VoiceState](https://docs.zynko.org/api/objetos.md#estado-de-voz-voicestate)[] | Quem está nas salas de voz que a conta vê, e em chamadas nas DMs dela. | | `session_id` | string | Id 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=...`). Trate também a conexão que não chega a abrir (o servidor reiniciando, a rede fora). No Node.js, o `WebSocket` avisa esse caso só com o evento `error`, sem `close`: um bot que espera o `close` para tentar de novo para na primeira tentativa que falha e fica offline até ser reiniciado. 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ódigo | motivo | o que fazer | |---|---|---| | `4000` | Sessã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. | | `4003` | Não autenticado: IDENTIFY não chegou em 15 s, frame inválido, ou outro opcode antes do IDENTIFY. | Corrigir e reconectar. | | `4004` | Falha de autenticação: token inválido. | **Não** reconectar com o mesmo token. | | `1011` | Erro 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`](https://docs.zynko.org/api/rotas.md#entrar-numa-sala), 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 } } ``` | campo | tipo | descrição | |---|---|---| | `channel_id` | id? | A sala de voz (ou DM). `null` (ou ausente) = sair da voz. | | `self_mute` | boolean | Microfone desligado. Padrão `false`. | | `self_deaf` | boolean | Som desligado. Padrão `false`. | | `self_stream` | boolean | Compartilhando a tela. Padrão `false`. | | `self_video` | boolean | Câ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}`); } }); // Caiu, ou nem chegou a abrir: tente de novo, uma vez só por conexão. let ended = false; const retry = (code) => { if (ended) return; ended = true; clearInterval(heartbeat); if (code === 4004) { console.error('Token inválido.'); return; } console.log(`Conexão fechada (${code ?? 'não abriu'}); reconectando em 5 s`); setTimeout(connect, 5000); }; ws.addEventListener('close', (ev) => retry(ev.code)); // No Node.js, a conexão que não abre avisa só com `error`, sem `close`. ws.addEventListener('error', () => retry()); } connect(); ``` Para responder, chame a API com o mesmo token; veja [Seu primeiro bot](https://docs.zynko.org/bots/primeiro-bot.md). # Eventos Tudo o que o [gateway](https://docs.zynko.org/api/gateway.md) envia com `op: 0`. O nome do evento vem em `t` e os dados em `d`: ```json { "op": 0, "t": "MESSAGE_DELETE", "d": { "id": "1234567890123456789", "channel_id": "1234567890123450000" } } ``` Cada evento chega só às conexões que pediram o [intent](https://docs.zynko.org/api/gateway.md#intents) dele, e só às contas que podem vê-lo. "Quem vê o canal" quer dizer: todos os membros do servidor, se o canal é aberto a todos; senão, só quem tem **Ver canais** nele (veja [Permissões de canal](https://docs.zynko.org/api/permissoes.md#permissoes-de-canal)); numa DM, as duas contas. Os eventos de um mesmo canal chegam na ordem em que aconteceram. Trate-os de forma idempotente: um evento que já está refletido no `READY` pode chegar logo depois dele. ## Resumo | evento | intent | quando | |---|---|---| | [`READY`](#ready) | sempre | Depois do IDENTIFY. | | [`GUILD_CREATE`](#guild-create) | sempre | A conta entrou num servidor. | | [`GUILD_UPDATE`](#guild-update) | `GUILDS` | Nome ou ícone do servidor mudou. | | [`GUILD_DELETE`](#guild-delete) | sempre | O servidor foi apagado, ou a conta saiu dele. | | [`GUILD_ROLE_CREATE`](#guild-role-create) | `GUILDS` | Cargo criado. | | [`GUILD_ROLE_UPDATE`](#guild-role-update) | `GUILDS` | Cargo editado ou mudou de posição. | | [`GUILD_ROLE_DELETE`](#guild-role-delete) | `GUILDS` | Cargo apagado. | | [`GUILD_EMOJIS_UPDATE`](#guild-emojis-update) | `GUILDS` | Emoji criado, renomeado ou apagado. | | [`CHANNEL_CREATE`](#channel-create) | sempre | Canal, postagem ou DM nova; ou a conta passou a ver um canal. | | [`CHANNEL_UPDATE`](#channel-update) | `GUILDS` | Canal editado. | | [`CHANNEL_DELETE`](#channel-delete) | `GUILDS` | Canal apagado; ou a conta deixou de vê-lo. | | [`GUILD_MEMBER_ADD`](#guild-member-add) | `GUILD_MEMBERS` | Alguém entrou no servidor. | | [`GUILD_MEMBER_UPDATE`](#guild-member-update) | `GUILD_MEMBERS` | Apelido, ADM ou cargos de um membro mudaram. | | [`GUILD_MEMBER_REMOVE`](#guild-member-remove) | `GUILD_MEMBERS` | Alguém saiu, foi removido ou banido. | | [`PRESENCE_UPDATE`](#presence-update) | `PRESENCES` | Alguém ficou online ou offline. | | [`USER_UPDATE`](#user-update) | sempre | Alguém mudou o perfil. | | [`MESSAGE_CREATE`](#message-create) | `MESSAGES` | Mensagem nova. | | [`MESSAGE_UPDATE`](#message-update) | `MESSAGES` | Mensagem editada, ou prévias de links prontas. | | [`MESSAGE_DELETE`](#message-delete) | `MESSAGES` | Mensagem apagada. | | [`MESSAGE_ACK`](#message-ack) | `MESSAGES` | A própria conta marcou um canal como lido. | | [`MESSAGE_REACTION_ADD`](#message-reaction-add) | `REACTIONS` | Reação adicionada. | | [`MESSAGE_REACTION_REMOVE`](#message-reaction-remove) | `REACTIONS` | Reação tirada. | | [`TYPING_START`](#typing-start) | `TYPING` | Alguém está digitando. | | [`VOICE_STATE_UPDATE`](#voice-state-update) | `VOICE_STATES` | Alguém entrou, saiu ou mudou o estado numa sala de voz. | | [`INTERACTION_CREATE`](#interaction-create) | sempre | Alguém usou um comando do bot ou clicou num botão dele. Só o bot recebe. | | [`INTERACTION_RESPONSE`](#interaction-response) | sempre | O bot respondeu a uma interação sua. | | [`RELATIONSHIP_ADD`](#relationship-add) | sempre | Amizade, pedido ou bloqueio novo ou mudado. | | [`RELATIONSHIP_REMOVE`](#relationship-remove) | sempre | Amizade, pedido ou bloqueio desfeito. | ## Conexão ### READY O primeiro evento depois do IDENTIFY, com o estado inteiro da conta. Os campos estão em [Gateway → READY](https://docs.zynko.org/api/gateway.md#ready). ## Servidores ### GUILD_CREATE A conta entrou num servidor: criou, entrou por convite ou (para um bot) foi adicionada. Só a conta que entrou recebe. `d`: [GuildFull](https://docs.zynko.org/api/objetos.md#servidor-completo-guildfull), com os canais que ela pode ver. ### GUILD_UPDATE O nome ou o ícone do servidor mudou. Todos os membros recebem. `d`: [Guild](https://docs.zynko.org/api/objetos.md#servidor-guild). ### GUILD_DELETE O servidor foi apagado (todos os membros recebem), ou a conta saiu, foi removida ou banida (só ela recebe). Esqueça o servidor e tudo dele. ```json { "id": "1234567890123456789" } ``` | campo | tipo | descrição | |---|---|---| | `id` | id | O servidor. | ### GUILD_ROLE_CREATE Cargo criado, inclusive o cargo de um bot que acabou de entrar. Todos os membros recebem. `d`: [Role](https://docs.zynko.org/api/objetos.md#cargo-role) (tem `guild_id`). ### GUILD_ROLE_UPDATE Cargo editado. Também chega para cada cargo cuja `position` mudou: ao criar um cargo (os outros sobem), ao apagar (os de cima descem) e ao reordenar (chega um para cada cargo). Todos os membros recebem. `d`: [Role](https://docs.zynko.org/api/objetos.md#cargo-role). ### GUILD_ROLE_DELETE Cargo apagado. Todos os membros recebem. Os membros que tinham o cargo chegam em seguida em `GUILD_MEMBER_UPDATE`. | campo | tipo | descrição | |---|---|---| | `guild_id` | id | O servidor. | | `role_id` | id | O cargo apagado. | ### GUILD_EMOJIS_UPDATE Os emojis do servidor mudaram. Traz a lista **inteira**: substitua a que você tem. Todos os membros recebem. | campo | tipo | descrição | |---|---|---| | `guild_id` | id | O servidor. | | `emojis` | [Emoji](https://docs.zynko.org/api/objetos.md#emoji-emoji)[] | Todos os emojis do servidor agora. | ## Canais ### CHANNEL_CREATE Chega quando: - um canal é criado (recebe quem vê o canal); - uma postagem é aberta num fórum (recebe quem vê o fórum); - uma DM é aberta pela primeira vez (as duas contas recebem; `recipients` traz a **outra**); - a conta **passou a ver** um canal que já existia, porque ganhou um cargo ou uma permissão de canal mudou. `d`: [Channel](https://docs.zynko.org/api/objetos.md#canal-channel). ### CHANNEL_UPDATE Um canal foi editado (nome, tópico, posição, categoria, modo lento, tags, servidor de voz...), ou suas permissões mudaram. Também chega para os canais que ficaram sem categoria quando a categoria deles foi apagada. Recebe quem vê o canal. `d`: [Channel](https://docs.zynko.org/api/objetos.md#canal-channel), completo. ### CHANNEL_DELETE O canal foi apagado, ou a conta **deixou de ver** o canal (perdeu um cargo, ou uma permissão de canal mudou). Recebe quem via o canal. Apagar um fórum manda só o `CHANNEL_DELETE` do fórum: as postagens dele somem junto. `d`: [Channel](https://docs.zynko.org/api/objetos.md#canal-channel), como era. ## Membros ### GUILD_MEMBER_ADD Alguém entrou no servidor (por convite, ou um bot adicionado). Todos os membros recebem, inclusive quem entrou. `d`: [Member](https://docs.zynko.org/api/objetos.md#membro-member) (tem `guild_id`). ### GUILD_MEMBER_UPDATE Mudou o apelido, a marcação de ADM ou os cargos de um membro. Todos os membros recebem. `d`: [Member](https://docs.zynko.org/api/objetos.md#membro-member), completo. ### GUILD_MEMBER_REMOVE Alguém saiu, foi removido ou banido, ou era um bot que foi apagado. Os membros que ficaram recebem (quem saiu recebe `GUILD_DELETE`). | campo | tipo | descrição | |---|---|---| | `guild_id` | id | O servidor. | | `user_id` | id | Quem saiu. | ## Contas ### PRESENCE_UPDATE Uma conta abriu a primeira conexão com o gateway (`online`) ou fechou a última (`offline`). Recebem as contas relacionadas a ela: de servidores em comum, de DMs com ela e amigas. Também chega para o servidor inteiro quando alguém online entra nele. | campo | tipo | descrição | |---|---|---| | `user_id` | id | A conta. | | `status` | string | `"online"` ou `"offline"`. | ### USER_UPDATE Uma conta mudou o perfil (nome de exibição, avatar...). Recebem a própria conta e as relacionadas a ela. `d`: [PublicUser](https://docs.zynko.org/api/objetos.md#usuario-publicuser). ### RELATIONSHIP_ADD Uma relação da conta foi criada ou mudou (pedido de amizade recebido ou aceito, bloqueio). Só a conta dona da relação recebe. `d`: [Relationship](https://docs.zynko.org/api/objetos.md#relacao-relationship). ### RELATIONSHIP_REMOVE Uma relação da conta deixou de existir. | campo | tipo | descrição | |---|---|---| | `id` | id | A outra conta. | ## Mensagens ### MESSAGE_CREATE Mensagem nova num canal de servidor, numa postagem ou numa DM. Recebe quem vê o canal, **incluindo quem mandou**: um bot recebe as próprias mensagens e deve ignorá-las (`d.author.id === meuId`). `d`: [Message](https://docs.zynko.org/api/objetos.md#mensagem-message). Não tem `guild_id`; descubra o servidor pelo `channel_id` (os canais vieram no `READY` e no `GUILD_CREATE`). `embeds` chega vazio; se a mensagem tiver links, um `MESSAGE_UPDATE` traz as prévias pouco depois. ### MESSAGE_UPDATE Chega em dois casos, sempre **parcial**: - **Edição.** Traz `id`, `channel_id`, `content` e `edited_timestamp`. Se os links mudaram, traz também `embeds: []` (as prévias antigas saem); se houver links novos, outro `MESSAGE_UPDATE` traz as prévias deles. - **Prévias prontas.** Traz `id`, `channel_id`, `embeds` com as prévias, e também `content` e `edited_timestamp` (este pode ser `null`, se a mensagem nunca foi editada). | campo | tipo | descrição | |---|---|---| | `id` | id | A mensagem. | | `channel_id` | id | O canal. | | `content` | string | O texto atual. | | `edited_timestamp` | ms? | A última edição. | | `embeds` | [Embed](https://docs.zynko.org/api/objetos.md#previa-de-link-embed)[] | **opcional**. Só quando as prévias mudaram. | Atualize só os campos que vieram. Recebe quem vê o canal. ### MESSAGE_DELETE | campo | tipo | descrição | |---|---|---| | `id` | id | A mensagem apagada. | | `channel_id` | id | O canal. | ### MESSAGE_ACK A própria conta marcou um canal como lido (`POST /channels/{id}/ack`), talvez em outra conexão. Só a própria conta recebe. | campo | tipo | descrição | |---|---|---| | `channel_id` | id | O canal. | | `message_id` | id | A última mensagem lida. | ### MESSAGE_REACTION_ADD Uma conta reagiu a uma mensagem. Recebe quem vê o canal (inclusive quem reagiu). | campo | tipo | descrição | |---|---|---| | `message_id` | id | A mensagem. | | `channel_id` | id | O canal. | | `user_id` | id | Quem reagiu. | | `emoji` | string | Emoji Unicode, ou `nome:id` para um emoji de servidor. | ### MESSAGE_REACTION_REMOVE Uma conta tirou a própria reação. Mesmos campos de `MESSAGE_REACTION_ADD`. ### TYPING_START Alguém começou (ou continua) a digitar num canal. Recebe quem vê o canal, **menos** quem está digitando. Considere que a pessoa parou depois de uns 9 segundos sem outro `TYPING_START`. | campo | tipo | descrição | |---|---|---| | `channel_id` | id | O canal. | | `user_id` | id | Quem está digitando. | | `timestamp` | ms | Quando. | ## Voz ### VOICE_STATE_UPDATE Alguém entrou numa sala de voz, saiu, trocou de sala ou mudou o estado (microfone, som, câmera, tela). Recebe quem vê a sala; numa chamada de DM, as duas contas. Quando a sala ou o servidor é apagado, cada um que estava nela recebe o evento de saída. `d`: [VoiceState](https://docs.zynko.org/api/objetos.md#estado-de-voz-voicestate). Quando a conta **sai**, `channel_id` vem `null` (e os `self_*` vêm `false`). Ao trocar de sala, chega primeiro a saída da sala antiga e depois a entrada na nova. ## Interações ### INTERACTION_CREATE Alguém usou um comando do bot, ou clicou num botão de uma mensagem dele. Só o bot recebe. Responda com [`POST /interactions/{id}/{token}/callback`](https://docs.zynko.org/api/rotas.md#responder) em até 15 minutos. | campo | tipo | descrição | |---|---|---| | `id` | id | A interação. | | `token` | string | Para responder. Vale 15 minutos. | | `type` | string | `"command"` ou `"button"`. | | `channel_id` | id | Onde. | | `guild_id` | id? | O servidor; `null` numa DM. | | `user` | [PublicUser](https://docs.zynko.org/api/objetos.md#usuario-publicuser) | Quem usou. | | `member` | [Member](https://docs.zynko.org/api/objetos.md#membro-member)? | Quem usou, como membro do servidor. | | `command` | objeto | Só em comandos: `{ name, options: [{ name, type, value }] }`, na ordem do comando, só as preenchidas. `user`, `channel` e `role` vêm como ids. | | `custom_id` | string | Só em cliques: o `custom_id` do botão. | | `message` | [Message](https://docs.zynko.org/api/objetos.md#mensagem-message) | Só em cliques: a mensagem do botão. | ### INTERACTION_RESPONSE O bot respondeu a uma interação que a conta fez. Os apps usam para tirar o "pensando…". | campo | tipo | descrição | |---|---|---| | `id` | id | A interação. | | `channel_id` | id | Onde. | # Rotas Todas as rotas HTTP que um bot pode usar, agrupadas por assunto. Os caminhos são relativos a `https://zynko.org/api`, e todas pedem o cabeçalho `Authorization` (veja [Autenticação](https://docs.zynko.org/api/index.md#autenticacao)), exceto onde está escrito **sem autenticação**. - **Permissão** é o que a conta precisa ter. Num canal, conta a permissão **no canal**, depois das [permissões de canal](https://docs.zynko.org/api/permissoes.md#permissoes-de-canal). Administradores e o dono passam em todas. - Rotas marcadas **só pessoas** respondem `403` (ou `400`, onde indicado) para bots. - Os tipos de retorno estão em [Objetos](https://docs.zynko.org/api/objetos.md). - Em corpos de `PATCH`, campo ausente = não muda. Onde está escrito "`null` apaga", mandar `null` limpa o valor. ## Usuários ### Ver a própria conta `GET /users/@me` Devolve o seu [PublicUser](https://docs.zynko.org/api/objetos.md#usuario-publicuser). ### Editar o próprio perfil `PATCH /users/@me` · limite: 20 por minuto | campo | tipo | descrição | |---|---|---| | `display_name` | string | Nome de exibição, 1 a 32 caracteres. | | `avatar_id` | id? | Imagem enviada em [`POST /media`](#enviar-imagem). `null` apaga. | | `avatar_url` | string | Um GIF do KLIPY (`https://static.klipy.com/...` terminando em `.gif` ou `.webp`), no lugar de um upload. | | `banner_id` | id? | Como `avatar_id`, para o banner. | | `banner_url` | string | Como `avatar_url`, para o banner. | | `bio` | string? | "Sobre mim", até 190 caracteres. `null` ou `""` apaga. | | `accent` | string? | Cor `"#rrggbb"`. `null` apaga. | Devolve o [Profile](https://docs.zynko.org/api/objetos.md#perfil-profile) completo. Quem se relaciona com você recebe `USER_UPDATE`. ### Ver um perfil `GET /users/{user_id}/profile` `user_id` pode ser `@me`. Só funciona para contas que você "conhece": que estão num servidor seu, numa DM com você, ou são suas amigas. Para as outras, `404`. Devolve [Profile](https://docs.zynko.org/api/objetos.md#perfil-profile). ### Abrir uma DM `POST /users/@me/channels` | campo | tipo | descrição | |---|---|---| | `recipient_id` | id | Com quem conversar. | Devolve o [Channel](https://docs.zynko.org/api/objetos.md#canal-channel) da DM (tipo `1`), criando se ainda não existe. Para criar, a outra conta precisa estar num servidor em comum com você (ou ser sua amiga), e nenhuma das duas pode ter bloqueado a outra. Se a DM é nova, as duas contas recebem `CHANNEL_CREATE`. Depois, mande mensagens com [`POST /channels/{id}/messages`](#enviar-mensagem). ### Relações | rota | o que faz | |---|---| | `GET /users/@me/relationships` | Lista suas [Relationship](https://docs.zynko.org/api/objetos.md#relacao-relationship). | | `PUT /users/@me/relationships/{user_id}` | Com `{ "type": 2 }`, bloqueia a conta. Sem corpo (ou outro `type`), pede amizade: bots recebem `400`. | | `DELETE /users/@me/relationships/{user_id}` | Desbloqueia (ou desfaz amizade/pedido). | | `POST /users/@me/relationships` | Pede amizade por `username`. Bots recebem `400`. | As duas pontas recebem `RELATIONSHIP_ADD` / `RELATIONSHIP_REMOVE`. ## Servidores ### Criar um servidor `POST /guilds` · **só pessoas** ### Editar o servidor `PATCH /guilds/{guild_id}` · permissão: **Gerenciar o servidor** (`MANAGE_GUILD`) | campo | tipo | descrição | |---|---|---| | `name` | string | 2 a 100 caracteres. | | `icon_id` | id? | Imagem de [`POST /media`](#enviar-imagem). `null` apaga. | | `icon_url` | string | Um GIF do KLIPY no lugar de um upload. | Devolve o [Guild](https://docs.zynko.org/api/objetos.md#servidor-guild). Todos recebem `GUILD_UPDATE`. ### Apagar o servidor `DELETE /guilds/{guild_id}` · só o **dono** (um bot nunca é dono) ## Membros ### Listar membros `GET /guilds/{guild_id}/members` · qualquer membro Devolve [Member](https://docs.zynko.org/api/objetos.md#membro-member)[] em ordem de entrada. Não há paginação. Os mesmos membros já vêm em `READY` / `GUILD_CREATE`. ### Editar um membro `PATCH /guilds/{guild_id}/members/{user_id}` `user_id` pode ser `@me`. | campo | tipo | descrição | permissão | |---|---|---|---| | `nick` | string | Apelido, até 32. `""` apaga. | O próprio apelido: nenhuma. O de outro: **Mudar apelidos** (`MANAGE_NICKNAMES`) e estar acima da pessoa. | | `is_admin` | boolean | Torna ADM ou tira. | Só o **dono**. | Devolve o [Member](https://docs.zynko.org/api/objetos.md#membro-member). Todos recebem `GUILD_MEMBER_UPDATE`. "Estar acima" quer dizer: o seu cargo mais alto está acima do cargo mais alto da outra pessoa. ADMs ficam acima de qualquer cargo; só o dono mexe em ADMs. Veja [Hierarquia](https://docs.zynko.org/api/permissoes.md#hierarquia). ### Remover um membro (ou sair) `DELETE /guilds/{guild_id}/members/{user_id}` - Com `@me` (ou o seu id): você sai do servidor. O dono não pode sair. - Com outro id: expulsa. Permissão: **Remover pessoas** (`KICK_MEMBERS`) e estar acima da pessoa. O dono não pode ser removido. Devolve `{ "ok": true }`. O servidor recebe `GUILD_MEMBER_REMOVE`; quem saiu recebe `GUILD_DELETE`. ## Banimentos | rota | permissão | o que faz | |---|---|---| | `GET /guilds/{guild_id}/bans` | **Banir pessoas** (`BAN_MEMBERS`) | Lista [Ban](https://docs.zynko.org/api/objetos.md#banimento-ban)[], do mais recente. | | `PUT /guilds/{guild_id}/bans/{user_id}` | **Banir pessoas** e estar acima da pessoa | Bane: tira do servidor (se estiver) e impede de voltar por convite. Funciona para quem não é membro. Sem corpo. | | `DELETE /guilds/{guild_id}/bans/{user_id}` | **Banir pessoas** | Desbane. `404` se não estava banida. | As três devolvem `{ "ok": true }` (exceto a lista). Ninguém bane o dono nem a si mesmo. ## Cargos Gerenciar cargos pede **Gerenciar cargos** (`MANAGE_ROLES`) e só alcança cargos **abaixo** do seu cargo mais alto. Ninguém dá ou tira uma permissão que não tem. ### Criar um cargo `POST /guilds/{guild_id}/roles` | campo | tipo | descrição | |---|---|---| | `name` | string | **obrigatório**. 1 a 32 caracteres. | | `color` | number? | `0x000000` a `0xFFFFFF`. | | `permissions` | number | Bits; só os que você tem. Padrão `0`. | | `hoist` | boolean | Mostrar separado na lista de membros. Padrão `false`. | O cargo novo entra embaixo de todos, logo acima de `@everyone` (os outros sobem uma posição). Máximo de 100 cargos. Devolve o [Role](https://docs.zynko.org/api/objetos.md#cargo-role). O servidor recebe `GUILD_ROLE_CREATE` (e `GUILD_ROLE_UPDATE` para os que subiram). ### Editar um cargo `PATCH /guilds/{guild_id}/roles/{role_id}` | campo | tipo | descrição | |---|---|---| | `name` | string | 1 a 32. O nome de `@everyone` não muda. | | `color` | number? | `null` tira a cor. | | `permissions` | number | Os bits que mudarem precisam ser bits que você tem. | | `hoist` | boolean | Ignorado em `@everyone`. | Para editar `@everyone`, use o id do servidor como `role_id`. Devolve o [Role](https://docs.zynko.org/api/objetos.md#cargo-role). ### Reordenar cargos `PATCH /guilds/{guild_id}/roles` | campo | tipo | descrição | |---|---|---| | `order` | id[] | **Todos** os cargos, do mais alto para o mais baixo, sem `@everyone`. | Cargos no nível do seu ou acima precisam ficar onde estão. Devolve todos os [Role](https://docs.zynko.org/api/objetos.md#cargo-role)[]. ### Apagar um cargo `DELETE /guilds/{guild_id}/roles/{role_id}` `@everyone` não pode ser apagado. As permissões de canal desse cargo vão junto. Devolve `{ "ok": true }`. ### Dar ou tirar um cargo `PUT /guilds/{guild_id}/members/{user_id}/roles/{role_id}` (dar)
`DELETE /guilds/{guild_id}/members/{user_id}/roles/{role_id}` (tirar) Sem corpo. Permissão: **Gerenciar cargos**, com o cargo abaixo do seu. Devolve `{ "ok": true }`; o servidor recebe `GUILD_MEMBER_UPDATE`, e quem passou a ver (ou deixou de ver) canais recebe `CHANNEL_CREATE` / `CHANNEL_DELETE`. ## Emojis | rota | permissão | o que faz | |---|---|---| | `GET /guilds/{guild_id}/emojis` | qualquer membro | Lista [Emoji](https://docs.zynko.org/api/objetos.md#emoji-emoji)[]. | | `POST /guilds/{guild_id}/emojis` | **Gerenciar emojis** (`MANAGE_EMOJIS`) | Cria. Corpo: `name` (2 a 32 letras, números ou `_`) e `image_id` (de [`POST /media`](#enviar-imagem)). Máximo 100 por servidor. Devolve o Emoji. | | `PATCH /guilds/{guild_id}/emojis/{emoji_id}` | **Gerenciar emojis** | Renomeia. Corpo: `name`. Devolve o Emoji. | | `DELETE /guilds/{guild_id}/emojis/{emoji_id}` | **Gerenciar emojis** | Apaga. `{ "ok": true }`. | | `GET /emojis/{emoji_id}` | **sem autenticação** | Redireciona para a imagem. | Um nome repetido no mesmo servidor dá `400`. Toda mudança manda `GUILD_EMOJIS_UPDATE` com a lista inteira. ## Convites ### Criar um convite `POST /guilds/{guild_id}/invites` · permissão: **Criar convites** (`CREATE_INVITE`) · limite: 20 por hora | campo | tipo | descrição | |---|---|---| | `max_uses` | number? | Limite de usos (até 10000). Ausente ou 0 = sem limite. | | `max_age_secs` | number? | Validade em segundos (até 30 dias). Ausente ou 0 = não expira. | Mande pelo menos `{}` como corpo. Devolve o [Invite](https://docs.zynko.org/api/objetos.md#convite-invite). ### Outras rotas | rota | permissão | o que faz | |---|---|---| | `GET /guilds/{guild_id}/invites` | **Gerenciar o servidor** | Lista os [Invite](https://docs.zynko.org/api/objetos.md#convite-invite)[]. | | `GET /invites/{code}` | **sem autenticação** | Prévia: `{ code, guild, guild_id, member_count }`. `400` se o convite expirou ou acabou. | | `DELETE /invites/{code}` | quem criou, ou **Gerenciar o servidor** | Revoga. `{ "ok": true }`. | | `POST /invites/{code}` | **só pessoas** (bots: `400`) | Entra no servidor. Um bot entra pelo [link de convite de bot](https://docs.zynko.org/api/permissoes.md#o-cargo-do-bot). | ## Canais ### Criar um canal `POST /guilds/{guild_id}/channels` · permissão: **Gerenciar canais** (`MANAGE_CHANNELS`) | campo | tipo | descrição | |---|---|---| | `name` | string | **obrigatório**. 1 a 100. Em canais de texto e fóruns vira minúsculas, com hífens. | | `type` | number | `0` texto (padrão), `2` voz, `4` categoria ou `15` fórum. | | `parent_id` | id? | Categoria. Ignorado para categorias. | | `topic` | string? | Até 1024. | | `permission_overwrites` | [Overwrite](https://docs.zynko.org/api/objetos.md#permissao-de-canal-overwrite)[] | Começar com estas. Sem isso, o canal copia as da categoria. Mudar o que a categoria tem pede **Gerenciar cargos** nela. | | `rate_limit_per_user` | number | Modo lento, 0 a 21600 s. | Máximo de 200 canais por servidor. Devolve o [Channel](https://docs.zynko.org/api/objetos.md#canal-channel); quem pode vê-lo recebe `CHANNEL_CREATE`. ### Editar um canal `PATCH /channels/{channel_id}` | campo | tipo | descrição | |---|---|---| | `name` | string | Novo nome (numa postagem, o título). | | `topic` | string | Novo tópico. `""` apaga (`null` não muda nada). | | `position` | number | 0 a 10000. | | `parent_id` | id? | Mover para outra categoria. `null` tira da categoria. Categorias não entram em categorias. | | `rate_limit_per_user` | number | Modo lento, 0 a 21600 s. | | `available_tags` | [ForumTag](https://docs.zynko.org/api/objetos.md#tag-de-forum-forumtag)[] | Só fóruns: a lista **inteira** de tags (até 20). Tags que já existem mantêm o `id`; novas vão sem `id`. Tags tiradas saem também das postagens. | | `applied_tags` | id[] | Só postagens: as tags dela (até 5). | | `pinned` | boolean | Só postagens: fixar. | | `locked` | boolean | Só postagens: trancar. | Permissão: - Canal comum: **Gerenciar canais** no canal. - Postagem: o autor ou quem tem **Gerenciar mensagens** muda `name` e `applied_tags`; só **Gerenciar mensagens** muda `pinned`, `locked` e `rate_limit_per_user`. Tags marcadas como `moderated` só a moderação põe ou tira. DMs não se editam (`403`). Devolve o [Channel](https://docs.zynko.org/api/objetos.md#canal-channel); quem vê o canal recebe `CHANNEL_UPDATE`. ### Apagar um canal `DELETE /channels/{channel_id}` Permissão: **Gerenciar canais**; numa postagem, o autor ou **Gerenciar mensagens**. DMs não se apagam. Apagar uma categoria deixa os canais dela sem categoria (`CHANNEL_UPDATE`); apagar um fórum leva as postagens. Devolve `{ "ok": true }`. ### Fóruns: listar postagens `GET /channels/{forum_id}/posts` | query | descrição | |---|---| | `sort` | `activity` (padrão: última mensagem) ou `created` (criação). | | `before` | Para a próxima página: a chave de ordenação da última postagem recebida (o `last_message_id` dela, ou o `id` se não houver, em `activity`; o `id` em `created`). | | `tag` | Só postagens com esta tag. | | `q` | Só postagens com este texto no título. | | `limit` | 1 a 50, padrão 25. | Postagens fixadas vêm primeiro, só na primeira página. Sem **Ler o histórico**, a lista vem vazia. Devolve [Post](https://docs.zynko.org/api/objetos.md#postagem-listada-post)[]. ### Fóruns: abrir uma postagem `POST /channels/{forum_id}/posts` · permissão: **Enviar mensagens** · limite: 10 por minuto | campo | tipo | descrição | |---|---|---| | `name` | string | **obrigatório**. Título, 1 a 100. | | `applied_tags` | id[] | Tags do fórum, até 5. | Respeita o modo lento do fórum. Devolve o [Channel](https://docs.zynko.org/api/objetos.md#canal-channel) da postagem (tipo `11`). A primeira mensagem vai em seguida, por [`POST /channels/{post_id}/messages`](#enviar-mensagem). ## Permissões de canal Todas pedem **Gerenciar cargos** (`MANAGE_ROLES`) **no canal**, e só mudam bits que você tem nele. Devolvem o [Channel](https://docs.zynko.org/api/objetos.md#canal-channel) atualizado. Postagens seguem o fórum e não aceitam permissões próprias. Veja [Permissões de canal](https://docs.zynko.org/api/permissoes.md#permissoes-de-canal). | rota | corpo | o que faz | |---|---|---| | `PUT /channels/{channel_id}/permissions` | `{ "permission_overwrites": Overwrite[] }` | Troca a lista inteira (até 100). | | `PUT /channels/{channel_id}/permissions/{target_id}` | `{ "type": 0 \| 1, "allow": n, "deny": n }` | Define a de um cargo (`type` 0) ou membro (`type` 1). | | `DELETE /channels/{channel_id}/permissions/{target_id}` | — | Tira a de um cargo ou membro. | | `POST /channels/{channel_id}/permissions/sync` | — | Copia de novo as da categoria. | Mudar uma categoria muda junto os canais dela que estavam iguais a ela ("sincronizados"). Quem passa a ver um canal recebe `CHANNEL_CREATE`; quem deixa de ver, `CHANNEL_DELETE`; os outros, `CHANNEL_UPDATE`. ## Mensagens As rotas de mensagens funcionam em canais de texto, DMs e postagens de fórum. Em categorias, salas de voz e fóruns, respondem `400`. ### Listar mensagens `GET /channels/{channel_id}/messages` | query | descrição | |---|---| | `before` | Id: mensagens anteriores a ela. | | `after` | Id: mensagens posteriores a ela. Se vier junto com `before`, `before` é ignorado. | | `limit` | 1 a 100, padrão 50. | Devolve [Message](https://docs.zynko.org/api/objetos.md#mensagem-message)[] **da mais antiga para a mais nova**. Sem cursor, vem a página mais recente. Sem **Ler o histórico** (`READ_MESSAGE_HISTORY`) no canal, a lista vem vazia: a conta só vê o que chega pelo gateway. ```js // Todas as mensagens de um canal, das mais novas para trás. let before; for (;;) { const q = new URLSearchParams({ limit: '100', ...(before && { before }) }); const page = await api('GET', `/channels/${id}/messages?${q}`); if (page.length === 0) break; processar(page); before = page[0].id; // a mais antiga da página } ``` Não há rota para buscar uma mensagem só. ### Enviar mensagem `POST /channels/{channel_id}/messages` · permissão: **Enviar mensagens** (`SEND_MESSAGES`) · limite: 10 a cada 10 s | campo | tipo | descrição | |---|---|---| | `content` | string | Texto, até 4000 caracteres (espaços no fim são cortados). Pode faltar se houver anexos. | | `nonce` | string | Opcional, até 64 caracteres. Volta igual na mensagem e no `MESSAGE_CREATE`. | | `reply_to` | id | Responder a esta mensagem (do mesmo canal). | | `attachment_ids` | id[] | Até 10 anexos enviados antes, por você, [neste canal](#enviar-anexo) e ainda não usados. | | `cards` | [Card](https://docs.zynko.org/api/objetos.md#cartao-card)[] | **Só bots**. Até 5 cartões. Com cartões, o texto pode faltar. | | `components` | [Button](https://docs.zynko.org/api/objetos.md#botao-button)[][] | **Só bots**. Até 5 fileiras de 5 botões. | ```json { "content": "Olá, <@1234567890123456789>!", "reply_to": "1234567890123456999" } ``` Devolve a [Message](https://docs.zynko.org/api/objetos.md#mensagem-message). Quem vê o canal (incluindo você) recebe `MESSAGE_CREATE`; se houver links, um `MESSAGE_UPDATE` com as prévias vem logo depois. Erros comuns: mensagem vazia (`400`), postagem trancada sem **Gerenciar mensagens** (`400`), DM com alguém que bloqueou ou foi bloqueado (`400`), modo lento (`429 slow_mode`). ### Editar mensagem `PATCH /channels/{channel_id}/messages/{message_id}` · só o **autor** · limite: 10 a cada 10 s | campo | tipo | descrição | |---|---|---| | `content` | string | O texto novo, até 4000. Só pode ficar vazio se a mensagem tiver anexos ou cartões. | | `cards` | [Card](https://docs.zynko.org/api/objetos.md#cartao-card)[] | **Só bots**. Troca os cartões; sem o campo, ficam os que estavam. | | `components` | [Button](https://docs.zynko.org/api/objetos.md#botao-button)[][] | **Só bots**. Troca os botões; `[]` tira todos. | Mande só o que muda. Devolve a [Message](https://docs.zynko.org/api/objetos.md#mensagem-message) completa. O evento `MESSAGE_UPDATE` é parcial (veja [Eventos](https://docs.zynko.org/api/eventos.md#message-update)). ### Apagar mensagem `DELETE /channels/{channel_id}/messages/{message_id}` Permissão: ser o autor, ou ter **Gerenciar mensagens** (`MANAGE_MESSAGES`) no canal. Os anexos vão junto. Devolve `{ "ok": true }`; quem vê o canal recebe `MESSAGE_DELETE`. ### Reagir `PUT /channels/{channel_id}/messages/{message_id}/reactions/{emoji}` · limite: 20 a cada 10 s `{emoji}` é um emoji Unicode ou um emoji de servidor como `nome:id`, **codificado para URL** (`encodeURIComponent('👍')`). Um emoji de servidor precisa ser de um servidor em que você está. Somar-se a uma reação que já existe sempre pode; criar uma reação nova pede **Adicionar reações** (`ADD_REACTIONS`). Uma mensagem tem no máximo 20 emojis diferentes. Sem corpo. Devolve `{ "ok": true }`; se a reação é nova para você, todos recebem `MESSAGE_REACTION_ADD`. ### Tirar a reação `DELETE /channels/{channel_id}/messages/{message_id}/reactions/{emoji}` Tira **a sua** reação. Não há rota para tirar reações dos outros. Devolve `{ "ok": true }`. ### Digitando `POST /channels/{channel_id}/typing` · limite: 10 a cada 10 s Mostra "digitando..." para os outros (`TYPING_START`). Sem corpo. O app esconde o aviso depois de 9 segundos; repita a cada ~8 s enquanto o bot estiver trabalhando. ### Marcar como lido `POST /channels/{channel_id}/ack` | campo | tipo | descrição | |---|---|---| | `message_id` | id | A última mensagem lida. | Devolve `{ "ok": true }`. Só a própria conta recebe `MESSAGE_ACK`. Bots raramente precisam. ## Arquivos ### Enviar anexo `POST /channels/{channel_id}/attachments` · permissão: **Enviar arquivos** (`ATTACH_FILES`) · limite: 30 por minuto Corpo `multipart/form-data`: | campo | descrição | |---|---| | `file` | **obrigatório**. O arquivo. Até 50 MB (o padrão do servidor). | | `thumbnail` | Opcional, só para vídeos: uma miniatura JPEG ou WebP de até 512 KB. | | `width`, `height` | Opcionais: dimensões em pixels (1 a 16384). | `thumbnail`, `width` e `height` precisam vir **antes** de `file`; o que vier depois é ignorado. Devolve o [Attachment](https://docs.zynko.org/api/objetos.md#anexo-attachment). O anexo só aparece quando for usado numa mensagem, em `attachment_ids`; se não for usado em um dia, é apagado. ```js const form = new FormData(); form.append('file', new Blob([bytes], { type: 'image/png' }), 'grafico.png'); const anexo = await fetch(`${API}/channels/${canal}/attachments`, { method: 'POST', headers: { Authorization: `Bot ${TOKEN}` }, // sem Content-Type: o fetch põe o boundary body: form, }).then((r) => r.json()); await api('POST', `/channels/${canal}/messages`, { content: 'Aqui está', attachment_ids: [anexo.id] }); ``` Não funciona em categorias nem salas de voz. `413 too_large` se passar do limite; `503` se o servidor não tiver armazenamento configurado. ### Baixar anexo `GET /attachments/{id}` e `GET /attachments/{id}/thumb` Só para quem pode ver o canal do anexo; para todo o resto, `404`. Use `Authorization: Bearer ` (o prefixo `Bot ` não é aceito aqui). Aceita `Range`. ### Enviar imagem `POST /media` · limite: 10 por minuto Corpo `multipart/form-data` com o campo `file`: PNG, JPEG, GIF, WebP ou AVIF, até 16 MB. Devolve `{ "id": "..." }`, para usar como `avatar_id`, `banner_id`, `icon_id` ou `image_id` (emoji). Imagens de mídia ficam públicas em `/api/media/{id}/{segredo}`. ## Voz Entrar numa sala de voz tem duas partes: pedir um token do LiveKit pela API e avisar pelo gateway onde a conta está (op 4, veja [Gateway](https://docs.zynko.org/api/gateway.md#estado-de-voz-op-4)). O áudio passa pelo LiveKit, com qualquer SDK dele. ### Entrar numa sala `POST /channels/{channel_id}/voice` · permissão: **Entrar em salas** (`CONNECT`) · limite: 20 por minuto Funciona em salas de voz (tipo `2`) e DMs (uma chamada). Sem corpo. Devolve: | campo | tipo | descrição | |---|---|---| | `url` | string | Endereço `wss://` do LiveKit. | | `server` | string | Id do servidor de voz usado. | | `token` | string | Token do LiveKit, válido por 12 horas. | | `room` | string | Nome da sala no LiveKit (`ch-{channel_id}`). | | `can_speak` | boolean | Tem **Falar** (`SPEAK`): pode publicar o microfone. | | `can_stream` | boolean | Tem **Vídeo e tela** (`STREAM`): pode publicar câmera e tela. | ### Trocar o servidor de voz da sala `PUT /channels/{channel_id}/voice/server` · permissão: **Entrar em salas** · limite: 10 por minuto | campo | tipo | descrição | |---|---|---| | `server` | string | Id de um servidor de voz (veja `GET /config`). | Devolve o [Channel](https://docs.zynko.org/api/objetos.md#canal-channel) com `voice_server`; quem vê a sala recebe `CHANNEL_UPDATE` e deve reconectar no servidor novo. ### Configuração `GET /config` Devolve os recursos opcionais do servidor: ```json { "gifs": { "provider": "klipy", "app_key": "..." }, "voice": { "servers": [{ "id": "vixpi", "name": "Brasil 2" }], "default": "vixpi" } } ``` `gifs` é `null` quando não há busca de GIFs. ## Bots Rotas para criar e cuidar de bots. Quase todas são **só pessoas**: o dono usa com o token dele (ou em [dev.zynko.org](https://dev.zynko.org)). | rota | quem | o que faz | |---|---|---| | `GET /users/@me/bots` | qualquer conta | Lista os seus [Bot](https://docs.zynko.org/api/objetos.md#bot-bot)[]. | | `POST /users/@me/bots` | **só pessoas** · 10 por hora | Cria. Corpo: `username` (2 a 32, letras minúsculas, números, `_` e `.`), `display_name` (opcional), `public` (padrão `false`). Máximo de 10 bots por pessoa. Devolve o Bot **com `token`**. `409` se o nome já existe. | | `PATCH /users/@me/bots/{bot_id}` | **só pessoas**, dono | Edita o perfil do bot, com os mesmos campos de [`PATCH /users/@me`](#editar-o-proprio-perfil). Devolve [Profile](https://docs.zynko.org/api/objetos.md#perfil-profile). | | `PUT /users/@me/bots/{bot_id}/public` | **só pessoas**, dono | Corpo `{ "public": true }`: abre (ou fecha) o link de convite para todos. Servidores em que ele já está continuam com ele. Devolve o Bot. | | `POST /users/@me/bots/{bot_id}/token` | **só pessoas**, dono | Gera um token novo. O antigo para de funcionar e as conexões dele caem (`4000`). Devolve o Bot **com `token`**. | | `DELETE /users/@me/bots/{bot_id}` | **só pessoas**, dono | Apaga: o bot sai de todos os servidores, o token para de funcionar e o `username` fica livre. As mensagens dele continuam lá. | | `GET /bots/{bot_id}` | qualquer conta | A página de convite: o Bot com `owner`. Só bots públicos, ou os seus. | | `PUT /guilds/{guild_id}/bots/{bot_id}` | **só pessoas**, **Gerenciar o servidor** | Coloca o bot no servidor. Veja abaixo. | ### Adicionar um bot a um servidor `PUT /guilds/{guild_id}/bots/{bot_id}` · **só pessoas** · permissão: **Gerenciar o servidor** | campo | tipo | descrição | |---|---|---| | `permissions` | number | Bits que o bot pede. Se não for 0, o bot ganha um cargo com o nome dele e essas permissões. Quem adiciona precisa ter todas. | | `admin` | boolean | Fazer o bot ADM. Só o dono do servidor, e só para um bot dele. | O corpo pode faltar. Funciona com os seus bots e com qualquer bot público; um bot banido do servidor não entra. Devolve o [Member](https://docs.zynko.org/api/objetos.md#membro-member) do bot. O servidor recebe `GUILD_MEMBER_ADD` e o bot recebe `GUILD_CREATE`. Se o bot já está no servidor, nada muda (exceto `admin: true`). Veja [O cargo do bot](https://docs.zynko.org/api/permissoes.md#o-cargo-do-bot). ## Interações Comandos com "/" e botões. O guia está em [Comandos, cartões e botões](https://docs.zynko.org/bots/comandos.md). | rota | quem | o que faz | |---|---|---| | `PUT /users/@me/commands` | **só bots** · 10 por minuto | Troca a lista inteira de [Command](https://docs.zynko.org/api/objetos.md#comando-command) do bot (até 100). Devolve a lista como ficou (nomes em minúsculas). | | `GET /users/@me/commands` | **só bots** | Os comandos do bot. | | `GET /channels/{channel_id}/commands` | quem vê o canal | `[{ bot, commands }]`: os comandos dos bots que veem o canal. É o que os apps mostram ao digitar `/`. | | `POST /channels/{channel_id}/interactions` | **só pessoas** · 20 a cada 10 s | Usa um comando ou clica num botão (o que os apps fazem). Veja abaixo. | | `POST /interactions/{id}/{token}/callback` | o bot que recebeu | Responde. Veja abaixo. | ### Usar um comando ou clicar `POST /channels/{channel_id}/interactions` ```json { "type": "command", "bot_id": "123", "name": "dado", "options": [{ "name": "lados", "value": 6 }] } { "type": "button", "message_id": "456", "custom_id": "de-novo" } ``` Comandos precisam de **Enviar mensagens** no canal. O servidor confere as opções (tipos, obrigatórias, limites, escolhas; um número pode vir como texto) e se o bot vê o canal e está online, e então manda `INTERACTION_CREATE` só para o bot. Devolve `{ "id": "..." }`; quando o bot responder, quem usou recebe `INTERACTION_RESPONSE` com esse id. ### Responder `POST /interactions/{id}/{token}/callback` · até 15 minutos depois | campo | tipo | descrição | |---|---|---| | `type` | string | `"message"` (padrão): uma mensagem nova no canal. `"update"`: muda a mensagem do botão clicado (só em cliques). | | `content` | string | Texto. | | `cards` | [Card](https://docs.zynko.org/api/objetos.md#cartao-card)[] | Cartões. | | `components` | [Button](https://docs.zynko.org/api/objetos.md#botao-button)[][] | Botões. | | `ephemeral` | boolean | Com `"message"`: só quem usou vê, e nada fica guardado. Sem botões. | Devolve a [Message](https://docs.zynko.org/api/objetos.md#mensagem-message). A mensagem leva `interaction` (quem usou e o comando). Pode responder mais de uma vez; cada `"message"` é uma mensagem nova. `404` se o id ou o token estiverem errados, ou se já passaram 15 minutos. ## Outras `GET /health` responde `ok` (texto), sem autenticação.
# Objetos Os formatos JSON que a API devolve e o gateway envia. Os nomes dos campos são exatamente os do JSON. Convenções: - **id** é um snowflake em string (veja [IDs](https://docs.zynko.org/api/index.md#ids)). - **ms** é um número: milissegundos desde a época Unix. - `?` depois do tipo quer dizer que o valor pode ser `null`. - Campos marcados **opcional** às vezes nem aparecem no JSON (em vez de virem `null`). ## Usuário (`PublicUser`) O que qualquer um vê de uma conta, pessoa ou bot. Aparece como autor de mensagens, dentro de membros, em `READY.user` e no evento `USER_UPDATE`. | campo | tipo | descrição | |---|---|---| | `id` | id | Id da conta. | | `username` | string | Nome de usuário único, em minúsculas (2 a 32: letras, números, `_` e `.`). | | `display_name` | string | Nome de exibição (1 a 32 caracteres). | | `avatar` | string? | Imagem do avatar: `/api/media/...` ou URL de GIF. Veja [Imagens](https://docs.zynko.org/api/index.md#imagens-e-arquivos). | | `bot` | boolean | `true` para bots (aparece com a etiqueta BOT). | | `plus` | objeto? | **opcional**. Visual do Zynko+, presente só enquanto a pessoa assina: `frame` (moldura do avatar), `name` (estilo do nome), `effect` (efeito do perfil), `theme` (duas cores `#rrggbb`) e `since` (início da assinatura, em ms). Todos os campos menos `since` são opcionais. Bots podem ignorar. | | `badges` | string[] | **opcional**. Cargos globais da plataforma, mostrados embaixo do nome de usuário: `founder` (Fundador), `staff` (Equipe Zynko), `moderator`, `developer`, `partner`, `tester`, `pioneer` e `official` (bot oficial). Ausente quando não há nenhum. | ## Perfil (`Profile`) Devolvido por `GET /users/{id}/profile` e `PATCH /users/@me`. O usuário vem **aninhado** em `user`. | campo | tipo | descrição | |---|---|---| | `user` | [PublicUser](#usuario-publicuser) | A conta. | | `bio` | string? | "Sobre mim", até 190 caracteres (pode ter quebras de linha). | | `banner` | string? | Imagem do banner, no mesmo formato de `avatar`. | | `accent` | string? | Cor `"#rrggbb"` mostrada quando não há banner. | ## Bot (`Bot`) Como o dono (e a página de convite) vê um bot. Os campos de [PublicUser](#usuario-publicuser) vêm no mesmo nível. | campo | tipo | descrição | |---|---|---| | `id`, `username`, `display_name`, `avatar`, `bot` | | Como em [PublicUser](#usuario-publicuser). | | `public` | boolean | Se qualquer pessoa que gerencia um servidor pode adicioná-lo pelo link de convite. | | `bio` | string? | "Sobre mim" do bot. | | `owner_id` | id | Quem criou o bot. | | `guild_count` | number | Em quantos servidores ele está. | | `token` | string | **Só** na criação e ao gerar um token novo. Guarde: não aparece de novo. | | `owner` | [PublicUser](#usuario-publicuser) | **Só** em `GET /bots/{bot_id}`. | ## Servidor (`Guild`) | campo | tipo | descrição | |---|---|---| | `id` | id | Id do servidor. É também o id do cargo `@everyone`. | | `name` | string | Nome (2 a 100 caracteres). | | `icon` | string? | Ícone, no mesmo formato de `avatar`. | | `owner_id` | id | O dono. Sempre administrador; não pode sair nem ser removido. | ### Servidor completo (`GuildFull`) Chega em `READY.guilds` e no evento `GUILD_CREATE`. Tem os campos de [Guild](#servidor-guild) no mesmo nível e mais: | campo | tipo | descrição | |---|---|---| | `channels` | [Channel](#canal-channel)[] | Os canais **que você pode ver**. Postagens de fórum só vêm se tiveram atividade nos últimos 7 dias; as outras se buscam com `GET /channels/{id}/posts`. | | `members` | [Member](#membro-member)[] | Todos os membros. | | `presences` | string[] | Ids dos membros online neste momento. | | `roles` | [Role](#cargo-role)[] | Todos os cargos, incluindo `@everyone`. | | `emojis` | [Emoji](#emoji-emoji)[] | Os emojis do servidor. | ## Canal (`Channel`) Canais de texto, salas de voz, categorias, fóruns, postagens de fórum e conversas privadas (DMs) são todos canais. ### Tipos de canal | `type` | nome | o que é | |---|---|---| | `0` | `CHANNEL_TEXT` | Canal de texto. | | `1` | `CHANNEL_DM` | Conversa privada entre duas contas. | | `2` | `CHANNEL_VOICE` | Sala de voz. Não tem mensagens. | | `4` | `CHANNEL_CATEGORY` | Categoria (seção) que agrupa canais. | | `11` | `CHANNEL_POST` | Postagem dentro de um fórum: um canal próprio, com título e autor. Tem mensagens. | | `15` | `CHANNEL_FORUM` | Fórum: guarda postagens em vez de mensagens. | ### Campos | campo | tipo | descrição | |---|---|---| | `id` | id | Id do canal. | | `guild_id` | id? | Servidor do canal; `null` em DMs. | | `type` | number | Um dos [tipos](#tipos-de-canal). | | `name` | string | Nome (1 a 100). Canais de texto e fóruns ficam em minúsculas, com hífens no lugar de espaços. Em DMs é `""`. Numa postagem, é o título. | | `topic` | string? | Tópico, até 1024 caracteres. | | `position` | number | Ordem entre os canais do mesmo tipo dentro da mesma categoria. | | `parent_id` | id? | Categoria do canal; numa postagem, o fórum dela. | | `last_message_id` | id? | Última mensagem. Num fórum, muda também quando há resposta numa postagem. | | `rate_limit_per_user` | number | Modo lento, em segundos (0 = desligado; máximo 21600). | | `owner_id` | id | **opcional**. Autor de uma postagem. | | `pinned` | boolean | **opcional** (só aparece quando `true`). Postagem fixada. | | `locked` | boolean | **opcional** (só aparece quando `true`). Postagem trancada: só a moderação responde. | | `message_count` | number | **opcional** (não aparece quando é 0). Quantas mensagens o canal tem. | | `applied_tags` | id[] | **opcional** (não aparece quando vazio). Tags de uma postagem, tiradas de `available_tags` do fórum. | | `voice_server` | string | **opcional**. Servidor de voz escolhido para a sala (veja [Voz](https://docs.zynko.org/api/rotas.md#voz)). Sem ele, a sala usa o padrão. | | `available_tags` | [ForumTag](#tag-de-forum-forumtag)[] | **opcional**. Tags de um fórum. | | `recipients` | [PublicUser](#usuario-publicuser)[] | **opcional**. Numa DM: a outra pessoa (você não aparece). | | `permission_overwrites` | [Overwrite](#permissao-de-canal-overwrite)[] | **opcional**. Em canais de servidor: as permissões próprias do canal. Uma postagem traz as do fórum dela. | ### Tag de fórum (`ForumTag`) | campo | tipo | descrição | |---|---|---| | `id` | id | Id da tag. | | `name` | string | Nome, 1 a 20 caracteres. | | `emoji` | string? | Emoji mostrado com a tag. | | `moderated` | boolean | Se só a moderação (Gerenciar mensagens) pode pôr ou tirar a tag. | ### Postagem listada (`Post`) O que `GET /channels/{forum_id}/posts` devolve: os campos de [Channel](#canal-channel) no mesmo nível, e mais: | campo | tipo | descrição | |---|---|---| | `starter` | [Message](#mensagem-message)? | A primeira mensagem da postagem. | ## Permissão de canal (`Overwrite`) Bits liberados e negados num canal para um cargo ou um membro. Veja [Permissões de canal](https://docs.zynko.org/api/permissoes.md#permissoes-de-canal). | campo | tipo | descrição | |---|---|---| | `id` | id | Id do cargo ou do membro. O id do servidor é o cargo `@everyone`. | | `type` | number | `0` = cargo, `1` = membro. | | `allow` | number | Bits liberados. | | `deny` | number | Bits negados. | ## Membro (`Member`) Uma conta dentro de um servidor. O usuário vem **aninhado** em `user`. | campo | tipo | descrição | |---|---|---| | `guild_id` | id | O servidor. | | `user` | [PublicUser](#usuario-publicuser) | A conta. | | `nick` | string? | Apelido neste servidor (até 32). | | `is_admin` | boolean | Se é ADM (administrador por marcação, além dos cargos). O dono sempre é. | | `joined_at` | ms | Quando entrou. | | `roles` | id[] | Cargos do membro. `@everyone` **não** aparece aqui; todos têm. | ## Cargo (`Role`) | campo | tipo | descrição | |---|---|---| | `id` | id | Id do cargo. O cargo cujo id é o do servidor é `@everyone`. | | `guild_id` | id | O servidor. | | `name` | string | Nome, 1 a 32. | | `color` | number? | Cor `0xRRGGBB`, ou `null` sem cor. | | `position` | number | Altura na lista: `@everyone` é 0; quanto maior, mais alto. | | `permissions` | number | Bits de [permissão](https://docs.zynko.org/api/permissoes.md). | | `hoist` | boolean | Se os membros com o cargo aparecem separados na lista de membros. | ## Emoji (`Emoji`) | campo | tipo | descrição | |---|---|---| | `id` | id | Id do emoji. | | `guild_id` | id | O servidor. | | `name` | string | Nome, 2 a 32 letras, números ou `_`. | | `url` | string | Imagem (`/api/media/...`). | | `animated` | boolean | Se é um GIF. | Nas mensagens, escreva `<:nome:id>` (ou `` se animado). Nas reações, `nome:id`. ## Mensagem (`Message`) | campo | tipo | descrição | |---|---|---| | `id` | id | Id da mensagem. | | `channel_id` | id | O canal. **Não há `guild_id`**: descubra pelo canal. | | `author` | [PublicUser](#usuario-publicuser) | Quem mandou. | | `content` | string | Texto, até 4000 caracteres. Pode ser vazio se houver anexos. Menções são `<@id>` dentro do texto. | | `timestamp` | ms | Quando foi enviada (é o momento do id). | | `edited_timestamp` | ms? | Última edição. | | `reply_to` | [MessageRef](#resposta-messageref)? | A mensagem que esta responde. | | `attachments` | [Attachment](#anexo-attachment)[] | Arquivos (até 10). | | `reactions` | [ReactionCount](#reacao-reactioncount)[] | Reações, na ordem em que apareceram. | | `nonce` | string? | O `nonce` enviado por quem mandou (até 64 caracteres), para reconhecer a própria mensagem quando ela chegar pelo gateway. | | `embeds` | [Embed](#previa-de-link-embed)[] | Prévias de links. Chegam vazias no `MESSAGE_CREATE` e são preenchidas logo depois, por um `MESSAGE_UPDATE`. Mensagens com cartões não ganham prévias. | | `cards` | [Card](#cartao-card)[] | **opcional**. Cartões de um bot. | | `components` | [Button](#botao-button)[][] | **opcional**. Fileiras de botões de um bot. | | `interaction` | [InteractionRef](#interacao-interactionref) | **opcional**. O comando ou clique que esta mensagem (de um bot) responde. | | `ephemeral` | boolean | **opcional** (só aparece quando `true`). Só quem usou o comando vê, e não fica guardada. | ### Cartão (`Card`) Mandado por bots; veja [Comandos, cartões e botões](https://docs.zynko.org/bots/comandos.md#cartoes). | campo | tipo | descrição | |---|---|---| | `title` | string | **opcional**. Até 256. | | `description` | string | **opcional**. Markdown, até 4096. | | `url` | string | **opcional**. Link do título. | | `color` | number | **opcional**. Cor da borda, `0xRRGGBB`. | | `author` | objeto | **opcional**. `{ name, url?, icon_url? }`. | | `fields` | objeto[] | **opcional**. Até 25 `{ name, value, inline }`. | | `image` | objeto | **opcional**. `{ url }`, imagem grande. | | `thumbnail` | objeto | **opcional**. `{ url }`, imagem pequena ao lado. | | `footer` | objeto | **opcional**. `{ text, icon_url? }`. | | `timestamp` | ms | **opcional**. Mostrado no rodapé. | Os links de imagem voltam trocados por `/api/embeds/image?...`: o servidor busca as imagens. ### Botão (`Button`) | campo | tipo | descrição | |---|---|---| | `style` | string | `primary`, `secondary`, `success`, `danger` ou `link`. | | `label` | string | **opcional**. Até 80. | | `emoji` | string | **opcional**. | | `custom_id` | string | Todos menos `link`. Até 100, único na mensagem. | | `url` | string | Só `link`. | | `disabled` | boolean | **opcional**. | ### Interação (`InteractionRef`) | campo | tipo | descrição | |---|---|---| | `id` | id | A interação. | | `name` | string | **opcional**. O comando usado; não aparece num clique de botão. | | `user` | [PublicUser](#usuario-publicuser) | Quem usou. | ### Comando (`Command`) | campo | tipo | descrição | |---|---|---| | `name` | string | 1 a 32 letras minúsculas, números, `_` ou `-`. | | `description` | string | 1 a 100. | | `options` | objeto[] | Até 25 `{ name, description, type, required, choices?, min_value?, max_value? }`; `type` é `string`, `integer`, `number`, `boolean`, `user`, `channel` ou `role`; `choices` são até 25 `{ name, value }`. | ### Resposta (`MessageRef`) | campo | tipo | descrição | |---|---|---| | `id` | id | Id da mensagem respondida. | | `author` | [PublicUser](#usuario-publicuser)? | O autor dela; `null` se ela foi apagada. | | `content` | string? | Os primeiros 200 caracteres dela; `null` se foi apagada. | ### Anexo (`Attachment`) | campo | tipo | descrição | |---|---|---| | `id` | id | Id do anexo. | | `filename` | string | Nome do arquivo (limpo: letras, números, `.`, `-` e `_`, até 100). | | `content_type` | string | Tipo MIME. | | `size` | number | Tamanho em bytes. | | `url` | string | `/api/attachments/{id}`. Precisa de token para baixar. | | `thumbnail` | string? | `/api/attachments/{id}/thumb`, quando é um vídeo com miniatura. | | `width` | number? | Largura informada por quem enviou. | | `height` | number? | Altura informada por quem enviou. | ### Reação (`ReactionCount`) | campo | tipo | descrição | |---|---|---| | `emoji` | string | Um emoji Unicode (`"👍"`) ou um emoji de servidor como `nome:id`. | | `count` | number | Quantas contas reagiram com ele. | | `me` | boolean | Se quem pediu a mensagem está entre elas. | ### Prévia de link (`Embed`) Feita pelo servidor para até 3 links da mensagem. Só `url` sempre aparece. | campo | tipo | descrição | |---|---|---| | `url` | string | O link. | | `site_name` | string | **opcional**. Nome do site. | | `title` | string | **opcional**. Título. | | `description` | string | **opcional**. Descrição. | | `author` | string | **opcional**. Quem publicou (um canal do YouTube, por exemplo). | | `color` | string | **opcional**. Cor do site, `"#rrggbb"`. | | `image` | objeto | **opcional**. `{ url, width?, height?, large }`; `url` passa por `/api/embeds/image`; `large` diz se a imagem vai grande, embaixo do texto. | | `video` | objeto | **opcional**. `{ provider, id }`: um player que o app pode abrir (por exemplo YouTube). | ## Convite (`Invite`) | campo | tipo | descrição | |---|---|---| | `code` | string | Código de 8 caracteres. O link é `https://zynko.org/invite/{code}`. | | `guild_id` | id | O servidor. | | `creator_id` | id | Quem criou. | | `uses` | number | Quantas vezes foi usado. | | `max_uses` | number? | Limite de usos; `null` = sem limite. | | `expires_at` | ms? | Quando expira; `null` = nunca. | `GET /invites/{code}` devolve uma prévia: `{ code, guild: Guild, guild_id, member_count }`. ## Banimento (`Ban`) | campo | tipo | descrição | |---|---|---| | `user` | [PublicUser](#usuario-publicuser) | Quem foi banido. | | `created_at` | ms | Quando. | ## Estado de voz (`VoiceState`) Quem está em qual sala de voz (ou chamada numa DM). Chega em `READY.voice_states` e no evento `VOICE_STATE_UPDATE`. | campo | tipo | descrição | |---|---|---| | `user_id` | id | A conta. | | `guild_id` | id? | O servidor; `null` numa chamada de DM. | | `channel_id` | id? | A sala. `null` quando a conta **saiu** da voz. | | `self_mute` | boolean | Microfone desligado. | | `self_deaf` | boolean | Som desligado. | | `self_stream` | boolean | Compartilhando a tela. | | `self_video` | boolean | Câmera ligada. | | `session_id` | string | A conexão do gateway que está na voz (o `session_id` do `READY` dela). | | `joined_at` | integer | Quando a pessoa entrou nesta sala, em milissegundos Unix. Não muda ao ligar o microfone, a câmera etc. Ausente quando a pessoa saiu. | | `room_since` | integer | Quando a call da sala começou: o momento em que alguém entrou nela vazia (ms Unix). É o mesmo para todos na sala e recomeça depois que ela esvazia. | | `at` | integer | O relógio do servidor quando o evento saiu (ms Unix). Use `agora - at` para corrigir o seu relógio antes de mostrar durações. | ## Estado de leitura (`ReadState`) Até onde a conta já leu cada canal. Chega em `READY.read_states`. | campo | tipo | descrição | |---|---|---| | `channel_id` | id | O canal. | | `last_read_id` | id | A última mensagem lida. | ## Relação (`Relationship`) Amizades, pedidos e bloqueios. Bots não fazem amizades, mas podem bloquear. | campo | tipo | descrição | |---|---|---| | `type` | number | `1` amigo, `2` bloqueado, `3` pedido recebido, `4` pedido enviado. | | `since` | ms | Desde quando. | | `user` | [PublicUser](#usuario-publicuser) | A outra conta. | | `online` | boolean | Se está online (só para amigos; senão `false`). | # Permissões O que uma conta pode fazer num servidor é decidido por **bits**: cada cargo tem um número `permissions`, e cada canal pode liberar ou negar alguns bits por cargo ou por membro. Um bot é um membro como qualquer outro: seus cargos e a marcação de ADM decidem o que ele pode. ## Bits | nome | valor | decimal | rótulo no app | o que permite | |---|---|---|---|---| | `ADMINISTRATOR` | `1 << 0` | 1 | Administrador | Pode tudo, inclusive o que não está nesta lista. Ignora as permissões de canal. | | `MANAGE_GUILD` | `1 << 1` | 2 | Gerenciar o servidor | Mudar nome e ícone, ver e revogar convites, adicionar bots. | | `MANAGE_CHANNELS` | `1 << 2` | 4 | Gerenciar canais | Criar, editar e excluir canais, salas e seções. | | `MANAGE_ROLES` | `1 << 3` | 8 | Gerenciar cargos | Criar e editar cargos abaixo do seu e dá-los às pessoas. Num canal: editar as permissões dele. | | `KICK_MEMBERS` | `1 << 4` | 16 | Remover pessoas | Tirar do servidor quem tem cargo abaixo do seu. | | `BAN_MEMBERS` | `1 << 5` | 32 | Banir pessoas | Tirar e impedir de voltar, e desbanir. | | `MANAGE_MESSAGES` | `1 << 6` | 64 | Gerenciar mensagens | Excluir mensagens de outras pessoas; fixar, trancar e moderar postagens; ignorar o modo lento. | | `MANAGE_NICKNAMES` | `1 << 7` | 128 | Mudar apelidos | Trocar o apelido de quem tem cargo abaixo do seu. | | `CREATE_INVITE` | `1 << 8` | 256 | Criar convites | Chamar gente nova para o servidor. | | `SEND_MESSAGES` | `1 << 9` | 512 | Enviar mensagens | Escrever nos canais de texto e abrir postagens em fóruns. | | `ATTACH_FILES` | `1 << 10` | 1024 | Enviar arquivos | Anexar imagens e arquivos. | | `CONNECT` | `1 << 11` | 2048 | Entrar em salas | Entrar nas salas de voz para ouvir. | | `SPEAK` | `1 << 12` | 4096 | Falar | Usar o microfone nas salas. | | `MANAGE_EMOJIS` | `1 << 13` | 8192 | Gerenciar emojis | Enviar, renomear e excluir os emojis do servidor. | | `VIEW_CHANNEL` | `1 << 14` | 16384 | Ver canais | Ver os canais e salas. Sem isso, só aparecem os canais liberados um a um. | | `ADD_REACTIONS` | `1 << 15` | 32768 | Adicionar reações | Reagir com um emoji novo. Somar-se a uma reação que já existe sempre pode. | | `READ_MESSAGE_HISTORY` | `1 << 16` | 65536 | Ler o histórico | Ver as mensagens enviadas antes (`GET /channels/{id}/messages`). | | `STREAM` | `1 << 17` | 131072 | Vídeo e tela | Ligar a câmera e compartilhar a tela nas salas. | Todos os bits juntos valem `262143` (`(1 << 18) - 1`). Bits desconhecidos são descartados. ```js const P = { VIEW_CHANNEL: 1 << 14, SEND_MESSAGES: 1 << 9, READ_MESSAGE_HISTORY: 1 << 16 }; const pode = (perms, bit) => (perms & bit) === bit; ``` ## @everyone Todo servidor tem o cargo `@everyone`, cujo **id é o id do servidor**. Todo membro tem esse cargo, mesmo que ele não apareça em `member.roles`. Num servidor novo, `@everyone` começa com: `CREATE_INVITE | SEND_MESSAGES | ATTACH_FILES | CONNECT | SPEAK | VIEW_CHANNEL | ADD_REACTIONS | READ_MESSAGE_HISTORY | STREAM` = **253696** Ou seja: todo mundo vê os canais, conversa, manda arquivos, reage, lê o histórico, entra nas salas, fala, liga a câmera e convida gente. As permissões de gerenciamento ficam para cargos. ## Permissões no servidor As permissões de um membro no servidor inteiro são as de `@everyone` **somadas** (OU bit a bit) às de todos os cargos dele: ```js function base(guild, member) { const everyone = guild.roles.find((r) => r.id === guild.id); return guild.roles .filter((r) => member.roles.includes(r.id)) .reduce((p, r) => p | r.permissions, everyone.permissions); } ``` O membro é **administrador**, e pode tudo, se qualquer um destes for verdade: - é o dono do servidor (`guild.owner_id`); - é ADM (`member.is_admin`); - algum cargo dele (ou `@everyone`) tem `ADMINISTRATOR`. ## Permissões de canal Cada canal de servidor pode ter uma lista de [Overwrite](https://docs.zynko.org/api/objetos.md#permissao-de-canal-overwrite) (`permission_overwrites`): bits liberados (`allow`) e negados (`deny`) para um cargo (`type` 0) ou um membro (`type` 1). Só estes bits podem ser mudados por canal: `VIEW_CHANNEL`, `MANAGE_CHANNELS`, `MANAGE_ROLES`, `SEND_MESSAGES`, `ATTACH_FILES`, `ADD_REACTIONS`, `READ_MESSAGE_HISTORY`, `MANAGE_MESSAGES`, `CONNECT`, `SPEAK`, `STREAM`. As permissões de um membro num canal são calculadas como no Discord: 1. Administradores têm **tudo**, sempre. Pare aqui. 2. Comece pelas permissões no servidor (acima). 3. Aplique a permissão de canal de `@everyone` (o overwrite cujo `id` é o do servidor): tire o `deny`, ponha o `allow`. 4. Junte as permissões de canal de **todos os cargos** do membro: some todos os `deny` e todos os `allow`. Tire os `deny` somados, ponha os `allow` somados. (Então, entre cargos, liberar ganha de negar.) 5. Aplique a permissão de canal do próprio membro, se houver: tire o `deny`, ponha o `allow`. 6. Sem `VIEW_CHANNEL`, não sobra **nada** (o canal some para ele). 7. Sem `SEND_MESSAGES`, sai `ATTACH_FILES`. 8. Sem `CONNECT`, saem `SPEAK` e `STREAM`. ```js const OVERWRITE_ROLE = 0, OVERWRITE_MEMBER = 1; const ALL = (1 << 18) - 1; function noCanal(guild, member, channel) { let p = base(guild, member); const admin = guild.owner_id === member.user.id || member.is_admin || (p & 1) !== 0; if (admin) return ALL; const ows = channel.permission_overwrites ?? []; const todos = ows.find((o) => o.type === OVERWRITE_ROLE && o.id === guild.id); if (todos) p = (p & ~todos.deny) | todos.allow; let allow = 0, deny = 0; for (const o of ows) { if (o.type === OVERWRITE_ROLE && o.id !== guild.id && member.roles.includes(o.id)) { allow |= o.allow; deny |= o.deny; } } p = (p & ~deny) | allow; const meu = ows.find((o) => o.type === OVERWRITE_MEMBER && o.id === member.user.id); if (meu) p = (p & ~meu.deny) | meu.allow; if (!(p & (1 << 14))) return 0; // VIEW_CHANNEL if (!(p & (1 << 9))) p &= ~(1 << 10); // sem SEND_MESSAGES, sem ATTACH_FILES if (!(p & (1 << 11))) p &= ~((1 << 12) | (1 << 17)); // sem CONNECT, sem SPEAK e STREAM return p; } ``` Detalhes: - **Postagens** de fórum (tipo `11`) usam as permissões do fórum. O `permission_overwrites` de uma postagem já vem com as do fórum. - Um canal novo numa categoria começa com as permissões da categoria. Quando a categoria muda, os canais que ainda estavam **iguais** a ela mudam junto. - Num `Overwrite`, `deny` ganha de `allow` para o mesmo bit; o servidor limpa o `allow`. - Editar as permissões de um canal pede `MANAGE_ROLES` **naquele canal**, e só deixa mudar os bits que você tem lá. - **DMs** não têm cargos: nelas as duas contas podem ver, escrever, mandar arquivos, reagir, ler o histórico e fazer chamadas. O servidor só manda para cada conta os canais que ela pode ver (em `READY`, `GUILD_CREATE` e nos eventos). Quando um cargo ou uma permissão de canal muda, quem passou a ver um canal recebe `CHANNEL_CREATE`, e quem deixou de ver recebe `CHANNEL_DELETE`. ## Hierarquia Os cargos têm uma ordem (`position`: `@everyone` é 0; quanto maior, mais alto). O **nível** de um membro é a posição do seu cargo mais alto. Administradores ficam acima de todos os cargos, e o dono acima de todo mundo. - Para editar, apagar, dar ou tirar um cargo, ele precisa estar **abaixo** do seu nível. - Para remover, banir ou mudar o apelido de alguém, o nível dele precisa estar **abaixo** do seu. Só o dono age sobre ADMs. - Ninguém dá ou tira uma permissão (de cargo ou de canal) que não tem. - `@everyone` pode ser editado por quem tem `MANAGE_ROLES`, mas não apagado nem renomeado. ## O cargo do bot Um bot entra num servidor pelas mãos de alguém com **Gerenciar o servidor**, pelo link de convite do bot (veja [Convites de bot](https://docs.zynko.org/bots/convite.md)) ou pela rota [`PUT /guilds/{guild_id}/bots/{bot_id}`](https://docs.zynko.org/api/rotas.md#adicionar-um-bot-a-um-servidor). O link pede um número `permissions`. Quando o bot entra: - se `permissions` não for 0, o servidor cria um cargo **com o nome de exibição do bot** (até 32 caracteres) e essas permissões, logo acima de `@everyone`, e dá esse cargo ao bot; - quem adiciona só pode pedir permissões que tem; - se `permissions` for 0, o bot fica só com `@everyone`. Depois disso, é um cargo comum: o pessoal do servidor pode mudar as permissões dele, subir ou descer, ou dar outros cargos ao bot. Como o cargo nasce embaixo de todos, um bot com `KICK_MEMBERS` ou `MANAGE_ROLES` só alcança quem está abaixo dele; peça para subirem o cargo se ele precisar agir sobre outros. O dono do servidor também pode fazer ADM um bot **que seja dele** (`admin: true`). Bots públicos de outras pessoas só recebem permissões pelo cargo. > **Quanto pedir** > > Peça o mínimo. Um bot de comandos simples precisa de `VIEW_CHANNEL | SEND_MESSAGES | > READ_MESSAGE_HISTORY` = **82432**. Some `ADD_REACTIONS` (32768) se ele reagir e `ATTACH_FILES` > (1024) se mandar arquivos. # Começando O Zynko é um lugar para a sua turma: servidores com canais de texto, salas de voz com vídeo e tela, mensagens diretas e chamadas. Funciona no navegador em [zynko.org](https://zynko.org) e nos apps para Windows, macOS, Linux e Android, todos com a mesma conta. ## Baixar o app Na página inicial de [zynko.org](https://zynko.org), em **Baixar o app**, há o instalador de cada sistema: | Sistema | Arquivo | |---|---| | Windows | `_x64-setup.exe` (o normal), `_x86-setup.exe` (32 bits), `_arm64-setup.exe` (notebooks ARM) ou `.msi` | | macOS | `.dmg` universal (Apple Silicon e Intel). Na primeira vez, abra em Ajustes do Sistema › Privacidade e Segurança › Abrir mesmo assim | | Linux | `.deb`, `.rpm`, `.pkg.tar.zst` (Arch) ou `.AppImage` | | Android | `.apk` (Android 7 ou mais novo) | No computador o app se atualiza sozinho. No Android, o app avisa quando sai versão nova e atualiza com um toque (na primeira vez o Android pede para permitir que o Zynko instale apps). Versões antes da 0.9.8 não avisam: baixe a nova e instale por cima. ## Servidores e canais - Um **servidor** reúne pessoas, canais e salas. Você entra por um convite (`zynko.org/invite/...`) ou cria o seu. - **Canais de texto** guardam a conversa. Arquivos e imagens enviados ficam criptografados e só quem vê o canal consegue abrir. - **Canais de postagens** são fóruns: cada postagem tem título, tags e a sua própria conversa. - **Salas de voz** são para falar, ligar a câmera e mostrar a tela. Veja [Salas de voz](https://docs.zynko.org/guia/voz.md). - **Cargos** dão permissões às pessoas: quem pode gerenciar canais, remover gente, falar nas salas. ## Bots Alguns servidores têm bots: contas marcadas com **BOT**, controladas por um programa. Quem gerencia um servidor adiciona bots pelo link de convite de cada um. Para fazer o seu, veja [Bots](https://docs.zynko.org/bots/index.md). # Salas de voz Clique numa sala para entrar. Embaixo, à esquerda, ficam o microfone, o fone, a câmera, a tela e o botão de sair. A luz em volta da foto acende quando a pessoa fala. ## Servidor: Brasil 1 ou Brasil 2 Cada sala roda num servidor de voz: - **Brasil 1**, em São Paulo. - **Brasil 2**, o padrão das salas que ninguém mudou. Qualquer pessoa que pode entrar na sala pode trocar o servidor, antes de entrar (abaixo do botão **Entrar na sala**) ou durante a chamada (clicando no nome do servidor no topo da sala, ou no ícone de sinal). A troca vale para todos: quem está na sala reconecta sozinho no servidor novo, em poucos segundos. ## Conexão: Padrão ou Alternativa A conexão é sua, e fica guardada para cada sala. - **Padrão**: a voz vai direto, pelo caminho mais rápido. Use sempre que estiver tudo bem. - **Alternativa**: a voz passa pelo relay do servidor. Use quando o seu áudio chega picotado para os outros: há redes que bloqueiam ou limitam a conexão padrão. O app liga a alternativa sozinho numa sala quando percebe que está perdendo muito do que você manda. > **Rede que bloqueia UDP** > > No Brasil 2, a alternativa passa pela porta 443 com TLS, que quase nenhuma rede bloqueia. No > Brasil 1 ela usa UDP. Se a sua rede bloqueia UDP de vez, use Brasil 2 com a conexão alternativa. ## Compartilhar a tela No botão de tela você escolhe a qualidade (720p, 1080p ou personalizada, até 60 fps) e se o som vai junto. No app de Windows, ao compartilhar uma janela, vai só o som daquela janela; ao compartilhar a tela inteira, vai o som do computador sem as vozes da chamada. ## Diagnóstico O ícone de sinal da chamada abre **Conexão de voz**: qualidade, ping, perda e cortes de quem está falando. O botão **Copiar diagnóstico** copia tudo para mandar a quem estiver ajudando. # Sua conta ## Criar conta Usuário (2 a 32 caracteres: letras, números, `_` e `.`), e-mail e senha com pelo menos 8 caracteres. O Zynko manda um link de confirmação para o e-mail; a conta só entra depois de confirmar. O link vale por 48 horas. ## Esqueci minha senha Na tela de entrar, **Esqueci minha senha**. Informe o usuário ou o e-mail: o link para escolher uma senha nova vai para o e-mail confirmado da conta. - O link vale por **1 hora** e só uma vez. Pedir de novo invalida o anterior. - Ao salvar a senha nova, você entra e as outras sessões abertas saem. - Não chegou? Confira o spam. > **Contas sem e-mail** > > As primeiras contas foram criadas antes de o cadastro pedir e-mail e não recebem o link. Nesse > caso, fale com quem cuida do Zynko. ## Trocar senha e e-mail Em **Ajustes › Conta** você troca a senha (as outras sessões saem) e o e-mail (o novo endereço precisa ser confirmado antes de valer).