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

如何实现discord.py视图持久化,让机器人重启后按钮仍可工作?

问题分析与解决方案

核心问题

机器人重启后点击按钮出现「This interaction failed」,是因为你只给GameRoleButtons设置了timeout=None,但没有在机器人启动时注册持久化视图。Discord需要机器人重启后能关联旧消息的按钮交互到对应的处理类,否则无法识别。

解决步骤

  • 在on_ready事件中添加client.add_view(GameRoleButtons()),注册你的持久化视图。
  • 确保所有按钮的custom_id唯一且固定(你已经正确设置了,比如er_btn、removegameroles_btn,这是持久化的关键标识)。
  • 若后续修改View结构(增删按钮),需重新发送带View的消息,旧消息按钮会因结构不匹配失效。

修改后的完整代码

import discord
from discord import Intents, Client, Message, app_commands

# 替换为你的TOKEN
TOKEN = "YOUR_TOKEN_HERE"

intents: Intents = Intents.default()
intents.message_content = True
client: Client = Client(intents=intents)
tree = app_commands.CommandTree(client)

badcommand_embed = discord.Embed(color=0xfffb00, title='Invalid command',
                                type='rich', description='You cannot use this command here.')

class GameRoleButtons(discord.ui.View):
    def __init__(self) -> None:
        # 设置timeout=None让视图持久化
        super().__init__(timeout=None)

    @discord.ui.button(label='Elden Ring', style=discord.ButtonStyle.secondary, 
                       emoji=discord.PartialEmoji(name='EldenRing', id='[EMOJI_ID_HERE]'), 
                       custom_id='er_btn')
    async def eldenring_button(self, interaction: discord.Interaction, button: discord.ui.Button):
        role = interaction.guild.get_role([ROLE_ID_HERE])
        if role not in interaction.user.roles:
            await interaction.user.add_roles(role)
            await interaction.response.send_message(
                f'The <:EldenRing:[EMOJI_ID_HERE]> <@&[ROLE_ID_HERE]> role has been assigned to you.', 
                ephemeral=True
            )
        else:
            await interaction.user.remove_roles(role)
            await interaction.response.send_message(
                f'The <:EldenRing:[EMOJI_ID_HERE]> <@&[ROLE_ID_HERE]> role has been removed from you.', 
                ephemeral=True
            )

    @discord.ui.button(label='Remove game roles', style=discord.ButtonStyle.danger, 
                       emoji='✖', custom_id='removegameroles_btn')
    async def removegameroles_button(self, interaction: discord.Interaction, button: discord.ui.Button):
        # 这里可以添加多个角色移除逻辑,当前只移除了一个示例角色
        role = interaction.guild.get_role([ROLE_ID_HERE])
        await interaction.user.remove_roles(role)
        await interaction.response.send_message('Your game roles have been removed.', ephemeral=True)

# 注意:get_response函数未定义,若不需要请删除此部分或补充定义
async def send_message(message: Message, user_message: str) -> None:
    if not user_message:
        print('Message was empty as Intents were not enabled.')
        return

    is_private = user_message[0] == '$'
    if is_private:
        user_message = user_message[1:]

    try:
        # 替换为你的实际响应逻辑,或删除此部分
        # response: str = get_response(user_message, message.author)
        # await message.author.send(response) if is_private else await message.channel.send(response)
        pass
    except Exception as e:
        print(e)

@client.event
async def on_ready() -> None:
    await tree.sync(guild=discord.Object(id=[GUILD_ID_HERE]))
    # 注册持久化视图,关键步骤!
    client.add_view(GameRoleButtons())
    print(f'{client.user} is now running.')

# 游戏角色选择命令
@tree.command(name="gameroles", description="Displays the list of roles you can obtain.", guild=discord.Object(id=[GUILD_ID_HERE]))
async def gameroles_command(interaction: discord.Interaction):
    if interaction.channel_id == [CHANNEL_ID_HERE]:
        gameroles_embed = discord.Embed(color=0xfffb00, title='Select a game role',
                                        type='rich', description='Choose roles for the games that you play.')
        await interaction.channel.send(embed=gameroles_embed, view=GameRoleButtons())
        # 回复交互,避免Discord提示交互超时
        await interaction.response.send_message("角色选择面板已发送!", ephemeral=True)
    else:
        await interaction.response.send_message(embed=badcommand_embed, ephemeral=True)

@client.event
async def on_message(message: Message) -> None:
    if message.author == client.user:
        return

    username: str = str(message.author)
    user_message: str = message.content
    channel: str = str(message.channel)
    guild: str = str(message.guild.name)

    print(f'[{guild} - #{channel}] {username}: {user_message}')

    # 若使用前缀命令,需添加此句处理;若仅用slash命令可删除
    # await client.process_commands(message)
    await send_message(message, user_message)

def main() -> None:
    client.run(token=TOKEN)

if __name__ == '__main__':
    main()

额外优化建议

  • 在slash命令中,使用interaction.response.send_message替代interaction.channel.send来回复交互,避免Discord提示“交互已过期”(原代码中gameroles_command里直接发消息但没回复交互,会导致用户看到加载状态)。
  • 移除未定义的get_response函数,或补充其实现,否则运行会报错。
  • 避免同时使用on_message和slash命令的逻辑冲突,若仅用slash命令,可删除on_message相关的消息处理部分。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 18:22:04