hikka_config.py 34 KB

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