如何在Discord.py运行时更新View组件的按钮样式?
Discord Bot 动态更新按钮样式问题解决方案
问题现状
使用discord.py开发的游戏Bot,支持展示带按钮的消息,点击按钮可编辑消息内容。按钮需根据每日定时更新的数据显示红、绿、灰三种样式,但Bot运行后无法动态更新已添加View组件的按钮颜色,只能每日重启Docker容器重新加载View。
现有核心代码如下:
基类Buttons实现
class Buttons(discord.ui.View): bosses_always_expected = [...] bosses_world_changes = [...] bosses = [] def __init__(self, bot, *, timeout=None): super().__init__(timeout=timeout) self.channel_logs = LOG_CHANNEL self.bot = bot @staticmethod def ifBossExpected(label): sorted_boss_list = tibia.expectations.readTableFromGuildStats(SERVER) for boss in sorted_boss_list: if label.lower().__contains__(boss.name.lower()): print("match " + boss.name) return True @staticmethod def create_button(label, style): @discord.ui.button(label=label, style=style) async def button(self, button: discord.ui.Button, interaction: discord.Interaction): await self.handle_button_click(interaction, label) return button @staticmethod def replace_line(original_content, place, user, time, date): updated_lines = [] for line in original_content.split("\n"): if place in line: text_checked = Buttons.create_txt(place, user, time, date) updated_lines.append(text_checked) else: updated_lines.append(line) updated_content = "\n".join(updated_lines) return updated_content @staticmethod def create_txt(place, user, time, date): return f"**{place}**: checked at {time} by {user} ({date})" async def handle_button_click(self, interaction: discord.Interaction, place: str): user = interaction.user current_time = datetime.datetime.now().strftime("%H:%M") current_day = datetime.datetime.now().strftime("%d.%m") original_content = interaction.message.content channel = self.bot.get_channel(self.channel_logs) message = Buttons.create_txt(place, user.display_name, current_time, current_day) await channel.send(message) new_content = Buttons.replace_line(original_content, place, user.mention, current_time, current_day) await interaction.response.edit_message(content=new_content)
Venore子类实现
class Venore(Buttons): bosses = ["Captain Jones", "Dharalion", "Rukor Zad", "Yaga The Crone", "Xenia"] def __init__(self, bot, *, timeout=None): super().__init__(bot, timeout=timeout) for boss in bosses: style = discord.ButtonStyle.red if Buttons.ifBossExpected(boss) or boss in Buttons.bosses_always_expected: style = discord.ButtonStyle.green if boss in Buttons.bosses_world_changes: style = discord.ButtonStyle.gray locals()[f"{boss.lower().replace(' ', '_')}_button"] = Buttons.create_button(boss, style)
主函数发送消息
await venore_checking(channel_thais_veno) ... async def venore_checking(channel): venore = Venore(bot) message = "## Venore\n" \ "**Captain Jones** has not yet been checked \n" \ "**Dharalion** has not yet been checked \n" \ "**Rukor Zad** has not yet been checked \n" \ "**Yaga the Crone** has not yet been checked \n" \ "**Xenia** has not yet been checked " await channel.send(message, view=venore)
解决方案
1. 重构View类,在初始化阶段动态生成按钮
原Venore类的按钮创建逻辑在类定义层级,只会在类加载时执行一次,无法动态更新样式。将按钮生成逻辑移到__init__方法中,使用View的add_item标准方法添加按钮,确保每次实例化View都重新计算样式:
class Venore(Buttons): bosses = ["Captain Jones", "Dharalion", "Rukor Zad", "Yaga The Crone", "Xenia"] def __init__(self, bot, *, timeout=None): super().__init__(bot, timeout=timeout) # 初始化时动态生成按钮 self._add_boss_buttons() def _add_boss_buttons(self): for boss in self.bosses: style = discord.ButtonStyle.red if self.ifBossExpected(boss) or boss in self.bosses_always_expected: style = discord.ButtonStyle.green if boss in self.bosses_world_changes: style = discord.ButtonStyle.gray # 创建按钮并添加到View button = self.create_button(boss, style) self.add_item(button)
同时,将create_button从静态方法改为类方法,避免绑定问题:
@classmethod def create_button(cls, label, style): @discord.ui.button(label=label, style=style) async def button(self, button: discord.ui.Button, interaction: discord.Interaction): await self.handle_button_click(interaction, label) return button
2. 保存消息对象,定时更新View
在Bot启动时保存发送的带View的消息对象,通过定时任务每日生成新的View(带最新样式),并编辑消息替换旧View:
# 全局存储消息对象,或挂载到bot属性中 venore_message = None async def venore_checking(channel): global venore_message venore = Venore(bot) message_content = "## Venore\n" \ "**Captain Jones** has not yet been checked \n" \ "**Dharalion** has not yet been checked \n" \ "**Rukor Zad** has not yet been checked \n" \ "**Yaga the Crone** has not yet been checked \n" \ "**Xenia** has not yet been checked " venore_message = await channel.send(message_content, view=venore)
添加定时任务(使用discord.ext.tasks):
from discord.ext import tasks @tasks.loop(hours=24) async def update_boss_buttons(): global venore_message if venore_message is None: return # 生成带最新样式的View new_view = Venore(bot) # 编辑消息替换View await venore_message.edit(view=new_view) # Bot就绪后启动定时任务 @bot.event async def on_ready(): await update_boss_buttons.start()
3. 确保数据实时性
确保ifBossExpected方法每次调用都读取最新的服务器数据,避免使用缓存的旧数据,保证按钮样式计算基于最新信息。
核心原理
- 将按钮生成逻辑从类定义层级移到实例初始化层级,使每次创建View都重新计算按钮样式。
- 通过编辑已发送消息的
view参数,实现无需重启Bot即可动态更新按钮样式。 - 使用discord.py标准的
add_item方法管理View组件,避免非标准方式导致的组件注册问题。
内容的提问来源于stack exchange,提问作者gh0sty
相关产品推荐
相关产品推荐

