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. try:
  138. module = next(
  139. mod
  140. for mod in self.allmodules.modules
  141. if mod.strings("name").lower() == args.lower()
  142. )
  143. except Exception:
  144. module = None
  145. if not module:
  146. args = args.lower()
  147. args = args[1:] if args.startswith(self.get_prefix()) else args
  148. if args in self.allmodules.commands:
  149. module = self.allmodules.commands[args].__self__
  150. if not module:
  151. module_name = next( # skipcq: PTC-W0063
  152. reversed(
  153. sorted(
  154. [module.strings["name"] for module in self.allmodules.modules],
  155. key=lambda x: difflib.SequenceMatcher(
  156. None,
  157. args.lower(),
  158. x,
  159. ).ratio(),
  160. )
  161. )
  162. )
  163. module = next( # skipcq: PTC-W0063
  164. module
  165. for module in self.allmodules.modules
  166. if module.strings["name"] == module_name
  167. )
  168. exact = False
  169. try:
  170. name = module.strings("name")
  171. except KeyError:
  172. name = getattr(module, "name", "ERROR")
  173. reply = self.strings("single_mod_header").format(utils.escape_html(name))
  174. if module.__doc__:
  175. reply += "<i>\nℹ️ " + utils.escape_html(inspect.getdoc(module)) + "\n</i>"
  176. commands = {
  177. name: func
  178. for name, func in module.commands.items()
  179. if await self.allmodules.check_security(message, func)
  180. }
  181. if hasattr(module, "inline_handlers"):
  182. for name, fun in module.inline_handlers.items():
  183. reply += self.strings("ihandler").format(
  184. f"@{self.inline.bot_username} {name}",
  185. (
  186. utils.escape_html(inspect.getdoc(fun))
  187. if fun.__doc__
  188. else self.strings("undoc_ihandler")
  189. ),
  190. )
  191. for name, fun in commands.items():
  192. reply += self.strings("single_cmd").format(
  193. self.get_prefix(),
  194. name,
  195. (
  196. utils.escape_html(inspect.getdoc(fun))
  197. if fun.__doc__
  198. else self.strings("undoc_cmd")
  199. ),
  200. )
  201. await utils.answer(
  202. message, f"{reply}\n\n{'' if exact else self.strings('not_exact')}"
  203. )
  204. @loader.unrestricted
  205. async def helpcmd(self, message: Message):
  206. """[module] [-f] - Show help"""
  207. args = utils.get_args_raw(message)
  208. force = False
  209. if "-f" in args:
  210. args = args.replace(" -f", "").replace("-f", "")
  211. force = True
  212. if args:
  213. await self.modhelp(message, args)
  214. return
  215. count = 0
  216. for i in self.allmodules.modules:
  217. try:
  218. if i.commands or i.inline_handlers:
  219. count += 1
  220. except Exception:
  221. pass
  222. hidden = self.get("hide", [])
  223. reply = self.strings("all_header").format(count, 0 if force else len(hidden))
  224. shown_warn = False
  225. plain_ = []
  226. core_ = []
  227. inline_ = []
  228. no_commands_ = []
  229. for mod in self.allmodules.modules:
  230. if not hasattr(mod, "commands"):
  231. logger.debug(f"Module {mod.__class__.__name__} is not inited yet")
  232. continue
  233. if mod.strings["name"] in self.get("hide", []) and not force:
  234. continue
  235. tmp = ""
  236. try:
  237. name = mod.strings["name"]
  238. except KeyError:
  239. name = getattr(mod, "name", "ERROR")
  240. inline = (
  241. hasattr(mod, "callback_handlers")
  242. and mod.callback_handlers
  243. or hasattr(mod, "inline_handlers")
  244. and mod.inline_handlers
  245. )
  246. if not inline:
  247. for cmd_ in mod.commands.values():
  248. try:
  249. inline = "await self.inline.form(" in inspect.getsource(
  250. cmd_.__code__
  251. )
  252. except Exception:
  253. pass
  254. core = mod.__origin__ == "<core>"
  255. if core:
  256. emoji = self.config["core_emoji"]
  257. elif inline:
  258. emoji = self.config["hikka_emoji"]
  259. else:
  260. emoji = self.config["plain_emoji"]
  261. if (
  262. not getattr(mod, "commands", None)
  263. and not getattr(mod, "inline_handlers", None)
  264. and not getattr(mod, "callback_handlers", None)
  265. ):
  266. no_commands_ += [
  267. self.strings("mod_tmpl").format(self.config["empty_emoji"], name)
  268. ]
  269. continue
  270. tmp += self.strings("mod_tmpl").format(emoji, name)
  271. first = True
  272. commands = [
  273. name
  274. for name, func in mod.commands.items()
  275. if await self.allmodules.check_security(message, func) or force
  276. ]
  277. for cmd in commands:
  278. if first:
  279. tmp += self.strings("first_cmd_tmpl").format(cmd)
  280. first = False
  281. else:
  282. tmp += self.strings("cmd_tmpl").format(cmd)
  283. icommands = [
  284. name
  285. for name, func in mod.inline_handlers.items()
  286. if await self.inline.check_inline_security(
  287. func=func,
  288. user=message.sender_id,
  289. )
  290. or force
  291. ]
  292. for cmd in icommands:
  293. if first:
  294. tmp += self.strings("first_cmd_tmpl").format(f"🎹 {cmd}")
  295. first = False
  296. else:
  297. tmp += self.strings("cmd_tmpl").format(f"🎹 {cmd}")
  298. if commands or icommands:
  299. tmp += " )"
  300. if core:
  301. core_ += [tmp]
  302. elif inline:
  303. inline_ += [tmp]
  304. else:
  305. plain_ += [tmp]
  306. elif not shown_warn and (mod.commands or mod.inline_handlers):
  307. reply = (
  308. "<i>You have permissions to execute only these"
  309. f" commands</i>\n{reply}"
  310. )
  311. shown_warn = True
  312. plain_.sort(key=lambda x: x.split()[1])
  313. core_.sort(key=lambda x: x.split()[1])
  314. inline_.sort(key=lambda x: x.split()[1])
  315. no_commands_.sort(key=lambda x: x.split()[1])
  316. no_commands_ = "".join(no_commands_) if force else ""
  317. partial_load = (
  318. ""
  319. if self.lookup("Loader")._fully_loaded
  320. else f"\n\n{self.strings('partial_load')}"
  321. )
  322. await utils.answer(
  323. message,
  324. f"{reply}\n{''.join(core_)}{''.join(plain_)}{''.join(inline_)}{no_commands_}{partial_load}",
  325. )
  326. async def supportcmd(self, message):
  327. """Joins the support Hikka chat"""
  328. if await self.allmodules.check_security(
  329. message,
  330. security.OWNER | security.SUDO,
  331. ):
  332. await self._client(JoinChannelRequest("https://t.me/hikka_talks"))
  333. try:
  334. await self.inline.form(
  335. self.strings("joined"),
  336. reply_markup=[
  337. [{"text": "👩‍💼 Chat", "url": "https://t.me/hikka_talks"}]
  338. ],
  339. ttl=10,
  340. message=message,
  341. )
  342. except Exception:
  343. await utils.answer(message, self.strings("joined"))
  344. else:
  345. try:
  346. await self.inline.form(
  347. self.strings("join"),
  348. reply_markup=[
  349. [{"text": "👩‍💼 Chat", "url": "https://t.me/hikka_talks"}]
  350. ],
  351. ttl=10,
  352. message=message,
  353. )
  354. except Exception:
  355. await utils.answer(message, self.strings("join"))