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.pyclient.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). |
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_STATESGUILDS, 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.
| 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.
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.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):
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.