help.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. # ©️ Dan Gazizullin, 2021-2023
  2. # This file is a part of Hikka Userbot
  3. # 🌐 https://github.com/hikariatama/Hikka
  4. # You can redistribute it and/or modify it under the terms of the GNU AGPLv3
  5. # 🔑 https://www.gnu.org/licenses/agpl-3.0.html
  6. import difflib
  7. import inspect
  8. import logging
  9. from hikkatl.extensions.html import CUSTOM_EMOJIS
  10. from hikkatl.tl.types import Message
  11. from .. import loader, utils
  12. from ..compat.dragon import DRAGON_EMOJI
  13. from ..types import DragonModule
  14. logger = logging.getLogger(__name__)
  15. @loader.tds
  16. class Help(loader.Module):
  17. """Shows help for modules and commands"""
  18. strings = {"name": "Help"}
  19. def __init__(self):
  20. self.config = loader.ModuleConfig(
  21. loader.ConfigValue(
  22. "core_emoji",
  23. "▪️",
  24. lambda: "Core module bullet",
  25. validator=loader.validators.Emoji(length=1),
  26. ),
  27. loader.ConfigValue(
  28. "plain_emoji",
  29. "▫️",
  30. lambda: "Plain module bullet",
  31. validator=loader.validators.Emoji(length=1),
  32. ),
  33. loader.ConfigValue(
  34. "empty_emoji",
  35. "🙈",
  36. lambda: "Empty modules bullet",
  37. validator=loader.validators.Emoji(length=1),
  38. ),
  39. )
  40. @loader.command()
  41. async def helphide(self, message: Message):
  42. if not (modules := utils.get_args(message)):
  43. await utils.answer(message, self.strings("no_mod"))
  44. return
  45. currently_hidden = self.get("hide", [])
  46. hidden, shown = [], []
  47. for module in filter(
  48. lambda module: self.lookup(module, include_dragon=True), modules
  49. ):
  50. module = self.lookup(module, include_dragon=True)
  51. module = (
  52. module.name
  53. if isinstance(module, DragonModule)
  54. else module.__class__.__name__
  55. )
  56. if module in currently_hidden:
  57. currently_hidden.remove(module)
  58. shown += [module]
  59. else:
  60. currently_hidden += [module]
  61. hidden += [module]
  62. self.set("hide", currently_hidden)
  63. await utils.answer(
  64. message,
  65. self.strings("hidden_shown").format(
  66. len(hidden),
  67. len(shown),
  68. "\n".join([f"👁‍🗨 <i>{m}</i>" for m in hidden]),
  69. "\n".join([f"👁 <i>{m}</i>" for m in shown]),
  70. ),
  71. )
  72. def find_aliases(self, command: str) -> list:
  73. """Find aliases for command"""
  74. aliases = []
  75. _command = self.allmodules.commands[command]
  76. if getattr(_command, "alias", None) and not (
  77. aliases := getattr(_command, "aliases", None)
  78. ):
  79. aliases = [_command.alias]
  80. return aliases or []
  81. async def modhelp(self, message: Message, args: str):
  82. exact = True
  83. if not (module := self.lookup(args, include_dragon=True)):
  84. if method := self.allmodules.dispatch(
  85. args.lower().strip(self.get_prefix())
  86. )[1]:
  87. module = method.__self__
  88. else:
  89. module = self.lookup(
  90. next(
  91. (
  92. reversed(
  93. sorted(
  94. [
  95. module.strings["name"]
  96. for module in self.allmodules.modules
  97. ],
  98. key=lambda x: difflib.SequenceMatcher(
  99. None,
  100. args.lower(),
  101. x,
  102. ).ratio(),
  103. )
  104. )
  105. ),
  106. None,
  107. )
  108. )
  109. exact = False
  110. is_dragon = isinstance(module, DragonModule)
  111. try:
  112. name = module.strings("name")
  113. except (KeyError, AttributeError):
  114. name = getattr(module, "name", "ERROR")
  115. _name = (
  116. "{} (v{}.{}.{})".format(
  117. utils.escape_html(name),
  118. module.__version__[0],
  119. module.__version__[1],
  120. module.__version__[2],
  121. )
  122. if hasattr(module, "__version__")
  123. else utils.escape_html(name)
  124. )
  125. reply = "{} <b>{}</b>:".format(
  126. (
  127. DRAGON_EMOJI
  128. if is_dragon
  129. else "<emoji document_id=5188377234380954537>🌘</emoji>"
  130. ),
  131. _name,
  132. )
  133. if module.__doc__:
  134. reply += (
  135. "<i>\n<emoji document_id=5787544344906959608>ℹ️</emoji> "
  136. + utils.escape_html(inspect.getdoc(module))
  137. + "\n</i>"
  138. )
  139. commands = (
  140. module.commands
  141. if is_dragon
  142. else {
  143. name: func
  144. for name, func in module.commands.items()
  145. if await self.allmodules.check_security(message, func)
  146. }
  147. )
  148. if hasattr(module, "inline_handlers") and not is_dragon:
  149. for name, fun in module.inline_handlers.items():
  150. reply += (
  151. "\n<emoji document_id=5372981976804366741>🤖</emoji>"
  152. " <code>{}</code> {}".format(
  153. f"@{self.inline.bot_username} {name}",
  154. (
  155. utils.escape_html(inspect.getdoc(fun))
  156. if fun.__doc__
  157. else self.strings("undoc")
  158. ),
  159. )
  160. )
  161. for name, fun in commands.items():
  162. reply += (
  163. "\n<emoji document_id=4971987363145188045>▫️</emoji>"
  164. " <code>{}{}</code>{} {}".format(
  165. utils.escape_html(self.get_prefix("dragon" if is_dragon else None)),
  166. name,
  167. (
  168. " ({})".format(
  169. ", ".join(
  170. "<code>{}{}</code>".format(
  171. utils.escape_html(
  172. self.get_prefix("dragon" if is_dragon else None)
  173. ),
  174. alias,
  175. )
  176. for alias in self.find_aliases(name)
  177. )
  178. )
  179. if self.find_aliases(name)
  180. else ""
  181. ),
  182. (
  183. utils.escape_html(fun)
  184. if is_dragon
  185. else (
  186. utils.escape_html(inspect.getdoc(fun))
  187. if fun.__doc__
  188. else self.strings("undoc")
  189. )
  190. ),
  191. )
  192. )
  193. await utils.answer(
  194. message,
  195. reply
  196. + (f"\n\n{self.strings('not_exact')}" if not exact else "")
  197. + (
  198. f"\n\n{self.strings('core_notice')}"
  199. if module.__origin__.startswith("<core")
  200. else ""
  201. ),
  202. )
  203. @loader.command()
  204. async def help(self, message: Message):
  205. args = utils.get_args_raw(message)
  206. force = False
  207. if "-f" in args:
  208. args = args.replace(" -f", "").replace("-f", "")
  209. force = True
  210. if args:
  211. await self.modhelp(message, args)
  212. return
  213. hidden = self.get("hide", [])
  214. reply = self.strings("all_header").format(
  215. len(self.allmodules.modules) + len(self.allmodules.dragon_modules),
  216. (
  217. 0
  218. if force
  219. else sum(
  220. module.__class__.__name__ in hidden
  221. for module in self.allmodules.modules
  222. )
  223. + sum(
  224. module.name in hidden for module in self.allmodules.dragon_modules
  225. )
  226. ),
  227. )
  228. shown_warn = False
  229. plain_ = []
  230. core_ = []
  231. no_commands_ = []
  232. dragon_ = []
  233. for mod in self.allmodules.dragon_modules:
  234. if mod.name in self.get("hide", []) and not force:
  235. continue
  236. tmp = "\n{} <code>{}</code>".format(DRAGON_EMOJI, mod.name)
  237. first = True
  238. for cmd in mod.commands:
  239. cmd = cmd.split()[0]
  240. if first:
  241. tmp += f": ( {cmd}"
  242. first = False
  243. else:
  244. tmp += f" | {cmd}"
  245. dragon_ += [tmp + " )"]
  246. for mod in self.allmodules.modules:
  247. if not hasattr(mod, "commands"):
  248. logger.debug("Module %s is not inited yet", mod.__class__.__name__)
  249. continue
  250. if mod.__class__.__name__ in self.get("hide", []) and not force:
  251. continue
  252. tmp = ""
  253. try:
  254. name = mod.strings["name"]
  255. except KeyError:
  256. name = getattr(mod, "name", "ERROR")
  257. if (
  258. not getattr(mod, "commands", None)
  259. and not getattr(mod, "inline_handlers", None)
  260. and not getattr(mod, "callback_handlers", None)
  261. ):
  262. no_commands_ += [
  263. "\n{} <code>{}</code>".format(self.config["empty_emoji"], name)
  264. ]
  265. continue
  266. core = mod.__origin__.startswith("<core")
  267. tmp += "\n{} <code>{}</code>".format(
  268. self.config["core_emoji"] if core else self.config["plain_emoji"], name
  269. )
  270. first = True
  271. commands = [
  272. name
  273. for name, func in mod.commands.items()
  274. if await self.allmodules.check_security(message, func) or force
  275. ]
  276. for cmd in commands:
  277. if first:
  278. tmp += f": ( {cmd}"
  279. first = False
  280. else:
  281. tmp += f" | {cmd}"
  282. icommands = [
  283. name
  284. for name, func in mod.inline_handlers.items()
  285. if await self.inline.check_inline_security(
  286. func=func,
  287. user=message.sender_id,
  288. )
  289. or force
  290. ]
  291. for cmd in icommands:
  292. if first:
  293. tmp += f": ( 🤖 {cmd}"
  294. first = False
  295. else:
  296. tmp += f" | 🤖 {cmd}"
  297. if commands or icommands:
  298. tmp += " )"
  299. if core:
  300. core_ += [tmp]
  301. else:
  302. plain_ += [tmp]
  303. elif not shown_warn and (mod.commands or mod.inline_handlers):
  304. reply = (
  305. "<i>You have permissions to execute only these"
  306. f" commands</i>\n{reply}"
  307. )
  308. shown_warn = True
  309. plain_.sort(key=lambda x: x.split()[1])
  310. core_.sort(key=lambda x: x.split()[1])
  311. no_commands_.sort(key=lambda x: x.split()[1])
  312. dragon_.sort()
  313. await utils.answer(
  314. message,
  315. "{}\n{}{}".format(
  316. reply,
  317. "".join(core_ + plain_ + dragon_ + (no_commands_ if force else [])),
  318. (
  319. ""
  320. if self.lookup("Loader").fully_loaded
  321. else f"\n\n{self.strings('partial_load')}"
  322. ),
  323. ),
  324. )
  325. @loader.command()
  326. async def support(self, message):
  327. if message.out:
  328. await self.request_join("@hikka_talks", self.strings("request_join"))
  329. await utils.answer(
  330. message,
  331. self.strings("support").format(
  332. (
  333. utils.get_platform_emoji()
  334. if self._client.hikka_me.premium and CUSTOM_EMOJIS
  335. else "🌘"
  336. )
  337. ),
  338. )