Guide

Fixing an old discord.py bot for discord.py 2

Most discord.py tutorials and many bots on GitHub were written for version 1, and pip installs version 2. The errors look unrelated, but there are only about ten of them, and each one has a one-line fix.

7 minute read

discord.py 2.0 came out in August 2022 and changed a few things on purpose. If your code was written before then, or copied from something that was, expect some of the errors below. Check which version you have with pip show discord.py, and pin a current one in requirements.txt: discord.py>=2.4 (>=2.5 on Python 3.13 and later).

TypeError: ... missing 1 required keyword-only argument: 'intents'

Intents say which events Discord sends your bot. In version 2 you have to pass them:

import discord
from discord.ext import commands

intents = discord.Intents.default()
intents.message_content = True   # only if the bot reads what people type

bot = commands.Bot(command_prefix="!", intents=intents)

discord.Client(intents=intents) works the same way.

The bot is online, but prefix commands do nothing

Reading message text needs the Message Content intent, in two places: intents.message_content = True in your code, and the switch under Bot, Privileged Gateway Intents in the Developer Portal. Without it, message.content is empty and !ping never matches. Slash commands do not need it, which is one reason Discord pushes them. See online but not responding for the other causes.

RuntimeWarning: coroutine 'BotBase.load_extension' was never awaited

load_extension, add_cog and a cog file's setup are all async now. Load extensions in setup_hook, which runs once before the bot connects:

class MyBot(commands.Bot):
    async def setup_hook(self):
        await self.load_extension("cogs.fun")

# cogs/fun.py
async def setup(bot):
    await bot.add_cog(Fun(bot))

The bot starts without complaint when you get this wrong; the commands in those cogs just never exist. More in cogs.

AttributeError: loop attribute cannot be accessed in non-async contexts

Old code often did bot.loop.create_task(my_task()) at the top of the file. The loop does not exist until the bot starts. Start background work from setup_hook instead, or use discord.ext.tasks (see scheduled tasks):

class MyBot(commands.Bot):
    async def setup_hook(self):
        self.loop.create_task(my_task())   # fine here

Things that were renamed or removed

  • member.avatar_url: now member.display_avatar.url (the server avatar if they have one, otherwise their own, otherwise the default). member.avatar is None for someone with no avatar, so member.avatar.url can fail.
  • await bot.logout(): now await bot.close().
  • discord.Embed.Empty: gone; use None.
  • await channel.history(limit=50).flatten(): now [m async for m in channel.history(limit=50)].
  • member.permissions_in(channel): now channel.permissions_for(member).
  • Webhook.from_url(url, adapter=RequestsWebhookAdapter()): now discord.SyncWebhook.from_url(url) from ordinary code, or discord.Webhook.from_url(url, session=...) with an aiohttp session inside the bot.

Slash commands from a separate library

Before version 2, slash commands came from add-on packages such as discord-py-slash-command, discord_slash or dislash.py. They do not work with discord.py 2, which has slash commands built in as app_commands. Remove the old package from requirements.txt and follow slash commands in discord.py.

One library, not two

Pycord, nextcord and disnake started as copies of discord.py, and Pycord installs itself under the same name, discord. With both in requirements.txt they overwrite each other's files and you get errors like module 'discord' has no attribute 'Bot' (that one is Pycord code running on discord.py). Pick one, keep only that line, and install again from clean: on SnowServers, delete the .local folder in Files (for a bot in bots, its .snowservers/bots/NAME/venv) and restart.

A file called discord.py

If your own file is named discord.py, import discord imports it instead of the library: partially initialized module 'discord' has no attribute 'Client'. Rename it to bot.py and delete __pycache__.

On SnowServers the console explains most of these as they happen, under a Hint: line. If yours is not here, troubleshooting has the rest.