Pular para conteúdo

Rodando um bot

Bostie comemorando com confete

bot.run()

bot.run() é o programa inteiro para a maioria dos bots. Ele configura o logging em INFO no stderr, a menos que o logger raiz já tenha handlers, inicia um event loop e roda o bot até ele parar. Ctrl+C e, na thread principal, SIGTERM o encerram de forma limpa.

O token vem da variável de ambiente BOSTIE_TOKEN, ou de bot.run(token). Um Bot no início é removido, e um token ausente levanta ValueError. run também aceita loop_factory, uma função que cria o event loop, repassada para asyncio.run.

O ciclo de vida

bot.start() faz o trabalho por trás de run():

  1. Ele envia o evento Starting e faz await de cada um dos listeners dele, um de cada vez. O REST funciona aqui, o gateway não; um listener que levanta uma exceção interrompe a inicialização.
  2. Ele sincroniza os comandos, a menos que auto_sync=False.
  3. Ele conecta os shards do gateway. Quando todos os shards estão prontos e os servidores deles chegaram, ele envia Started uma vez, inicia as tarefas em segundo plano e deixa wait_until_ready() retornar.
  4. Ele trata eventos até close(), ou até o Discord fechar o gateway de vez, o que levanta uma exceção, por exemplo InvalidToken ou PrivilegedIntentsRequired.
import logging

import bostie
from bostie.events import Started, Starting, Stopped, Stopping

log = logging.getLogger("my-bot")
bot = bostie.Bot()


@bot.listen
async def starting(event: Starting) -> None:
    application = await bot.rest.application.fetch()
    log.info("starting %s", application.name)


@bot.listen
async def started(event: Started) -> None:
    log.info("ready in %d guilds, latency %s s", len(bot.cache.guilds()), bot.latency)


@bot.listen
async def stopping(event: Stopping) -> None:
    log.info("saying goodbye while the gateway is still open")


@bot.listen
async def stopped(event: Stopped) -> None:
    log.info("the gateway and REST are closed")


bot.run()

await bot.close() encerra nesta ordem:

  1. Se Started foi enviado, ele envia Stopping e dá aos listeners dele até 5 segundos enquanto o gateway ainda está aberto.
  2. Ele sai de todos os canais de voz e depois fecha cada shard, o que encerra a sessão dele com o código de fechamento 1000, ou a guarda para uma retomada.
  3. Ele cancela as tarefas em segundo plano. Os handlers ainda em execução recebem shutdown_grace segundos, 0 por padrão, antes de serem cancelados também.
  4. Ele fecha o cliente HTTP e envia Stopped, de cujos listeners faz await. bot.http, bot.rest e bot.gateway deixam de existir.

close() pode ser chamado de qualquer lugar, mais de uma vez, e enquanto start() roda; o bot pode ser iniciado de novo depois. async with bot: o fecha quando o bloco termina.

Dentro de outro programa

Quando o seu programa já roda um event loop, inicie o bot como uma tarefa:

import asyncio
import logging

import bostie
from bostie import StateError

log = logging.getLogger("my-bot")
bot = bostie.Bot(shutdown_grace=10.0)


async def main() -> None:
    logging.basicConfig(level=logging.INFO)
    running = asyncio.create_task(bot.start())
    try:
        await bot.wait_until_ready()
    except StateError:
        await running
        raise
    log.info("%s is in %d guilds", bot.user, len(bot.cache.guilds()))
    try:
        await running
    finally:
        await bot.close()


if __name__ == "__main__":
    asyncio.run(main())

await bot.wait_until_ready() retorna assim que Started foi enviado, na hora se já tiver sido, e levanta StateError se o bot parar antes; fazer await da tarefa de start() então levanta o motivo. bot.is_ready diz o mesmo sem esperar. O exemplo de app web roda um bot dentro do lifespan de um servidor web e publica mensagens a partir de uma rota web.

Um bot que só responde a interações por HTTP não precisa de gateway: veja Interações por HTTP. await bot.open() o inicia desse jeito e retorna: ele abre o cliente HTTP, busca o aplicativo e o usuário do bot, envia Starting, sincroniza os comandos, inicia as tarefas em segundo plano e retorna o aplicativo. ASGIApp o chama, e close() o encerra. Um script que só chama a API REST usa um RESTClient.

Enquanto ele roda

Atributo Valor
bot.user o CurrentUser do bot, depois de conectado
bot.application_id o ID do app, depois de iniciado
bot.latency a média do tempo de ida e volta do heartbeat dos shards em segundos, ou None
bot.is_running se start() ou open() está rodando o bot, até close() encerrá-lo
bot.is_ready se Started foi enviado desde o último start()
bot.rest, bot.http a API REST, depois de iniciado
bot.gateway os shards do gateway, depois de conectado
bot.cache o cache
bot.voice as conexões de voz
bot.auto_defer o atraso padrão do adiamento automático; pode ser mudado a qualquer momento

bot.rest, bot.http e bot.gateway levantam StateError antes de start().

Logging

O bostie escreve logs no módulo padrão logging com estes nomes:

Logger O que ele informa
bostie.bot handlers que falharam ou não responderam, intents de que um listener precisa, interações não tratadas
bostie.gateway conexões, desconexões, retomadas, contrapressão, requisições inválidas
bostie.rest rate limits e novas tentativas, vindos do cliente HTTP em Rust
bostie.sync o que cada sincronização de comandos mudou
bostie.context respostas que não puderam ser aplicadas, como uma flag efêmera em uma edição
bostie.events payloads que não couberam no seu evento, uma vez por nome de evento
bostie.voice conexões de voz, DAVE, sinks, receptores e players
bostie.bridge a ligação entre o Python e o núcleo em Rust

O núcleo em Rust escreve logs pelos mesmos loggers e lê os níveis deles quando o bot inicia: defina logging.getLogger("bostie.gateway").setLevel(logging.DEBUG) antes de run() para ver o gateway em detalhe.

Apontando o bostie para outro lugar

Bot(api_url=...) envia as requisições REST para outra URL base, como um proxy de rate limit compartilhado por vários processos ou um Discord falso em testes. Se for omitido, o bostie lê BOSTIE_API_URL quando o bot inicia, e usa https://discord.com/api/v10 quando ela também não está definida. Atrás de um proxy de rate limit, passe http_settings=HTTPSettings(rate_limiter=False), para que o bostie deixe a espera por conta do proxy. O guia de REST cobre as outras configurações de HTTP.

Vários bots em um processo

Cada Bot tem a sua própria conexão com o núcleo em Rust, o seu próprio cache e o seu próprio cliente HTTP, então um processo pode rodar vários bots, cada um iniciado e fechado por conta própria, em um único event loop.

Guardando estado

Um bot é um objeto comum: crie uma subclasse de Bot para dar atributos a ele, como o exemplo de ciclo de vida faz com uma conexão de banco de dados aberta em um listener de Starting e fechada em um de Stopped. Todas as opções de Bot estão listadas na referência de opções do Bot.