chio.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. """
  2. Главный файл для обрабоки ввода/вывода через движки.
  3. Author: Milinuri Nirvalen
  4. """
  5. from .files import Config
  6. from .files import fcheck
  7. from .modules import Context
  8. from .ui import Logger
  9. import asyncio
  10. import shlex
  11. import time
  12. class Chio:
  13. """Главный класс бота."""
  14. def __init__(self, backend=None):
  15. # главные атрибуты Чио
  16. self.ver = "2.7.3.2 (70)"
  17. self.l = Logger('Core')
  18. self.c = Config('core', model={'name':'Rin', 'ver':self.ver,
  19. 'names':['/', 'Rin', 'Chio', 'Рин', 'Чио'],
  20. 'ignore_files':[], 'ignore_pid':[], 'ignore_path':[],
  21. 'secure_call':True})
  22. self.__dict__.update(self.c.group_data)
  23. if backend:
  24. self.backend = backend(self)
  25. else:
  26. self.backend = None
  27. self.plugins = []
  28. self.commands = 0
  29. self.start_time = time.time()
  30. # атрибуты обработчиков
  31. self.routers = {}
  32. self.handlers = {'init':[], 'before':[], 'after':[], 'except':[]}
  33. self.sp = {}
  34. self.triggers = {}
  35. self.check_funcs = {}
  36. def start(self, asy=True, debug=False):
  37. '''метод запуска бота
  38. :optional param async: использовать ли async/await
  39. :optional param debug: вызов startEvent без запуска движка'''
  40. if debug:
  41. self.l.info('запуск Чио: РЕЖИМ ОТЛАДКИ')
  42. asyncio.run(self.startEvent(debug=True))
  43. try:
  44. if asy:
  45. self.l.info('запуск Чио')
  46. asyncio.run(self.backend.start())
  47. else:
  48. self.l.log('запуск чио: СИНХРОННЫЙ РЕЖИМ')
  49. self.backend.start()
  50. except KeyboardInterrupt:
  51. asyncio.run(self.stopEvent())
  52. # events: Поведение Чио в определённых ситуациях
  53. # ===============================================
  54. async def startEvent(self, debug=False):
  55. '''метод щапуска Чио
  56. :optional param debug: выключать ли Чио после успешного запуска'''
  57. # проверки дирректорий
  58. self.l.info('проверка директорий Чио')
  59. for x in ['backends', 'data', 'plugins']:
  60. fcheck(x, True)
  61. # инициализирующая прозвонка.
  62. self.l.info('запуск начальной прозвонки')
  63. await self.initCall()
  64. if debug:
  65. self.l.log('проверка запуска завершена.', 'StartEvent')
  66. await self.stopEvent()
  67. else:
  68. self.l.log('Утречка, семпай.', 'StartEvent')
  69. # статус безопасного режима
  70. if self.secure_call:
  71. status = f'{self.l.lgreen}ВКЛЮЧЁН'
  72. else:
  73. status = f'{self.l.lyellow}ОТКЛЮЧЁН'
  74. self.l.log(f'Безопасный режим: {status}.', 'config: secure_call')
  75. async def stopEvent(self):
  76. # поведение Чио при остановке
  77. self.l.log('Спокойной ночи, семпай.', 'StopEvent')
  78. exit()
  79. async def messageEvent(self, event, only_cmd=False):
  80. '''поведение Чио при получении сообщения
  81. :param event: экземпляр события
  82. :optional param only_cmd: запустить только обработчик команд'''
  83. # пропускаем пустые события
  84. if not event:
  85. return True
  86. prefix = None # префикс команд
  87. cmd_prefix = None # дополнительные префикс плагина
  88. handler = {} # функция, которую необходимо выполнить
  89. cmd = '' # название обработчика, отвечающего за функцию
  90. args = [] # список аргументов после команды
  91. sargs = '' # часть текста после команды
  92. utime = time.time()
  93. event = self.backend.setLevel(event,
  94. Config(self.backend.name).group_data)
  95. # перебор преффиксов
  96. for p in self.names:
  97. if event.get('text', '').lower().startswith(p.lower()):
  98. # удаляем префикс и пробелы после него
  99. event.set('text', event.get('text')[len(p):].strip(', '))
  100. prefix = p
  101. text = event.get('text')
  102. break
  103. # если нету префикса или текста - вызываем before и after обработчики
  104. if not prefix or not text:
  105. return await self.eventCall(event)
  106. # перебор преффиков плагинов
  107. for sp in self.sp:
  108. if text.lower().split()[0] == sp:
  109. cmd_prefix = sp
  110. text = text[len(sp):].strip()
  111. break
  112. # формирования словаря обработчиков
  113. router = {}
  114. if cmd_prefix:
  115. target = self.sp[cmd_prefix]
  116. else:
  117. target = self.routers
  118. # если есть уровень, в ином случае не будет ни одной комманды
  119. if event.get('level'):
  120. for k, v in target.items():
  121. # если команда для администраторов бота
  122. if v["admins"] and event.get("level") == 10:
  123. for x in v["cmds"]:
  124. router[x] = v
  125. # команды для обычных пользователей
  126. elif not v["admins"]:
  127. for x in v["cmds"]:
  128. router[x] = v
  129. # заменяем все пропуски строки на пробелы + пропущенные строки
  130. # разделяем сообщение по пробелам
  131. words = text.replace('\n', ' \n').split(' ')
  132. # получаем команду из всего текста
  133. while words:
  134. # если слово есть в словаре обработчиков
  135. if words[0].lower().replace('-', '') in router:
  136. # задаём переменные и останавливаем цыкл
  137. handler = router[words[0].lower().replace('-', '')]
  138. cmd = words[0].lower().replace('-', '')
  139. words.pop(0)
  140. break
  141. # если нет --> удаляем это слово
  142. else:
  143. words.pop(0)
  144. # если есть команда и есть words после цыкла --> получаем аргументы
  145. if handler and words:
  146. # shlex считаем аргумент в кавычках как 1 "1 2 3"
  147. try:
  148. args = shlex.split(' '.join(words).strip())
  149. except Exception as e:
  150. self.l.log(e, 'shlex <e>')
  151. args = ' '.join(words).strip()
  152. sargs = ' '.join(words).strip('\n')
  153. # вызываем прозвонку
  154. return await self.eventCall(
  155. event, args, prefix, sargs, handler, cmd, only_cmd, utime=utime)
  156. # ###########################
  157. # handlers:
  158. # добавление новых обработчиков.
  159. # ###########################
  160. def add_handler(self, name, func, admins=False):
  161. '''добаляет команду и её функцию в саисок
  162. :param name: имя команды
  163. :param func: выполняемая командой функция
  164. :optional param admins: является ли команда для администраторов'''
  165. self.router[name] = {'func':func, 'level':admins}
  166. def add_event_handler(self, func, types='init'):
  167. '''добавить обработчик события
  168. :param func: выполняемая функция обработчика
  169. :param types: список типов событий,
  170. для которых необходимо запускать обработчик'''
  171. if types in self.handlers:
  172. self.handlers[types].append(func)
  173. else:
  174. self.l.log(f'неизвестный тип события: {types}', '<w> addHandler')
  175. # ###########################
  176. # call func:
  177. # функции вызова обработчиков.
  178. # ###########################
  179. async def call(self, func, *args):
  180. """вызов функции с определёнными аргументами
  181. :param func: вызываемая функция
  182. :*param args: передаваемые аргументы в функию"""
  183. if self.secure_call:
  184. try:
  185. return await func(*args)
  186. except Exception as e:
  187. self.l.log(e, 'e')
  188. await self.exceptionCall(e, *args)
  189. else:
  190. return await func(*args)
  191. async def commandCall(self, handler, event, ctx):
  192. """вызов команды
  193. :param handler: набор функций обработчика
  194. :param event: экземпляр события
  195. :param ctx: экземпляр контекста"""
  196. # если есть проверяющая функция
  197. res = True
  198. for k, v in handler["check_func"]:
  199. if k in self.check_funcs:
  200. res = await self.call(self.check_funcs[k], event, ctx, v)
  201. self.l.log(f'{k} ({v}) -> {res}', 'ChF')
  202. else:
  203. self.l.error(f'ChF "{k}" не найдена')
  204. res = False
  205. if not res:
  206. break
  207. if res:
  208. return await self.call(handler["func"], event, ctx)
  209. else:
  210. for k, v in handler["else_func"]:
  211. if k in self.check_funcs:
  212. await self.call(self.check_funcs[k], event, ctx, v)
  213. self.l.log(f'{k} ({v}) -> {res}', 'else F')
  214. else:
  215. self.l.error(f'else F "{k}" не найдена')
  216. async def initCall(self):
  217. # вызов init обработчиков.
  218. for x in self.handlers["init"]:
  219. await self.call(x, self)
  220. async def beforeCommandsCall(self, event, ctx):
  221. """вызов before обработчиков
  222. :param event: экземпляр события
  223. :param ctx: экземпляр контекста"""
  224. resp = []
  225. for x in self.handlers["before"]:
  226. r = await self.call(x, event, ctx)
  227. if r:
  228. resp.append(r)
  229. return resp
  230. async def afterCommandsCall(self, event, ctx):
  231. """вызов after обработчиков
  232. :param event: экземпляр события
  233. :param ctx: экземпляр контекста"""
  234. resp = []
  235. for x in self.handlers["after"]:
  236. r = await self.call(x, event, ctx)
  237. if r:
  238. resp.append(r)
  239. return resp
  240. async def exceptionCall(self, e, event, ctx):
  241. """вызов except обработчиков при возниконовении исключении
  242. :param e: экхемпляр Exception
  243. :param event: экземпляр события
  244. :param ctx: экземпляр контекста"""
  245. for x in self.handlers["except"]:
  246. return await self.call(x, e, event, ctx)
  247. async def eventCall(self, event, args=[], prefix='', sargs='', handler=None,
  248. cmd_name='', only_cmd=False, router={}, utime=0):
  249. ''' событие вызова прозвонки
  250. :param event: экземпляр события
  251. :optional param args: список аргументов команды
  252. :optional param prefix: префикс команды
  253. :optional param sargs: текст после команды
  254. :optional param handler : функции обработчика
  255. :optional param cmd_name : имя комсанды
  256. :optional param only_cmd : вызывать ли команду без обработчиков'''
  257. ctx = Context(self, self.backend, event)
  258. ctx.cmd = cmd_name
  259. ctx.args = args
  260. ctx.prefix = prefix
  261. ctx.sargs = sargs
  262. ctx.handler = handler
  263. ctx.utime = utime
  264. resp = []
  265. if not only_cmd:
  266. resp.append(await self.beforeCommandsCall(event, ctx))
  267. if handler:
  268. resp.append(await self.commandCall(handler, event, ctx))
  269. if not only_cmd:
  270. resp.append(await self.afterCommandsCall(event, ctx))
  271. return resp