Discord.py 让@commands.has_role在DM中校验服务器角色的方法
我正在开发一款Discord小型验证机器人:用户可在服务器内启动验证流程,后续流程将通过机器人私信(DM)完成。由于验证各阶段均需要用户输入或权限校验,我设计通过角色划分管控流程节点,仅持有指定角色的用户可调用对应阶段的命令。
当前遇到问题:私信场景下的用户对象不携带服务器角色信息。我已自行编写角色处理模块,可在私信流程中定位用户对应的服务器成员对象完成角色的授予与移除,但该逻辑无法适配@commands.has_role装饰器的内置校验规则。
目标是实现让@commands.has_role(role)装饰器在DM场景下,正确校验用户在对应服务器内的角色身份。
当前问题代码如下:
@commands.has_role("Verf Stage 2") #Issue @slash.slash( name="IamReady", description="Allows you to continue with the verification.", guild_ids=guildIds, permissions = permission2 ) async def _IamReady(ctx:SlashContext): global user_obj role = "Verf Stage 2" await ctx.author.send('Let me see what i can find') username = user_obj.usrname #For testing tempateId = os.getenv('TEMPLATE_ID_TEST') state = await getUserData(userName = username, templateId = tempateId) if state == "NoAssetfound": pass elif state == "Usernotfound": pass else: nftemebed = await getNftEmbed(state) user_obj.assetId = state['asset_id'] # Addrole await rolehandler(user = user_obj.auth, newrole = role, guild = bot.get_guild(id=guildIds[-1])) await ctx.author.send(embed = nftemebed)
注:当前项目仍在开发迭代中。
@commands.has_role内置校验默认仅读取当前上下文(ctx)直接关联的成员角色,DM场景下上下文不归属任何服务器,无法直接获取服务器角色列表,因此内置装饰器必然校验失败,可通过以下两种方案实现需求:
方案1:自定义兼容DM场景的角色校验装饰器(适配现有逻辑)
自行编写装饰器替换原生@commands.has_role,逻辑中主动拉取目标服务器的成员对象完成角色判断,不改动原有命令的业务代码:
from discord.ext import commands from functools import wraps def has_guild_role(role_name, target_guild_id): def decorator(func): @wraps(func) async def wrapper(ctx, *args, **kwargs): # 拉取验证服务器对象 guild = ctx.bot.get_guild(target_guild_id) if not guild: await ctx.send("校验失败:无法连接到验证服务器") return # 获取用户在验证服务器内的成员对象,缓存不存在时主动请求接口兜底 member = guild.get_member(ctx.author.id) or await guild.fetch_member(ctx.author.id) if not member: await ctx.send("校验失败:你不在验证服务器内,无法执行操作") return # 校验角色持有状态 if not any(role.name == role_name for role in member.roles): await ctx.send(f"校验失败:你缺少`{role_name}`权限,无法继续验证流程") return # 校验通过执行原命令 return await func(ctx, *args, **kwargs) return wrapper return decorator
使用时直接替换原有装饰器,传入对应角色名和验证服务器ID即可:
# 替换原有的@commands.has_role("Verf Stage 2") @has_guild_role("Verf Stage 2", target_guild_id=guildIds[-1]) @slash.slash( name="IamReady", description="Allows you to continue with the verification.", guild_ids=guildIds, permissions = permission2 ) async def _IamReady(ctx:SlashContext): # 原有业务逻辑无需修改 ...
方案2:重写内置角色校验全局钩子
如果项目中大量命令都需要在DM场景下做服务器角色校验,可直接重写discord.py的权限校验回调,全局补全DM场景下的角色拉取逻辑,无需逐个替换命令上的装饰器。该方案会影响所有使用内置@commands.has_role的命令,适合全项目统一兼容的场景。
实现时注意几个细节:
get_member读取本地缓存,新加入服务器的用户可能存在缓存缺失,补fetch_member主动请求接口兜底可提升稳定性- 角色名匹配默认区分大小写,如果服务器角色名存在大小写调整的可能,可将角色名、用户持有的角色名统一转小写后再比对
- 校验失败的返回提示可根据业务调整,不建议向普通用户暴露过多内部逻辑细节
内容的提问来源于stack exchange,提问作者Pascal von Flüe

