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-nodePara 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.tsO 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). |
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_STATESGUILDS, 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) => { /* ... */ });| evento | argumentos | quando |
|---|---|---|
ready | user | Conectou (de novo). |
messageCreate | Message | Mensagem nova. |
messageUpdate | Message | Mensagem 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. |
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 e messageUpdate entregam 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.
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.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 usa tudo isso: comandos, fila, volume e saída automática quando a sala esvazia.