# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀ ▄▀█ ▀█▀ ▄▀█ █▀▄▀█ ▄▀█ # █▀█ █ █ █ █▀█ █▀▄ █ ▄ █▀█ █ █▀█ █ ▀ █ █▀█ # # © Copyright 2022 # # https://t.me/hikariatama # # 🔒 Licensed under the GNU GPLv3 # 🌐 https://www.gnu.org/licenses/agpl-3.0.html # scope: inline import ast import logging from typing import Optional, Union, Any from telethon.tl.types import Message from .. import loader, utils, translations from ..inline.types import InlineCall logger = logging.getLogger(__name__) @loader.tds class HikkaConfigMod(loader.Module): """Interactive configurator for Hikka Userbot""" strings = { "name": "HikkaConfig", "configure": "🎚 Here you can configure your modules' configs", "configuring_mod": "🎚 Choose config option for mod {}\n\nCurrent options:\n\n{}", "configuring_option": "🎚 Configuring option {} of mod {}\nℹ️ {}\n\nDefault: {}\n\nCurrent: {}\n\n{}", "option_saved": "🎚 Option {} of mod {} saved!\nCurrent: {}", "option_reset": "♻️ Option {} of mod {} has been reset to default\nCurrent: {}", "args": "🚫 You specified incorrect args", "no_mod": "🚫 Module doesn't exist", "no_option": "🚫 Configuration option doesn't exist", "validation_error": "🚫 You entered incorrect config value. \nError: {}", "try_again": "🔁 Try again", "typehint": "🕵️ Must be a{eng_art} {}", "set": "set", "set_default_btn": "♻️ Reset default", "enter_value_btn": "✍️ Enter value", "enter_value_desc": "✍️ Enter new configuration value for this option", "add_item_desc": "✍️ Enter item to add", "remove_item_desc": "✍️ Enter item to remove", "back_btn": "👈 Back", "close_btn": "🚫 Close", "add_item_btn": "➕ Add item", "remove_item_btn": "➖ Remove item", "show_hidden": "🚸 Show value", "hide_value": "🔒 Hide value", } strings_ru = { "configure": "🎚 Здесь можно управлять настройками модулей", "configuring_mod": "🎚 Выбери параметр для модуля {}\n\nТекущие настройки:\n\n{}", "configuring_option": "🎚 Управление параметром {} модуля {}\nℹ️ {}\n\nСтандартное: {}\n\nТекущее: {}\n\n{}", "option_saved": "🎚 Параметр {} модуля {} сохранен!\nТекущее: {}", "option_reset": "♻️ Параметр {} модуля {} сброшен до значения по умолчанию\nТекущее: {}", "_cmd_doc_config": "Настройки модулей", "_cmd_doc_fconfig": "<имя модуля> <имя конфига> <значение> - Расшифровывается как ForceConfig - Принудительно устанавливает значение в конфиге, если это не удалось сделать через inline бота", "_cls_doc": "Интерактивный конфигуратор Hikka", "args": "🚫 Ты указал неверные аргументы", "no_mod": "🚫 Модуль не существует", "no_option": "🚫 У модуля нет такого значения конфига", "validation_error": "🚫 Введено некорректное значение конфига. \nОшибка: {}", "try_again": "🔁 Попробовать еще раз", "typehint": "🕵️ Должно быть {}", "set": "поставить", "set_default_btn": "♻️ Значение по умолчанию", "enter_value_btn": "✍️ Ввести значение", "enter_value_desc": "✍️ Введи новое значение этого параметра", "add_item_desc": "✍️ Введи элемент, который нужно добавить", "remove_item_desc": "✍️ Введи элемент, который нужно удалить", "back_btn": "👈 Назад", "close_btn": "🚫 Закрыть", "add_item_btn": "➕ Добавить элемент", "remove_item_btn": "➖ Удалить элемент", "show_hidden": "🚸 Показать значение", "hide_value": "🔒 Скрыть значение", } async def client_ready(self, client, db): self._db = db self._client = client @staticmethod def prep_value(value: Any) -> Any: if isinstance(value, str): return utils.escape_html(value.strip()) if isinstance(value, list) and value: return utils.escape_html(", ".join(list(map(str, value)))) return utils.escape_html(value) @staticmethod def hide_value(value: Any) -> str: if isinstance(value, str): return "*" * len(value) if isinstance(value, list) and value: return str(["*" * len(str(i)) for i in value]) return "*" * len(str(value)) async def inline__set_config( self, call: InlineCall, query: str, mod: str, option: str, inline_message_id: str, ): try: self.lookup(mod).config[option] = query except loader.validators.ValidationError as e: await call.edit( self.strings("validation_error").format(e.args[0]), reply_markup={ "text": self.strings("try_again"), "callback": self.inline__configure_option, "args": (mod, option), }, ) return await call.edit( self.strings("option_saved").format( utils.escape_html(mod), utils.escape_html(option), self.prep_value(self.lookup(mod).config[option]) if not self.lookup(mod).config._config[option].validator or self.lookup(mod).config._config[option].validator.internal_id != "Hidden" else self.hide_value(self.lookup(mod).config[option]), ), reply_markup=[ [ { "text": self.strings("back_btn"), "callback": self.inline__configure, "args": (mod,), }, {"text": self.strings("close_btn"), "action": "close"}, ] ], inline_message_id=inline_message_id, ) async def inline__reset_default(self, call: InlineCall, mod: str, option: str): mod_instance = self.lookup(mod) mod_instance.config[option] = mod_instance.config.getdef(option) await call.edit( self.strings("option_reset").format( utils.escape_html(mod), utils.escape_html(option), self.prep_value(self.lookup(mod).config[option]) if not self.lookup(mod).config._config[option].validator or self.lookup(mod).config._config[option].validator.internal_id != "Hidden" else self.hide_value(self.lookup(mod).config[option]), ), reply_markup=[ [ { "text": self.strings("back_btn"), "callback": self.inline__configure, "args": (mod,), }, {"text": self.strings("close_btn"), "action": "close"}, ] ], ) async def inline__set_bool( self, call: InlineCall, mod: str, option: str, value: bool, ): try: self.lookup(mod).config[option] = value except loader.validators.ValidationError as e: await call.edit( self.strings("validation_error").format(e.args[0]), reply_markup={ "text": self.strings("try_again"), "callback": self.inline__configure_option, "args": (mod, option), }, ) return validator = self.lookup(mod).config._config[option].validator doc = utils.escape_html( validator.doc.get( self._db.get(translations.__name__, "lang", "en"), validator.doc["en"] ) ) await call.edit( self.strings("configuring_option").format( utils.escape_html(option), utils.escape_html(mod), utils.escape_html(self.lookup(mod).config.getdoc(option)), self.prep_value(self.lookup(mod).config.getdef(option)), self.prep_value(self.lookup(mod).config[option]) if not validator or validator.internal_id != "Hidden" else self.hide_value(self.lookup(mod).config[option]), self.strings("typehint").format( doc, eng_art="n" if doc.lower().startswith(tuple("euioay")) else "", ) if doc else "", ), reply_markup=self._generate_bool_markup(mod, option), ) await call.answer("✅") def _generate_bool_markup(self, mod: str, option: str) -> list: return [ [ *( [ { "text": f"✅ {self.strings('set')} `True`", "callback": self.inline__set_bool, "args": (mod, option, True), } ] if not self.lookup(mod).config[option] else [ { "text": f"❌ {self.strings('set')} `False`", "callback": self.inline__set_bool, "args": (mod, option, False), } ] ), ], [ *( [ { "text": self.strings("set_default_btn"), "callback": self.inline__reset_default, "args": (mod, option), } ] if self.lookup(mod).config[option] != self.lookup(mod).config.getdef(option) else [] ) ], [ { "text": self.strings("back_btn"), "callback": self.inline__configure, "args": (mod,), }, {"text": self.strings("close_btn"), "action": "close"}, ], ] async def inline__add_item( self, call: InlineCall, query: str, mod: str, option: str, inline_message_id: str, ): try: try: query = ast.literal_eval(query) except Exception: pass if isinstance(query, (set, tuple)): query = list(query) if not isinstance(query, list): query = [query] self.lookup(mod).config[option] = self.lookup(mod).config[option] + query except loader.validators.ValidationError as e: await call.edit( self.strings("validation_error").format(e.args[0]), reply_markup={ "text": self.strings("try_again"), "callback": self.inline__configure_option, "args": (mod, option), }, ) return await call.edit( self.strings("option_saved").format( utils.escape_html(mod), utils.escape_html(option), self.prep_value(self.lookup(mod).config[option]) if not self.lookup(mod).config._config[option].validator or self.lookup(mod).config._config[option].validator.internal_id != "Hidden" else self.hide_value(self.lookup(mod).config[option]), ), reply_markup=[ [ { "text": self.strings("back_btn"), "callback": self.inline__configure, "args": (mod,), }, {"text": self.strings("close_btn"), "action": "close"}, ] ], inline_message_id=inline_message_id, ) async def inline__remove_item( self, call: InlineCall, query: str, mod: str, option: str, inline_message_id: str, ): try: try: query = ast.literal_eval(query) except Exception: pass if isinstance(query, (set, tuple)): query = list(query) if not isinstance(query, list): query = [query] query = list(map(str, query)) found = False while True: for i, item in enumerate(self.lookup(mod).config[option]): if str(item) in query: del self.lookup(mod).config[option][i] found = True break else: break if not found: raise loader.validators.ValidationError( f"Nothing from passed value ({self.prep_value(query)}) is not in target list" ) except loader.validators.ValidationError as e: await call.edit( self.strings("validation_error").format(e.args[0]), reply_markup={ "text": self.strings("try_again"), "callback": self.inline__configure_option, "args": (mod, option), }, ) return await call.edit( self.strings("option_saved").format( utils.escape_html(mod), utils.escape_html(option), self.prep_value(self.lookup(mod).config[option]) if not self.lookup(mod).config._config[option].validator or self.lookup(mod).config._config[option].validator.internal_id != "Hidden" else self.hide_value(self.lookup(mod).config[option]), ), reply_markup=[ [ { "text": self.strings("back_btn"), "callback": self.inline__configure, "args": (mod,), }, {"text": self.strings("close_btn"), "action": "close"}, ] ], inline_message_id=inline_message_id, ) def _generate_series_markup(self, call: InlineCall, mod: str, option: str) -> list: return [ [ { "text": self.strings("enter_value_btn"), "input": self.strings("enter_value_desc"), "handler": self.inline__set_config, "args": (mod, option, call.inline_message_id), } ], [ *( [ { "text": self.strings("remove_item_btn"), "input": self.strings("remove_item_desc"), "handler": self.inline__remove_item, "args": (mod, option, call.inline_message_id), }, { "text": self.strings("add_item_btn"), "input": self.strings("add_item_desc"), "handler": self.inline__add_item, "args": (mod, option, call.inline_message_id), }, ] if self.lookup(mod).config[option] else [] ), ], [ *( [ { "text": self.strings("set_default_btn"), "callback": self.inline__reset_default, "args": (mod, option), } ] if self.lookup(mod).config[option] != self.lookup(mod).config.getdef(option) else [] ) ], [ { "text": self.strings("back_btn"), "callback": self.inline__configure, "args": (mod,), }, {"text": self.strings("close_btn"), "action": "close"}, ], ] async def inline__configure_option( self, call: InlineCall, mod: str, config_opt: str, force_hidden: Optional[bool] = False, ): module = self.lookup(mod) args = [ utils.escape_html(config_opt), utils.escape_html(mod), utils.escape_html(module.config.getdoc(config_opt)), self.prep_value(module.config.getdef(config_opt)), self.prep_value(module.config[config_opt]) if not module.config._config[config_opt].validator or module.config._config[config_opt].validator.internal_id != "Hidden" or force_hidden else self.hide_value(module.config[config_opt]), ] if ( module.config._config[config_opt].validator and module.config._config[config_opt].validator.internal_id == "Hidden" ): additonal_button_row = ( [ [ { "text": self.strings("hide_value"), "callback": self.inline__configure_option, "args": (mod, config_opt, False), } ] ] if force_hidden else [ [ { "text": self.strings("show_hidden"), "callback": self.inline__configure_option, "args": (mod, config_opt, True), } ] ] ) else: additonal_button_row = [] try: validator = module.config._config[config_opt].validator doc = utils.escape_html( validator.doc.get( self._db.get(translations.__name__, "lang", "en"), validator.doc["en"], ) ) except Exception: doc = None validator = None args += [""] else: args += [ self.strings("typehint").format( doc, eng_art="n" if doc.lower().startswith(tuple("euioay")) else "", ) ] if validator.internal_id == "Boolean": await call.edit( self.strings("configuring_option").format(*args), reply_markup=additonal_button_row + self._generate_bool_markup(mod, config_opt), ) return if validator.internal_id == "Series": await call.edit( self.strings("configuring_option").format(*args), reply_markup=additonal_button_row + self._generate_series_markup(call, mod, config_opt), ) return await call.edit( self.strings("configuring_option").format(*args), reply_markup=additonal_button_row + [ [ { "text": self.strings("enter_value_btn"), "input": self.strings("enter_value_desc"), "handler": self.inline__set_config, "args": (mod, config_opt, call.inline_message_id), } ], [ { "text": self.strings("set_default_btn"), "callback": self.inline__reset_default, "args": (mod, config_opt), } ], [ { "text": self.strings("back_btn"), "callback": self.inline__configure, "args": (mod,), }, {"text": self.strings("close_btn"), "action": "close"}, ], ], ) async def inline__configure(self, call: InlineCall, mod: str): btns = [] for param in self.lookup(mod).config: btns += [ { "text": param, "callback": self.inline__configure_option, "args": (mod, param), } ] await call.edit( self.strings("configuring_mod").format( utils.escape_html(mod), "\n".join( [ f"▫️ {utils.escape_html(key)}: {self.prep_value(value) if not self.lookup(mod).config._config[key].validator or self.lookup(mod).config._config[key].validator.internal_id != 'Hidden' else self.hide_value(value)}" for key, value in self.lookup(mod).config.items() ] ), ), reply_markup=list(utils.chunks(btns, 2)) + [ [ { "text": self.strings("back_btn"), "callback": self.inline__global_config, }, {"text": self.strings("close_btn"), "action": "close"}, ] ], ) async def inline__global_config( self, call: Union[Message, InlineCall], ): to_config = [ mod.strings("name") for mod in self.allmodules.modules if hasattr(mod, "config") and callable(mod.strings) ] kb = [] for mod_row in utils.chunks(to_config, 3): row = [ {"text": btn, "callback": self.inline__configure, "args": (btn,)} for btn in mod_row ] kb += [row] kb += [[{"text": self.strings("close_btn"), "action": "close"}]] if isinstance(call, Message): await self.inline.form( self.strings("configure"), reply_markup=kb, message=call, ) else: await call.edit(self.strings("configure"), reply_markup=kb) async def configcmd(self, message: Message): """Configure modules""" args = utils.get_args_raw(message) if self.lookup(args): form = await self.inline.form( "🌘 Loading configuration", message, {"text": "🌘", "data": "empty"}, ttl=24 * 60 * 60, ) await self.inline__configure(form, args) return await self.inline__global_config(message) async def fconfigcmd(self, message: Message): """ - Stands for ForceConfig - Set the config value if it is not possible using default method""" args = utils.get_args_raw(message).split(maxsplit=2) if len(args) < 3: await utils.answer(message, self.strings("args")) return mod, option, value = args instance = self.lookup(mod) if not instance: await utils.answer(message, self.strings("no_mod")) return if option not in instance.config: await utils.answer(message, self.strings("no_option")) return instance.config[option] = value await utils.answer( message, self.strings("option_saved").format( utils.escape_html(option), utils.escape_html(mod), self.prep_value(instance.config[option]) if not instance.config._config[option].validator or instance.config._config[option].validator.internal_id != "Hidden" else self.hide_value(instance.config[option]), ), )