d.py根据用户加入所用邀请码分配角色偶发NoneType错误如何解决
问题原因
报错AttributeError: 'NoneType' object has no attribute 'uses'的核心是find_invite_by_code(invites_after_join, invite.code)返回了None,说明你本地缓存的旧邀请码在新拉取的服务端邀请列表里找不到,触发场景包括:
- 机器人运行过程中有人新增、删除了邀请码,你只在
on_ready时拉取一次邀请列表的缓存逻辑过时,和服务端实际数据不一致 - 你有两个
on_ready事件监听器,托管主机网络延迟更高,当前cog的on_ready还没跑完把对应服务器的邀请列表写入缓存,就有用户加入触发了on_member_join,读取到了空/旧的缓存 - 用户通过服务器自定义vanity链接、发现页等非公开邀请路径加入,没有匹配的邀请码记录
- 你在
on_member_join里写了await asyncio.sleep(1200)的20分钟阻塞逻辑,会导致事件队列堆积,后续邀请列表更新不及时,进一步放大缓存不一致的问题
修复方案
1. 空值判断兜底
先判断邀请查询结果是否存在,避免直接访问属性触发报错
2. 完善邀请缓存更新逻辑
新增on_invite_create、on_invite_delete监听器,实时同步缓存数据,不要只在on_ready拉一次
3. 替换阻塞式延时逻辑
用异步任务处理20分钟后删角色的逻辑,不要阻塞事件监听器
4. 缓存不存在的兜底处理
on_member_join触发时如果当前服务器没有缓存,当场拉取邀请列表
修改后可运行代码
from discord.ext import commands, tasks import asyncio from datetime import datetime, timedelta from variables import no_invite_role_id, disboard_invite, logs_channel_id invites = {} def find_invite_by_code(invite_list, code): for inv in invite_list: if inv.code == code: return inv return None class Disboard(commands.Cog): def __init__(self, bot): self.bot = bot self.remove_role_task.start() self.pending_remove = [] # 存储待删角色的用户ID、服务器ID、到期时间 def cog_unload(self): self.remove_role_task.cancel() # 定时任务每分钟扫一次到期的用户,删角色 @tasks.loop(minutes=1) async def remove_role_task(self): now = datetime.now() to_remove = [item for item in self.pending_remove if item['expire_at'] <= now] for item in to_remove: self.pending_remove.remove(item) guild = self.bot.get_guild(item['guild_id']) if not guild: continue member = guild.get_member(item['member_id']) if not member: logs_channel = self.bot.get_channel(logs_channel_id) if logs_channel: await logs_channel.send(f"{item['member_id']} 已在no-invite周期到期前离开服务器") continue no_invite_role = guild.get_role(no_invite_role_id) if no_invite_role in member.roles: await member.remove_roles(no_invite_role, reason="no-invite周期已到期") @remove_role_task.before_loop async def before_remove_task(self): await self.bot.wait_until_ready() @commands.Cog.listener() async def on_ready(self): await self.bot.wait_until_ready() for guild in self.bot.guilds: invites[guild.id] = await guild.invites() # 新增邀请时更新缓存 @commands.Cog.listener() async def on_invite_create(self, invite): if invite.guild.id not in invites: invites[invite.guild.id] = [] invites[invite.guild.id].append(invite) # 删除邀请时更新缓存 @commands.Cog.listener() async def on_invite_delete(self, invite): if invite.guild.id not in invites: return invites[invite.guild.id] = [inv for inv in invites[invite.guild.id] if inv.code != invite.code] @commands.Cog.listener() async def on_member_join(self, member): # 缓存不存在就当场拉取 if member.guild.id not in invites: invites[member.guild.id] = await member.guild.invites() invites_before_join = invites[member.guild.id] no_invite_role = member.guild.get_role(no_invite_role_id) logs_channel = self.bot.get_channel(logs_channel_id) invites_after_join = await member.guild.invites() used_invite = None for invite in invites_before_join: matched_invite = find_invite_by_code(invites_after_join, invite.code) # 空值直接跳过 if not matched_invite: continue if invite.uses < matched_invite.uses: used_invite = invite break # 更新缓存,不管有没有找到邀请都要更新 invites[member.guild.id] = invites_after_join if not used_invite: # 没有匹配到邀请可以记日志,也可以直接忽略 if logs_channel: await logs_channel.send(f"{member.mention} `{member.id}` 未匹配到有效邀请码加入服务器") return if logs_channel: await logs_channel.send(f"{member.mention} `{member.id}` 通过邀请码 {used_invite.code} 加入服务器") if disboard_invite in used_invite.code and no_invite_role: await member.add_roles(no_invite_role, reason="通过Disboard邀请加入") # 加入待删队列,20分钟后删角色 self.pending_remove.append({ "member_id": member.id, "guild_id": member.guild.id, "expire_at": datetime.now() + timedelta(minutes=20) }) def setup(bot): bot.add_cog(Disboard(bot))
额外优化说明
如果还是偶发匹配不到邀请的情况,可以给机器人开通View Audit Log权限,获取不到邀请码时直接查审计日志里的成员加入记录,匹配对应的邀请创建者即可。
内容的提问来源于stack exchange,提问作者Mihai Cristian
相关产品推荐
相关产品推荐

