Discord.py如何将配置菜单的表情反应交互替换为按钮交互
注意:需确保你使用的discord.py版本≥2.0,按钮交互是该版本新增的功能
核心改动说明
- 完全兼容原有
configmenu配置逻辑,你之前配置菜单的操作流程不变,输入的emoji会自动作为按钮的图标展示 - 按钮交互逻辑完全复用原有命令触发逻辑,功能和之前的反应菜单100%一致
- 超时后自动禁用所有按钮,避免用户无效点击
- 新增按钮点击权限校验,只有对应线程的发起者可以点击菜单按钮
改造后完整代码
import asyncio import copy import dateutil import discord from discord.ext import commands from discord.ext.commands.view import StringView from core import checks from core.models import DummyMessage, PermissionLevel from core.utils import normalize_alias class Menu(commands.Cog): """基于按钮交互的线程菜单""" def __init__(self, bot): self.bot = bot self.db = self.bot.plugin_db.get_partition(self) @commands.Cog.listener() async def on_thread_ready(self, thread, creator, category, initial_message): """向用户发送菜单""" menu_config = await self.db.find_one({'_id': 'config'}) if menu_config: message = DummyMessage(copy.copy(initial_message)) message.author = self.bot.modmail_guild.me message.content = menu_config['content'] # 创建按钮视图,超时时间120秒和原有逻辑一致 view = discord.ui.View(timeout=120) custom_id_to_cmd = {} # 遍历配置生成对应按钮 for emoji, cmd in menu_config['options'].items(): btn = discord.ui.Button( emoji=emoji, style=discord.ButtonStyle.secondary ) # 绑定按钮回调 async def btn_callback(interaction: discord.Interaction): # 校验点击用户是否为线程创建者 if interaction.user != thread.recipient: await interaction.response.send_message("你没有权限点击这个按钮", ephemeral=True) return await interaction.response.defer() view.selected_cmd = custom_id_to_cmd[interaction.data["custom_id"]] view.stop() btn.callback = btn_callback custom_id_to_cmd[btn.custom_id] = cmd view.add_item(btn) # 发送带按钮的菜单消息 msg, _ = await thread.reply(message, view=view) # 等待按钮点击或者超时 await view.wait() # 超时未点击处理 if not hasattr(view, "selected_cmd"): message.content = '等待点击超时,菜单已自动关闭' # 禁用所有按钮 for item in view.children: item.disabled = True await msg.edit(view=view) await thread.reply(message) return # 点击后禁用所有按钮 for item in view.children: item.disabled = True await msg.edit(view=view) alias = view.selected_cmd ctxs = [] if alias is not None: aliases = normalize_alias(alias) for alias in aliases: view = StringView(self.bot.prefix + alias) ctx_ = commands.Context(prefix=self.bot.prefix, view=view, bot=self.bot, message=message) ctx_.thread = thread discord.utils.find(view.skip_string, await self.bot.get_prefix()) ctx_.invoked_with = view.get_word().lower() ctx_.command = self.bot.all_commands.get(ctx_.invoked_with) ctxs += [ctx_] for ctx in ctxs: if ctx.command: old_checks = copy.copy(ctx.command.checks) ctx.command.checks = [checks.has_permissions(PermissionLevel.INVALID)] await self.bot.invoke(ctx) ctx.command.checks = old_checks continue @checks.has_permissions(PermissionLevel.MODERATOR) @commands.command() async def configmenu(self, ctx): """创建新菜单""" config = {} try: await ctx.send('请输入菜单显示的内容?') m = await self.bot.wait_for('message', check=lambda x: ctx.message.channel == x.channel and ctx.message.author == x.author, timeout=300) config['content'] = m.content await ctx.send('菜单有多少个选项?') m = await self.bot.wait_for('message', check=lambda x: ctx.message.channel == x.channel and ctx.message.author == x.author and x.content.isdigit(), timeout=300) options_len = int(m.content) config['options'] = {} for _ in range(options_len): await ctx.send('选项对应的emoji是什么?') while True: m = await self.bot.wait_for('message', check=lambda x: ctx.message.channel == x.channel and ctx.message.author == x.author, timeout=300) try: await m.add_reaction(m.content) except discord.HTTPException: await ctx.send('无效的emoji,请重新输入。') else: emoji = m.content break await ctx.send('选项对应的命令是什么?(例如 `reply 正在转接 && move 1238343847384`)') m = await self.bot.wait_for('message', check=lambda x: ctx.message.channel == x.channel and ctx.message.author == x.author, timeout=300) config['options'][emoji] = m.content except asyncio.TimeoutError: await ctx.send('操作超时,请重新运行命令创建菜单。') else: await self.db.find_one_and_update({'_id': 'config'}, {'$set': config}, upsert=True) await ctx.send('配置成功') @checks.has_permissions(PermissionLevel.MODERATOR) @commands.command() async def clearmenu(self, ctx): """清空现有菜单配置""" await self.db.find_one_and_delete({'_id': 'config'}) await ctx.send('清空成功') def setup(bot): bot.add_cog(Menu(bot))
内容的提问来源于stack exchange,提问作者Ex Dev
相关产品推荐
相关产品推荐

