Discord.py 持久化 View:解决机器人会话恢复后按钮失效问题

酷涛小哥_4373

酷涛小哥_4373

2026-07-31

978人浏览

原创

Discord.py 持久化 View:解决机器人会话恢复后按钮失效问题

本文详解如何通过设置 timeout=none、为按钮分配唯一 custom_id 并在启动时注册 view,使 discord.py 的交互式按钮在机器人重启或会话中断(如出现 “shared id none has successfully resumed session”)后仍能正常响应。

本文详解如何通过设置 timeout=none、为按钮分配唯一 custom_id 并在启动时注册 view,使 discord.py 的交互式按钮在机器人重启或会话中断(如出现 “shared id none has successfully resumed session”)后仍能正常响应。

在使用 discord.py 构建交互式应用(如服务器申请审核系统)时,你可能会遇到一种典型故障:按钮点击后显示 “This interaction failed”,控制台却无报错,仅偶现日志 Shared ID None has successfully RESUMED session。这并非网络或权限问题,而是 View 缺乏持久性(persistence)导致的底层状态丢失——当 bot 会话因断连、重启或心跳恢复而重建时,原始 View 实例已从内存中销毁,Discord 服务端无法再将用户点击路由到有效的处理器。

✅ 正确实现持久化 View 的三大关键步骤

1. 禁用超时,并显式声明 timeout=None

默认情况下,discord.ui.View 在 60 秒无交互后自动销毁。持久化 View 必须永久存活(由 bot 主动管理生命周期),因此需在初始化时明确禁用超时:

class ReviewView(discord.ui.View):
    def __init__(self, embed_user: discord.User, floor: int):
        # 关键:timeout=None 表示该 View 不自动过期
        super().__init__(timeout=None)
        self.embed_user = embed_user
        self.floor = floor

⚠️ 注意:timeout=None 仅表示“不自动超时”,不代表 View 可跨进程/重启存活——它仍需配合后续步骤才能真正持久。

2. 为每个组件(如按钮)分配唯一且稳定的 custom_id

Discord 依赖 custom_id 将用户点击映射到对应按钮处理器。若未指定,框架会生成临时 ID,重启后该 ID 失效,导致交互无法分发。

✅ 正确做法:为按钮硬编码或生成确定性、唯一、可复用的 custom_id。例如:

@discord.ui.button(
    label="Accept",
    style=discord.ButtonStyle.success,
    custom_id="review_accept_button"  # ✅ 静态 ID(适用于单实例场景)
)
async def approve(self, interaction: discord.Interaction, button: discord.ui.Button):
    # 处理逻辑保持不变...

? 若需支持多个并发申请(即多个 ReviewView 实例),则 custom_id 必须全局唯一。推荐方案是拼接业务标识:

def __init__(self, embed_user: discord.User, floor: int):
    super().__init__(timeout=None)
    self.embed_user = embed_user
    self.floor = floor
    # 为按钮动态生成唯一 ID(如基于用户ID+楼层)
    self.custom_id_base = f"review_{embed_user.id}_{floor}"

@discord.ui.button(
    label="Accept",
    style=discord.ButtonStyle.success,
    custom_id=lambda self: f"{self.custom_id_base}_accept"  # ❌ 错误:lambda 不可序列化
)
# ✅ 正确写法:在 __init__ 中预设,并在装饰器中引用
# → 实际应使用类属性或工厂函数,详见下方完整示例

更健壮的做法是在 View 初始化时预先绑定按钮 ID(避免装饰器内动态计算):

Learning Prompt
Learning Prompt

Learning Prompt是一款AI提示词工具,免费的AI提示词学习平台。

下载
class ReviewView(discord.ui.View):
    def __init__(self, embed_user: discord.User, floor: int):
        super().__init__(timeout=None)
        self.embed_user = embed_user
        self.floor = floor
        # 生成稳定唯一 ID(确保不重复)
        self.accept_id = f"review_accept_{embed_user.id}_{floor}"

        # 动态添加按钮(绕过装饰器限制)
        self.add_item(discord.ui.Button(
            label="Accept",
            style=discord.ButtonStyle.success,
            custom_id=self.accept_id
        ))
        # 绑定回调(需手动处理)
        self.children[-1].callback = self.approve

    async def approve(self, interaction: discord.Interaction):
        # 此处可安全访问 self.embed_user 和 self.floor
        ...

但更推荐使用官方推荐的 @discord.ui.button(custom_id=...) + 启动时全局注册 模式(见下文)。

3. 在 bot 启动时注册 View(add_view),而非运行时创建

这是持久化的决定性一步。bot 必须在启动早期(setup_hook)将 View 类注册到内部路由表,使 Discord 的交互请求能在任何时间点被正确分发到对应处理器。

✅ 推荐方式(使用 setup_hook):

# 在 bot 初始化后、登录前注册
async def setup_hook():
    # 注意:此处传入的是 View 类(无需实例化),且不能带参数!
    bot.add_view(ReviewView())  # ✅ 注册空 View 类(用于匹配 custom_id)

@bot.event
async def on_ready():
    print(f'Logged in as {bot.user}')

# 设置 hook(discord.py v2.0+)
bot.setup_hook = setup_hook

⚠️ 关键约束:

  • add_view(ReviewView()) 中的 ReviewView() 必须是无参构造的实例(即 __init__ 不能强制要求 embed_user 等运行时参数);
  • 因此,持久化 View 的业务数据(如 embed_user, floor)不能存于 View 实例属性中,而应通过 custom_id 编码并解析。

✅ 最佳实践:将状态编码进 custom_id,并在回调中解码:

import json

class ReviewView(discord.ui.View):
    def __init__(self):
        super().__init__(timeout=None)

    @discord.ui.button(
        label="Accept",
        style=discord.ButtonStyle.success,
        custom_id="review_accept"  # ✅ 静态 ID,供全局注册
    )
    async def approve(self, interaction: discord.Interaction, button: discord.ui.Button):
        # 从 interaction.message.embeds[0] 或 message.content 中提取原始参数
        # 更可靠的方式:在发送 View 时,将必要数据存入 embed 的 footer 或 field(不可见)
        # 或 —— 推荐:利用 custom_id 编码(需保证长度 ≤100)
        # 示例:custom_id = "review_accept|123456789|3" → 用户ID|楼层
        pass

# 发送消息时,使用带参数的 View 实例(仅用于渲染,不参与持久路由)
# 而持久路由由上面注册的无参 View 类处理
view = ReviewView()  # 无参实例,仅用于本次发送
await interaction.followup.send(embed=embed, view=view, ephemeral=False)

但更清晰的模式是分离「渲染 View」与「持久 View」:

# 持久化处理器(无状态,仅响应)
class PersistentReviewView(discord.ui.View):
    def __init__(self):
        super().__init__(timeout=None)

    @discord.ui.button(label="Accept", style=discord.ButtonStyle.success, custom_id="persistent_review_accept")
    async def approve(self, interaction: discord.Interaction, button: discord.ui.Button):
        # 解析 custom_id 或从消息中提取上下文
        # 例如:检查 interaction.message.embeds[0].footer.text 是否含 "user:123|floor:3"
        embed = interaction.message.embeds[0]
        footer = embed.footer.text or ""
        if "|" in footer:
            parts = footer.split("|")
            if len(parts) >= 2:
                try:
                    user_id = int(parts[0].split(":")[1])
                    floor = int(parts[1].split(":")[1])
                    # 执行业务逻辑...
                    await interaction.response.send_message("✅ 已接受申请", ephemeral=True)
                except (ValueError, IndexError):
                    await interaction.response.send_message("❌ 数据解析失败", ephemeral=True)
        else:
            await interaction.response.send_message("❌ 缺少上下文信息", ephemeral=True)

# 启动注册
async def setup_hook():
    bot.add_view(PersistentReviewView())
bot.setup_hook = setup_hook

? 总结:持久化三要素缺一不可

要素 作用 错误示例 正确做法
timeout=None 防止 View 自动销毁 super().__init__()(默认 60s) super().__init__(timeout=None)
custom_id 提供跨重启的交互路由键 未设置(自动生成临时 ID) 显式声明静态或确定性 ID(≤100 字符)
bot.add_view(ViewClass()) 注册处理器到全局路由表 仅在命令中 view=View() 在 setup_hook 中调用 bot.add_view(ViewClass())

完成以上配置后,即使 bot 断线重连、容器重启或触发 RESUMED session,按钮交互也将稳定响应,彻底告别 “This interaction failed”。务必测试:重启 bot 后点击历史消息中的按钮,验证是否仍可触发 approve 回调。

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

2023.07.20

1551

4

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

2023.07.25

3624

7

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.07.31

1549

3

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

2023.08.03

20717

23

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

2567

5

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

2627

5

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

1063

5

python合并两个列表
python合并两个列表

Python是一种强大的编程语言,具有许多方便的功能和工具。在Python中,有多种方法可以合并两个列表。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.10

576

4

python是前端还是后端
python是前端还是后端

Python属于前端也属于后端,其灵活性和丰富的生态系统使得开发人员能够在不同的领域中灵活运用。本专题为大家提供python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

2023

5

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.6万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 133.1万人学习