Lancer un bot¶

bot.run()¶
bot.run() constitue tout le programme de la plupart des bots. Il configure la journalisation au niveau INFO sur stderr, sauf si le logger racine a déjà des gestionnaires, démarre une boucle d'événements et fait tourner le bot jusqu'à son arrêt. Ctrl+C et, sur le thread principal, SIGTERM le ferment proprement.
Le token provient de la variable d'environnement BOSTIE_TOKEN, ou de bot.run(token). Un préfixe Bot est retiré, et un token absent lève ValueError. run accepte aussi loop_factory, une fonction qui crée la boucle d'événements, transmise à asyncio.run.
Le cycle de vie¶
bot.start() fait le travail derrière run() :
- Il envoie l'événement
Startinget attend chacun de ses écouteurs tour à tour. Le REST fonctionne à ce stade, la gateway non ; un écouteur qui lève une exception arrête le démarrage. - Il synchronise les commandes, sauf si
auto_sync=False. - Il connecte les shards de la gateway. Quand chaque shard est prêt et que ses serveurs sont arrivés, il envoie
Startedune seule fois, démarre les tâches de fond et laissewait_until_ready()rendre la main. - Il traite les événements jusqu'à
close(), ou jusqu'à ce que Discord ferme la gateway définitivement, ce qui lève une exception, par exempleInvalidTokenouPrivilegedIntentsRequired.
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() arrête le bot dans cet ordre :
- Si
Starteda été envoyé, il envoieStoppinget laisse jusqu'à 5 secondes à ses écouteurs, pendant que la gateway est encore ouverte. - Il quitte chaque salon vocal, puis ferme chaque shard, ce qui termine sa session avec le code de fermeture 1000, ou la sauvegarde pour une reprise.
- Il annule les tâches de fond. Les gestionnaires encore en cours disposent de
shutdown_gracesecondes, 0 par défaut, avant d'être annulés à leur tour. - Il ferme le client HTTP et envoie
Stopped, dont il attend les écouteurs.bot.http,bot.restetbot.gatewayn'existent plus.
close() peut être appelé de n'importe où, plusieurs fois, et pendant que start() tourne ; le bot peut ensuite être redémarré. async with bot: le ferme à la fin du bloc.
Dans un autre programme¶
Quand votre programme fait déjà tourner une boucle d'événements, démarrez le bot comme une tâche :
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() rend la main une fois Started envoyé, immédiatement s'il l'a déjà été, et lève StateError si le bot s'arrête avant ; attendre la tâche start() lève alors la cause. bot.is_ready indique la même chose sans attendre. L'exemple d'application web fait tourner un bot pendant la durée de vie (lifespan) d'un serveur web et publie des messages depuis une route web.
Un bot qui ne répond aux interactions que par HTTP n'a pas besoin de gateway : voir Interactions HTTP. await bot.open() le démarre de cette façon et rend la main : il ouvre le client HTTP, récupère l'application et l'utilisateur du bot, envoie Starting, synchronise les commandes, démarre les tâches de fond et renvoie l'application. ASGIApp l'appelle, et close() y met fin. Un script qui n'appelle que l'API REST utilise un RESTClient.
Pendant l'exécution¶
| Attribut | Valeur |
|---|---|
bot.user |
le CurrentUser du bot, une fois connecté |
bot.application_id |
l'id de l'application, une fois démarré |
bot.latency |
l'aller-retour moyen du heartbeat des shards, en secondes, ou None |
bot.is_running |
si start() ou open() fait tourner le bot, jusqu'à ce que close() l'arrête |
bot.is_ready |
si Started a été envoyé depuis le dernier start() |
bot.rest, bot.http |
l'API REST, une fois démarré |
bot.gateway |
les shards de la gateway, une fois connecté |
bot.cache |
le cache |
bot.voice |
les connexions vocales |
bot.auto_defer |
le délai par défaut de la réponse différée automatique ; modifiable à tout moment |
bot.rest, bot.http et bot.gateway lèvent StateError avant start().
Journalisation¶
bostie journalise via le module standard logging, sous ces noms :
| Logger | Ce qu'il signale |
|---|---|
bostie.bot |
les gestionnaires qui ont échoué ou n'ont pas répondu, les intents dont un écouteur a besoin, les interactions non traitées |
bostie.gateway |
les connexions, déconnexions, reprises, la contre-pression (backpressure), les requêtes invalides |
bostie.rest |
les limites de débit et les nouvelles tentatives, depuis le client HTTP en Rust |
bostie.sync |
ce que chaque synchronisation des commandes a changé |
bostie.context |
les réponses qui n'ont pas pu s'appliquer, comme un flag éphémère sur une modification |
bostie.events |
les payloads qui ne correspondaient pas à leur événement, une fois par nom d'événement |
bostie.voice |
les connexions vocales, DAVE, les sinks, les récepteurs et les lecteurs |
bostie.bridge |
le lien entre Python et le cœur Rust |
Le cœur Rust journalise via les mêmes loggers et lit leurs niveaux au démarrage du bot : appelez logging.getLogger("bostie.gateway").setLevel(logging.DEBUG) avant run() pour voir la gateway en détail.
Diriger bostie ailleurs¶
Bot(api_url=...) envoie les requêtes REST vers une autre URL de base, comme un proxy de limite de débit partagé par plusieurs processus ou un Discord factice dans les tests. Sans cette option, bostie lit BOSTIE_API_URL au démarrage du bot, et utilise https://discord.com/api/v10 si elle n'est pas définie non plus. Derrière un proxy de limite de débit, passez http_settings=HTTPSettings(rate_limiter=False), pour que bostie laisse l'attente au proxy. Le guide REST présente les autres réglages HTTP.
Plusieurs bots dans un processus¶
Chaque Bot a sa propre connexion au cœur Rust, son propre cache et son propre client HTTP : un seul processus peut donc faire tourner plusieurs bots, chacun démarré et fermé indépendamment, dans une même boucle d'événements.
Conserver un état¶
Un bot est un objet ordinaire : sous-classez Bot pour lui donner des attributs, comme le fait l'exemple de cycle de vie avec une connexion à une base de données ouverte dans un écouteur Starting et fermée dans un écouteur Stopped. Toutes les options de Bot sont listées dans la référence des options de Bot.