Skip to content

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, que precisa estar instalado na máquina (ou indicado em FFMPEG_PATH).

Enquanto o pacote não está no npm

O código está no repositório do Zynko, em sdk/js. Até a publicação no npm, instale apontando para a pasta: npm install ../caminho/para/sdk/js.

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.userA conta do bot.
client.guildsMap dos servidores, cada um com channels, members, roles (todos Map), emojis e online.
client.channelsMap de todos os canais que o bot vê, DMs incluídas.
client.usersMap das contas que o bot já viu.
client.voiceStatesMap de quem está em voz, por id da conta (precisa de VOICE_STATES).
client.restA API inteira (veja 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.

Eventos ​

ts
client.on('messageCreate', (msg) => { /* ... */ });
eventoargumentosquando
readyuserConectou (de novo).
messageCreateMessageMensagem nova.
messageUpdateMessageMensagem editada (ou prévia de link pronta).
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.
guildCreateservidorBot adicionado a um servidor (e cada servidor do READY).
guildUpdateservidorNome, ícone ou dono mudou.
guildDeleteguildIdBot saiu ou o servidor foi apagado.
channelCreate, channelDeleteChannelCanal novo ou apagado (uma DM nova é um canal novo).
channelUpdateChannel, anteriorCanal mudou.
memberAddMemberAlguém entrou num servidor.
memberUpdateMember, anteriorApelido ou cargos mudaram.
memberRemove{ guild_id, user_id }Alguém saiu.
presenceUpdate{ user_id, status }Ficou online ou offline.
voiceStateUpdateVoiceState, anteriorAlguém entrou, saiu ou mudou na voz.
interactionInteractionAlguém usou um comando do bot ou clicou num botão dele.
rawtype, dataTodo evento do gateway, como chegou.
reconnecting{ code, reason, wait }A conexão caiu e vai voltar.
errorErrorErro 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 e messageUpdate entregam um Message:

id, channelId, author, content, timestamp, attachments, reactions, embeds, replyToOs dados da mensagem.
guildIdO servidor, ou null numa DM.
channelO Channel, se o bot já o viu.
memberO autor como membro do servidor (apelido, cargos).
mineSe 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.

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.

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.

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.playerO tocador (abaixo).
voice.leave()Sai da sala.
voice.channelId, voice.serverA sala e o servidor de voz dela (Brasil 1, Brasil 2...).
voice.canSpeakSe 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 caminhoAberto 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 usa tudo isso: comandos, fila, volume e saída automática quando a sala esvazia.

Zynko · zynko.org