# meta developer: @codrago_m import html from .. import loader, utils class DBMod(loader.Module): strings = { "name": "DBMod", "del_text": "Database\n\nSelect a key to view", "deleted": "🗑 Key {key} deleted", "deleted_all": "🗑 Deleted {count} keys", "close_btn": "❌ Close", "back_btn": "⬅ Back", "del_btn": "🗑 Delete", "del_all_btn": "❌ Delete all", "not_found": "🔍 Key {key} not found", "invalid_key": "⚠ Invalid key", "page": "📄 Page {current}/{total}", "module_not_found": "🔍 Module '{module}' not found in database", "confirm_delete": "⚠ Are you sure you want to delete this?", "view_path": "Path: {path}", "root_path": "Root", "value_display": "Value: {value}", "yes_btn": "✅ Yes", "no_btn": "❌ No", "list_item_display": "List item [{index}]", } strings_ru = { "del_text": "База данных\n\nВыберите ключ для просмотра", "deleted": "🗑 Ключ {key} удален", "deleted_all": "🗑 Удалено {count} ключей", "close_btn": "❌ Закрыть", "back_btn": "⬅ Назад", "del_btn": "🗑 Удалить", "del_all_btn": "❌ Удалить все", "not_found": "🔍 Ключ {key} не найден", "invalid_key": "⚠ Некорректный ключ", "page": "📄 Страница {current}/{total}", "module_not_found": "🔍 Модуль '{module}' не найден в базе данных", "confirm_delete": "⚠ Вы уверены, что хотите удалить это?", "view_path": "Путь: {path}", "root_path": "Корень", "value_display": "Значение: {value}", "yes_btn": "✅ Да", "no_btn": "❌ Нет", "list_item_display": "Элемент списка [{index}]", } async def client_ready(self): self.page_state = {} def _make_path_text(self, key_path): path = "/".join(map(str, key_path)) if key_path else self.strings["root_path"] return self.strings["view_path"].format(path=path) def _make_list_item_path_text(self, key_path, index): """Создает заголовок для элемента списка""" if key_path: path = "/".join(map(str, key_path)) + f"[{index}]" else: path = f"[{index}]" return self.strings["list_item_display"].format(index=index) async def show_menu(self, message, key_path=None, page=0): if key_path is None: key_path = [] self.page_state[tuple(key_path)] = page current_data = self._db for key in key_path: if isinstance(current_data, (dict, list)): if isinstance(current_data, dict) and key in current_data: current_data = current_data[key] elif ( isinstance(current_data, list) and isinstance(key, int) and 0 <= key < len(current_data) ): current_data = current_data[key] else: await utils.answer(message, self.strings["invalid_key"]) return else: await utils.answer(message, self.strings["invalid_key"]) return header = self._make_path_text(key_path) if isinstance(current_data, (dict, list)) and current_data: markup = self.generate_nested_markup(current_data, key_path, page) await utils.answer(message, header, reply_markup=markup) else: text = f"{header}\n\n" + self.strings["value_display"].format( value=html.escape(str(current_data)) ) markup = self.generate_value_markup(key_path, page) await utils.answer(message, text, reply_markup=markup) async def navigate_db(self, call, key_path=None, page=0): if key_path is None: key_path = [] self.page_state[tuple(key_path)] = page current_data = self._db for key in key_path: if isinstance(current_data, (dict, list)): if isinstance(current_data, dict) and key in current_data: current_data = current_data[key] elif ( isinstance(current_data, list) and isinstance(key, int) and 0 <= key < len(current_data) ): current_data = current_data[key] else: await call.answer(self.strings["invalid_key"]) return else: await call.answer(self.strings["invalid_key"]) return is_list_item = False if key_path: parent_data = self._db for key in key_path[:-1]: if isinstance(parent_data, (dict, list)): if isinstance(parent_data, dict) and key in parent_data: parent_data = parent_data[key] elif ( isinstance(parent_data, list) and isinstance(key, int) and 0 <= key < len(parent_data) ): parent_data = parent_data[key] else: break if ( isinstance(parent_data, list) and isinstance(key_path[-1], int) and 0 <= key_path[-1] < len(parent_data) ): is_list_item = True if is_list_item: header = self._make_list_item_path_text(key_path[:-1], key_path[-1]) text = f"{header}\n\n" + self.strings["value_display"].format( value=html.escape(str(current_data)) ) await call.edit( text, reply_markup=self.generate_list_item_markup(key_path, page) ) elif isinstance(current_data, (dict, list)) and current_data: header = self._make_path_text(key_path) await call.edit( header, reply_markup=self.generate_nested_markup(current_data, key_path, page), ) else: header = self._make_path_text(key_path) text = f"{header}\n\n" + self.strings["value_display"].format( value=html.escape(str(current_data)) ) await call.edit( text, reply_markup=self.generate_value_markup(key_path, page) ) def generate_nested_markup(self, data, key_path, page=0): if isinstance(data, list) and data: return self.generate_list_markup(data, key_path, page) items = list(data.items()) if isinstance(data, dict) else [] items_per_page = 9 total_pages = (len(items) + items_per_page - 1) // items_per_page start_idx = page * items_per_page end_idx = min(start_idx + items_per_page, len(items)) page_items = items[start_idx:end_idx] markup = [] row = [] for i, (key, value) in enumerate(page_items): if i % 3 == 0 and row: markup.append(row) row = [] row.append( { "text": f"{key}", "callback": self.navigate_db, "args": [key_path + [key], 0], } ) if row: markup.append(row) nav_buttons = [] if key_path: parent_page = self.page_state.get(tuple(key_path[:-1]), 0) nav_buttons.append( { "text": self.strings["back_btn"], "callback": self.navigate_db, "args": [key_path[:-1], parent_page], } ) if total_pages > 1: if page > 0: nav_buttons.append( { "text": "◀️", "callback": self.navigate_db, "args": [key_path, page - 1], } ) nav_buttons.append( { "text": self.strings["page"].format( current=page + 1, total=total_pages ), "callback": self.navigate_db, "args": [key_path, page], } ) if page < total_pages - 1: nav_buttons.append( { "text": "▶️", "callback": self.navigate_db, "args": [key_path, page + 1], } ) if nav_buttons: markup.append(nav_buttons) if key_path: markup.append( [ { "text": self.strings["del_all_btn"], "callback": self.confirm_delete_all, "style": "danger", "args": [key_path], } ] ) if not key_path: markup.append([{"text": self.strings["close_btn"], "action": "close"}]) return markup def generate_list_markup(self, data, key_path, page=0): """Генерирует разметку для списка, показывая элементы напрямую""" items_per_page = 9 total_pages = (len(data) + items_per_page - 1) // items_per_page start_idx = page * items_per_page end_idx = min(start_idx + items_per_page, len(data)) page_items = list(enumerate(data[start_idx:end_idx], start_idx)) markup = [] row = [] for i, (index, value) in enumerate(page_items): if i % 3 == 0 and row: markup.append(row) row = [] if isinstance(value, (dict, list)): btn_text = f"[{index}]" else: value_str = str(value) if len(value_str) > 10: btn_text = f"{value_str[:10]}..." else: btn_text = value_str row.append( { "text": btn_text, "callback": self.navigate_db, "args": [key_path + [index], 0], } ) if row: markup.append(row) nav_buttons = [] if key_path: parent_page = self.page_state.get(tuple(key_path[:-1]), 0) nav_buttons.append( { "text": self.strings["back_btn"], "callback": self.navigate_db, "style": "primary", "args": [key_path[:-1], parent_page], } ) if total_pages > 1: if page > 0: nav_buttons.append( { "text": "◀️", "callback": self.navigate_db, "args": [key_path, page - 1], } ) nav_buttons.append( { "text": self.strings["page"].format( current=page + 1, total=total_pages ), "callback": self.navigate_db, "args": [key_path, page], } ) if page < total_pages - 1: nav_buttons.append( { "text": "▶️", "callback": self.navigate_db, "args": [key_path, page + 1], } ) if nav_buttons: markup.append(nav_buttons) if key_path: markup.append( [ { "text": self.strings["del_all_btn"], "callback": self.confirm_delete_all, "style": "danger", "args": [key_path], } ] ) return markup def generate_list_item_markup(self, key_path, page=0): """Генерирует разметку для отдельного элемента списка""" parent_page = self.page_state.get(tuple(key_path[:-1]), 0) return [ [ { "text": self.strings["del_btn"], "callback": self.delete_key, "styles": "danger", "args": [key_path], } ], [ { "text": self.strings["back_btn"], "style": "primary", "callback": self.navigate_db, "style": "primary", "args": [key_path[:-1], parent_page], } ], ] def generate_value_markup(self, key_path, page=0): parent_page = self.page_state.get(tuple(key_path[:-1]), 0) return [ [ { "text": self.strings["del_btn"], "callback": self.delete_key, "style": "danger", "args": [key_path], } ], [ { "text": self.strings["back_btn"], "callback": self.navigate_db, "style": "primary", "args": [key_path[:-1], parent_page], } ], ] async def confirm_delete_all(self, call, key_path): await call.edit( self.strings["confirm_delete"], reply_markup=[ [ { "text": self.strings["yes_btn"], "callback": self.delete_all_keys, "args": [key_path], } ], [ { "text": self.strings["no_btn"], "callback": self.navigate_db, "args": [ key_path, self.page_state.get(tuple(key_path), 0), ], } ], ], ) async def delete_all_keys(self, call, key_path): if not key_path: count = len(self._db) self._db.clear() self._db.save() await call.answer(self.strings["deleted_all"].format(count=count)) await self.navigate_db(call, [], self.page_state.get((), 0)) else: current = self._db for key in key_path[:-1]: if isinstance(current, (dict, list)): if isinstance(current, dict) and key in current: current = current[key] elif ( isinstance(current, list) and isinstance(key, int) and 0 <= key < len(current) ): current = current[key] else: await call.answer( self.strings["not_found"].format(key=key_path[-1]) ) return if isinstance(current, (dict, list)) and key_path[-1] in current: if isinstance(current[key_path[-1]], (dict, list)): count = len(current[key_path[-1]]) else: count = 1 del current[key_path[-1]] self._db.save() await call.answer(self.strings["deleted_all"].format(count=count)) await self.navigate_db( call, key_path[:-1], self.page_state.get(tuple(key_path[:-1]), 0), ) else: await call.answer(self.strings["not_found"].format(key=key_path[-1])) async def delete_key(self, call, key_path): parent_page = self.page_state.get(tuple(key_path[:-1]), 0) if len(key_path) == 1: if key_path[0] in self._db: del self._db[key_path[0]] self._db.save() await call.answer(self.strings["deleted"].format(key=key_path[0])) await self.navigate_db(call, [], parent_page) else: await call.answer(self.strings["not_found"].format(key=key_path[0])) else: current = self._db for key in key_path[:-1]: if isinstance(current, (dict, list)): if isinstance(current, dict) and key in current: current = current[key] elif ( isinstance(current, list) and isinstance(key, int) and 0 <= key < len(current) ): current = current[key] else: await call.answer( self.strings["not_found"].format(key=key_path[-1]) ) return if isinstance(current, dict) and key_path[-1] in current: deleted_value = current[key_path[-1]] del current[key_path[-1]] key_display = key_path[-1] self._db.save() await call.answer(self.strings["deleted"].format(key=key_display)) await self.navigate_db(call, key_path[:-1], parent_page) elif ( isinstance(current, list) and isinstance(key_path[-1], int) and 0 <= key_path[-1] < len(current) ): deleted_value = current.pop(key_path[-1]) key_display = f"[{key_path[-1]}] = {deleted_value}" self._db.save() await call.answer(self.strings["deleted"].format(key=key_display)) await self.navigate_db(call, key_path[:-1], parent_page) else: await call.answer(self.strings["not_found"].format(key=key_path[-1])) def find_module_key(self, module_name): module_name_lower = module_name.lower() for key in self._db.keys(): if key.lower() == module_name_lower: return key return None @loader.command(ru_doc="Просмотр базы данных") async def mydb(self, message): """Viewing the database""" args = utils.get_args_raw(message) if args: module_key = self.find_module_key(args) if module_key: await self.show_menu( message, [module_key], self.page_state.get((module_key,), 0) ) return else: await utils.answer( message, self.strings["module_not_found"].format(module=args) ) return await self.show_menu(message, [], self.page_state.get((), 0))