Discord.py 如何在on_guild_join等监听器事件中调用自定义命令?
报错根源定位
你的报错是两个层面的问题共同导致的:
bot.invoke()的设计要求是接收一个已经完成初始化的Context上下文对象作为入参。你现在把bot.get_command('setup')返回的Command实例直接传给了invoke,方法内部第一行就会尝试校验传入对象的ctx.command属性,而Command实例本身根本不存在这个属性,因此直接抛出AttributeError: 'Command' object has no attribute 'command'。- 你之前的判断完全准确:
on_guild_join是生命周期事件,本身不会携带任何消息交互上下文,天然没有可以直接传给invoke的现成ctx对象,硬套invoke流程本质上违背了这个方法的设计初衷。
最优实现方案:抽离公共业务逻辑
社区通用的正确做法是不要硬凑模拟ctx走invoke流程,这种写法耦合度高、跨版本兼容性差,属于明确的反模式。
正确的代码复用逻辑非常简单:
- 把原
setup命令里的所有业务操作抽成一个独立的协程,这个协程不依赖Context对象,只接收必要的参数(比如guild实例)。 - 不管是用户手动发消息触发setup命令,还是机器人加入服务器时自动执行初始化,都直接调用这个独立协程即可。
参考实现代码:
class SetupTasksCog(commands.Cog): def __init__(self, bot): self.bot = bot # 独立的可复用业务逻辑:不依赖任何ctx属性 async def run_guild_initialization(self, guild: discord.Guild): # 这里迁移原来setup命令里的全部逻辑:创建默认角色、配置频道权限、写入数据库默认配置等 await guild.create_role(name="已验证用户") await guild.create_text_channel("官方公告") # 其他初始化操作... # 用户手动触发的setup命令,仅负责桥接ctx和公共逻辑 @commands.command() async def setup(self, ctx: commands.Context): await self.run_guild_initialization(ctx.guild) # 机器人入服事件触发的初始化,直接调用公共逻辑 @commands.Cog.listener() async def on_guild_join(self, guild: discord.Guild): await self.run_guild_initialization(guild)
不推荐的兼容写法:手动构造模拟ctx走invoke
如果你的setup命令逻辑极其复杂,抽离改造成本太高,必须走invoke的完整命令执行流,就一定要构造符合规范的ctx对象,不能直接传Command实例:
- 先定位当前服务器内机器人有发消息权限的频道,构造一个模拟的
Message对象,再通过bot.get_context()生成合法的ctx实例,最后把ctx传给invoke。 - 这种写法的局限性非常明显:模拟消息的作者是机器人自身,所有命令内置的权限校验、用户身份判断逻辑都会按机器人的权限值运行,很容易出现逻辑偏差;而且构造模拟消息需要调用discord.py的内部连接状态接口,跨版本更新时极易失效。
参考实现代码:
@commands.Cog.listener() async def on_guild_join(self, guild: discord.Guild): # 定位机器人有权限发消息的频道 target_channel = guild.system_channel or next( (ch for ch in guild.text_channels if ch.permissions_for(guild.me).send_messages), None ) if not target_channel: return # 构造模拟消息对象 fake_message = discord.Message( state=self.bot._connection, channel=target_channel, data={ "id": 0, "content": "!setup", "author": self.bot.user, "type": 0, "tts": False, "mentions": [], "mention_roles": [], "mention_everyone": False, "attachments": [], "embeds": [], "pinned": False } ) # 基于模拟消息生成合法ctx ctx = await self.bot.get_context(fake_message) # 传入ctx给invoke,即可避免之前的属性报错 await self.bot.invoke(ctx)
内容的提问来源于stack exchange,提问作者c0dezer019
相关产品推荐
相关产品推荐

