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

Discord机器人反应角色命令故障:添加反应后无法赋予角色

排查Discord Reaction Roles功能无响应问题

我在Discord机器人中实现简易的reaction roles(反应角色)功能,现有代码能为指定消息添加表情,但触发表情反应时,机器人并未添加对应角色,且无任何报错信息。代码如下:

@bot.slash_command(name="rr")
async def rr_setup(ctx, message_link: str, role: discord.Role, emoji: str,
                   dm: str, dm_rem: str):
    if not ctx.author.guild_permissions.administrator:
        await ctx.respond("You don't have permissions for this command.", ephemeral=True)
        return
    try:
        message_id = message_link.split('/')[-1]
        message = await ctx.channel.fetch_message(message_id)
        if not message:
            await ctx.send("<:no__:974587445138038794> Incorrect syntax!", ephemeral=True)
            return
        await message.add_reaction(emoji)
        def check(reaction, user):
            return str(reaction.emoji) == str(emoji) and user != bot.user and reaction.message.id == message.id
        while True:
            reaction, user = await bot.wait_for('reaction_add', check=check)
            if role not in user.roles:
                await user.add_roles(role)
                await user.send(dm)
            reaction, user = await bot.wait_for('reaction_remove', check=check)
            if role in user.roles:
                await user.remove_roles(role)
                await user.send(dm_rem)
        await ctx.respond("Successfully added reaction to role.", ephemeral=True)
    except Exception as e:
        await ctx.respond("<:no__:974587445138038794> Incorrect syntax!", ephemeral=True)

问题分析与修复

1. 无限循环阻塞协程,监听直接失效

代码里的while True死循环会卡死slash命令的协程,而Discord的slash命令有执行超时限制,一旦协程被系统回收,wait_for的监听就会终止,自然无法响应后续的表情操作。另外,循环后的成功提示await ctx.respond永远不会执行。

修复:把监听逻辑拆成独立异步函数,用asyncio.create_task启动,让命令协程正常结束并返回提示:

import asyncio

async def reaction_role_listener(bot, message, emoji, role, dm, dm_rem, guild_me):
    def check(reaction, user):
        # 兼容自定义表情的匹配逻辑
        if isinstance(reaction.emoji, discord.PartialEmoji):
            return reaction.emoji.id == emoji.id or str(reaction.emoji) == str(emoji)
        return str(reaction.emoji) == str(emoji) and user != bot.user and reaction.message.id == message.id

    while True:
        try:
            # 监听添加反应
            reaction, user = await bot.wait_for('reaction_add', check=check, timeout=None)
            if role not in user.roles:
                # 检查机器人权限和角色位置
                if guild_me.guild_permissions.manage_roles and role.position < guild_me.top_role.position:
                    await user.add_roles(role)
                    # 处理DM发送失败的情况
                    try:
                        await user.send(dm)
                    except discord.Forbidden:
                        print(f"无法给用户{user.name}发送DM")
            # 监听移除反应
            reaction, user = await bot.wait_for('reaction_remove', check=check, timeout=None)
            if role in user.roles:
                if guild_me.guild_permissions.manage_roles and role.position < guild_me.top_role.position:
                    await user.remove_roles(role)
                    try:
                        await user.send(dm_rem)
                    except discord.Forbidden:
                        print(f"无法给用户{user.name}发送DM")
        except Exception as e:
            print(f"监听反应时出错: {e}")
            continue

@bot.slash_command(name="rr")
async def rr_setup(ctx, message_link: str, role: discord.Role, emoji: str,
                   dm: str, dm_rem: str):
    if not ctx.author.guild_permissions.administrator:
        await ctx.respond("You don't have permissions for this command.", ephemeral=True)
        return
    try:
        message_id = message_link.split('/')[-1]
        message = await ctx.channel.fetch_message(message_id)
        if not message:
            await ctx.respond("<:no__:974587445138038794> Incorrect syntax!", ephemeral=True)
            return
        
        # 解析表情,兼容自定义表情
        parsed_emoji = None
        try:
            parsed_emoji = discord.PartialEmoji.from_str(emoji)
            await message.add_reaction(parsed_emoji)
        except:
            parsed_emoji = emoji
            await message.add_reaction(emoji)
        
        # 启动异步监听任务
        asyncio.create_task(reaction_role_listener(bot, message, parsed_emoji, role, dm, dm_rem, ctx.guild.me))
        
        await ctx.respond("Successfully added reaction to role.", ephemeral=True)
    except Exception as e:
        print(f"设置反应角色时出错: {e}")
        await ctx.respond("<:no__:974587445138038794> Incorrect syntax!", ephemeral=True)

2. 表情匹配逻辑不兼容自定义表情

如果用户输入的是自定义表情(格式为<:name:id>),直接用str(reaction.emoji)对比输入字符串可能不匹配——因为reaction.emoji是PartialEmoji对象,若用户输入的是表情名称或直接粘贴表情,就会匹配失败。

修复:添加自定义表情解析逻辑,优先用ID匹配,再回退到字符串匹配。

3. 缺少机器人权限与角色位置检查

机器人必须拥有**管理角色(Manage Roles)**权限,且目标角色的位置必须低于机器人的最高角色,否则无法添加/移除角色。原代码没有这部分检查,失败时无任何提示。

修复:在添加/移除角色前,先验证权限和角色位置。

4. DM发送失败导致监听崩溃

若用户关闭了服务器DM权限,await user.send()会抛出discord.Forbidden异常,原代码未处理该异常,会导致监听协程直接崩溃,后续反应无法被处理。

修复:给DM发送代码套上try-except,捕获异常并打印日志,不中断监听逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 00:24:54