discord.py开发Discord机器人报datetime对象不可调用错误
问题根因
- 直接触发
TypeError: 'datetime.datetime' object is not callable的原因:代码中先执行timenow = datetime.now(timezone.utc),已经把timenow赋值为具体的datetime时间对象(不是可调用的方法/函数),下一行又写timenow()试图把它当函数调用,必然抛出不可调用错误。 - 之前出现的
can’t compare offset-naive and offset-aware datetimes时区错误:是因为混用了带时区标识、不带时区标识的两类datetime对象,同时混用pytz和Python标准库datetime.timezone两套时区处理逻辑导致的。实际上discord.py返回的member.joined_at本身就是UTC时区的带标识时间对象,不需要额外做时区转换。 - 现有逻辑存在部署级隐患:用
asyncio.sleep(604800)硬等7天的写法在Heroku平台完全不可靠,Heroku免费/基础版dyno每天至少重启1次,所有等待中的异步任务会直接丢失,到期不会触发踢人逻辑。
修复方案
1. 修正基础语法与时区逻辑
先清理重复、冲突的导入,修正时间计算的写法:
import discord import asyncio from discord.ext import commands from datetime import datetime, timedelta, timezone # 删除冗余的import datetime、pytz相关导入,避免命名冲突 TOKEN = "*something*" intents = discord.Intents.all() intents.members = True bot = commands.Bot(command_prefix='.', intents=intents) bot.remove_command('help')
修正on_member_update事件中的错误逻辑:
@bot.event async def on_member_update(before, after): if len(before.roles) < len(after.roles): new_role = next(role for role in after.roles if role not in before.roles) guild = bot.get_guild(*something*) if '.• 미인증 유저' in new_role.name: unverified_role = discord.utils.get(guild.roles, name=".• 미인증 유저") await asyncio.sleep(604800) # 等待7天 # 直接计算阈值时间,不要把时间对象赋值后再当函数调用 kick_threshold = datetime.now(timezone.utc) - timedelta(weeks=1) log_channel = bot.get_channel(*something*) # 重新拉取最新成员信息,避免缓存数据不准 latest_member = await guild.fetch_member(after.id) # 直接用自带UTC时区的joined_at比较,不需要额外转时区 if unverified_role in latest_member.roles and latest_member.joined_at < kick_threshold: await latest_member.kick(reason="일주일동안 인증하지 않으셨습니다.") embed = discord.Embed( title="강퇴", colour=0xB11B1B, timestamp=datetime.now(timezone.utc) ) # discord.py 2.0+版本用avatar.url,1.x版本改回avatar_url即可 embed.set_thumbnail(url=latest_member.avatar.url) fields = [ ("이름", f"{latest_member.name}", False), ("사유", f"7일간 미인증으로 머물러 강퇴당함.", False) ] for name, value, inline in fields: embed.add_field(name=name, value=value, inline=inline) await log_channel.send(embed=embed)
2. 补全部署可靠性逻辑
增加机器人启动后的轮询检查任务,解决Heroku重启导致任务丢失的问题:
@bot.event async def on_ready(): bot.loop.create_task(unverified_user_check_loop()) print(f"Bot online: {bot.user}") async def unverified_user_check_loop(): await bot.wait_until_ready() guild = bot.get_guild(*something*) unverified_role = discord.utils.get(guild.roles, name=".• 미인증 유저") log_channel = bot.get_channel(*something*) while not bot.is_closed(): kick_threshold = datetime.now(timezone.utc) - timedelta(weeks=1) # 遍历所有持有未认证角色的用户,补踢重启期间到期的用户 for member in unverified_role.members: if member.joined_at < kick_threshold: await member.kick(reason="일주일동안 인증하지 않으셨습니다.") embed = discord.Embed( title="강퇴", colour=0xB11B1B, timestamp=datetime.now(timezone.utc) ) embed.set_thumbnail(url=member.avatar.url) fields = [ ("이름", f"{member.name}", False), ("사유", f"7일간 미인증으로 머물러 강퇴당함.", False) ] for name, value, inline in fields: embed.add_field(name=name, value=value, inline=inline) await log_channel.send(embed=embed) # 每1小时扫描一次,避免请求超限 await asyncio.sleep(3600)
关键注意点
- 不要混用两套时区处理逻辑,discord.py返回的所有时间对象默认都是UTC带时区标识的,直接和
datetime.now(timezone.utc)比较即可,不需要额外转换。 - 长周期的
asyncio.sleep不要作为唯一的定时触发逻辑,平台重启、网络波动都会导致等待中断,必须搭配轮询任务兜底。 - 注意discord.py版本适配:1.x版本用
avatar_url,2.0+版本用avatar.url,否则会抛出属性不存在的错误。
内容的提问来源于stack exchange,提问作者Kim
相关产品推荐
相关产品推荐

