Skip to content

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

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.

eventoargumentosquando
readyuserConectou (de novo).
message_createMessageMensagem nova.
message_updateMessageMensagem 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_createGuildBot adicionado a um servidor (e cada servidor do READY).
guild_updateGuildNome, ícone ou dono mudou.
guild_deleteguild_idBot saiu ou o servidor foi apagado.
channel_create, channel_deleteChannelCanal novo ou apagado (uma DM nova é um canal novo).
channel_updateChannel, anteriorCanal mudou.
member_addMemberAlguém entrou num servidor.
member_updateMember, anteriorApelido ou cargos mudaram.
member_remove{guild_id, user_id}Alguém saiu.
presence_update{user_id, status}Ficou online ou offline.
voice_state_updateVoiceState, 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.
errorExceptionErro 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_toOs dados da mensagem.
guild_idO servidor, ou None 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.
rawO 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.commandO nome do comando.
i.option(nome, padrão)O valor de uma opção; padrão (ou None) se ficou vazia.
i.custom_idO 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.

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.

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.

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.playerO tocador (abaixo).
await voice.leave()Sai da sala.
voice.channel_id, voice.serverA sala e o servidor de voz dela (Brasil 1, Brasil 2...).
voice.can_speakSe 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 caminhoAberto 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):

  • 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 <link>, 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.

Zynko · zynko.org