diff --git a/cogs/threadmenu.py b/cogs/threadmenu.py index 7f9e193844..0e225d527f 100644 --- a/cogs/threadmenu.py +++ b/cogs/threadmenu.py @@ -178,6 +178,9 @@ def typecheck(m): if label.lower() == "cancel": return await ctx.send("Cancelled.") + if label.lower() == "main menu": + return await ctx.send("You cannot use that label.") + if sanitized_label in conf["options"]: await ctx.send("That option already exists. Use `threadmenu edit` to edit it.") return diff --git a/core/thread.py b/core/thread.py index bf77180f8c..b3a9bf35a5 100644 --- a/core/thread.py +++ b/core/thread.py @@ -849,11 +849,9 @@ async def send_genesis_message(): if getattr(self, "_selected_thread_creation_menu_option", None) and self.bot.config.get( "thread_creation_menu_selection_log" ): - opt = self._selected_thread_creation_menu_option + path = self._selected_thread_creation_menu_option try: - log_txt = f"Selected menu option: {opt.get('label')} ({opt.get('type')})" - if opt.get("type") == "command": - log_txt += f" -> {opt.get('callback')}" + log_txt = f"Selected menu path: {' -> '.join(path)}" await channel.send(embed=discord.Embed(description=log_txt, color=self.bot.mod_color)) except Exception: logger.warning( @@ -2659,29 +2657,44 @@ async def create( placeholder = "Select an option to contact the staff team." timeout = 20 - options = self.bot.config.get("thread_creation_menu_options") or {} - submenus = self.bot.config.get("thread_creation_menu_submenus") or {} - # Minimal inline view implementation (avoid importing plugin code) thread.ready = False # not ready yet class _ThreadCreationMenuSelect(discord.ui.Select): - def __init__(self, outer_thread: Thread): + def __init__( + self, + bot, + outer_thread: Thread, + option_data: dict, + menu_msg: discord.Message, + path: list, + is_home: bool = True, + ): + self.bot = bot self.outer_thread = outer_thread - opts = [ + self.option_data = option_data + self.menu_msg = menu_msg + self.path = path + options = [ discord.SelectOption( label=o["label"], description=o["description"], emoji=o["emoji"], ) - for o in options.values() + for o in option_data.values() ] + if not is_home: + options.append( + discord.SelectOption( + label="main menu", description="Return to the main menu", emoji="🏠" + ) + ) super().__init__( placeholder=placeholder, min_values=1, max_values=1, - options=opts, + options=options, ) async def callback(self, interaction: discord.Interaction): @@ -2696,8 +2709,45 @@ async def callback(self, interaction: discord.Interaction): chosen_label = self.values[0] # Resolve option key key = chosen_label.lower().replace(" ", "_") - selected = options.get(key) - self.outer_thread._selected_thread_creation_menu_option = selected + if key == "main_menu": + option_data = self.bot.config.get("thread_creation_menu_options") or {} + new_view = _ThreadCreationMenuView( + self.bot, + self.outer_thread, + option_data, + self.menu_msg, + path=[], + is_home=True, + ) + return await self.menu_msg.edit(view=new_view) + selected: dict = self.option_data.get(key, {}) + next_path = [*self.path, chosen_label] + if selected.get("type", "command") == "submenu": + submenu_data = self.bot.config.get("thread_creation_menu_submenus") or {} + submenu_key = selected.get("callback", key) + option_data = submenu_data.get(submenu_key, {}) + if not option_data: + home_options = self.bot.config.get("thread_creation_menu_options") or {} + new_view = _ThreadCreationMenuView( + self.bot, + self.outer_thread, + home_options, + self.menu_msg, + path=[], + is_home=True, + ) + return await self.menu_msg.edit(view=new_view) + new_view = _ThreadCreationMenuView( + self.bot, + self.outer_thread, + option_data, + self.menu_msg, + path=next_path, + is_home=False, + ) + return await self.menu_msg.edit(view=new_view) + + self.outer_thread._selected_thread_creation_menu_option = next_path # Reflect the selection in the original DM by editing the embed/body try: msg = getattr(interaction, "message", None) @@ -2936,10 +2986,30 @@ async def callback(self, interaction: discord.Interaction): ctx_.command.checks = old_checks class _ThreadCreationMenuView(discord.ui.View): - def __init__(self, outer_thread: Thread): + def __init__( + self, + bot, + outer_thread: Thread, + option_data: dict, + menu_msg: discord.Message, + path: list, + is_home: bool = True, + ): super().__init__(timeout=timeout) self.outer_thread = outer_thread - self.add_item(_ThreadCreationMenuSelect(outer_thread)) + self.path = path + self.menu_msg = menu_msg + self.option_data = option_data + self.add_item( + _ThreadCreationMenuSelect( + bot, + outer_thread, + option_data=option_data, + menu_msg=menu_msg, + path=self.path, + is_home=is_home, + ) + ) async def on_timeout(self): # Timeout -> abort thread creation @@ -3061,8 +3131,12 @@ async def on_timeout(self): embed.set_thumbnail(url=embed_thumb) except Exception as e: logger.debug("Thumbnail set failed (ignored): %s", e) - menu_view = _ThreadCreationMenuView(thread) - menu_msg = await recipient.send(embed=embed, view=menu_view) + menu_msg = await recipient.send(embed=embed) + option_data = self.bot.config.get("thread_creation_menu_options") or {} + menu_view = _ThreadCreationMenuView( + self.bot, thread, option_data, menu_msg, path=[], is_home=True + ) + menu_msg = await menu_msg.edit(view=menu_view) # mark thread as pending menu selection thread._pending_menu = True # Explicitly attach the message to the view for safety in callbacks