Einen Bot betreiben¶

bot.run()¶
Für die meisten Bots ist bot.run() das ganze Programm. Es richtet das Logging auf INFO nach stderr ein, sofern der Root-Logger noch keine Handler hat, startet eine Event-Loop und lässt den Bot laufen, bis er stoppt. Strg+C und, im Haupt-Thread, SIGTERM beenden ihn sauber.
Das Token kommt aus der Umgebungsvariable BOSTIE_TOKEN oder aus bot.run(token). Ein vorangestelltes Bot wird entfernt, und ein fehlendes Token löst ValueError aus. run nimmt außerdem loop_factory, eine Funktion, die die Event-Loop erzeugt und an asyncio.run weitergereicht wird.
Der Lebenszyklus¶
bot.start() erledigt die Arbeit hinter run():
- Es sendet das Event
Startingund wartet nacheinander auf jeden seiner Listener. REST funktioniert hier, das Gateway nicht; ein Listener, der eine Exception wirft, bricht den Start ab. - Es synchronisiert die Befehle, außer bei
auto_sync=False. - Es verbindet die Gateway-Shards. Sind alle Shards bereit und ihre Server angekommen, sendet es einmal
Started, startet die Hintergrund-Tasks und lässtwait_until_ready()zurückkehren. - Es verarbeitet Events bis zu
close()oder bis Discord das Gateway endgültig schließt, was eine Exception auslöst, zum BeispielInvalidTokenoderPrivilegedIntentsRequired.
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() fährt in dieser Reihenfolge herunter:
- Wurde
Startedgesendet, sendet esStoppingund gibt dessen Listenern bis zu 5 Sekunden, während das Gateway noch offen ist. - Es verlässt jeden Sprachkanal und schließt dann jeden Shard, was dessen Sitzung mit dem Close-Code 1000 beendet oder sie für ein Resume speichert.
- Es bricht die Hintergrund-Tasks ab. Handler, die noch laufen, bekommen
shutdown_graceSekunden, standardmäßig 0, bevor auch sie abgebrochen werden. - Es schließt den HTTP-Client und sendet
Stopped, auf dessen Listener es wartet.bot.http,bot.restundbot.gatewaysind danach weg.
close() lässt sich von überall aufrufen, auch mehrmals und während start() läuft; der Bot kann danach erneut gestartet werden. async with bot: schließt ihn, wenn der Block endet.
Innerhalb eines anderen Programms¶
Wenn dein Programm bereits eine Event-Loop betreibt, starte den Bot als Task:
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() kehrt zurück, sobald Started gesendet wurde, sofort, wenn das schon geschehen ist, und löst StateError aus, wenn der Bot vorher stoppt; ein await auf den start()-Task löst dann den Grund aus. bot.is_ready sagt dasselbe, ohne zu warten. Das Web-App-Beispiel betreibt einen Bot im Lifespan eines Webservers und postet Nachrichten aus einer Web-Route.
Ein Bot, der nur über HTTP auf Interaktionen antwortet, braucht kein Gateway: siehe HTTP-Interaktionen. await bot.open() startet ihn auf diese Weise und kehrt zurück: Es öffnet den HTTP-Client, holt die Anwendung und den Benutzer des Bots, sendet Starting, synchronisiert die Befehle, startet die Hintergrund-Tasks und gibt die Anwendung zurück. ASGIApp ruft es auf, und close() beendet es. Ein Skript, das nur die REST-API aufruft, verwendet einen RESTClient.
Während er läuft¶
| Attribut | Wert |
|---|---|
bot.user |
der CurrentUser des Bots, sobald er verbunden ist |
bot.application_id |
die ID der App, sobald er gestartet ist |
bot.latency |
die mittlere Heartbeat-Umlaufzeit der Shards in Sekunden, oder None |
bot.is_running |
ob start() oder open() den Bot betreibt, bis close() ihn beendet |
bot.is_ready |
ob seit dem letzten start() Started gesendet wurde |
bot.rest, bot.http |
die REST-API, sobald er gestartet ist |
bot.gateway |
die Gateway-Shards, sobald er verbunden ist |
bot.cache |
der Cache |
bot.voice |
die Sprachverbindungen |
bot.auto_defer |
die Standardverzögerung für automatisches Aufschieben; jederzeit änderbar |
bot.rest, bot.http und bot.gateway lösen vor start() StateError aus.
Logging¶
bostie loggt über das Standardmodul logging unter diesen Namen:
| Logger | Was er meldet |
|---|---|
bostie.bot |
Handler, die fehlgeschlagen sind oder nicht geantwortet haben, Intents, die ein Listener braucht, unverarbeitete Interaktionen |
bostie.gateway |
Verbindungen, Verbindungsabbrüche, Resumes, Backpressure, ungültige Anfragen |
bostie.rest |
Rate-Limits und Wiederholungen, aus dem Rust-HTTP-Client |
bostie.sync |
was jede Befehlssynchronisierung geändert hat |
bostie.context |
Antworten, die nicht angewendet werden konnten, etwa ein Ephemeral-Flag bei einer Bearbeitung |
bostie.events |
Payloads, die nicht zu ihrem Event passten, einmal pro Event-Name |
bostie.voice |
Sprachverbindungen, DAVE, Sinks, Receiver und Player |
bostie.bridge |
die Verbindung zwischen Python und dem Rust-Kern |
Der Rust-Kern loggt über dieselben Logger und liest ihre Level beim Start des Bots: Setz logging.getLogger("bostie.gateway").setLevel(logging.DEBUG) vor run(), um das Gateway im Detail zu sehen.
bostie auf eine andere Adresse richten¶
Bot(api_url=...) sendet REST-Anfragen an eine andere Basis-URL, etwa an einen Rate-Limit-Proxy, den sich mehrere Prozesse teilen, oder in Tests an ein Fake-Discord. Fehlt die Angabe, liest bostie beim Start des Bots BOSTIE_API_URL und verwendet https://discord.com/api/v10, wenn auch das nicht gesetzt ist. Hinter einem Rate-Limit-Proxy übergibst du http_settings=HTTPSettings(rate_limiter=False), damit bostie das Warten dem Proxy überlässt. Die REST-Anleitung behandelt die übrigen HTTP-Einstellungen.
Mehrere Bots in einem Prozess¶
Jeder Bot hat seine eigene Verbindung zum Rust-Kern, seinen eigenen Cache und seinen eigenen HTTP-Client. Ein Prozess kann also mehrere Bots in einer Event-Loop betreiben, die jeweils für sich gestartet und geschlossen werden.
Zustand halten¶
Ein Bot ist ein gewöhnliches Objekt: Leite von Bot ab, um ihm Attribute zu geben, wie es das Lebenszyklus-Beispiel mit einer Datenbankverbindung tut, die in einem Starting-Listener geöffnet und in einem Stopped-Listener geschlossen wird. Jede Bot-Option steht in der Referenz der Bot-Optionen.