hikka_config.py 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165
  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 ast
  9. import functools
  10. from math import ceil
  11. import typing
  12. from telethon.tl.types import Message
  13. from .. import loader, utils, translations
  14. from ..inline.types import InlineCall
  15. # Everywhere in this module, we use the following naming convention:
  16. # `obj_type` of non-core module = False
  17. # `obj_type` of core module = True
  18. # `obj_type` of library = "library"
  19. @loader.tds
  20. class HikkaConfigMod(loader.Module):
  21. """Interactive configurator for Hikka Userbot"""
  22. strings = {
  23. "name": "HikkaConfig",
  24. "choose_core": "🎚 <b>Choose a category</b>",
  25. "configure": "🎚 <b>Choose a module to configure</b>",
  26. "configure_lib": "🪴 <b>Choose a library to configure</b>",
  27. "configuring_mod": (
  28. "🎚 <b>Choose config option for mod</b> <code>{}</code>\n\n<b>Current"
  29. " options:</b>\n\n{}"
  30. ),
  31. "configuring_lib": (
  32. "🪴 <b>Choose config option for library</b> <code>{}</code>\n\n<b>Current"
  33. " options:</b>\n\n{}"
  34. ),
  35. "configuring_option": (
  36. "🎚 <b>Configuring option </b><code>{}</code><b> of mod"
  37. " </b><code>{}</code>\n<i>ℹ️ {}</i>\n\n<b>Default: {}</b>\n\n<b>Current:"
  38. " {}</b>\n\n{}"
  39. ),
  40. "configuring_option_lib": (
  41. "🪴 <b>Configuring option </b><code>{}</code><b> of library"
  42. " </b><code>{}</code>\n<i>ℹ️ {}</i>\n\n<b>Default: {}</b>\n\n<b>Current:"
  43. " {}</b>\n\n{}"
  44. ),
  45. "option_saved": (
  46. "🎚 <b>Option </b><code>{}</code><b> of module </b><code>{}</code><b>"
  47. " saved!</b>\n<b>Current: {}</b>"
  48. ),
  49. "option_saved_lib": (
  50. "🪴 <b>Option </b><code>{}</code><b> of library </b><code>{}</code><b>"
  51. " saved!</b>\n<b>Current: {}</b>"
  52. ),
  53. "option_reset": (
  54. "♻️ <b>Option </b><code>{}</code><b> of module </b><code>{}</code><b> has"
  55. " been reset to default</b>\n<b>Current: {}</b>"
  56. ),
  57. "option_reset_lib": (
  58. "♻️ <b>Option </b><code>{}</code><b> of library </b><code>{}</code><b> has"
  59. " been reset to default</b>\n<b>Current: {}</b>"
  60. ),
  61. "args": "🚫 <b>You specified incorrect args</b>",
  62. "no_mod": "🚫 <b>Module doesn't exist</b>",
  63. "no_option": "🚫 <b>Configuration option doesn't exist</b>",
  64. "validation_error": "🚫 <b>You entered incorrect config value. \nError: {}</b>",
  65. "try_again": "🔁 Try again",
  66. "typehint": "🕵️ <b>Must be a{eng_art} {}</b>",
  67. "set": "set",
  68. "set_default_btn": "♻️ Reset default",
  69. "enter_value_btn": "✍️ Enter value",
  70. "enter_value_desc": "✍️ Enter new configuration value for this option",
  71. "add_item_desc": "✍️ Enter item to add",
  72. "remove_item_desc": "✍️ Enter item to remove",
  73. "back_btn": "👈 Back",
  74. "close_btn": "🔻 Close",
  75. "add_item_btn": "➕ Add item",
  76. "remove_item_btn": "➖ Remove item",
  77. "show_hidden": "🚸 Show value",
  78. "hide_value": "🔒 Hide value",
  79. "builtin": "🛰 Built-in",
  80. "external": "🛸 External",
  81. "libraries": "🪴 Libraries",
  82. }
  83. strings_ru = {
  84. "choose_core": "🎚 <b>Выбери категорию</b>",
  85. "configure": "🎚 <b>Выбери модуль для настройки</b>",
  86. "configure_lib": "🪴 <b>Выбери библиотеку для настройки</b>",
  87. "configuring_mod": (
  88. "🎚 <b>Выбери параметр для модуля</b> <code>{}</code>\n\n<b>Текущие"
  89. " настройки:</b>\n\n{}"
  90. ),
  91. "configuring_lib": (
  92. "🪴 <b>Выбери параметр для библиотеки</b> <code>{}</code>\n\n<b>Текущие"
  93. " настройки:</b>\n\n{}"
  94. ),
  95. "configuring_option": (
  96. "🎚 <b>Управление параметром </b><code>{}</code><b> модуля"
  97. " </b><code>{}</code>\n<i>ℹ️ {}</i>\n\n<b>Стандартное:"
  98. " {}</b>\n\n<b>Текущее: {}</b>\n\n{}"
  99. ),
  100. "configuring_option_lib": (
  101. "🪴 <b>Управление параметром </b><code>{}</code><b> библиотеки"
  102. " </b><code>{}</code>\n<i>ℹ️ {}</i>\n\n<b>Стандартное:"
  103. " {}</b>\n\n<b>Текущее: {}</b>\n\n{}"
  104. ),
  105. "option_saved": (
  106. "🎚 <b>Параметр </b><code>{}</code><b> модуля </b><code>{}</code><b>"
  107. " сохранен!</b>\n<b>Текущее: {}</b>"
  108. ),
  109. "option_saved_lib": (
  110. "🪴 <b>Параметр </b><code>{}</code><b> библиотеки </b><code>{}</code><b>"
  111. " сохранен!</b>\n<b>Текущее: {}</b>"
  112. ),
  113. "option_reset": (
  114. "♻️ <b>Параметр </b><code>{}</code><b> модуля </b><code>{}</code><b>"
  115. " сброшен до значения по умолчанию</b>\n<b>Текущее: {}</b>"
  116. ),
  117. "option_reset_lib": (
  118. "♻️ <b>Параметр </b><code>{}</code><b> библиотеки </b><code>{}</code><b>"
  119. " сброшен до значения по умолчанию</b>\n<b>Текущее: {}</b>"
  120. ),
  121. "_cls_doc": "Интерактивный конфигуратор Hikka",
  122. "args": "🚫 <b>Ты указал неверные аргументы</b>",
  123. "no_mod": "🚫 <b>Модуль не существует</b>",
  124. "no_option": "🚫 <b>У модуля нет такого значения конфига</b>",
  125. "validation_error": (
  126. "🚫 <b>Введено некорректное значение конфига. \nОшибка: {}</b>"
  127. ),
  128. "try_again": "🔁 Попробовать еще раз",
  129. "typehint": "🕵️ <b>Должно быть {}</b>",
  130. "set": "поставить",
  131. "set_default_btn": "♻️ Значение по умолчанию",
  132. "enter_value_btn": "✍️ Ввести значение",
  133. "enter_value_desc": "✍️ Введи новое значение этого параметра",
  134. "add_item_desc": "✍️ Введи элемент, который нужно добавить",
  135. "remove_item_desc": "✍️ Введи элемент, который нужно удалить",
  136. "back_btn": "👈 Назад",
  137. "close_btn": "🔻 Закрыть",
  138. "add_item_btn": "➕ Добавить элемент",
  139. "remove_item_btn": "➖ Удалить элемент",
  140. "show_hidden": "🚸 Показать значение",
  141. "hide_value": "🔒 Скрыть значение",
  142. "builtin": "🛰 Встроенные",
  143. "external": "🛸 Внешние",
  144. "libraries": "🪴 Библиотеки",
  145. }
  146. _row_size = 3
  147. _num_rows = 5
  148. @staticmethod
  149. def prep_value(value: typing.Any) -> typing.Any:
  150. if isinstance(value, str):
  151. return f"</b><code>{utils.escape_html(value.strip())}</code><b>"
  152. if isinstance(value, list) and value:
  153. return (
  154. "</b><code>[</code>\n "
  155. + "\n ".join(
  156. [f"<code>{utils.escape_html(str(item))}</code>" for item in value]
  157. )
  158. + "\n<code>]</code><b>"
  159. )
  160. return f"</b><code>{utils.escape_html(value)}</code><b>"
  161. def hide_value(self, value: typing.Any) -> str:
  162. if isinstance(value, list) and value:
  163. return self.prep_value(["*" * len(str(i)) for i in value])
  164. return self.prep_value("*" * len(str(value)))
  165. async def inline__set_config(
  166. self,
  167. call: InlineCall,
  168. query: str,
  169. mod: str,
  170. option: str,
  171. inline_message_id: str,
  172. obj_type: typing.Union[bool, str] = False,
  173. ):
  174. try:
  175. self.lookup(mod).config[option] = query
  176. except loader.validators.ValidationError as e:
  177. await call.edit(
  178. self.strings("validation_error").format(e.args[0]),
  179. reply_markup={
  180. "text": self.strings("try_again"),
  181. "callback": self.inline__configure_option,
  182. "args": (mod, option),
  183. "kwargs": {"obj_type": obj_type},
  184. },
  185. )
  186. return
  187. await call.edit(
  188. self.strings(
  189. "option_saved" if isinstance(obj_type, bool) else "option_saved_lib"
  190. ).format(
  191. utils.escape_html(option),
  192. utils.escape_html(mod),
  193. self.prep_value(self.lookup(mod).config[option])
  194. if not self.lookup(mod).config._config[option].validator
  195. or self.lookup(mod).config._config[option].validator.internal_id
  196. != "Hidden"
  197. else self.hide_value(self.lookup(mod).config[option]),
  198. ),
  199. reply_markup=[
  200. [
  201. {
  202. "text": self.strings("back_btn"),
  203. "callback": self.inline__configure,
  204. "args": (mod,),
  205. "kwargs": {"obj_type": obj_type},
  206. },
  207. {"text": self.strings("close_btn"), "action": "close"},
  208. ]
  209. ],
  210. inline_message_id=inline_message_id,
  211. )
  212. async def inline__reset_default(
  213. self,
  214. call: InlineCall,
  215. mod: str,
  216. option: str,
  217. obj_type: typing.Union[bool, str] = False,
  218. ):
  219. mod_instance = self.lookup(mod)
  220. mod_instance.config[option] = mod_instance.config.getdef(option)
  221. await call.edit(
  222. self.strings(
  223. "option_reset" if isinstance(obj_type, bool) else "option_reset_lib"
  224. ).format(
  225. utils.escape_html(option),
  226. utils.escape_html(mod),
  227. self.prep_value(self.lookup(mod).config[option])
  228. if not self.lookup(mod).config._config[option].validator
  229. or self.lookup(mod).config._config[option].validator.internal_id
  230. != "Hidden"
  231. else self.hide_value(self.lookup(mod).config[option]),
  232. ),
  233. reply_markup=[
  234. [
  235. {
  236. "text": self.strings("back_btn"),
  237. "callback": self.inline__configure,
  238. "args": (mod,),
  239. "kwargs": {"obj_type": obj_type},
  240. },
  241. {"text": self.strings("close_btn"), "action": "close"},
  242. ]
  243. ],
  244. )
  245. async def inline__set_bool(
  246. self,
  247. call: InlineCall,
  248. mod: str,
  249. option: str,
  250. value: bool,
  251. obj_type: typing.Union[bool, str] = False,
  252. ):
  253. try:
  254. self.lookup(mod).config[option] = value
  255. except loader.validators.ValidationError as e:
  256. await call.edit(
  257. self.strings("validation_error").format(e.args[0]),
  258. reply_markup={
  259. "text": self.strings("try_again"),
  260. "callback": self.inline__configure_option,
  261. "args": (mod, option),
  262. "kwargs": {"obj_type": obj_type},
  263. },
  264. )
  265. return
  266. validator = self.lookup(mod).config._config[option].validator
  267. doc = utils.escape_html(
  268. next(
  269. (
  270. validator.doc[lang]
  271. for lang in self._db.get(translations.__name__, "lang", "en").split(
  272. " "
  273. )
  274. if lang in validator.doc
  275. ),
  276. validator.doc["en"],
  277. )
  278. )
  279. await call.edit(
  280. self.strings(
  281. "configuring_option"
  282. if isinstance(obj_type, bool)
  283. else "configuring_option_lib"
  284. ).format(
  285. utils.escape_html(option),
  286. utils.escape_html(mod),
  287. utils.escape_html(self.lookup(mod).config.getdoc(option)),
  288. self.prep_value(self.lookup(mod).config.getdef(option)),
  289. self.prep_value(self.lookup(mod).config[option])
  290. if not validator or validator.internal_id != "Hidden"
  291. else self.hide_value(self.lookup(mod).config[option]),
  292. self.strings("typehint").format(
  293. doc,
  294. eng_art="n" if doc.lower().startswith(tuple("euioay")) else "",
  295. )
  296. if doc
  297. else "",
  298. ),
  299. reply_markup=self._generate_bool_markup(mod, option, obj_type),
  300. )
  301. await call.answer("✅")
  302. def _generate_bool_markup(
  303. self,
  304. mod: str,
  305. option: str,
  306. obj_type: typing.Union[bool, str] = False,
  307. ) -> list:
  308. return [
  309. [
  310. *(
  311. [
  312. {
  313. "text": f"❌ {self.strings('set')} `False`",
  314. "callback": self.inline__set_bool,
  315. "args": (mod, option, False),
  316. "kwargs": {"obj_type": obj_type},
  317. }
  318. ]
  319. if self.lookup(mod).config[option]
  320. else [
  321. {
  322. "text": f"✅ {self.strings('set')} `True`",
  323. "callback": self.inline__set_bool,
  324. "args": (mod, option, True),
  325. "kwargs": {"obj_type": obj_type},
  326. }
  327. ]
  328. )
  329. ],
  330. [
  331. *(
  332. [
  333. {
  334. "text": self.strings("set_default_btn"),
  335. "callback": self.inline__reset_default,
  336. "args": (mod, option),
  337. "kwargs": {"obj_type": obj_type},
  338. }
  339. ]
  340. if self.lookup(mod).config[option]
  341. != self.lookup(mod).config.getdef(option)
  342. else []
  343. )
  344. ],
  345. [
  346. {
  347. "text": self.strings("back_btn"),
  348. "callback": self.inline__configure,
  349. "args": (mod,),
  350. "kwargs": {"obj_type": obj_type},
  351. },
  352. {"text": self.strings("close_btn"), "action": "close"},
  353. ],
  354. ]
  355. async def inline__add_item(
  356. self,
  357. call: InlineCall,
  358. query: str,
  359. mod: str,
  360. option: str,
  361. inline_message_id: str,
  362. obj_type: typing.Union[bool, str] = False,
  363. ):
  364. try:
  365. try:
  366. query = ast.literal_eval(query)
  367. except Exception:
  368. pass
  369. if isinstance(query, (set, tuple)):
  370. query = list(query)
  371. if not isinstance(query, list):
  372. query = [query]
  373. self.lookup(mod).config[option] = self.lookup(mod).config[option] + query
  374. except loader.validators.ValidationError as e:
  375. await call.edit(
  376. self.strings("validation_error").format(e.args[0]),
  377. reply_markup={
  378. "text": self.strings("try_again"),
  379. "callback": self.inline__configure_option,
  380. "args": (mod, option),
  381. "kwargs": {"obj_type": obj_type},
  382. },
  383. )
  384. return
  385. await call.edit(
  386. self.strings(
  387. "option_saved" if isinstance(obj_type, bool) else "option_saved_lib"
  388. ).format(
  389. utils.escape_html(option),
  390. utils.escape_html(mod),
  391. self.prep_value(self.lookup(mod).config[option])
  392. if not self.lookup(mod).config._config[option].validator
  393. or self.lookup(mod).config._config[option].validator.internal_id
  394. != "Hidden"
  395. else self.hide_value(self.lookup(mod).config[option]),
  396. ),
  397. reply_markup=[
  398. [
  399. {
  400. "text": self.strings("back_btn"),
  401. "callback": self.inline__configure,
  402. "args": (mod,),
  403. "kwargs": {"obj_type": obj_type},
  404. },
  405. {"text": self.strings("close_btn"), "action": "close"},
  406. ]
  407. ],
  408. inline_message_id=inline_message_id,
  409. )
  410. async def inline__remove_item(
  411. self,
  412. call: InlineCall,
  413. query: str,
  414. mod: str,
  415. option: str,
  416. inline_message_id: str,
  417. obj_type: typing.Union[bool, str] = False,
  418. ):
  419. try:
  420. try:
  421. query = ast.literal_eval(query)
  422. except Exception:
  423. pass
  424. if isinstance(query, (set, tuple)):
  425. query = list(query)
  426. if not isinstance(query, list):
  427. query = [query]
  428. query = list(map(str, query))
  429. old_config_len = len(self.lookup(mod).config[option])
  430. self.lookup(mod).config[option] = [
  431. i for i in self.lookup(mod).config[option] if str(i) not in query
  432. ]
  433. if old_config_len == len(self.lookup(mod).config[option]):
  434. raise loader.validators.ValidationError(
  435. f"Nothing from passed value ({self.prep_value(query)}) is not in"
  436. " target list"
  437. )
  438. except loader.validators.ValidationError as e:
  439. await call.edit(
  440. self.strings("validation_error").format(e.args[0]),
  441. reply_markup={
  442. "text": self.strings("try_again"),
  443. "callback": self.inline__configure_option,
  444. "args": (mod, option),
  445. "kwargs": {"obj_type": obj_type},
  446. },
  447. )
  448. return
  449. await call.edit(
  450. self.strings(
  451. "option_saved" if isinstance(obj_type, bool) else "option_saved_lib"
  452. ).format(
  453. utils.escape_html(option),
  454. utils.escape_html(mod),
  455. self.prep_value(self.lookup(mod).config[option])
  456. if not self.lookup(mod).config._config[option].validator
  457. or self.lookup(mod).config._config[option].validator.internal_id
  458. != "Hidden"
  459. else self.hide_value(self.lookup(mod).config[option]),
  460. ),
  461. reply_markup=[
  462. [
  463. {
  464. "text": self.strings("back_btn"),
  465. "callback": self.inline__configure,
  466. "args": (mod,),
  467. "kwargs": {"obj_type": obj_type},
  468. },
  469. {"text": self.strings("close_btn"), "action": "close"},
  470. ]
  471. ],
  472. inline_message_id=inline_message_id,
  473. )
  474. def _generate_series_markup(
  475. self,
  476. call: InlineCall,
  477. mod: str,
  478. option: str,
  479. obj_type: typing.Union[bool, str] = False,
  480. ) -> list:
  481. return [
  482. [
  483. {
  484. "text": self.strings("enter_value_btn"),
  485. "input": self.strings("enter_value_desc"),
  486. "handler": self.inline__set_config,
  487. "args": (mod, option, call.inline_message_id),
  488. "kwargs": {"obj_type": obj_type},
  489. }
  490. ],
  491. [
  492. *(
  493. [
  494. {
  495. "text": self.strings("remove_item_btn"),
  496. "input": self.strings("remove_item_desc"),
  497. "handler": self.inline__remove_item,
  498. "args": (mod, option, call.inline_message_id),
  499. "kwargs": {"obj_type": obj_type},
  500. },
  501. {
  502. "text": self.strings("add_item_btn"),
  503. "input": self.strings("add_item_desc"),
  504. "handler": self.inline__add_item,
  505. "args": (mod, option, call.inline_message_id),
  506. "kwargs": {"obj_type": obj_type},
  507. },
  508. ]
  509. if self.lookup(mod).config[option]
  510. else []
  511. ),
  512. ],
  513. [
  514. *(
  515. [
  516. {
  517. "text": self.strings("set_default_btn"),
  518. "callback": self.inline__reset_default,
  519. "args": (mod, option),
  520. "kwargs": {"obj_type": obj_type},
  521. }
  522. ]
  523. if self.lookup(mod).config[option]
  524. != self.lookup(mod).config.getdef(option)
  525. else []
  526. )
  527. ],
  528. [
  529. {
  530. "text": self.strings("back_btn"),
  531. "callback": self.inline__configure,
  532. "args": (mod,),
  533. "kwargs": {"obj_type": obj_type},
  534. },
  535. {"text": self.strings("close_btn"), "action": "close"},
  536. ],
  537. ]
  538. async def _choice_set_value(
  539. self,
  540. call: InlineCall,
  541. mod: str,
  542. option: str,
  543. value: bool,
  544. obj_type: typing.Union[bool, str] = False,
  545. ):
  546. try:
  547. self.lookup(mod).config[option] = value
  548. except loader.validators.ValidationError as e:
  549. await call.edit(
  550. self.strings("validation_error").format(e.args[0]),
  551. reply_markup={
  552. "text": self.strings("try_again"),
  553. "callback": self.inline__configure_option,
  554. "args": (mod, option),
  555. "kwargs": {"obj_type": obj_type},
  556. },
  557. )
  558. return
  559. validator = self.lookup(mod).config._config[option].validator
  560. await call.edit(
  561. self.strings(
  562. "option_saved" if isinstance(obj_type, bool) else "option_saved_lib"
  563. ).format(
  564. utils.escape_html(option),
  565. utils.escape_html(mod),
  566. self.prep_value(self.lookup(mod).config[option])
  567. if not validator.internal_id == "Hidden"
  568. else self.hide_value(self.lookup(mod).config[option]),
  569. ),
  570. reply_markup=[
  571. [
  572. {
  573. "text": self.strings("back_btn"),
  574. "callback": self.inline__configure,
  575. "args": (mod,),
  576. "kwargs": {"obj_type": obj_type},
  577. },
  578. {"text": self.strings("close_btn"), "action": "close"},
  579. ]
  580. ],
  581. )
  582. await call.answer("✅")
  583. async def _multi_choice_set_value(
  584. self,
  585. call: InlineCall,
  586. mod: str,
  587. option: str,
  588. value: bool,
  589. obj_type: typing.Union[bool, str] = False,
  590. ):
  591. try:
  592. if value in self.lookup(mod).config._config[option].value:
  593. self.lookup(mod).config._config[option].value.remove(value)
  594. else:
  595. self.lookup(mod).config._config[option].value += [value]
  596. self.lookup(mod).config.reload()
  597. except loader.validators.ValidationError as e:
  598. await call.edit(
  599. self.strings("validation_error").format(e.args[0]),
  600. reply_markup={
  601. "text": self.strings("try_again"),
  602. "callback": self.inline__configure_option,
  603. "args": (mod, option),
  604. "kwargs": {"obj_type": obj_type},
  605. },
  606. )
  607. return
  608. await self.inline__configure_option(call, mod, option, False, obj_type)
  609. await call.answer("✅")
  610. def _generate_choice_markup(
  611. self,
  612. call: InlineCall,
  613. mod: str,
  614. option: str,
  615. obj_type: typing.Union[bool, str] = False,
  616. ) -> list:
  617. possible_values = list(
  618. self.lookup(mod)
  619. .config._config[option]
  620. .validator.validate.keywords["possible_values"]
  621. )
  622. return [
  623. [
  624. {
  625. "text": self.strings("enter_value_btn"),
  626. "input": self.strings("enter_value_desc"),
  627. "handler": self.inline__set_config,
  628. "args": (mod, option, call.inline_message_id),
  629. "kwargs": {"obj_type": obj_type},
  630. }
  631. ],
  632. *utils.chunks(
  633. [
  634. {
  635. "text": (
  636. f"{'☑️' if self.lookup(mod).config[option] == value else '🔘'} "
  637. f"{value if len(str(value)) < 20 else str(value)[:20]}"
  638. ),
  639. "callback": self._choice_set_value,
  640. "args": (mod, option, value, obj_type),
  641. }
  642. for value in possible_values
  643. ],
  644. 2,
  645. )[
  646. : 6
  647. if self.lookup(mod).config[option]
  648. != self.lookup(mod).config.getdef(option)
  649. else 7
  650. ],
  651. [
  652. *(
  653. [
  654. {
  655. "text": self.strings("set_default_btn"),
  656. "callback": self.inline__reset_default,
  657. "args": (mod, option),
  658. "kwargs": {"obj_type": obj_type},
  659. }
  660. ]
  661. if self.lookup(mod).config[option]
  662. != self.lookup(mod).config.getdef(option)
  663. else []
  664. )
  665. ],
  666. [
  667. {
  668. "text": self.strings("back_btn"),
  669. "callback": self.inline__configure,
  670. "args": (mod,),
  671. "kwargs": {"obj_type": obj_type},
  672. },
  673. {"text": self.strings("close_btn"), "action": "close"},
  674. ],
  675. ]
  676. def _generate_multi_choice_markup(
  677. self,
  678. call: InlineCall,
  679. mod: str,
  680. option: str,
  681. obj_type: typing.Union[bool, str] = False,
  682. ) -> list:
  683. possible_values = list(
  684. self.lookup(mod)
  685. .config._config[option]
  686. .validator.validate.keywords["possible_values"]
  687. )
  688. return [
  689. [
  690. {
  691. "text": self.strings("enter_value_btn"),
  692. "input": self.strings("enter_value_desc"),
  693. "handler": self.inline__set_config,
  694. "args": (mod, option, call.inline_message_id),
  695. "kwargs": {"obj_type": obj_type},
  696. }
  697. ],
  698. *utils.chunks(
  699. [
  700. {
  701. "text": (
  702. f"{'☑️' if value in self.lookup(mod).config[option] else '◻️'} "
  703. f"{value if len(str(value)) < 20 else str(value)[:20]}"
  704. ),
  705. "callback": self._multi_choice_set_value,
  706. "args": (mod, option, value, obj_type),
  707. }
  708. for value in possible_values
  709. ],
  710. 2,
  711. )[
  712. : 6
  713. if self.lookup(mod).config[option]
  714. != self.lookup(mod).config.getdef(option)
  715. else 7
  716. ],
  717. [
  718. *(
  719. [
  720. {
  721. "text": self.strings("set_default_btn"),
  722. "callback": self.inline__reset_default,
  723. "args": (mod, option),
  724. "kwargs": {"obj_type": obj_type},
  725. }
  726. ]
  727. if self.lookup(mod).config[option]
  728. != self.lookup(mod).config.getdef(option)
  729. else []
  730. )
  731. ],
  732. [
  733. {
  734. "text": self.strings("back_btn"),
  735. "callback": self.inline__configure,
  736. "args": (mod,),
  737. "kwargs": {"obj_type": obj_type},
  738. },
  739. {"text": self.strings("close_btn"), "action": "close"},
  740. ],
  741. ]
  742. async def inline__configure_option(
  743. self,
  744. call: InlineCall,
  745. mod: str,
  746. config_opt: str,
  747. force_hidden: bool = False,
  748. obj_type: typing.Union[bool, str] = False,
  749. ):
  750. module = self.lookup(mod)
  751. args = [
  752. utils.escape_html(config_opt),
  753. utils.escape_html(mod),
  754. utils.escape_html(module.config.getdoc(config_opt)),
  755. self.prep_value(module.config.getdef(config_opt)),
  756. self.prep_value(module.config[config_opt])
  757. if not module.config._config[config_opt].validator
  758. or module.config._config[config_opt].validator.internal_id != "Hidden"
  759. or force_hidden
  760. else self.hide_value(module.config[config_opt]),
  761. ]
  762. if (
  763. module.config._config[config_opt].validator
  764. and module.config._config[config_opt].validator.internal_id == "Hidden"
  765. ):
  766. additonal_button_row = (
  767. [
  768. [
  769. {
  770. "text": self.strings("hide_value"),
  771. "callback": self.inline__configure_option,
  772. "args": (mod, config_opt, False),
  773. "kwargs": {"obj_type": obj_type},
  774. }
  775. ]
  776. ]
  777. if force_hidden
  778. else [
  779. [
  780. {
  781. "text": self.strings("show_hidden"),
  782. "callback": self.inline__configure_option,
  783. "args": (mod, config_opt, True),
  784. "kwargs": {"obj_type": obj_type},
  785. }
  786. ]
  787. ]
  788. )
  789. else:
  790. additonal_button_row = []
  791. try:
  792. validator = module.config._config[config_opt].validator
  793. doc = utils.escape_html(
  794. next(
  795. (
  796. validator.doc[lang]
  797. for lang in self._db.get(
  798. translations.__name__, "lang", "en"
  799. ).split(" ")
  800. if lang in validator.doc
  801. ),
  802. validator.doc["en"],
  803. )
  804. )
  805. except Exception:
  806. doc = None
  807. validator = None
  808. args += [""]
  809. else:
  810. args += [
  811. self.strings("typehint").format(
  812. doc,
  813. eng_art="n" if doc.lower().startswith(tuple("euioay")) else "",
  814. )
  815. ]
  816. if validator.internal_id == "Boolean":
  817. await call.edit(
  818. self.strings(
  819. "configuring_option"
  820. if isinstance(obj_type, bool)
  821. else "configuring_option_lib"
  822. ).format(*args),
  823. reply_markup=additonal_button_row
  824. + self._generate_bool_markup(mod, config_opt, obj_type),
  825. )
  826. return
  827. if validator.internal_id == "Series":
  828. await call.edit(
  829. self.strings(
  830. "configuring_option"
  831. if isinstance(obj_type, bool)
  832. else "configuring_option_lib"
  833. ).format(*args),
  834. reply_markup=additonal_button_row
  835. + self._generate_series_markup(call, mod, config_opt, obj_type),
  836. )
  837. return
  838. if validator.internal_id == "Choice":
  839. await call.edit(
  840. self.strings(
  841. "configuring_option"
  842. if isinstance(obj_type, bool)
  843. else "configuring_option_lib"
  844. ).format(*args),
  845. reply_markup=additonal_button_row
  846. + self._generate_choice_markup(call, mod, config_opt, obj_type),
  847. )
  848. return
  849. if validator.internal_id == "MultiChoice":
  850. await call.edit(
  851. self.strings(
  852. "configuring_option"
  853. if isinstance(obj_type, bool)
  854. else "configuring_option_lib"
  855. ).format(*args),
  856. reply_markup=additonal_button_row
  857. + self._generate_multi_choice_markup(
  858. call, mod, config_opt, obj_type
  859. ),
  860. )
  861. return
  862. await call.edit(
  863. self.strings(
  864. "configuring_option"
  865. if isinstance(obj_type, bool)
  866. else "configuring_option_lib"
  867. ).format(*args),
  868. reply_markup=additonal_button_row
  869. + [
  870. [
  871. {
  872. "text": self.strings("enter_value_btn"),
  873. "input": self.strings("enter_value_desc"),
  874. "handler": self.inline__set_config,
  875. "args": (mod, config_opt, call.inline_message_id),
  876. "kwargs": {"obj_type": obj_type},
  877. }
  878. ],
  879. [
  880. {
  881. "text": self.strings("set_default_btn"),
  882. "callback": self.inline__reset_default,
  883. "args": (mod, config_opt),
  884. "kwargs": {"obj_type": obj_type},
  885. }
  886. ],
  887. [
  888. {
  889. "text": self.strings("back_btn"),
  890. "callback": self.inline__configure,
  891. "args": (mod,),
  892. "kwargs": {"obj_type": obj_type},
  893. },
  894. {"text": self.strings("close_btn"), "action": "close"},
  895. ],
  896. ],
  897. )
  898. async def inline__configure(
  899. self,
  900. call: InlineCall,
  901. mod: str,
  902. obj_type: typing.Union[bool, str] = False,
  903. ):
  904. btns = [
  905. {
  906. "text": param,
  907. "callback": self.inline__configure_option,
  908. "args": (mod, param),
  909. "kwargs": {"obj_type": obj_type},
  910. }
  911. for param in self.lookup(mod).config
  912. ]
  913. await call.edit(
  914. self.strings(
  915. "configuring_mod" if isinstance(obj_type, bool) else "configuring_lib"
  916. ).format(
  917. utils.escape_html(mod),
  918. "\n".join(
  919. [
  920. f"▫️ <code>{utils.escape_html(key)}</code>: <b>{{}}</b>".format(
  921. self.prep_value(value)
  922. if (
  923. not self.lookup(mod).config._config[key].validator
  924. or self.lookup(mod)
  925. .config._config[key]
  926. .validator.internal_id
  927. != "Hidden"
  928. )
  929. else self.hide_value(value)
  930. )
  931. for key, value in self.lookup(mod).config.items()
  932. ]
  933. ),
  934. ),
  935. reply_markup=list(utils.chunks(btns, 2))
  936. + [
  937. [
  938. {
  939. "text": self.strings("back_btn"),
  940. "callback": self.inline__global_config,
  941. "kwargs": {"obj_type": obj_type},
  942. },
  943. {"text": self.strings("close_btn"), "action": "close"},
  944. ]
  945. ],
  946. )
  947. async def inline__choose_category(self, call: typing.Union[Message, InlineCall]):
  948. await utils.answer(
  949. call,
  950. self.strings("choose_core"),
  951. reply_markup=[
  952. [
  953. {
  954. "text": self.strings("builtin"),
  955. "callback": self.inline__global_config,
  956. "kwargs": {"obj_type": True},
  957. },
  958. {
  959. "text": self.strings("external"),
  960. "callback": self.inline__global_config,
  961. },
  962. ],
  963. *(
  964. [
  965. [
  966. {
  967. "text": self.strings("libraries"),
  968. "callback": self.inline__global_config,
  969. "kwargs": {"obj_type": "library"},
  970. }
  971. ]
  972. ]
  973. if self.allmodules.libraries
  974. and any(hasattr(lib, "config") for lib in self.allmodules.libraries)
  975. else []
  976. ),
  977. [{"text": self.strings("close_btn"), "action": "close"}],
  978. ],
  979. )
  980. async def inline__global_config(
  981. self,
  982. call: InlineCall,
  983. page: int = 0,
  984. obj_type: typing.Union[bool, str] = False,
  985. ):
  986. if isinstance(obj_type, bool):
  987. to_config = [
  988. mod.strings("name")
  989. for mod in self.allmodules.modules
  990. if hasattr(mod, "config")
  991. and callable(mod.strings)
  992. and (mod.__origin__.startswith("<core") or not obj_type)
  993. and (not mod.__origin__.startswith("<core") or obj_type)
  994. ]
  995. else:
  996. to_config = [
  997. lib.name for lib in self.allmodules.libraries if hasattr(lib, "config")
  998. ]
  999. to_config.sort()
  1000. kb = []
  1001. for mod_row in utils.chunks(
  1002. to_config[
  1003. page
  1004. * self._num_rows
  1005. * self._row_size : (page + 1)
  1006. * self._num_rows
  1007. * self._row_size
  1008. ],
  1009. 3,
  1010. ):
  1011. row = [
  1012. {
  1013. "text": btn,
  1014. "callback": self.inline__configure,
  1015. "args": (btn,),
  1016. "kwargs": {"obj_type": obj_type},
  1017. }
  1018. for btn in mod_row
  1019. ]
  1020. kb += [row]
  1021. if len(to_config) > self._num_rows * self._row_size:
  1022. kb += self.inline.build_pagination(
  1023. callback=functools.partial(
  1024. self.inline__global_config, obj_type=obj_type
  1025. ),
  1026. total_pages=ceil(len(to_config) / (self._num_rows * self._row_size)),
  1027. current_page=page + 1,
  1028. )
  1029. kb += [
  1030. [
  1031. {
  1032. "text": self.strings("back_btn"),
  1033. "callback": self.inline__choose_category,
  1034. },
  1035. {"text": self.strings("close_btn"), "action": "close"},
  1036. ]
  1037. ]
  1038. await call.edit(
  1039. self.strings(
  1040. "configure" if isinstance(obj_type, bool) else "configure_lib"
  1041. ),
  1042. reply_markup=kb,
  1043. )
  1044. @loader.command(ru_doc="Настроить модули")
  1045. async def configcmd(self, message: Message):
  1046. """Configure modules"""
  1047. args = utils.get_args_raw(message)
  1048. if self.lookup(args) and hasattr(self.lookup(args), "config"):
  1049. form = await self.inline.form("🌘 <b>Loading configuration</b>", message)
  1050. mod = self.lookup(args)
  1051. if isinstance(mod, loader.Library):
  1052. type_ = "library"
  1053. else:
  1054. type_ = mod.__origin__.startswith("<core")
  1055. await self.inline__configure(form, args, obj_type=type_)
  1056. return
  1057. await self.inline__choose_category(message)
  1058. @loader.command(
  1059. ru_doc=(
  1060. "<модуль> <настройка> <значениеЮ - установить значение конфига для модуля"
  1061. )
  1062. )
  1063. async def fconfig(self, message: Message):
  1064. """<module_name> <property_name> <config_value> - set the config value for the module
  1065. """
  1066. args = utils.get_args_raw(message).split(maxsplit=2)
  1067. if len(args) < 3:
  1068. await utils.answer(message, self.strings("args"))
  1069. return
  1070. mod, option, value = args
  1071. instance = self.lookup(mod)
  1072. if not instance:
  1073. await utils.answer(message, self.strings("no_mod"))
  1074. return
  1075. if option not in instance.config:
  1076. await utils.answer(message, self.strings("no_option"))
  1077. return
  1078. instance.config[option] = value
  1079. await utils.answer(
  1080. message,
  1081. self.strings(
  1082. "option_saved"
  1083. if isinstance(instance, loader.Module)
  1084. else "option_saved_lib"
  1085. ).format(
  1086. utils.escape_html(option),
  1087. utils.escape_html(mod),
  1088. self.prep_value(instance.config[option])
  1089. if not instance.config._config[option].validator
  1090. or instance.config._config[option].validator.internal_id != "Hidden"
  1091. else self.hide_value(instance.config[option]),
  1092. ),
  1093. )