bot.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. from dataclasses import dataclass
  2. from typing import Any, Generic, TypeVar
  3. import traceback
  4. import asyncio
  5. import logging
  6. import aiosqlite
  7. import aiohttp
  8. from .types import CommandConfiguration, Message, Response, Context, Command
  9. RawMsg = TypeVar("RawMsg")
  10. @dataclass
  11. class Rollbot(Generic[RawMsg]):
  12. command_config: CommandConfiguration
  13. database_file: str
  14. def __post_init__(self):
  15. self.context = Context(
  16. config=self.read_config,
  17. respond=self.respond,
  18. request=aiohttp.ClientSession(),
  19. database=lambda: aiosqlite.connect(self.database_file),
  20. logger=logging.getLogger(),
  21. )
  22. def read_config(self, key: str) -> Any:
  23. raise NotImplementedError("Must be implemented by driver")
  24. def parse(self, incoming: RawMsg) -> Message:
  25. raise NotImplementedError("Must be implemented by driver")
  26. async def respond(self, response: Response):
  27. raise NotImplementedError("Must be implemented by driver")
  28. async def on_startup(self):
  29. await asyncio.gather(*[task(self.context) for task in self.command_config.startup])
  30. async def on_shutdown(self):
  31. await asyncio.gather(*[task(self.context) for task in self.command_config.shutdown])
  32. await self.context.request.close()
  33. async def on_message(self, incoming: RawMsg):
  34. message = self.parse(incoming)
  35. if message.text is None:
  36. return
  37. message.command = Command.from_text(message.text)
  38. if message.command is None or message.command.bang not in self.command_config.bangs:
  39. return
  40. command = self.command_config.aliases.get(message.command.name, message.command.name)
  41. if command == "help":
  42. args = message.command.args.split(maxsplit=1)
  43. if len(args) == 0:
  44. help_msg = "Use this command to look up the help info of another command, try !help roll for example!"
  45. else:
  46. cmd = self.command_config.commands.get(args[0], None)
  47. if cmd is None or cmd.__doc__ is None:
  48. help_msg = "Sorry! I don't have any explanation of that command"
  49. else:
  50. help_msg = cmd.__doc__.strip()
  51. await self.respond(Response.from_message(message, help_msg))
  52. return
  53. res = self.command_config.call_and_response.get(command, None)
  54. if res is not None:
  55. await self.respond(Response.from_message(message, res))
  56. return
  57. command_call = self.command_config.commands.get(command, None)
  58. if command_call is None:
  59. await self.respond(
  60. Response.from_message(message, f"Sorry! I don't know the command {command}.")
  61. )
  62. return
  63. try:
  64. await command_call(message, self.context)
  65. except Exception as e:
  66. self.context.logger.exception(
  67. f"Unhandled error during execution of {command} in {message}"
  68. )
  69. await self.respond(
  70. Response.from_message(
  71. message, f"Encountered an unhandled error: {type(e).__name__}"
  72. )
  73. )
  74. self.context.debugging = "".join(traceback.format_exc())