Procedural Discord.py command usage messages - python

I am implementing error messages into my Discord.py bot, where am using cogs to implement commands.
When a user incorrectly uses a command, for example passes no arguments into a command that requires them, I would like the bot to inform them of the correct usage of that specific command.
For example, here I have a simple cog test.py;
from discord.ext import commands
class Test(commands.Cog):
def __init__(self, client):
self.client = client
#commands.command()
async def test_command(self, ctx, arg1: str, arg2: int):
msg = f"{arg1} and {arg2 + 5}"
await ctx.reply(msg)
def setup(client):
client.add_cog(Test(client))
If the user uses the command incorrectly, for example types !test_command foo, I would like the bot to return a message along the lines of
Correct usage: !test_command <arg1> <arg2>
How would I go about doing this? I would like to do it procedurally, and not have to pick from a list of pre-written usage help messages for each command.
Thanks in advance.
EDIT:
Please note I already have the error checking logic in place. I am asking - in the event of an error triggering - how to automatically generate a message to inform the user on how to use the command.

You're looking for Command.signature. This will automatically give you the usage of the command. Just use it in conjunction with the on_command_error and the MissingRequiredArgument error.
Example:
if isinstance(error, commands.MissingRequiredArgument):
await ctx.send(f"Correct Usage: {ctx.prefix}{ctx.command.name} {ctx.command.signature}")
This will return Correct Usage: !test_command <arg1> <arg2>

You can use try except
#commands.command()
async def test_command(self, ctx, arg1: str, arg2: int):
try:
msg = f"{arg1} and {arg2 + 5}"
await ctx.reply(msg)
except:
await ctx.send('Correct usage: !test_command <arg1> <arg2>')

I see a few errors in your code which might stop the bot from working.
First) Why do you use commands.Cog()? It should be client.command/commands.command. The commands just inherite from commands.Cog
Second) The reply method is a bit deprecated. Instead we use await ctx.send()
Now, to include an error code you have to include your own handler for it.
Have a look at the following code:
#commands.Cog.listener
async def on_command_error(self, ctx, error):
try:
if isinstance(error, commands.CommandNotFound):
pass
elif isinstance(error, commands.MissingRequiredArgument):
await ctx.send(str(error))
except Exception as error:
pass
This error handler informs you about the required arguments and mentions them.

Related

discord.py make the say command not ping everyone

Ok so I've tried everything for my command but nothing worked for my !say command to work and not tag #everyone. I tried doing it myself but it still doesn't want to. I'm a beginner so i'm bad but where is the problem and what do i do?
here is my code:
async def say(ctx, msg=None):
if msg is not None:
await ctx.send(msg)
await ctx.message.delete()
if message == "#everyone" or "#here":
break:
Avoid specific mention when sending a message
discord.py already has a feature built-in, to allow or not specific type of mention.
Combining discord.AllowedMentions and discord.abc.Messageable.send, as the following example.
async def avoid_everyone(ctx: commands.Context, *message: str)
# Disallow everyone when sending message
allowed = discord.AllowedMentions(everyone=False)
# Raise an exception if #everyone in message
await ctx.send(message, allowed_mentions=allowed)

Discord.py -- Make the same command with different parameters resulting in different outcomes

I'm new to discord.py.
#bot.command(pass_context=True)
async def AddToDo(ctx, ToDoItem):
if(ctx.message.author.name not in ToDoList):
temporaryList.append(ToDoItem)
ToDoList[ctx.message.author.name] = temporaryList
await ctx.send(f"Successfully added {ToDoItem} to your To Do List!")
The above code works fine. I want to add a message correcting the user when the user types '$AddToDo' with no parameters. How do I do that?
Note: My code doesn't contain a class, but I can add one. I'll need some help with the syntax, though.
You'll want to use command local error handlers.
For example:
#AddToDo.error
async def AddToDo_error(ctx, error):
error = getattr(error, 'original', error)
if isinstance(error, commands.MissingRequiredArgument):
await ctx.send("Missing arguments")

How do I correctly use tasks/the event loop in a discord.py bot?

I read the answer here and tried to adapt it for my own usage:
How to add a function to discord.py event loop?
My situation is as follows:
I'm developing a discord bot in which I have some commands users can run. These commands have a possibility of erroring, in which case I do client.loop.create_task(errorReportingFunction()) which reports the error to me by messaging me. I also have a command which uses asyncio.create_task() instead.
However I'm having memory leaks which vary from mild to crashing the bot after prolonged usage which leads me to think that I'm not using the tasks system correctly. Should I be cleaning up after the tasks I create and deleting them somehow after I'm done using them? Or is there a system that does that automatically.
I'm also not sure how asyncio.create_task() and client.loop.create_task() differ so I'd appreciate some advice on which to use when, or if they're basically the same.
I believe that what you want is to do something (for example sending a message) when an error happens. If so, there are better ways to handle this in discord.py instead of creating tasks.
In the case that you want to control this only for a certain function, you could create an error handler to track the error raised from that function and send you a message whenever one happens:
#bot.command()
async def info(ctx, *, member: discord.Member):
"""Tells you some info about the member."""
fmt = '{0} joined on {0.joined_at} and has {1} roles.'
await ctx.send(fmt.format(member, len(member.roles)))
#info.error
async def info_error(ctx, error):
if isinstance(error, commands.BadArgument):
await ctx.send('I could not find that member...')
# Send a message to the bot owner about the error
await self.bot.get_user(self.bot.owner_id).send('There has been an error in the command')
Although a common practice in discord bots is to have an error handling cog, which would allow you to centralize all the error handling in a single function. It could be something like this:
class ErrorCog(commands.Cog, name='Error'):
'''Cog in charge of the error handling functions.'''
def __init__(self, bot):
self.bot = bot
#commands.Cog.listener()
async def on_command_error(self, ctx, error):
'''Event that takes place when there is an error in a command.
Keyword arguments:
error -- error message '''
error = getattr(error, 'original', error)
# Wrong command
if isinstance(error, commands.CommandNotFound):
message = 'This is not a valid command'
return await ctx.send(message)
# Command is on cooldown
elif isinstance(error, commands.CommandOnCooldown):
if ctx.author.id is self.bot.owner_id:
ctx.command.reset_cooldown(ctx)
return await ctx.command.reinvoke(ctx)
cooldowns = {
commands.BucketType.default: f'for the whole bot.',
commands.BucketType.user: f'for you.',
commands.BucketType.guild: f'for this server.',
commands.BucketType.channel: f'for this channel.',
commands.BucketType.member: f'cooldown for you.',
commands.BucketType.category: f'for this channel category.',
commands.BucketType.role: f'for your role.'
}
return await ctx.send(f'The command `{ctx.command}` is on cooldown {cooldowns[error.cooldown.type]} ')
# Bot lacks permissions.
elif isinstance(error, commands.BotMissingPermissions):
permissions = '\n'.join([f'> {permission}' for permission in error.missing_perms])
message = f'I am missing the following permissions required to run the command `{ctx.command}`.\n{permissions}'
try:
return await ctx.send(message)
except discord.Forbidden:
try:
return await ctx.author.send(message)
except discord.Forbidden:
return
# Here you need to add more instances
# of errors according to your needs
def setup(bot):
bot.add_cog(ErrorCog(bot))

ctx.content in command and event difference (discord.py)

In discord.py is there some option how i can get access to msg.content (or ctx.content how everyone uses it) under a command ? Below you can see 2 examples of what i mean. The first one is event and i simply copy the message and let the bot to send it back. The second is command but there the msg.content doesnt work. My problem is that i dont want to use events so much and have everything under a command.
#bot.event
async def on_message(msg):
chat = bot.get_channel(797224597443051611)
if msg.channel.id != channel:
return
if msg.content.startswith("!rps"):
message = str(msg.content)
await chat.send(message)
Someone types !rps hello. Outpup in discord is !rps hello
#bot.command()
async def rps(msg):
if msg.channel.id != channel:
return
message = str(msg.content)
await msg.send(message)
Someone types !rps hello (my prefix is !). Output is error in console:
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: AttributeError: 'Context' object has no attribute 'content'
Commands always take commands.Context as the first argument, also you should call it ctx instead of msg, to access the message content you can use ctx.message.content
#bot.command()
async def rps(ctx):
if ctx.channel.id != channel:
return
message = str(ctx.message.content)
await ctx.send(message)
Take a look at the commands introduction
In order to get the rest of the message of a command, you need to pass another argument. This argument will include all the messages that's sent after !rps. Also, using ctx instead of msg is better in commands.
#bot.command()
async def rps(ctx, *, args):
if ctx.channel.id != channel:
return
await ctx.send(args)
In this code, args argument includes all the messages after the !rps.

A presence/activity set command?

So, I was wondering if there could be a command I could write that allows me to set the bots presence and activity (ex. ~~set presence idle or ~~set activity watching "people typing ~~help") or something like that.
Unrelated question: How do I set commands to be used by me only?
I haven't found any example code for this, and i'm a beginner.
You can use the is_owner check to ensure that you are the only person who can invoke a command.
To change the presence or status of the bot, use the change_presence method:
from discord.ext.commands import Bot, is_owner
from discord import Status, Activity, ActivityType
bot = Bot("~~")
def getEnum(enum):
def getter(arg):
return enum[arg]
return getter
#bot.group(invoke_without_command=True)
#is_owner()
async def set(ctx):
await ctx.send("You must provide a subcommand.")
#set.command()
async def presence(ctx, status: getEnum(Status)):
await bot.change_presence(status=status)
#set.command(invoke_without_command=True)
async def activity(ctx, type: getEnum(ActivityType), *, description):
await bot.change_presence(activity=Activity(type=type, name=description))
#set.error
async def set_error(ctx, error):
if isinstance(error, BadArgument):
await ctx.send(error.message)
await ctx.send(error.args)
bot.run("token")
The above will fail silently if you try to provide an unrecognized name to Status or ActivityType, you could also try writing an error handler to provide some feedback.

Categories