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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 23:24:03