plugins.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  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 in ("cfg", "config"):
  39. converters.append(lambda cmd, db, msg: cmd.bot.config)
  40. elif p.startswith("cfg") or p.endswith("cfg"):
  41. annot = annotations.get(p, p)
  42. converters.append(lambda cmd, db, msg: cmd.bot.config.get(annot))
  43. elif p.startswith("data") or p.endswith("data"):
  44. annot = annotations.get(p, p)
  45. converters.append(lambda cmd, db, msg, sing_cls=annot: sing_cls.get_or_create(db, msg))
  46. else:
  47. raise ValueError(p)
  48. return converters
  49. def as_plugin(command):
  50. if isinstance(command, str):
  51. command_name = command
  52. else:
  53. command_name = command.__name__
  54. def init_standin(self, bot, logger=logging.getLogger(__name__)):
  55. RollbotPlugin.__init__(self, command_name, bot, logger=logger)
  56. def decorator(fn):
  57. sig = inspect.signature(fn)
  58. try:
  59. converters = get_converters(sig.parameters, fn.__annotations__)
  60. except ValueError as ve:
  61. raise ValueError(f"Illegal argument name {str(ve)} in decorated plugin {command_name}")
  62. def on_command_standin(self, db, msg):
  63. res = fn(*[c(self, db, msg) for c in converters])
  64. if res is None:
  65. return RollbotResponse(msg, respond=False)
  66. elif isinstance(res, RollbotResponse):
  67. return res
  68. elif isinstance(res, RollbotFailure):
  69. return RollbotResponse(msg, failure=res, debugging=res.get_debugging())
  70. else:
  71. return RollbotResponse(msg, txt=str(res))
  72. return type(
  73. f"AutoGenerated`{command_name}`Command",
  74. (RollbotPlugin,),
  75. dict(
  76. __init__=init_standin,
  77. on_command=on_command_standin,
  78. )
  79. )
  80. if isinstance(command, str):
  81. return decorator
  82. else:
  83. return decorator(command)
  84. def with_help(help_msg):
  85. def getter(self):
  86. return help_msg
  87. def decorator(cls):
  88. setattr(cls, "help_msg", getter)
  89. return cls
  90. return decorator