plugins.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import logging
  2. import inspect
  3. from functools import reduce
  4. from .messaging import RollbotResponse, RollbotMessage, RollbotFailure
  5. class RollbotPlugin:
  6. def __init__(self, command, bot, logger=logging.getLogger(__name__)):
  7. self.command = command
  8. self.bot = bot
  9. self.logger = logger
  10. self.logger.info(f"Intializing {type(self).__name__} matching {command}")
  11. def on_start(self, db):
  12. self.logger.info(f"No on_start initialization of {type(self).__name__}")
  13. def on_shutdown(self, db):
  14. self.logger.info(f"No on_shutdown de-initialization of {type(self).__name__}")
  15. def on_command(self, db, message):
  16. raise NotImplementedError
  17. def help_msg(self):
  18. return f"No help message provided for {self.command} command!"
  19. @staticmethod
  20. def find_all_plugins():
  21. def get_subclasses(clazz):
  22. s = set(clazz.__subclasses__())
  23. return s | reduce(set.union, (get_subclasses(sc) for sc in s), set())
  24. return get_subclasses(RollbotPlugin)
  25. def get_converters(parameters, annotations):
  26. converters = []
  27. for p in parameters:
  28. if p in ("msg", "message", "_msg"):
  29. converters.append(lambda cmd, db, msg: msg)
  30. elif p in ("db", "database"):
  31. converters.append(lambda cmd, db, msg: db)
  32. elif p in ("log", "logger"):
  33. converters.append(lambda cmd, db, msg: cmd.logger)
  34. elif p in ("bot", "rollbot"):
  35. converters.append(lambda cmd, db, msg: cmd.bot)
  36. elif p in ("subc", "subcommand"):
  37. converters.append(lambda cmd, db, msg: RollbotMessage.from_subcommand(msg))
  38. elif p.startswith("data") or p.endswith("data"):
  39. annot = annotations.get(p, p)
  40. converters.append(lambda cmd, db, msg, sing_cls=annot: sing_cls.get_or_create(db, msg))
  41. else:
  42. raise ValueError(p)
  43. return converters
  44. def as_plugin(command):
  45. if isinstance(command, str):
  46. command_name = command
  47. else:
  48. command_name = command.__name__
  49. def init_standin(self, bot, logger=logging.getLogger(__name__)):
  50. RollbotPlugin.__init__(self, command_name, bot, logger=logger)
  51. def decorator(fn):
  52. sig = inspect.signature(fn)
  53. try:
  54. converters = get_converters(sig.parameters, fn.__annotations__)
  55. except ValueError as ve:
  56. raise ValueError(f"Illegal argument name {str(ve)} in decorated plugin {command_name}")
  57. def on_command_standin(self, db, msg):
  58. res = fn(*[c(self, db, msg) for c in converters])
  59. if res is None:
  60. return RollbotResponse(msg, respond=False)
  61. elif isinstance(res, RollbotResponse):
  62. return res
  63. elif isinstance(res, RollbotFailure):
  64. return RollbotResponse(msg, failure=res, debugging=res.get_debugging())
  65. else:
  66. return RollbotResponse(msg, txt=str(res))
  67. return type(
  68. f"AutoGenerated`{command_name}`Command",
  69. (RollbotPlugin,),
  70. dict(
  71. __init__=init_standin,
  72. on_command=on_command_standin,
  73. )
  74. )
  75. if isinstance(command, str):
  76. return decorator
  77. else:
  78. return decorator(command)
  79. def with_help(help_msg):
  80. def getter(self):
  81. return help_msg
  82. def decorator(cls):
  83. setattr(cls, "help_msg", getter)
  84. return cls
  85. return decorator