Discord.py 基于on_message事件实现批量Ban功能的开发异常排查
批量封禁功能异常排查与优化方案
现有代码核心问题定位
- 指令识别逻辑缺陷
原is_calling_command函数传入current_channel和allowed_channel时均赋值为当前消息的频道ID,两者永远相等,完全丧失频道权限校验作用;同时采用“字符串包含即触发”的判断逻辑,哪怕用户在日常聊天中提及my.ban字样,也会意外触发指令,没有校验指令是否在消息开头、是否带有有效操作参数。 - 异步调用流程错误
client.get_user()是异步方法,原代码在同步的get_member_objects函数中直接调用未添加await,返回的是协程对象而非真实的用户实例,这是控制台触发类型报错的核心原因;且client.get_user()获取的是全局跨服务器的User对象,缺少当前服务器的上下文关联,直接传入封禁接口会触发权限校验失败。 - 消息解析逻辑漏洞
原get_ids函数在遍历列表过程中直接修改列表元素,会出现索引错位、内容漏删的问题,比如用户昵称带数字时会被错误截断,解析结果残留非ID的无效字符串。 - 封禁执行流程上下文缺失
原代码直接调用全局client.ban()接口,没有关联当前操作的服务器对象,且每成功封禁一个用户就发送一条重复的确认消息,容易触发平台的消息频率限制。
可落地的优化实现代码
以下改造完全保留原有模块结构,直接整合到on_message事件流程中,无需依赖第三方批量封禁插件:
# 改造后的ID解析模块 def get_ids(message_content: str): # 清洗格式标记 cleaned_content = message_content.replace("<", "").replace(">", "").replace("@", "").replace("!", "") split_segments = cleaned_content.split() id_list = [] # 跳过第一个元素(即my.ban命令本身),遍历后续参数 for segment in split_segments[1:]: # 仅收集全数字内容作为用户ID if segment.isdigit(): id_list.append(int(segment)) return id_list # 改造后的指令识别模块 def is_calling_command(message_content: str, command_name: str, current_channel_id: int, allowed_channel_id: int, prefix="my."): # 强制要求指令在消息开头匹配,避免正文提及误触发 if not message_content.startswith(prefix + command_name): return False # 频道权限校验 if current_channel_id != allowed_channel_id: return False return True # 改造后的ID转对象模块,适配异步流程 async def get_member_objects(guild, message_content: str): member_objects = [] user_ids = get_ids(message_content) for user_id in user_ids: # 优先从当前服务器缓存获取Member对象 member = guild.get_member(user_id) if not member: try: # 缓存未命中时通过异步接口拉取用户信息 member = await client.fetch_user(user_id) except: continue member_objects.append(member) return member_objects # 改造后的on_message主流程 @client.event async def on_message(message): # 跳过机器人自身发送的消息,避免逻辑死循环 if message.author.bot: return # 指令触发校验 command_triggered = is_calling_command( message_content=message.content, command_name="ban", current_channel_id=message.channel.id, allowed_channel_id=message.channel.id # 可替换为固定的允许使用封禁指令的频道ID ) # 操作人权限校验 operator_has_perm = message.channel.permissions_for(message.author).administrator if command_triggered and operator_has_perm: current_guild = message.guild # 一次性获取所有目标对象,避免重复调用解析与拉取接口 target_members = await get_member_objects(current_guild, message.content) success_count = 0 for member in target_members: try: # 调用服务器级别的封禁接口,上下文更完整 await current_guild.ban(user=member, reason="cause we said so") success_count +=1 except Exception: await message.channel.send(f"封禁用户<@{member.id}>失败:机器人权限不足或用户角色优先级高于机器人") # 汇总结果一次性发送,避免重复消息触发频率限制 await message.channel.send(f"批量封禁完成,共成功封禁{success_count}名用户")
常见报错对照排查
- 触发
TypeError: object can't be used in await expression:为异步接口未添加await导致,返回协程对象而非真实实例。 - 触发
403 Forbidden: Missing Permissions:机器人未获得目标服务器的封禁成员权限,或被操作用户的角色排序优先级高于机器人。 - 触发
404 Not Found: User:解析得到的ID为空、输入的用户ID无效,或目标用户已离开当前服务器。 - 指令触发无响应:确认输入的指令从消息第一个字符开始书写
my.ban,无前置空格,且机器人已获得对应频道的发言权限。
内容的提问来源于stack exchange,提问作者Matthew Tujague
相关产品推荐
相关产品推荐

