| """
|
| Menu cog: Interactive command explorer with beautiful panels.
|
| Enhanced with stunning decorations, rich formatting, and multi-language support.
|
| """
|
|
|
| import random
|
|
|
| import discord
|
| from discord.ext import commands
|
|
|
| from bot.cogs.ai_suite import ImperialMotaz
|
| from bot.i18n import get_cmd_desc
|
| from bot.emojis import ui, E_DIAMOND, E_STAR, E_FIRE, E_SPARKLE, E_GEM, E_CROWN
|
| from bot.theme import (
|
| fancy_header, pick_neon_color, progress_bar, shimmer, panel_divider,
|
| NEON_CYAN, NEON_PINK, NEON_PURPLE, NEON_LIME, NEON_YELLOW, NEON_BLUE,
|
| NEON_GOLD, NEON_MAGENTA,
|
| double_line, triple_line, mega_title, fancy_divider, animated_header,
|
| gradient_header, panel_title, section_header, quick_stats_grid
|
| )
|
|
|
|
|
|
|
| _CATEGORY_EMOJIS: dict[str, str] = {
|
| "Music": "🎵",
|
| "Admin": "🛡️",
|
| "Fun": "🎮",
|
| "AI": "🤖",
|
| "Utility": "🔧",
|
| "Config": "⚙️",
|
| "Economy": "💰",
|
| "Moderation": "⚔️",
|
| "Tickets": "🎫",
|
| "Welcome": "👋",
|
| "Giveaway": "🎁",
|
| "Verification": "✅",
|
| "Tournament": "🏆",
|
| "Games": "🎲",
|
| "Level": "📊",
|
| "AutoMod": "🤖",
|
| "Logs": "📋",
|
| "DJ": "🎧",
|
| "Developer": "💻",
|
| "default": "📂",
|
| }
|
|
|
|
|
| _CATEGORY_DESCRIPTIONS: dict[str, str] = {
|
| "Music": "🎵 Music commands for playback control",
|
| "Admin": "🛡️ Server administration tools",
|
| "Fun": "🎮 Fun games and entertainment",
|
| "AI": "🤖 AI-powered features",
|
| "Utility": "🔧 Useful utility commands",
|
| "Config": "⚙️ Bot configuration settings",
|
| "Economy": "💰 Economy and currency system",
|
| "Moderation": "⚔️ Moderation commands",
|
| "Tickets": "🎫 Ticket support system",
|
| "Welcome": "👋 Welcome and goodbye messages",
|
| "Giveaway": "🎁 Giveaway management",
|
| "Verification": "✅ Member verification",
|
| "Tournament": "🏆 Tournament brackets",
|
| "Games": "🎲 Mini games and fun",
|
| "Level": "📊 XP and leveling system",
|
| "AutoMod": "🤖 Auto-moderation features",
|
| "Logs": "📋 Logging configuration",
|
| "DJ": "🎧 DJ music controls",
|
| "Developer": "💻 Developer tools",
|
| }
|
|
|
| _CATEGORY_BILINGUAL = {
|
| "__all__": "📚 All Commands | جميع الأوامر",
|
| "__ai__": "🤖 AI | الذكاء الاصطناعي",
|
| "Music": "🎵 Music | الموسيقى",
|
| "Admin": "🛡️ Admin | الإدارة",
|
| "Fun": "🎮 Games | الألعاب",
|
| "AI": "🤖 AI | الذكاء الاصطناعي",
|
| "Utility": "🔧 Utility | الأدوات",
|
| "Config": "⚙️ Config | الإعدادات",
|
| "Economy": "💰 Economy | الاقتصاد",
|
| "Moderation": "⚔️ Moderation | الإشراف",
|
| "Community": "💡 Community | المجتمع",
|
| "AISuite": "🤖 AI Suite | الذكاء",
|
| "Configuration": "⚙️ Configuration | الإعدادات",
|
| "Events": "📋 Events | الأحداث",
|
| "Verification": "✅ Verification | التحقق",
|
| }
|
|
|
|
|
| def _bilingual_category(name: str) -> str:
|
| return _CATEGORY_BILINGUAL.get(name, f"📂 {name} | {name}")
|
|
|
|
|
| def _safe_emoji(value: str) -> "discord.Emoji | discord.PartialEmoji | str | None":
|
| """Safely resolve an emoji value for use in Discord SelectOptions."""
|
| if not value:
|
| return None
|
| value = value.strip()
|
| if not value:
|
| return None
|
| if value.startswith("<") and value.endswith(">"):
|
| try:
|
| return discord.PartialEmoji.from_str(value)
|
| except Exception:
|
| return None
|
| return value
|
|
|
|
|
| class CommandsMenuSelect(discord.ui.Select):
|
| """Beautiful dropdown menu for selecting command categories."""
|
|
|
| def __init__(
|
| self,
|
| cog_names: list[str],
|
| all_label: str,
|
| ai_label: str,
|
| *,
|
| placeholder: str,
|
| all_desc: str,
|
| ai_desc: str,
|
| cog_desc_map: dict[str, str],
|
| selected_cog: str = "__all__",
|
| ) -> None:
|
|
|
| options = [
|
| discord.SelectOption(
|
| label=_bilingual_category("__all__"),
|
| description=all_desc,
|
| emoji=ui("book"),
|
| value="__all__",
|
| default=selected_cog == "__all__",
|
| ),
|
| discord.SelectOption(
|
| label=_bilingual_category("__ai__"),
|
| description=ai_desc,
|
| emoji=ui("robot"),
|
| value="__ai__",
|
| default=selected_cog == "__ai__",
|
| ),
|
| ]
|
|
|
|
|
| for name in sorted(cog_names):
|
| raw_emoji = _CATEGORY_EMOJIS.get(name, _CATEGORY_EMOJIS["default"])
|
| desc = cog_desc_map.get(name, _CATEGORY_DESCRIPTIONS.get(name, f"{name} commands"))
|
|
|
| desc = desc[:100]
|
|
|
| resolved_emoji = _safe_emoji(raw_emoji)
|
| options.append(
|
| discord.SelectOption(
|
| label=_bilingual_category(name)[:100],
|
| description=desc,
|
| emoji=resolved_emoji,
|
| value=name,
|
| default=selected_cog == name,
|
| )
|
| )
|
|
|
| super().__init__(
|
| placeholder=placeholder,
|
| min_values=1,
|
| max_values=1,
|
| options=options
|
| )
|
|
|
| async def callback(self, interaction: discord.Interaction) -> None:
|
| await interaction.response.defer()
|
| view = self.view
|
| if not isinstance(view, CommandsMenuView):
|
| return
|
| selected = self.values[0]
|
| view.selected_cog = selected
|
| view.page = 0
|
| await view.setup_items()
|
| embed = await view.build_embed(interaction.guild.id if interaction.guild else None, selected)
|
| await interaction.followup.edit_message(interaction.message.id, embed=embed, view=view)
|
|
|
|
|
| class CommandsMenuView(discord.ui.View):
|
| """Beautiful command menu view with rich decorations."""
|
|
|
| def __init__(self, bot: commands.Bot, guild_id: int | None = None) -> None:
|
| super().__init__(timeout=None)
|
| self.bot = bot
|
| self.guild_id = guild_id
|
| self.selected_cog = "__all__"
|
| self.page = 0
|
| self.page_size = 18
|
| self._accent_seed = random.randrange(0, 1024)
|
| self._color_palette = [NEON_CYAN, NEON_PINK, NEON_PURPLE, NEON_LIME, NEON_GOLD, NEON_MAGENTA]
|
|
|
| async def setup_items(self) -> None:
|
| """Setup the menu items."""
|
| cog_names = list(self.bot.cogs.keys())
|
| all_label = await self.bot.get_text(self.guild_id, "menu.all")
|
| ai_label = await self.bot.get_text(self.guild_id, "menu.ai")
|
| placeholder = await self.bot.get_text(self.guild_id, "menu.select_placeholder")
|
| all_desc = await self.bot.get_text(self.guild_id, "menu.select_all_desc")
|
| ai_desc = await self.bot.get_text(self.guild_id, "menu.select_ai_desc")
|
| cog_desc_map: dict[str, str] = {}
|
| for name in cog_names:
|
| key = f"menu.category_desc.{name.lower()}"
|
| desc = await self.bot.get_text(self.guild_id, key)
|
| if desc == key:
|
| desc = _CATEGORY_DESCRIPTIONS.get(name, f"📂 {name} commands")
|
| cog_desc_map[name] = desc
|
| self.clear_items()
|
| self.add_item(
|
| CommandsMenuSelect(
|
| cog_names,
|
| all_label,
|
| ai_label,
|
| placeholder=placeholder,
|
| all_desc=all_desc,
|
| ai_desc=ai_desc,
|
| cog_desc_map=cog_desc_map,
|
| selected_cog=self.selected_cog,
|
| )
|
| )
|
|
|
| refresh_label = await self.bot.get_text(self.guild_id, "menu.refresh")
|
| invite_label = await self.bot.get_text(self.guild_id, "menu.invite_button")
|
| self.add_item(RefreshButton(self, refresh_label))
|
| self.add_item(QuickCategoryButton(self, "Economy | الاقتصاد", "Economy", "💰", row=1))
|
| self.add_item(QuickCategoryButton(self, "Music | الموسيقى", "Music", "🎵", row=1))
|
| self.add_item(QuickCategoryButton(self, "Admin | الإدارة", "Admin", "🛡️", row=1))
|
| self.add_item(QuickCategoryButton(self, "Utility | الأدوات", "Utility", "ℹ️", row=1))
|
| self.add_item(QuickCategoryButton(self, "Community | المجتمع", "Community", "💡", row=2))
|
| self.add_item(QuickCategoryButton(self, "AI | الذكاء", "AISuite", "🤖", row=2))
|
| self.add_item(QuickCategoryButton(self, "Config | الإعداد", "Configuration", "⚙️", row=2))
|
| if self.page > 0:
|
| self.add_item(PageButton(self, "prev"))
|
| if self._has_next_page(self.selected_cog):
|
| self.add_item(PageButton(self, "next"))
|
|
|
| if self.bot.user:
|
| self.add_item(InviteButton(invite_label, self.bot.user.id))
|
|
|
| def _desc_key(self, qualified_name: str) -> str:
|
| """Get the translation key for a command description."""
|
| norm = qualified_name.strip().lower().replace(" ", "_")
|
| key_mapping = {
|
|
|
| "play": "menu.cmd.play",
|
| "music_play": "menu.cmd.music_play",
|
| "music_panel": "menu.cmd.music_panel",
|
| "music_skip": "menu.cmd.music_skip",
|
| "music_stop": "menu.cmd.music_stop",
|
| "music_pause": "menu.cmd.music_pause",
|
| "music_resume": "menu.cmd.music_resume",
|
| "music_queue": "menu.cmd.music_queue",
|
| "music_playlist": "menu.cmd.music_playlist",
|
| "music_playlist_save": "menu.cmd.music_playlist_save",
|
| "music_playlist_rename": "menu.cmd.music_playlist_rename",
|
| "music_playlist_delete": "menu.cmd.music_playlist_delete",
|
| "music_volume": "menu.cmd.music_volume",
|
| "music_nowplaying": "menu.cmd.music_nowplaying",
|
| "music_filter": "menu.cmd.music_filter",
|
| "music_loop": "menu.cmd.music_loop",
|
| "music_shuffle": "menu.cmd.music_shuffle",
|
| "music_247": "menu.cmd.music_247",
|
| "music_previous": "menu.cmd.music_previous",
|
| "music_seek": "menu.cmd.music_seek",
|
| "music_clear": "menu.cmd.music_clear",
|
| "music_remove": "menu.cmd.music_remove",
|
| "music_move": "menu.cmd.music_move",
|
| "music_jump": "menu.cmd.music_jump",
|
| "music_lyrics": "menu.cmd.music_lyrics",
|
|
|
|
|
| "purge": "menu.cmd.purge",
|
| "ban": "menu.cmd.ban",
|
| "unban": "menu.cmd.unban",
|
| "kick": "menu.cmd.kick",
|
| "mute": "menu.cmd.mute",
|
| "unmute": "menu.cmd.unmute",
|
| "warn": "menu.cmd.warn",
|
| "warnings": "menu.cmd.warnings",
|
| "clearwarn": "menu.cmd.clearwarn",
|
| "slowmode": "menu.cmd.slowmode",
|
| "lock": "menu.cmd.lock",
|
| "unlock": "menu.cmd.unlock",
|
| "cloneemoji": "menu.cmd.cloneemoji",
|
| "awesomeroles": "menu.cmd.awesomeroles",
|
| "backupserver": "menu.cmd.backupserver",
|
|
|
|
|
| "8ball": "menu.cmd.8ball",
|
| "meme": "menu.cmd.meme",
|
| "trivia": "menu.cmd.trivia",
|
| "gaming_news": "menu.cmd.gaming_news",
|
| "free_games": "menu.cmd.free_games",
|
| "gamehub": "menu.cmd.gamehub",
|
| "xo": "menu.cmd.xo",
|
| "choose": "menu.cmd.choose",
|
| "mario": "menu.cmd.mario",
|
| "dice": "menu.cmd.dice",
|
| "slots": "menu.cmd.slots",
|
| "coinflip": "menu.cmd.coinflip",
|
| "roll": "menu.cmd.roll",
|
|
|
|
|
| "chat": "menu.cmd.chat",
|
| "ask_image": "menu.cmd.ask_image",
|
| "imagine": "menu.cmd.imagine",
|
| "image_gen": "menu.cmd.image_gen",
|
| "upscale": "menu.cmd.upscale",
|
| "summarize": "menu.cmd.summarize",
|
| "ai": "menu.cmd.ai",
|
| "ai_chat": "menu.cmd.chat",
|
| "ai_ask_image": "menu.cmd.ask_image",
|
| "ai_imagine": "menu.cmd.imagine",
|
| "ai_image_gen": "menu.cmd.image_gen",
|
| "ai_upscale": "menu.cmd.upscale",
|
| "ai_summarize": "menu.cmd.summarize",
|
| "ai_debug": "menu.cmd.debug",
|
| "ai_code_gen": "menu.cmd.code_gen",
|
| "ai_voice": "menu.cmd.speak",
|
| "ai_speak": "menu.cmd.speak",
|
| "ai_tts": "menu.cmd.tts",
|
| "ai_translate_voice": "menu.cmd.translate_voice",
|
| "ai_model": "menu.cmd.ai_model",
|
| "ai_channel": "menu.cmd.ai_channel",
|
| "ai_auto": "menu.cmd.ai_auto",
|
| "ai_setup": "menu.cmd.ai",
|
| "ai_execute": "menu.cmd.ai_execute",
|
|
|
|
|
| "serverinfo": "menu.cmd.serverinfo",
|
| "userinfo": "menu.cmd.userinfo",
|
| "botstats": "menu.cmd.botstats",
|
| "poll": "menu.cmd.poll",
|
| "remind": "menu.cmd.remind",
|
| "avatar": "menu.cmd.avatar",
|
| "banner": "menu.cmd.banner",
|
| "translate": "menu.cmd.translate",
|
|
|
|
|
| "set": "menu.cmd.set",
|
| "set_log": "menu.cmd.set_log",
|
| "set_welcome": "menu.cmd.set_welcome",
|
| "set_suggestions": "menu.cmd.set_suggestions",
|
| "set_automod": "menu.cmd.set_automod",
|
| "set_dailymessage": "menu.cmd.set_dailymessage",
|
| "set_dailychannel": "menu.cmd.set_dailychannel",
|
| "set_dailytime": "menu.cmd.set_dailytime",
|
| "set_dailytitle": "menu.cmd.set_dailytitle",
|
| "set_dailyimage": "menu.cmd.set_dailyimage",
|
| "set_dailybutton": "menu.cmd.set_dailybutton",
|
| "set_dailytoggle": "menu.cmd.set_dailytoggle",
|
| "set_pollchannel": "menu.cmd.set_pollchannel",
|
| "set_freegames": "menu.cmd.set_freegames",
|
| "set_gamenews": "menu.cmd.set_gamenews",
|
| "set_supportai": "menu.cmd.set_supportai",
|
| "set_wisdom": "menu.cmd.set_wisdom",
|
|
|
|
|
| "language": "menu.cmd.language",
|
| "languages": "menu.cmd.languages",
|
|
|
|
|
| "menu": "menu.cmd.menu",
|
| "ping": "menu.cmd.ping",
|
| "load": "menu.cmd.load",
|
| "unload": "menu.cmd.unload",
|
| "reload": "menu.cmd.reload",
|
| "sync": "menu.cmd.sync",
|
| "shutdown": "menu.cmd.shutdown",
|
| "bot_emojis": "menu.cmd.emoji_scan",
|
| "emoji_scan": "menu.cmd.emoji_scan",
|
|
|
|
|
| "verify": "menu.cmd.verify",
|
| "verifysetup": "menu.cmd.verifysetup",
|
| "verify_config": "menu.cmd.verify",
|
| "verify_config_panel": "menu.cmd.verify",
|
| "verify_config_setup": "menu.cmd.verifysetup",
|
|
|
|
|
| "economy": "menu.cmd.economy",
|
| "economy_deposit": "menu.cmd.economy_deposit",
|
| "economy_withdraw": "menu.cmd.economy_withdraw",
|
| "economy_gamble": "menu.cmd.economy_gamble",
|
| "economy_rob": "menu.cmd.economy_rob",
|
| "balance": "menu.cmd.balance",
|
| "daily": "menu.cmd.daily",
|
| "work": "menu.cmd.work",
|
| "gamble": "menu.cmd.gamble",
|
| "rob": "menu.cmd.rob",
|
| "leaderboard": "menu.cmd.leaderboard",
|
| "shop": "menu.cmd.shop",
|
| "buy": "menu.cmd.buy",
|
| "inventory": "menu.cmd.inventory",
|
| "transfer": "menu.cmd.transfer",
|
|
|
|
|
| "xp": "menu.cmd.xp",
|
| "level": "menu.cmd.level",
|
| "rank": "menu.cmd.rank",
|
| "suggest": "menu.cmd.suggest",
|
| "suggestion": "menu.cmd.suggestion",
|
| "suggestion_show": "menu.cmd.suggestion_show",
|
| "suggestion_panel": "menu.cmd.suggestion_panel",
|
| "suggestion_voters": "menu.cmd.suggestion_voters",
|
| "giveaway_create": "menu.cmd.giveaway_create",
|
| "giveaway_end": "menu.cmd.giveaway_end",
|
| "ticket_panel": "menu.cmd.ticket_panel",
|
|
|
|
|
| "tournament": "menu.cmd.tournament",
|
| "tournament_panel": "menu.cmd.tournament_panel",
|
| "tournament_create": "menu.cmd.tournament_create",
|
| "tournament_join": "menu.cmd.tournament_join",
|
| "tournament_start": "menu.cmd.tournament_start",
|
| "tournament_end": "menu.cmd.tournament_end",
|
| "tournament_gamehub": "menu.cmd.tournament_gamehub",
|
|
|
|
|
| "blackjack": "menu.cmd.blackjack",
|
| "roulette": "menu.cmd.roulette",
|
| "rpg": "menu.cmd.rpg",
|
|
|
|
|
| "ai_admin": "menu.cmd.ai_admin",
|
| "ai_help": "menu.cmd.ai_help",
|
|
|
| "admin": "menu.cmd.admin_panel",
|
| "admin_panel": "menu.cmd.admin_panel",
|
| "shield_level": "menu.cmd.shield_level",
|
| "shield_state": "menu.cmd.shield_state",
|
| "econ_admin": "menu.cmd.econ_admin",
|
| "econadmin": "menu.cmd.econ_admin",
|
| "economy_admin": "menu.cmd.econ_admin",
|
| "giveaway": "menu.cmd.giveaway",
|
| "ticket": "menu.cmd.ticket",
|
| "poll_legacy": "menu.cmd.poll",
|
| "setpollchannel": "menu.cmd.set_pollchannel",
|
| "setsuggestionchannel": "menu.cmd.set_suggestions",
|
| "set_freegame": "menu.cmd.set_freegames",
|
| "setupserver": "menu.cmd.setupserver",
|
| "organizechannels": "menu.cmd.organizechannels",
|
| "backup_panel": "menu.cmd.backup_panel",
|
| "system_audit": "menu.cmd.system_audit",
|
| "wisdom_today": "menu.cmd.wisdom_today",
|
| "freegames": "menu.cmd.free_games",
|
| "playlists": "menu.cmd.music_playlist",
|
| "music": "menu.cmd.music_panel",
|
| "profile": "menu.cmd.profile",
|
| "rps": "menu.cmd.rps",
|
| "guess": "menu.cmd.guess",
|
| "make_event": "menu.cmd.make_event",
|
| "gambling_panel": "menu.cmd.gambling_panel",
|
| "add_scam_image": "menu.cmd.add_scam_image",
|
| "set_banner": "menu.cmd.set_banner",
|
| "view_banner": "menu.cmd.view_banner",
|
| "remove_banner": "menu.cmd.remove_banner",
|
| "banner_help": "menu.cmd.banner_help",
|
| "boardgames": "menu.cmd.boardgames",
|
| "board_start": "menu.cmd.board_start",
|
| "board_move": "menu.cmd.board_move",
|
| "board_forfeit": "menu.cmd.board_forfeit",
|
| "games_panel": "menu.cmd.games_panel",
|
| "chess": "menu.cmd.chess",
|
| "checkers": "menu.cmd.checkers",
|
| "connect4": "menu.cmd.connect4",
|
| "othello": "menu.cmd.othello",
|
| "start": "menu.cmd.menu",
|
| "command_fill": "menu.cmd.menu",
|
| "tournament_lb": "menu.cmd.tournament_lb",
|
| "aisetup": "menu.cmd.ai",
|
| }
|
| return key_mapping.get(norm, f"menu.cmd.{norm}")
|
|
|
| async def _format_commands(self, guild_id: int | None, cmds: list[commands.Command], max_lines: int = 20, max_chars: int = 980) -> str:
|
| """Format commands list with beautiful decorations."""
|
| lines: list[str] = []
|
| used = 0
|
|
|
| for idx, cmd in enumerate(sorted(cmds, key=lambda c: c.qualified_name), start=1):
|
| if cmd.hidden:
|
| continue
|
|
|
| desc_key = self._desc_key(cmd.qualified_name)
|
| translated_desc = await self.bot.get_text(guild_id, desc_key)
|
| json_key = ""
|
| if translated_desc == desc_key:
|
| cog_key = (cmd.cog_name or "").lower()
|
| json_key = f"commands.{cog_key}.{cmd.name}_desc" if cog_key else ""
|
| if json_key:
|
| translated_desc = await self.bot.get_text(guild_id, json_key)
|
|
|
| if translated_desc == desc_key or (json_key and translated_desc == json_key):
|
| fallback_desc = (cmd.description or cmd.help or "").strip()
|
| desc = fallback_desc if fallback_desc else "-"
|
| else:
|
| desc = translated_desc
|
|
|
| is_hybrid = isinstance(cmd, commands.HybridCommand)
|
| invoke = f"`/{cmd.qualified_name}`" if is_hybrid else f"`!{cmd.qualified_name}`"
|
| line = f"- {invoke} - {desc[:78]}"
|
| projected = used + len(line) + (1 if lines else 0)
|
|
|
| if projected > max_chars:
|
| break
|
| lines.append(line)
|
| used = projected
|
|
|
| if len(lines) >= max_lines:
|
| break
|
|
|
| if len(lines) < len([c for c in cmds if not c.hidden]):
|
| suffix = "\n- ------------"
|
| if used + len(suffix) <= max_chars:
|
| lines.append("- ------------")
|
|
|
| return "\n".join(lines)
|
|
|
| def _visible_commands_page(self, selected_cog: str) -> tuple[list[commands.Command], int]:
|
| commands_list = sorted(self._selected_commands(selected_cog), key=lambda c: c.qualified_name)
|
| if not commands_list:
|
| return [], 1
|
| total_pages = max(1, (len(commands_list) + self.page_size - 1) // self.page_size)
|
| if self.page >= total_pages:
|
| self.page = total_pages - 1
|
| start = self.page * self.page_size
|
| return commands_list[start:start + self.page_size], total_pages
|
|
|
| def _has_next_page(self, selected_cog: str) -> bool:
|
| commands_list = self._selected_commands(selected_cog)
|
| total_pages = max(1, (len(commands_list) + self.page_size - 1) // self.page_size)
|
| return self.page + 1 < total_pages
|
|
|
| def _category_stats(self) -> list[tuple[str, int]]:
|
| """Get category statistics."""
|
| stats: list[tuple[str, int]] = []
|
| for name, cog in self.bot.cogs.items():
|
| cog_commands = [c for c in (cog.get_commands() if cog else []) if not c.hidden]
|
| if cog_commands:
|
| stats.append((name, len(cog_commands)))
|
| return sorted(stats, key=lambda pair: (-pair[1], pair[0]))
|
|
|
| async def _category_value(self, stats: list[tuple[str, int]], guild_id: int | None) -> str:
|
| """Format category statistics with beautiful decorations."""
|
| lang = await self.bot.get_guild_language(guild_id)
|
| if not stats:
|
| return await self.bot.get_text(guild_id, "menu.none")
|
|
|
| lines: list[str] = []
|
| for index, (name, count) in enumerate(stats[:8], start=1):
|
| badge = {1: "🥇", 2: "🥈", 3: "🥉"}.get(index, f"#{index}")
|
| emoji = _CATEGORY_EMOJIS.get(name, "📂")
|
|
|
| if lang == "ar":
|
| lines.append(f"{badge} {emoji} **{name}** — `{count}` أمر")
|
| else:
|
| lines.append(f"{badge} {emoji} **{name}** — `{count}` cmds")
|
|
|
| if len(stats) > 8:
|
| lines.append("✦ ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈")
|
|
|
| return "\n".join(lines)
|
|
|
| async def _stats_text(
|
| self,
|
| guild_id: int | None,
|
| total_commands: int,
|
| category_count: int,
|
| selection_label: str,
|
| visible_commands: int,
|
| ) -> str:
|
| """Format statistics with beautiful decorations."""
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| if lang == "ar":
|
| pieces = [
|
| f"📊 **إجمالي الأوامر:** `{total_commands}`",
|
| f"📂 **الفئات:** `{category_count}`",
|
| f"✨ **المحدد:** {selection_label} (`{visible_commands}` أمر)",
|
| f"📈 {progress_bar(visible_commands, total_commands, size=14)}",
|
| ]
|
| else:
|
| pieces = [
|
| f"📊 **Total Commands:** `{total_commands}`",
|
| f"📂 **Categories:** `{category_count}`",
|
| f"✨ **Selected:** {selection_label} (`{visible_commands}` cmds)",
|
| f"📈 {progress_bar(visible_commands, total_commands, size=14)}",
|
| ]
|
| return "\n".join(pieces)
|
|
|
| async def _selected_label(self, guild_id: int | None, selected_cog: str) -> str:
|
| """Get the label for the selected category."""
|
| return _bilingual_category(selected_cog)
|
|
|
| def _selected_commands(self, selected_cog: str) -> list[commands.Command]:
|
| """Get commands for the selected category."""
|
| def _flatten(cmds: list[commands.Command]) -> list[commands.Command]:
|
| collected: list[commands.Command] = []
|
| for command in cmds:
|
| if command.hidden:
|
| continue
|
| collected.append(command)
|
| if isinstance(command, commands.Group):
|
| collected.extend([sub for sub in command.walk_commands() if not sub.hidden])
|
| unique: dict[str, commands.Command] = {c.qualified_name: c for c in collected}
|
| return list(unique.values())
|
|
|
| if selected_cog == "__all__":
|
| return _flatten(list(self.bot.commands))
|
| if selected_cog == "__ai__":
|
| ai_cog = self.bot.get_cog("AISuite")
|
| return _flatten(list(ai_cog.get_commands() if ai_cog else []))
|
| cog = self.bot.get_cog(selected_cog)
|
| return _flatten(list(cog.get_commands() if cog else []))
|
|
|
| def _top_level_slash_count(self) -> int:
|
| """Count top-level slash-registered hybrid commands/groups (Discord limit: 100)."""
|
| total = 0
|
| for cmd in self.bot.commands:
|
| if not isinstance(cmd, commands.HybridCommand):
|
| continue
|
| if getattr(cmd, "with_app_command", True):
|
| total += 1
|
| return total
|
|
|
| async def build_embed(self, guild_id: int | None, selected_cog: str) -> discord.Embed:
|
| """Build the beautiful embed for the menu."""
|
| color = discord.Color(0x2B2D31)
|
|
|
| hub_welcome = await self.bot.get_text(guild_id, "menu.hub_welcome")
|
| hub_explore = await self.bot.get_text(guild_id, "menu.hub_explore")
|
| hub_usage = await self.bot.get_text(guild_id, "menu.hub_usage")
|
| stats_title = await self.bot.get_text(guild_id, "menu.stats_heading")
|
| categories_title = await self.bot.get_text(guild_id, "menu.categories_heading")
|
| tips_title = await self.bot.get_text(guild_id, "menu.tips_heading")
|
| quick_title = await self.bot.get_text(guild_id, "menu.quick_heading")
|
| updates_title = await self.bot.get_text(guild_id, "menu.updates_heading")
|
|
|
| title = "BOT- AI System"
|
| desc = f"{hub_welcome}\n{hub_explore}\n{hub_usage}"
|
| tips = (
|
| f"? {await self.bot.get_text(guild_id, 'menu.tip_line_1')}\n"
|
| f"? {await self.bot.get_text(guild_id, 'menu.tip_line_2')}\n"
|
| f"? {await self.bot.get_text(guild_id, 'menu.tip_line_3')}"
|
| )
|
| quick = (
|
| f"- `/music_panel` - {await self.bot.get_text(guild_id, 'menu.quick_music')}\n"
|
| f"- `/gamehub` - {await self.bot.get_text(guild_id, 'menu.quick_gamehub')}\n"
|
| f"- `/tournament panel` - {await self.bot.get_text(guild_id, 'menu.quick_tournament')}\n"
|
| f"- `/economy` - {await self.bot.get_text(guild_id, 'menu.quick_economy')}"
|
| )
|
| quick_ai = await self.bot.get_text(guild_id, 'menu.quick_ai')
|
| updates = (
|
| f"- `/admin emoji clone` - {await self.bot.get_text(guild_id, 'menu.update_cloneemoji')}\n"
|
| f"- `/admin shield add_image` - {await self.bot.get_text(guild_id, 'menu.update_shield_image')}\n"
|
| f"- `/poll create` - {await self.bot.get_text(guild_id, 'menu.update_poll_group')}\n"
|
| f"- `/economy deposit` - {await self.bot.get_text(guild_id, 'menu.update_economy_group')}\n"
|
| f"- `/ai execute` - {quick_ai if quick_ai != 'menu.quick_ai' else 'AI admin request'}"
|
| )
|
| footer = "Powered by BOT- AI Suite"
|
|
|
| top_divider = panel_divider("cyan")
|
| mid_divider = panel_divider("purple")
|
| embed = ImperialMotaz.craft_embed(
|
| title=title,
|
| description=f"{top_divider}\n{desc}\n{mid_divider}",
|
| color=color,
|
| footer=footer,
|
| )
|
|
|
| if self.bot.user:
|
| embed.set_thumbnail(url=self.bot.user.display_avatar.url)
|
|
|
| category_stats = self._category_stats()
|
| selected_label = await self._selected_label(guild_id, selected_cog)
|
| visible_cmds, total_pages = self._visible_commands_page(selected_cog)
|
| content = await self._format_commands(guild_id, visible_cmds, max_lines=18)
|
|
|
| total_guilds = len(self.bot.guilds)
|
| total_members = sum((g.member_count or 0) for g in self.bot.guilds)
|
| latency_ms = f"{round(self.bot.latency * 1000)}ms"
|
| slash_used = self._top_level_slash_count()
|
| slash_budget = f"{slash_used}/100"
|
| stats_grid = quick_stats_grid(
|
| [
|
| ("Guilds", str(total_guilds), "G"),
|
| ("Members", f"{total_members:,}", "M"),
|
| ("Latency", latency_ms, "L"),
|
| ("Slash", slash_budget, "S"),
|
| ],
|
| columns=2,
|
| )
|
| embed.add_field(name=stats_title, value=stats_grid, inline=False)
|
|
|
| embed.add_field(
|
| name=categories_title,
|
| value=await self._category_value(category_stats, guild_id),
|
| inline=True,
|
| )
|
|
|
| emoji = _CATEGORY_EMOJIS.get(selected_cog, "C")
|
| none_text = await self.bot.get_text(guild_id, "menu.none")
|
| embed.add_field(
|
| name=f"{emoji} {selected_label}",
|
| value=content or none_text,
|
| inline=False,
|
| )
|
|
|
| embed.add_field(name=quick_title, value=quick, inline=True)
|
| embed.add_field(name=tips_title, value=tips, inline=True)
|
| embed.add_field(name=updates_title, value=updates, inline=False)
|
|
|
|
|
| whats_new = await self.bot.get_text(guild_id, "menu.whats_new_heading")
|
| whats_new_content = await self.bot.get_text(guild_id, "menu.whats_new_content")
|
| if whats_new and whats_new != "menu.whats_new_heading" and whats_new_content and whats_new_content != "menu.whats_new_content":
|
| embed.add_field(name=whats_new, value=whats_new_content[:900], inline=False)
|
|
|
| embed.set_footer(text=footer)
|
| embed.description = f"{embed.description}\n\nPage {self.page + 1}/{total_pages}"
|
| return embed
|
|
|
|
|
| class MainMenuView(CommandsMenuView):
|
| """Named alias for persistent registration."""
|
|
|
|
|
| class InviteButton(discord.ui.Button):
|
| """Beautiful invite button."""
|
|
|
| def __init__(self, label: str, client_id: int) -> None:
|
| url = f"https://discord.com/api/oauth2/authorize?client_id={client_id}&permissions=0&scope=applications.commands%20bot"
|
| super().__init__(
|
| label=label,
|
| style=discord.ButtonStyle.link,
|
| url=url,
|
| emoji=ui("link")
|
| )
|
|
|
|
|
| class RefreshButton(discord.ui.Button):
|
| """Beautiful refresh button."""
|
|
|
| def __init__(self, parent: CommandsMenuView, label: str) -> None:
|
| super().__init__(
|
| label=label,
|
| emoji=ui("refresh"),
|
| style=discord.ButtonStyle.secondary
|
| )
|
| self.parent_view = parent
|
|
|
| async def callback(self, interaction: discord.Interaction) -> None:
|
| await interaction.response.defer()
|
| guild_id = interaction.guild.id if interaction.guild else None
|
| self.label = await self.parent_view.bot.get_text(guild_id, "menu.refresh")
|
| await self.parent_view.setup_items()
|
| embed = await self.parent_view.build_embed(guild_id, "__all__")
|
| await interaction.followup.edit_message(interaction.message.id, embed=embed, view=self.parent_view)
|
|
|
|
|
| class QuickCategoryButton(discord.ui.Button):
|
| def __init__(self, parent: CommandsMenuView, label: str, category: str, emoji: str, *, row: int = 1) -> None:
|
| super().__init__(
|
| label=label,
|
| emoji=emoji,
|
| style=discord.ButtonStyle.secondary,
|
| custom_id=f"menu:quick:{label.lower()}",
|
| row=row,
|
| )
|
| self.parent_view = parent
|
| self.category = category
|
|
|
| async def callback(self, interaction: discord.Interaction) -> None:
|
| await interaction.response.defer()
|
| self.parent_view.selected_cog = self.category
|
| self.parent_view.page = 0
|
| await self.parent_view.setup_items()
|
| embed = await self.parent_view.build_embed(interaction.guild.id if interaction.guild else None, self.category)
|
| await interaction.followup.edit_message(interaction.message.id, embed=embed, view=self.parent_view)
|
|
|
|
|
| class PageButton(discord.ui.Button):
|
| def __init__(self, parent: CommandsMenuView, direction: str) -> None:
|
| self.parent_view = parent
|
| self.direction = direction
|
| label = "Previous" if direction == "prev" else "Next"
|
| emoji = "⬅️" if direction == "prev" else "➡️"
|
| super().__init__(label=label, emoji=emoji, style=discord.ButtonStyle.secondary)
|
|
|
| async def callback(self, interaction: discord.Interaction) -> None:
|
| await interaction.response.defer()
|
| if self.direction == "prev":
|
| self.parent_view.page = max(0, self.parent_view.page - 1)
|
| else:
|
| if self.parent_view._has_next_page(self.parent_view.selected_cog):
|
| self.parent_view.page += 1
|
| await self.parent_view.setup_items()
|
| embed = await self.parent_view.build_embed(interaction.guild.id if interaction.guild else None, self.parent_view.selected_cog)
|
| await interaction.followup.edit_message(interaction.message.id, embed=embed, view=self.parent_view)
|
|
|
|
|
| class Menu(commands.Cog):
|
| """Interactive command menu with beautiful panels and multi-language support."""
|
|
|
| def __init__(self, bot: commands.Bot) -> None:
|
| self.bot = bot
|
|
|
| async def cog_load(self) -> None:
|
| self.bot.add_view(CommandsMenuView(self.bot))
|
|
|
| @commands.hybrid_command(name="menu", description=get_cmd_desc("commands.tools.menu_desc"))
|
| async def menu(self, ctx: commands.Context) -> None:
|
| """Display the beautiful command menu."""
|
| if ctx.interaction and not ctx.interaction.response.is_done():
|
| await ctx.interaction.response.defer()
|
| guild_id = ctx.guild.id if ctx.guild else None
|
| view = CommandsMenuView(self.bot, guild_id)
|
| await view.setup_items()
|
| embed = await view.build_embed(guild_id, "__all__")
|
| if ctx.interaction:
|
| await ctx.interaction.followup.send(embed=embed, view=view)
|
| else:
|
| await ctx.reply(embed=embed, view=view)
|
|
|
| @commands.hybrid_command(name="start", description=get_cmd_desc("commands.tools.start_desc"), with_app_command=False)
|
| async def start_menu(self, ctx: commands.Context) -> None:
|
| await self.menu(ctx)
|
|
|
| async def cog_command_error(self, ctx: commands.Context, error: Exception) -> None:
|
| embed = ImperialMotaz.craft_embed(
|
| title="⚠️ Command Error | خطأ في الأمر",
|
| description=f"「 {str(error)[:1000]} 」",
|
| color=discord.Color(0x2B2D31),
|
| footer="🏮 Powered by BOT- AI Suite 🏮",
|
| )
|
| try:
|
| await ctx.reply(embed=embed)
|
| except Exception:
|
| if ctx.channel:
|
| await ctx.channel.send(embed=embed)
|
|
|
|
|
| async def setup(bot: commands.Bot) -> None:
|
| await bot.add_cog(Menu(bot))
|
|
|
|
|
|
|