help.py 14 KB

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