如何让Discord.py动态创建的按钮实现持久化?
持久化按钮的正确实现方法
你的代码重启后按钮失效的核心问题是未正确注册持久化视图,以及重启后丢失了角色对象与按钮的关联逻辑,以下是具体修正方案:
问题根源
- 视图注册错误:
setup_hook中使用self.add_dynamic_items的方式不符合Discord.py要求,持久化视图必须通过bot.add_view()注册整个视图类,让机器人能识别对应custom_id的交互处理逻辑。 - 依赖临时角色列表:
Self_Roles类的回调依赖初始化时传入的roles列表,重启后这个临时列表不存在,导致无法找到角色对象。 - 冗余的数据存储:你存储了角色名称,但重启后应该通过角色ID直接从服务器获取角色对象——ID是唯一标识,名称可能被修改。
修正步骤
1. 修正视图注册逻辑
在Cog的setup_hook中注册Self_Roles视图类,确保机器人启动时就能识别该视图的交互:
async def setup_hook(self) -> None: # 注册持久化视图类,timeout=None表示永久有效 self.bot.add_view(Self_Roles(self.file_path))
2. 重构Self_Roles类
去掉对初始化时roles参数的依赖,在回调中通过角色ID从服务器获取角色对象,同时拆分按钮添加逻辑:
class Self_Roles(discord.ui.View): def __init__(self, file_path): super().__init__(timeout=None) self.file_path = file_path # 动态添加按钮的方法,创建面板时调用 def add_role_buttons(self, roles): for role in roles: button = discord.ui.Button(label=role.name, style=discord.ButtonStyle.primary, custom_id=str(role.id)) button.callback = self.button_callback self.add_item(button) async def button_callback(self, interaction: discord.Interaction): try: role_id = int(interaction.data['custom_id']) # 直接从服务器获取角色对象,不再依赖临时列表 role = interaction.guild.get_role(role_id) if role: if role in interaction.user.roles: await interaction.user.remove_roles(role) await interaction.response.send_message(f"O cargo '{role.name}' foi removido!", ephemeral=True) else: await interaction.user.add_roles(role) await interaction.response.send_message(f"O cargo '{role.name}' foi adicionado!", ephemeral=True) # 仅存储角色ID与名称(名称可选,核心用ID) with open(self.file_path, 'r') as f: data = json.load(f) data[str(role_id)] = role.name with open(self.file_path, 'w') as f: json.dump(data, f) except Exception as e: logging.error(f'Erro no callback: {e}')
3. 调整创建面板的逻辑
在_roles命令中,创建视图后调用add_role_buttons添加按钮:
# 创建视图并添加按钮 view = Self_Roles(self.file_path) view.add_role_buttons(list_roles) await channel.send(embed=eRole, view=view)
4. 确保机器人重启后加载数据
在Cog的__init__中确保数据文件夹存在,并加载存储的角色数据:
def __init__(self, bot: commands.Bot): self.bot = bot self.data_folder = os.path.join(os.getcwd(), 'data') self.file_path = os.path.join(self.data_folder, 'persistent_role.json') # 确保data文件夹存在 if not os.path.exists(self.data_folder): os.makedirs(self.data_folder) # 初始化JSON文件 if not os.path.exists(self.file_path): with open(self.file_path, 'w') as f: json.dump({}, f) # 加载存储的角色数据 self.load_persistent_roles()
修改后的完整代码
import os import json import discord from discord import app_commands from discord.ext import commands import logging class AddRolesCog(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot self.data_folder = os.path.join(os.getcwd(), 'data') self.file_path = os.path.join(self.data_folder, 'persistent_role.json') # 确保data文件夹存在 if not os.path.exists(self.data_folder): os.makedirs(self.data_folder) # 初始化JSON文件 if not os.path.exists(self.file_path): with open(self.file_path, 'w') as f: json.dump({}, f) # 加载存储的角色数据 self.load_persistent_roles() async def setup_hook(self) -> None: # 注册持久化视图类 self.bot.add_view(Self_Roles(self.file_path)) def load_persistent_roles(self): with open(self.file_path, 'r') as f: self.persistent_roles = json.load(f) @app_commands.command(name=app_commands.locale_str('add_roles', category='Moderation'), description='Create action roles.') @app_commands.default_permissions(mute_members=True) async def _roles( self, interaction: discord.Interaction, channel: discord.TextChannel, role_1: discord.Role, role_2: discord.Role = None, role_3: discord.Role = None, role_4: discord.Role = None, role_5: discord.Role = None, ): try: list_roles = [role for role in [role_1, role_2, role_3, role_4, role_5] if role is not None] await interaction.response.send_message(f'Painel de cargos criado com sucesso em {channel.mention}', ephemeral=True) eRole = discord.Embed( title="Pegue seu cargo!", description="Clique nos botões abaixo para adicionar ou remover os cargos desejados.", color=discord.Color.random() ) # 更新JSON存储的角色信息 for role in list_roles: self.persistent_roles[str(role.id)] = role.name with open(self.file_path, 'w') as f: json.dump(self.persistent_roles, f) # 创建视图并添加按钮 view = Self_Roles(self.file_path) view.add_role_buttons(list_roles) await channel.send(embed=eRole, view=view) except Exception as e: logging.error(str(e)) class Self_Roles(discord.ui.View): def __init__(self, file_path): super().__init__(timeout=None) self.file_path = file_path def add_role_buttons(self, roles): try: for role in roles: button = discord.ui.Button(label=role.name, style=discord.ButtonStyle.primary, custom_id=str(role.id)) button.callback = self.button_callback self.add_item(button) except Exception as e: print(f'Erro no for: {e}') async def button_callback(self, interaction: discord.Interaction): try: role_id = int(interaction.data['custom_id']) # 从服务器获取角色对象 role = interaction.guild.get_role(role_id) if role: if role in interaction.user.roles: await interaction.user.remove_roles(role) await interaction.response.send_message(f"O cargo '{role.name}' foi removido!", ephemeral=True) else: await interaction.user.add_roles(role) await interaction.response.send_message(f"O cargo '{role.name}' foi adicionado!", ephemeral=True) # 更新存储的角色名称(可选) with open(self.file_path, 'r') as f: data = json.load(f) data[str(role_id)] = role.name with open(self.file_path, 'w') as f: json.dump(data, f) except Exception as e: logging.error(f'Erro no callback: {e}') async def setup(bot: commands.Bot): await bot.add_cog(AddRolesCog(bot))
内容的提问来源于stack exchange,提问作者Eduardo Viana
相关产品推荐
相关产品推荐

