Subclassing Context in discord.py
Start by reading the guide on subclassing the Bot
class. A subclass of Bot has to be used to
inject your custom context subclass into discord.py.
Overview¶
The way this works is by creating a subclass of discord.py's Context
class
adding whatever functionality you wish. Usually this is done by adding custom methods or properties, so that you don't need to
copy it around or awkwardly import it elsewhere.
This guide will show you how to add a prompt()
method to the context and how to use it in a command.
Example subclass and code¶
The first part - of course - is creating the actual context subclass. This is done similarly to creating a bot subclass, it will look like this:
import asyncio
from typing import Optional
from discord import RawReactionActionEvent
from discord.ext import commands
class CustomContext(commands.Context):
async def prompt(
self,
message: str,
*,
timeout=30.0,
delete_after=True
) -> Optional[bool]:
"""Prompt the author with an interactive confirmation message.
This method will send the `message` content, and wait for max `timeout` seconds
(default is `30`) for the author to react to the message.
If `delete_after` is `True`, the message will be deleted before returning a
True, False, or None indicating whether the author confirmed, denied,
or didn't interact with the message.
"""
msg = await self.send(message)
for reaction in ('✅', '❌'):
await msg.add_reaction(reaction)
confirmation = None
# This function is a closure because it is defined inside of another
# function. This allows the function to access the self and msg
# variables defined above.
def check(payload: RawReactionActionEvent):
# 'nonlocal' works almost like 'global' except for functions inside of
# functions. This means that when 'confirmation' is changed, that will
# apply to the variable above
nonlocal confirmation
if payload.message_id != msg.id or payload.user_id != self.author.id:
return False
emoji = str(payload.emoji)
if emoji == '✅':
confirmation = True
return True
elif emoji == '❌':
confirmation = False
return True
# This means that it was neither of the two emojis added, so the author
# added some other unrelated reaction.
return False
try:
await self.bot.wait_for('raw_reaction_add', check=check, timeout=timeout)
except asyncio.TimeoutError:
# The 'confirmation' variable is still None in this case
pass
if delete_after:
await msg.delete()
return confirmation
After creating your context subclass, you need to override the get_context()
method on your
Bot class and change the default of the cls
parameter to this subclass:
from discord.ext import commands
class CustomBot(commands.Bot):
async def get_context(self, message, *, cls=CustomContext): # From the above codeblock
return await super().get_context(message, cls=cls)
Now that discord.py is using your custom context, you can use it in a command. For example:
import discord
from discord.ext import commands
# Enable the message intent so that we get message content. This is needed for
# the commands we define below
intents = discord.Intents.default()
intents.message_content = True
# Replace '...' with any additional arguments for the bot
bot = CustomBot(intents=intents, ...)
@bot.command()
async def massban(ctx: CustomContext, members: commands.Greedy[discord.Member]):
prompt = await ctx.prompt(f"Are you sure you want to ban {len(members)} members?")
if not prompt:
# Return if the author cancelled, or didn't react in time
return
... # Perform the mass-ban, knowing the author has confirmed this action