plugins.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. """
  2. Описания класса плагина для расширения функционала Чио.
  3. Author: Milinuri Nirvalen
  4. """
  5. from .ui import Logger
  6. from .modules import Context
  7. import asyncio
  8. import importlib.util
  9. import os
  10. class Plugin:
  11. '''класс плагина
  12. :param **kwargs: доплнительные параметры плагина
  13. str: name - имя плагина
  14. bool: admins - является ли плагин для администраторов
  15. bool: disabled - отключён ли плагин
  16. list: backends - список поддерживаемых движков'''
  17. def __init__(self, name, desc='no desc', path='data', **kwargs):
  18. # добавляем параметры к атрибутам
  19. self.__dict__.update(kwargs)
  20. self.l = Logger()
  21. self.name = name
  22. self.desc = desc
  23. self.dpath = path
  24. if not os.path.exists(path):
  25. os.mkdir(path)
  26. self.l.complite(f'created folder: {path}')
  27. # локальное хранилище
  28. self.handlers = {'init':[], "before":[], "after":[], 'except':[]}
  29. self.routers = {}
  30. self.triggers = []
  31. self.commands = 0
  32. self.check_funcs = {}
  33. def eventHandler(self, *event_types):
  34. ''' декоратор обработчика событий
  35. :param *event_types: типы событий'''
  36. def wrapper(func):
  37. for t in event_types:
  38. if t in self.handlers:
  39. self.handlers[t].append(func)
  40. else:
  41. self.log(f'неизвестный тип события "{t}"', 'w')
  42. return func
  43. return wrapper
  44. def check_func(self, name=False):
  45. def wrapper(func):
  46. if func.__name__ in self.check_funcs:
  47. self.l.warn(f'ChF "{func.__name__}" будет перезаписана')
  48. self.check_funcs[func.__name__] = func
  49. return func
  50. return wrapper
  51. def addCheckFunc(self, func, name=False):
  52. if not name:
  53. name = func.__name__
  54. if name in self.check_funcs:
  55. self.l.warn(f'ChF "{name}" будет перезаписана')
  56. self.check_funcs[name] = func
  57. def cf(self, args):
  58. res = []
  59. if isinstance(args, str):
  60. res.append((args, None))
  61. elif isinstance(args, dict):
  62. for k, v in args.items():
  63. res.append((k, v))
  64. elif isinstance(args, list):
  65. for x in args:
  66. if isinstance(x, str):
  67. res.append((x, None))
  68. elif isinstance(x, dict):
  69. for k, v in x.items():
  70. res.append((k, v))
  71. elif isinstance(x, list):
  72. for a in self.cf(*x):
  73. res.append(a)
  74. return res
  75. def convert_names(self, names, use_origin=True):
  76. res = []
  77. for x in map(lambda x: x.strip().lower(), names):
  78. if x and x[-1] == ')' and x.count('(') == 1:
  79. origin = x.split('(')[0].replace('-', '')
  80. if use_origin:
  81. res.append(origin)
  82. for m in x.split('(')[1][:-1].split('/'):
  83. if m.count('-') == 1:
  84. res.append(m.strip().replace('-', origin))
  85. else:
  86. res.append(origin+m)
  87. else:
  88. res.append(x.replace('-', ''))
  89. return res
  90. def command(self, *args, **params):
  91. '''декоратор команды
  92. :param *args: перечень названий обработчиков
  93. :param **params: дополнительные параметры команды'''
  94. self.commands += 1
  95. commands = []
  96. for cmd in self.convert_names(list(args), False):
  97. if not cmd:
  98. continue
  99. if cmd and cmd[0] == '<' and cmd[-1] == '>':
  100. cmds = cmd[1:-1].split(' ')
  101. for r in self.gen_names(
  102. self.convert_names(cmds[0].split('/')),
  103. self.convert_names(cmds[1].split('/'))):
  104. commands.append(r)
  105. else:
  106. commands.append(cmd)
  107. # устанавливаем уровень полнимочий (локальный, глобальный)
  108. if params.get('admins'):
  109. admins = True
  110. elif params.get('users'):
  111. admins = False
  112. else:
  113. admins = self.__dict__.get('admins')
  114. def wrapper(func):
  115. names = list(args)
  116. if func.__name__.lower() not in commands:
  117. commands.append(func.__name__.lower())
  118. names.append(func.__name__.lower())
  119. for c in commands:
  120. self.triggers.append(c)
  121. self.routers[func.__name__] = {'func':func, 'admins':admins,
  122. "check_func":self.cf(params.get('check_func', [])),
  123. "else_func":self.cf(params.get('else_func', [])),
  124. "usage":params.get('usage'),
  125. "names":names, 'cmds':commands,
  126. 'id':self.__dict__.get('name', '')+' / '+func.__name__}
  127. return func
  128. return wrapper
  129. def gen_names(self, a, b):
  130. """принимает пару слов/списков и генерирует все возможные варианты
  131. :param a: первая список/строка
  132. :param b: вторая список/строка
  133. :return: список сгенерированных имён"""
  134. res = []
  135. if isinstance(a, str):
  136. a = [a]
  137. if isinstance(b, str):
  138. b = [b]
  139. for ax in a:
  140. for bx in b:
  141. res.append(f'{ax}{bx}')
  142. res.append(f'{bx}{ax}')
  143. return res
  144. def load(self, path):
  145. '''метод загрузки плагина
  146. пример:
  147. m = p.load("main.py") == import main
  148. :param path: путь до импортируемово файла относительно папки плагина
  149. :return: импортированный модуль'''
  150. filepath = str(self.path)+ '/' + path + '.py'
  151. spec = importlib.util.spec_from_file_location(filepath, os.path.abspath(filepath))
  152. module = importlib.util.module_from_spec(spec)
  153. spec.loader.exec_module(module)
  154. return module
  155. def log(self, text, log_type='i'):
  156. self.l.log(text, log_type)