help.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. # █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
  2. # █▀█ █ █ █ █▀█ █▀▄ █
  3. # © Copyright 2022
  4. # https://t.me/hikariatama
  5. #
  6. # 🔒 Licensed under the GNU AGPLv3
  7. # 🌐 https://www.gnu.org/licenses/agpl-3.0.html
  8. import difflib
  9. import inspect
  10. import logging
  11. from telethon.tl.types import Message
  12. from .. import loader, utils
  13. logger = logging.getLogger(__name__)
  14. @loader.tds
  15. class HelpMod(loader.Module):
  16. """Shows help for modules and commands"""
  17. strings = {
  18. "name": "Help",
  19. "bad_module": "<b>🚫 <b>Module</b> <code>{}</code> <b>not found</b>",
  20. "single_mod_header": (
  21. "<emoji document_id=5188377234380954537>🌘</emoji> <b>{}</b>:"
  22. ),
  23. "single_cmd": "\n▫️ <code>{}{}</code> {}",
  24. "undoc_cmd": "🦥 No docs",
  25. "all_header": (
  26. "<emoji document_id=5188377234380954537>🌘</emoji> <b>{} mods available,"
  27. " {} hidden:</b>"
  28. ),
  29. "mod_tmpl": "\n{} <code>{}</code>",
  30. "first_cmd_tmpl": ": ( {}",
  31. "cmd_tmpl": " | {}",
  32. "no_mod": "🚫 <b>Specify module to hide</b>",
  33. "hidden_shown": (
  34. "<emoji document_id=5188377234380954537>🌘</emoji> <b>{} modules hidden,"
  35. " {} modules shown:</b>\n{}\n{}"
  36. ),
  37. "ihandler": "\n🎹 <code>{}</code> {}",
  38. "undoc_ihandler": "🦥 No docs",
  39. "support": (
  40. "{} <b>Link to </b><a href='https://t.me/hikka_talks'>support chat</a>"
  41. ),
  42. "partial_load": (
  43. "<emoji document_id=5472105307985419058>☝️</emoji> <b>Userbot is not"
  44. " fully loaded, so not all modules are shown</b>"
  45. ),
  46. "not_exact": (
  47. "<emoji document_id=5472105307985419058>☝️</emoji> <b>No exact match"
  48. " occured, so the closest result is shown instead</b>"
  49. ),
  50. "request_join": "You requested link for Hikka support chat",
  51. "core_notice": (
  52. "<emoji document_id=5472105307985419058>☝️</emoji> <b>This is a core"
  53. " module. You can't unload it nor replace</b>"
  54. ),
  55. }
  56. strings_ru = {
  57. "bad_module": "<b>🚫 <b>Модуль</b> <code>{}</code> <b>не найден</b>",
  58. "single_mod_header": (
  59. "<emoji document_id=5188377234380954537>🌘</emoji> <b>{}</b>:"
  60. ),
  61. "single_cmd": "\n▫️ <code>{}{}</code> {}",
  62. "undoc_cmd": "🦥 Нет описания",
  63. "all_header": (
  64. "<emoji document_id=5188377234380954537>🌘</emoji> <b>{} модулей доступно,"
  65. " {} скрыто:</b>"
  66. ),
  67. "mod_tmpl": "\n{} <code>{}</code>",
  68. "first_cmd_tmpl": ": ( {}",
  69. "cmd_tmpl": " | {}",
  70. "no_mod": "🚫 <b>Укажи модуль(-и), которые нужно скрыть</b>",
  71. "hidden_shown": (
  72. "<emoji document_id=5188377234380954537>🌘</emoji> <b>{} модулей скрыто,"
  73. " {} модулей показано:</b>\n{}\n{}"
  74. ),
  75. "ihandler": "\n🎹 <code>{}</code> {}",
  76. "undoc_ihandler": "🦥 Нет описания",
  77. "support": (
  78. "{} <b>Ссылка на </b><a href='https://t.me/hikka_talks'>чат помощи</a>"
  79. ),
  80. "_cls_doc": "Показывает помощь по модулям",
  81. "partial_load": (
  82. "<emoji document_id=5472105307985419058>☝️</emoji> <b>Юзербот еще не"
  83. " загрузился полностью, поэтому показаны не все модули</b>"
  84. ),
  85. "not_exact": (
  86. "<emoji document_id=5472105307985419058>☝️</emoji> <b>Точного совпадения"
  87. " не нашлось, поэтому было выбрано наиболее подходящее</b>"
  88. ),
  89. "request_join": "Вы запросили ссылку на чат помощи Hikka",
  90. "core_notice": (
  91. "<emoji document_id=5472105307985419058>☝️</emoji> <b>Это встроенный"
  92. " модуль. Вы не можете его выгрузить или заменить</b>"
  93. ),
  94. }
  95. def __init__(self):
  96. self.config = loader.ModuleConfig(
  97. loader.ConfigValue(
  98. "core_emoji",
  99. "▪️",
  100. lambda: "Core module bullet",
  101. validator=loader.validators.Emoji(length=1),
  102. ),
  103. loader.ConfigValue(
  104. "hikka_emoji",
  105. "🌘",
  106. lambda: "Hikka-only module bullet",
  107. validator=loader.validators.Emoji(length=1),
  108. ),
  109. loader.ConfigValue(
  110. "plain_emoji",
  111. "▫️",
  112. lambda: "Plain module bullet",
  113. validator=loader.validators.Emoji(length=1),
  114. ),
  115. loader.ConfigValue(
  116. "empty_emoji",
  117. "🙈",
  118. lambda: "Empty modules bullet",
  119. validator=loader.validators.Emoji(length=1),
  120. ),
  121. )
  122. @loader.command(
  123. ru_doc=(
  124. "<модуль или модули> - Спрятать модуль(-и) из помощи\n*Разделяй модули"
  125. " пробелами"
  126. )
  127. )
  128. async def helphide(self, message: Message):
  129. """<module or modules> - Hide module(-s) from help
  130. *Split modules by spaces"""
  131. modules = utils.get_args(message)
  132. if not modules:
  133. await utils.answer(message, self.strings("no_mod"))
  134. return
  135. mods = [i.__class__.__name__ for i in self.allmodules.modules]
  136. modules = list(filter(lambda module: module in mods, modules))
  137. currently_hidden = self.get("hide", [])
  138. hidden, shown = [], []
  139. for module in modules:
  140. if module in currently_hidden:
  141. currently_hidden.remove(module)
  142. shown += [module]
  143. else:
  144. currently_hidden += [module]
  145. hidden += [module]
  146. self.set("hide", currently_hidden)
  147. await utils.answer(
  148. message,
  149. self.strings("hidden_shown").format(
  150. len(hidden),
  151. len(shown),
  152. "\n".join([f"👁‍🗨 <i>{m}</i>" for m in hidden]),
  153. "\n".join([f"👁 <i>{m}</i>" for m in shown]),
  154. ),
  155. )
  156. async def modhelp(self, message: Message, args: str):
  157. exact = True
  158. module = self.lookup(args)
  159. if not module:
  160. _args = args.lower()
  161. _args = _args[1:] if _args.startswith(self.get_prefix()) else _args
  162. if _args in self.allmodules.commands:
  163. module = self.allmodules.commands[_args].__self__
  164. if not module:
  165. module = self.lookup(
  166. next(
  167. (
  168. reversed(
  169. sorted(
  170. [
  171. module.strings["name"]
  172. for module in self.allmodules.modules
  173. ],
  174. key=lambda x: difflib.SequenceMatcher(
  175. None,
  176. args.lower(),
  177. x,
  178. ).ratio(),
  179. )
  180. )
  181. ),
  182. None,
  183. )
  184. )
  185. exact = False
  186. try:
  187. name = module.strings("name")
  188. except KeyError:
  189. name = getattr(module, "name", "ERROR")
  190. _name = (
  191. "{} (v{}.{}.{})".format(
  192. utils.escape_html(name),
  193. module.__version__[0],
  194. module.__version__[1],
  195. module.__version__[2],
  196. )
  197. if hasattr(module, "__version__")
  198. else utils.escape_html(name)
  199. )
  200. reply = self.strings("single_mod_header").format(_name)
  201. if module.__doc__:
  202. reply += "<i>\nℹ️ " + utils.escape_html(inspect.getdoc(module)) + "\n</i>"
  203. commands = {
  204. name: func
  205. for name, func in module.commands.items()
  206. if await self.allmodules.check_security(message, func)
  207. }
  208. if hasattr(module, "inline_handlers"):
  209. for name, fun in module.inline_handlers.items():
  210. reply += self.strings("ihandler").format(
  211. f"@{self.inline.bot_username} {name}",
  212. (
  213. utils.escape_html(inspect.getdoc(fun))
  214. if fun.__doc__
  215. else self.strings("undoc_ihandler")
  216. ),
  217. )
  218. for name, fun in commands.items():
  219. reply += self.strings("single_cmd").format(
  220. self.get_prefix(),
  221. name,
  222. (
  223. utils.escape_html(inspect.getdoc(fun))
  224. if fun.__doc__
  225. else self.strings("undoc_cmd")
  226. ),
  227. )
  228. await utils.answer(
  229. message,
  230. f"{reply}\n\n{'' if exact else self.strings('not_exact')}"
  231. + (
  232. f"\n\n{self.strings('core_notice')}"
  233. if module.__origin__.startswith("<core")
  234. else ""
  235. ),
  236. )
  237. @loader.unrestricted
  238. @loader.command(ru_doc="[модуль] [-f] - Показать помощь")
  239. async def help(self, message: Message):
  240. """[module] [-f] - Show help"""
  241. args = utils.get_args_raw(message)
  242. force = False
  243. if "-f" in args:
  244. args = args.replace(" -f", "").replace("-f", "")
  245. force = True
  246. if args:
  247. await self.modhelp(message, args)
  248. return
  249. count = 0
  250. for i in self.allmodules.modules:
  251. try:
  252. if i.commands or i.inline_handlers:
  253. count += 1
  254. except Exception:
  255. pass
  256. hidden = self.get("hide", [])
  257. reply = self.strings("all_header").format(
  258. count,
  259. 0
  260. if force
  261. else len(
  262. [
  263. module
  264. for module in self.allmodules.modules
  265. if module.__class__.__name__ in hidden
  266. ]
  267. ),
  268. )
  269. shown_warn = False
  270. plain_ = []
  271. core_ = []
  272. inline_ = []
  273. no_commands_ = []
  274. for mod in self.allmodules.modules:
  275. if not hasattr(mod, "commands"):
  276. logger.debug("Module %s is not inited yet", mod.__class__.__name__)
  277. continue
  278. if mod.__class__.__name__ in self.get("hide", []) and not force:
  279. continue
  280. tmp = ""
  281. try:
  282. name = mod.strings["name"]
  283. except KeyError:
  284. name = getattr(mod, "name", "ERROR")
  285. inline = (
  286. hasattr(mod, "callback_handlers")
  287. and mod.callback_handlers
  288. or hasattr(mod, "inline_handlers")
  289. and mod.inline_handlers
  290. )
  291. if not inline:
  292. for cmd_ in mod.commands.values():
  293. try:
  294. inline = "await self.inline.form(" in inspect.getsource(
  295. cmd_.__code__
  296. )
  297. except Exception:
  298. pass
  299. core = mod.__origin__.startswith("<core")
  300. if core:
  301. emoji = self.config["core_emoji"]
  302. elif inline:
  303. emoji = self.config["hikka_emoji"]
  304. else:
  305. emoji = self.config["plain_emoji"]
  306. if (
  307. not getattr(mod, "commands", None)
  308. and not getattr(mod, "inline_handlers", None)
  309. and not getattr(mod, "callback_handlers", None)
  310. ):
  311. no_commands_ += [
  312. self.strings("mod_tmpl").format(self.config["empty_emoji"], name)
  313. ]
  314. continue
  315. tmp += self.strings("mod_tmpl").format(emoji, name)
  316. first = True
  317. commands = [
  318. name
  319. for name, func in mod.commands.items()
  320. if await self.allmodules.check_security(message, func) or force
  321. ]
  322. for cmd in commands:
  323. if first:
  324. tmp += self.strings("first_cmd_tmpl").format(cmd)
  325. first = False
  326. else:
  327. tmp += self.strings("cmd_tmpl").format(cmd)
  328. icommands = [
  329. name
  330. for name, func in mod.inline_handlers.items()
  331. if await self.inline.check_inline_security(
  332. func=func,
  333. user=message.sender_id,
  334. )
  335. or force
  336. ]
  337. for cmd in icommands:
  338. if first:
  339. tmp += self.strings("first_cmd_tmpl").format(f"🎹 {cmd}")
  340. first = False
  341. else:
  342. tmp += self.strings("cmd_tmpl").format(f"🎹 {cmd}")
  343. if commands or icommands:
  344. tmp += " )"
  345. if core:
  346. core_ += [tmp]
  347. elif inline:
  348. inline_ += [tmp]
  349. else:
  350. plain_ += [tmp]
  351. elif not shown_warn and (mod.commands or mod.inline_handlers):
  352. reply = (
  353. "<i>You have permissions to execute only these"
  354. f" commands</i>\n{reply}"
  355. )
  356. shown_warn = True
  357. plain_.sort(key=lambda x: x.split()[1])
  358. core_.sort(key=lambda x: x.split()[1])
  359. inline_.sort(key=lambda x: x.split()[1])
  360. no_commands_.sort(key=lambda x: x.split()[1])
  361. no_commands_ = "".join(no_commands_) if force else ""
  362. partial_load = (
  363. ""
  364. if self.lookup("Loader")._fully_loaded
  365. else f"\n\n{self.strings('partial_load')}"
  366. )
  367. await utils.answer(
  368. message,
  369. "{}\n{}{}{}{}{}".format(
  370. reply,
  371. "".join(core_),
  372. "".join(plain_),
  373. "".join(inline_),
  374. no_commands_,
  375. partial_load,
  376. ),
  377. )
  378. @loader.command(ru_doc="Показать ссылку на чат помощи Hikka")
  379. async def support(self, message):
  380. """Get link of Hikka support chat"""
  381. if message.out:
  382. await self.request_join("@hikka_talks", self.strings("request_join"))
  383. await utils.answer(
  384. message,
  385. self.strings("support").format(
  386. '<emoji document_id="5192765204898783881">🌘</emoji><emoji'
  387. ' document_id="5195311729663286630">🌘</emoji><emoji'
  388. ' document_id="5195045669324201904">🌘</emoji>'
  389. if self._client.hikka_me.premium
  390. else "🌘",
  391. ),
  392. )