discord.py-rewrite - Manipulating exceptions through cogs - discord

So, on my main file bot.py, I have:
class Bot(commands.Bot):
# BOT ATTRIBUTES
class MyException(Exception):
def __init__(self, argument):
self.argument = argument
bot = Bot(...)
#bot.event
async def on_command_error(ctx, error):
if isistance(error, bot.MyException):
await ctx.send("{} went wrong!".format(error.argument))
else:
print(error)
Now I also have a cog file, where I sometimes want to throw the Bot().MyException exception:
class Cog(commands.Cog):
def __init__(self, bot):
self.bot = bot
#commands.command()
async def a_command(self, ctx):
if a_condition:
raise self.bot.MyException("arg")
When I run the code, if the a_condition has been validated, the program raises the MyException exception, but the BOT do not send the desired message in the on_command_error() function in bot.py. Instead, the exception gets printed in the console and I get this error message:
Command raised an exception: MyException: arg
Can anyone please tell me how to make the BOT say the desired message in on_command_error() in bot.py?

Commands will only raise exceptions derived from CommandError. When your command raises a non-CommandError exception, it will be wrapped in a CommandInvokeError:
#bot.event
async def on_command_error(ctx, error):
if isinstance(error, commands.CommandInvokeError):
if isinstance(error.original, bot.MyException):
await ctx.send("{} went wrong!".format(error.argument))
return
print(error)

#Patrick Haugh Thank you a lot for this info, I managed to solve this issue by inheriting MyException class from commands.CommandError instead of Exception.
Basically by writing :
class MyException(commands.CommandError):
def __init__(self, argument):
self.argument = argument
instead of:
class MyException(Exception):
def __init__(self, argument):
self.argument = argument
and then leaving:
#bot.event
async def on_command_error(ctx, error):
if isistance(error, bot.MyException):
await ctx.send("{} went wrong!".format(error.argument))
else:
print(error)

Related

Making my discord bot listen for specific messages and answer back when it identifies one

So, I'm new to python and even more to coding bots on discord. At the moment I'm using discord.py and red-bot (https://docs.discord.red/en/stable/index.html).
At the moment I'm trying to make the bot listen to a new message and print something in response to it, but I just can't figure it out. Since I'm using red-bot I didn't go through the steps of using client = discord.Client() and setting a token on the code itself so using #client.event() doesn't seem to work, nor #bot.event(), and I can't really find any other way to make the bot listen for an on_message() event.
Edit with part of my code:
import discord
from discord.ext import commands
from redbot.core import commands
class MyCog(commands.Cog):
client = discord.Client()
def __init__(self, bot):
self.bot = bot
#bot.event()
async def on_message(self, message):
if 'test' in message.content:
await self.send_message(message.channel, 'it works!')
Console returns that bot in #bot.event() is not defined.
Also a separate initialization file, it's done this way to follow Red's guide of how to make a cog.
from .remind import MyCog
def setup(bot):
bot.add_cog(MyCog(bot))
Here is an example of a cog that replies to a specific word mentioned. It also has a cooldown to prevent the bot spamming which I find absolutely neccesary to include.
from curses.panel import bottom_panel
import discord
from discord.ext import commands
class brilliant(commands.Cog):
def __init__(self, bot):
self.bot = bot
self._cd = commands.CooldownMapping.from_cooldown(1, 60.0, commands.BucketType.member) # Change accordingly
# rate, per, BucketType
def ratelimit_check(self, message):
"""Returns the ratelimit left"""
bucket = self._cd.get_bucket(message)
return bucket.update_rate_limit()
#commands.Cog.listener()
async def on_message(self, message):
if message.author == self.bot.user:
return
msg = message.content.lower()
brilliant = ['brilliant', 'Brilliant', 'brilliant!', 'Brilliant!']
if any(word in msg for word in brilliant):
retry_after = self.ratelimit_check(message)
if retry_after is None:
await message.channel.send("Brilliant!")
await self.bot.process_commands(message)
else:
return
async def setup(bot):
await bot.add_cog(brilliant(bot))
If you want it in the main file, you can put something a lot simpler:
brilliant = ['brilliant', 'Brilliant', 'brilliant!', 'Brilliant!']
#bot.event
async def on_message(message):
if message.author == bot.user:
return
msg = message.content.lower()
if any(word in msg for word in brilliant):
await message.channel.send("Brilliant!")
await bot.process_commands(message)
Hope it helps.

#client.command not running, discord.py [duplicate]

Basically, everything appears to work fine and start up, but for some reason I can't call any of the commands. I've been looking around for easily an hour now and looking at examples/watching videos and I can't for the life of me figure out what is wrong. Code below:
import discord
import asyncio
from discord.ext import commands
bot = commands.Bot(command_prefix = '-')
#bot.event
async def on_ready():
print('Logged in as')
print(bot.user.name)
print(bot.user.id)
print('------')
#bot.event
async def on_message(message):
if message.content.startswith('-debug'):
await message.channel.send('d')
#bot.command(pass_context=True)
async def ping(ctx):
await ctx.channel.send('Pong!')
#bot.command(pass_context=True)
async def add(ctx, *, arg):
await ctx.send(arg)
The debug output I have in on_message actually does work and responds, and the whole bot runs wihout any exceptions, but it just won't call the commands.
From the documentation:
Overriding the default provided on_message forbids any extra commands from running. To fix this, add a bot.process_commands(message) line at the end of your on_message. For example:
#bot.event
async def on_message(message):
# do some extra stuff here
await bot.process_commands(message)
The default on_message contains a call to this coroutine, but when you override it with your own on_message, you need to call it yourself.
Ascertained that the problem stems from your definition of on_message, you could actually just implement debug as a command, since it appears to have the same prefix as your bot:
#bot.command()
async def debug(ctx):
await ctx.send("d")

Asking for Kick Member permission despite having given admin rights to Bot

So I am setting up a bot, and I am testing it out. A little function I have there is to kick, ban and unban users, set up in a cog, which goes as follows:
import discord
from discord.ext import commands
class Moderator(commands.Cog):
def __init__(self, bot):
self.bot = bot
#KICK command
#commands.command()
#commands.has_permissions(kick_members=True)
async def kick(self, ctx, member : discord.Member, *, reason=None):
('About to kick')
await member.kick(reason = reason)
#commands.command()
#commands.has_permissions(kick_members=False)
async def kick(self, ctx, member : discord.Member, *, reason=None):
await ctx.send(f'You do not have permission to kick any member, {ctx.message.author.mention}!')
#BAN command
#commands.command()
#commands.has_permissions(ban_members=True)
async def ban(self, ctx, member : discord.Member, *, reason=None):
await member.ban(reason = reason)
await ctx.send(f'Banned {member.mention}')
#commands.command()
#commands.has_permissions(kick_members=False)
async def ban(self, ctx, member : discord.Member, *, reason=None):
await ctx.send(f'You do not have permission to ban any member, {ctx.message.author.mention}!')
#UNBAN command
#commands.command()
#commands.has_permissions(ban_members=True)
async def unban(self, ctx, *, member):
banned_users = await ctx.guild.bans()
member_name, member_discriminator = member.split('#')
for ban_entry in banned_users:
user = ban_entry.user
if (user.name, user.discriminator) == (member_name, member_discriminator):
await ctx.guild.unban(user)
await ctx.send(f'Unbanned {user.mention}')
return
#commands.command()
#commands.has_permissions(kick_members=False)
async def unban(self, ctx, member : discord.Member, *, reason=None):
await ctx.send(f'You do not have permission to unban any member, {ctx.message.author.mention}!')
#CLEAR MESSAGES
#commands.command()
#commands.has_permissions(manage_messages=True)
async def clear(self, ctx, amount=2):
await ctx.channel.purge(limit=amount)
#commands.command()
#commands.has_permissions(manage_messages=False)
async def clear(self, ctx, amount=2):
await ctx.send(f'You do not have permission to delete messages in this way, {ctx.message.author.mention}!')
def setup(bot):
bot.add_cog(Moderator(bot))
Now I have formatted the above code with some spaces so that it fits in one codeblock, so you might face indentation errors if you copy paste it elsewhere.
Moving on, the bot itself has Administrator rights and separate Kicking and Banning rights too. It is also placed at the top of the role hierarchy, which is seen as:
The name of my bot is JunkBot.
Now, whenever I, being the server owner, try using the command .kick #user, it pops up the following error:
The textual form of the error is:
Ignoring exception in command kick:
Traceback (most recent call last):
File "C:\Users\Admin\AppData\Local\Programs\Python\Python38-32\lib\site-packages\discord\ext\commands\bot.py", line 939, in invoke
await ctx.command.invoke(ctx)
File "C:\Users\Admin\AppData\Local\Programs\Python\Python38-32\lib\site-packages\discord\ext\commands\core.py", line 855, in invoke
await self.prepare(ctx)
File "C:\Users\Admin\AppData\Local\Programs\Python\Python38-32\lib\site-packages\discord\ext\commands\core.py", line 777, in prepare
if not await self.can_run(ctx):
File "C:\Users\Admin\AppData\Local\Programs\Python\Python38-32\lib\site-packages\discord\ext\commands\core.py", line 1087, in can_run
return await discord.utils.async_all(predicate(ctx) for predicate in predicates)
File "C:\Users\Admin\AppData\Local\Programs\Python\Python38-32\lib\site-packages\discord\utils.py", line 348, in async_all
for elem in gen:
File "C:\Users\Admin\AppData\Local\Programs\Python\Python38-32\lib\site-packages\discord\ext\commands\core.py", line 1087, in <genexpr>
return await discord.utils.async_all(predicate(ctx) for predicate in predicates)
File "C:\Users\Admin\AppData\Local\Programs\Python\Python38-32\lib\site-packages\discord\ext\commands\core.py", line 1790, in predicate
raise MissingPermissions(missing)
discord.ext.commands.errors.MissingPermissions: You are missing Kick Members permission(s) to run this command.
Similar errors are raised for the ban, unban and clear messages commands.
Interesting part is, I, being the owner, get this error, but supposing another user, a friend of mine who does not have the kicking, banning or message managing role, runs their lines of codes perfectly, where she gets the message from the bot that she does not have permission to kick, ban or clear messages. The screenshot is enclosed.
I don't know where I went wrong. Please help me debug it.
Your problem is in rewriting the functions. I'm assuming that you are hoping to make an errorhandler. However, this isn't the way to do so.
Instead of rewriting the functions with different has_permissions arguments, you will need to make a proper version of an error handler.
First, delete all the duplicate functions. These are the functions that require False on the has_permissions arguments.
Next, make a errorhandler function. You can make one for the Cog commands only, for all commands, or a specific command. If its for all commands, you will use the event on_command_error, (I suggest using it in the main file or as a listener). If it's for the Cog, then you will have to use cog_command_error. If it's for a specific command, you should make the Command.error decorator.
I will be showing the Cog specific handler, but switching back and forth shouldn't take too long.
# Indent into Cog level
async def cog_command_error(self, ctx, error):
# Allows us to check for original exceptions raised and sent to CommandInvokeError.
# If nothing is found. We keep the exception passed to on_command_error.
error = getattr(error, 'original', error)
if isinstance(error, commands.BotMissingPermissions): # Check if bot is missing permissions
await ctx.send(f'I am missing these permissions to do this command:\n{self.lts(error.missing_perms)}')
elif isinstance(error, commands.MissingPermissions): # Check if user is missing permissions
await ctx.send(f'You are missing these permissions to do this command:\n{self.lts(error.missing_perms)}')
#staticmethod
def lts(list_: list): # Used to make reading the list of permissions easier.
"""List to string.
For use in `self.on_command_error`"""
return ', '.join([obj.name if isinstance(obj, discord.Role) else str(obj).replace('_', ' ') for obj in list_])
Here is the list of exceptions you can use in the handler: Exceptions
FYI, the lts function I have isn't required for the handler. It's used to make the list of permissions readable in one string. You can always remove it or change it to your needs.
That is not how error handling works. You should check out the error handling section of the docs for how to deal with missing permissions.
EDIT because people reminded me to give some more information
You shouldn't be handling errors through has_permissions. From what I can tell from your code, you are looking to send a message when the invoker doesn't have the permissions required to use the command. This can be achieved with an error handler. THe error handler will catch the MissingPermissions exception and do something based on that. Here is an example:
#commands.Cog.listener()
async def on_command_error(self, ctx, error): #this is the event that catches errors
if isinstance(error, commands.MissingPermissions): #seeing if the error is the missing permissions error
await ctx.send(f"You do not have the permission to do this, {ctx.author.mention}!")
This uses a global error handler, meaning that whenever an error is caught, this will catch it. However if you want an error handler for each command, all you have to change is the decorater and function name. EX:
#kick.error
async def on_kick_error(self, ctx, error):
if isinstance(error, commands.MissingPermissions):
await ctx.send(f"You do not have the permission to kick any member, {ctx.author.mention} ")
This one will only trigger on the kick command error, none of the others.

discord.py wikipedia search command does not reply

When I try to run my cog it gives an error "No value for argument 'arg' in function call'. Can someone help please?
from discord.ext import commands
from discord.ext.commands import Bot
import asyncio
import os
import datetime
import random
import wikipedia
class wiki(commands.Cog):
def __init__(self, bot):
self.bot = bot
#commands.Cog.listener()
async def on_ready(self):
print("Wikipedia Cog has been loaded\n-----")
#commands.command()
async def wiki(self,ctx,word):
def viki_sum(self,arg):
definition = wikipedia.summary(arg,sentences=3,chars=1000)
return definition
embed = discord.Embed(title="***Wiki'de Bulduklarım:***",description=viki_sum(word))
await ctx.send(embed=embed)
def setup(bot):
bot.add_cog(wiki(bot))```
Hello and welcome to Stack Overflow!
It seems that the error you are getting comes from this section of the code:
#commands.command()
async def wiki(self, ctx, word):
def viki_sum(self, arg):
definition = wikipedia.summary(arg, sentences=3, chars=1000)
return definition
The viki_sum function is a nested function. Even though its enclosing function is a class method and requires using self as the first argument, any nested functions do not require that argument.
So, to overcome the error, you need to remove the self argument from the viki_sum function.
Best of luck!

discord.py-rewrite - Best way to overwrite command cooldown

So I am writing commands with a cooldown on every single one. Like this:
#client.command()
#commands.cooldown(...)
async def bot_command(ctx):
pass
The problem with that is that the cooldown applies to every user. However, I want the cooldown to not apply to the bot developers (IDS are stored in a list). How can I do that in the more efficient way?
You can catch the exception in an exception handler, and invoke the command manually from there.
#client.command()
#commands.cooldown(...)
async def bot_command(ctx):
pass
#bot_command.error
async def bot_command_error(ctx, error):
if isinstance(error, CommandOnCooldown):
if ctx.author.id in list_of_ids:
args = ()
kwargs = {}
await ctx.invoke(ctx.command, *args, **kwargs)
else:
raise error
Note that Context.invoke doesn't do anything but call the callback with the arguments provided. You'll have to handle all the input conversion, pre/post invoke hooks, etc on your own.

Resources