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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 16:24:57