You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Discord.py复用确认View类出现异常问题求助

问题解决:Discord ConfirmView复用异常分析与修复

问题描述

我写了一个用于确认操作的ConfirmView类,打算在多个Discord命令里复用,但遇到两个异常:

  • 有时执行命令后,回复消息上的按钮直接处于禁用状态
  • 有时点击按钮完成操作后,仍会触发on_timeout方法,把消息编辑成“Timeout You took too long to respond!”

原View类代码:

confirm = Button(label="Confirm", emoji=tick, style=discord.ButtonStyle.green, custom_id="confirm")


class ConfirmView(View):
    def __init__(self, ctx, timeout):
        self.ctx = ctx
        super().__init__(timeout=timeout)

    async def on_timeout(self) -> None:
        for i in self.children:
            i.disabled = True
        await self.message.edit(content="Timeout You took too long to respond!", view=None)

    async def interaction_check(self, interaction) -> bool:
        if interaction.user.id == self.ctx.author.id:
            return True
        await interaction.response.send_message("You cannot interact with this view.", ephemeral=True)
        return False

class Cancel(Button):
    def __init__(self):
        super().__init__(label="Cancel", emoji=cross, style=discord.ButtonStyle.red, custom_id="cancel")

    async def callback(self, interaction):
        await interaction.response.edit_message(content="Cancelled!", view=None)

cancel = Cancel()

命令示例代码:

@client.command()
async def my_command_1(ctx):
    
    async def callback(interaction):
        # do my stuff here

    view = ConfirmView(ctx=ctx, timeout=30)
    view.add_item(confirm)
    view.add_item(cancel)

    confirm.callback = callback
    view.message = await ctx.send("One", view=view)

@client.command()
async def my_command_2(ctx):
    
    async def callback(interaction):
        # do my stuff here

    view = ConfirmView(ctx=ctx, timeout=30)
    view.add_item(confirm)
    view.add_item(cancel)

    confirm.callback = callback
    view.message = await ctx.send("Two", view=view)

@client.command()
async def my_command_3(ctx):
    
    async def callback(interaction):
        # do my stuff here

    view = ConfirmView(ctx=ctx, timeout=30)
    view.add_item(confirm)
    view.add_item(cancel)

    confirm.callback = callback
    view.message = await ctx.send("Three", view=view)

问题根源

  1. 全局按钮实例被共享:你定义了全局的confirm和cancel按钮对象,所有命令的ConfirmView都复用同一个实例。当其中一个View触发超时禁用按钮,或者修改了confirm的回调,其他View里的按钮状态和逻辑都会被影响,导致新命令的按钮一开始就禁用,或者回调被覆盖。
  2. 超时未主动停止:点击按钮后没有调用stop()方法终止View的超时计时器,导致即使操作完成,超时任务依然会执行,覆盖消息内容。
  3. 回调覆盖风险:每次命令都直接修改全局confirm的callback属性,并发场景下会导致回调逻辑混乱。

修复方案

重新设计ConfirmView,让每个View实例都创建独立的按钮对象,并且在按钮回调中主动停止超时计时器:

class ConfirmView(View):
    def __init__(self, ctx, timeout, confirm_callback):
        self.ctx = ctx
        self.confirm_callback = confirm_callback  # 传入确认后的业务逻辑
        super().__init__(timeout=timeout)
        
        # 为每个View实例创建独立的按钮
        confirm_btn = Button(label="Confirm", emoji=tick, style=discord.ButtonStyle.green, custom_id="confirm")
        confirm_btn.callback = self._confirm_callback
        self.add_item(confirm_btn)
        
        cancel_btn = Button(label="Cancel", emoji=cross, style=discord.ButtonStyle.red, custom_id="cancel")
        cancel_btn.callback = self._cancel_callback
        self.add_item(cancel_btn)

    async def _confirm_callback(self, interaction):
        # 先停止超时计时器,避免后续触发on_timeout
        self.stop()
        # 执行传入的确认逻辑
        await self.confirm_callback(interaction)
        # 禁用所有按钮并更新消息
        for item in self.children:
            item.disabled = True
        await interaction.response.edit_message(view=self)

    async def _cancel_callback(self, interaction):
        self.stop()
        for item in self.children:
            item.disabled = True
        await interaction.response.edit_message(content="Cancelled!", view=self)

    async def on_timeout(self) -> None:
        for item in self.children:
            item.disabled = True
        await self.message.edit(content="Timeout: You took too long to respond!", view=self)

    async def interaction_check(self, interaction) -> bool:
        if interaction.user.id == self.ctx.author.id:
            return True
        await interaction.response.send_message("You cannot interact with this view.", ephemeral=True)
        return False

修改后的命令调用方式:

@client.command()
async def my_command_1(ctx):
    async def confirm_logic(interaction):
        # 这里写my_command_1的确认操作逻辑
        await interaction.response.edit_message(content="操作已确认!")

    view = ConfirmView(ctx=ctx, timeout=30, confirm_callback=confirm_logic)
    view.message = await ctx.send("One", view=view)

@client.command()
async def my_command_2(ctx):
    async def confirm_logic(interaction):
        # 这里写my_command_2的确认操作逻辑
        await interaction.response.edit_message(content="操作已确认!")

    view = ConfirmView(ctx=ctx, timeout=30, confirm_callback=confirm_logic)
    view.message = await ctx.send("Two", view=view)

@client.command()
async def my_command_3(ctx):
    async def confirm_logic(interaction):
        # 这里写my_command_3的确认操作逻辑
        await interaction.response.edit_message(content="操作已确认!")

    view = ConfirmView(ctx=ctx, timeout=30, confirm_callback=confirm_logic)
    view.message = await ctx.send("Three", view=view)

修复关键点

  • 独立按钮实例:每个ConfirmView初始化时都会创建新的按钮对象,彻底避免全局实例共享导致的状态污染。
  • 主动停止超时:在按钮回调中调用self.stop(),直接终止超时计时器,防止后续触发on_timeout方法。
  • 传入确认逻辑:通过构造函数传入业务逻辑,替代直接修改按钮回调的方式,避免并发场景下的逻辑覆盖。
  • 统一状态更新:操作完成或超时后,禁用所有按钮并保留View(而非设为None),确保界面状态一致。

内容的提问来源于stack exchange,提问作者Dream

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 14:47:42