main.py 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. """Point d'entrée : lance le bot avec `python main.py`."""
  2. import logging
  3. import sys
  4. from logging.handlers import RotatingFileHandler
  5. from pathlib import Path
  6. import aiohttp
  7. import discord
  8. from bot.config import ConfigError, load_settings
  9. from bot.discord_bot import ChatBot
  10. def setup_logging(log_file: Path | None) -> None:
  11. """Journal sur la console et dans log_file (seulement sur la console si log_file vaut None)."""
  12. formatter = logging.Formatter("[%(asctime)s] %(levelname)s %(name)s : %(message)s", "%Y-%m-%d %H:%M:%S")
  13. root = logging.getLogger()
  14. root.setLevel(logging.INFO)
  15. handlers = [logging.StreamHandler()]
  16. if log_file is not None:
  17. handlers.append(RotatingFileHandler(log_file, maxBytes=5 * 1024 * 1024, backupCount=2, encoding="utf-8"))
  18. for handler in handlers:
  19. handler.setFormatter(formatter)
  20. root.addHandler(handler)
  21. # discord.py et le SDK Anthropic sont bavards : on ne garde que leurs avertissements.
  22. # Le SDK 2.x passe par httpx2 ; httpx reste pour les versions plus anciennes.
  23. for noisy in ("discord", "httpx", "httpx2", "anthropic"):
  24. logging.getLogger(noisy).setLevel(logging.WARNING)
  25. def main() -> None:
  26. try:
  27. settings = load_settings()
  28. except ConfigError as exc:
  29. # Aussi dans le fichier de journal quand LOG_FILE est utilisable : un redémarrage raté y laisse une trace.
  30. setup_logging(exc.log_file)
  31. if len(exc.errors) == 1:
  32. logging.critical("Erreur de configuration : %s", exc)
  33. else:
  34. logging.critical("%d erreurs de configuration :\n%s", len(exc.errors),
  35. "\n".join(f" - {error}" for error in exc.errors))
  36. sys.exit(1)
  37. setup_logging(settings.log_file)
  38. # Rika n'utilise pas le vocal : inutile de signaler l'absence de PyNaCl et de davey.
  39. discord.VoiceClient.warn_nacl = False
  40. discord.VoiceClient.warn_dave = False
  41. bot = ChatBot(settings)
  42. try:
  43. bot.run(settings.discord_token, log_handler=None)
  44. except discord.LoginFailure:
  45. logging.critical("Token Discord invalide : vérifie DISCORD_TOKEN dans le .env.")
  46. sys.exit(1)
  47. except discord.PrivilegedIntentsRequired:
  48. logging.critical(
  49. "L'intent « Message Content » n'est pas activé. Active-le dans le portail développeur "
  50. "Discord (onglet Bot > Privileged Gateway Intents)."
  51. )
  52. sys.exit(1)
  53. except discord.DiscordServerError as exc:
  54. logging.critical("Discord semble en panne (erreur %s) : réessaie dans quelques minutes.", exc.status)
  55. sys.exit(1)
  56. except discord.HTTPException as exc:
  57. logging.critical("Discord a refusé une requête au démarrage (erreur %s : %s).",
  58. exc.status, exc.text or "sans détail")
  59. sys.exit(1)
  60. # aiohttp.ClientError : pas de réseau, nom de domaine introuvable, connexion coupée ; TimeoutError : pas de réponse.
  61. except (aiohttp.ClientError, TimeoutError) as exc:
  62. logging.critical("Impossible de joindre Discord : vérifie la connexion Internet (%s).",
  63. str(exc) or type(exc).__name__)
  64. sys.exit(1)
  65. # Clé API ou modèle refusés (ChatBot.setup_hook), salon dédié inutilisable (ChatBot.announce) :
  66. # déjà expliqué dans le journal.
  67. if bot.config_error:
  68. sys.exit(1)
  69. if __name__ == "__main__":
  70. main()