Discord.py exception "Missing 1 required positional argument: 'ctx' " although I included 'ctx' as one of the function parameters - discord

so I'm having an issue while creating a discord music bot and I've been trying to solve it for a while now, but I can't find any solution.
Here's the constructor and three functions to search and play music that are necessary for the command to actually work (I'll only include the skip command since I get the same exception with the rest of the commands)
class music(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.is_playing = False
self.is_paused = False
self.music_queue = []
self.YDL_OPTIONS = {"format": "bestaudio", "noplaylist": "True"}
self.FFMPEG_OPTIONS = {"before_options": "-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5", "options": "-vn"}
self.vc = None
async def play_music(self, ctx):
if len(self.music_queue) > 0:
self.is_playing = True
m_url = self.music_queue[0][0]["source"]
if self.vc == None or not self.vc.is_connected():
self.vc == await self.music_queue[0][1].connect()
if self.vc == None:
await ctx.send("I'm sorry, but I can't join the voice channel")
return
else:
await self.vc.move_to(self.music_queue[0][1])
self.music_queue.pop(0)
self.vc.play(discord.FFmpegPCMAudio(m_url, **self.FFMPEG_OPTIONS), after=lambda e: self.play_next())
else:
self.is_playing = False
def play_next(self):
if len(self.music_queue) > 0:
self.is_playing = True
m_url = self.music_queue[0][0]["source"]
self.music_queue.pop(0)
self.vc.play(discord.FFmpegPCMAudio(m_url, **self.FFMPEG_OPTIONS), after=lambda e: self.play_next())
else:
self.is_playing = False
def search(self, item):
with YoutubeDL(self.YDL_OPTIONS) as ydl:
try:
info = ydl.extract_info("ytsearch:{0}".format(item), download=False)["entries"][0]
except Exception:
return False
return {"source": info["formats"][0]["url"], "title": info["title"]}
async def setup(client):
await client.add_cog(music(client))
bot.py with setup_hook and the command
class MyBot(commands.Bot):
async def setup_hook(self):
await self.load_extension("dustobot")
#commands.command(aliases=["s"], help="Skips the current song that is being played")
async def skip(self, ctx, *args):
if self.vc != None and self.vc:
self.vc.stop()
await self.play_music(ctx) #Calling a function that plays the next song in a queue
And this is the error I get:
2023-01-21 12:28:37 ERROR discord.ext.commands.bot Ignoring exception in command skip
Traceback (most recent call last):
File "C:\Users\private_path\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\discord\ext\commands\core.py", line 229, in wrapped
ret = await coro(*args, **kwargs)
TypeError: music.skip() missing 1 required positional argument: 'ctx'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "C:\Users\private_path\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\discord\ext\commands\bot.py", line 1349, in invoke
await ctx.command.invoke(ctx)
File "C:\Users\private_path\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\discord\ext\commands\core.py", line 1023, in invoke
await injected(*ctx.args, **ctx.kwargs) # type: ignore
File "C:\Users\private_path\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\discord\ext\commands\core.py", line 238, in wrapped
raise CommandInvokeError(exc) from exc
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: TypeError: music.skip() missing 1 required positional argument: 'ctx'
Is it because I'm using a deprecated discord.py syntax?
Thanks in advance for taking your time to help me, I really appreciate it.
Btw this is my main.py:
import os
import discord
from dustobot import music #This imports the music class
from dotenv import load_dotenv
from discord.ext import commands
load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
intents = discord.Intents.default()
intents.message_content = True
client = commands.Bot(command_prefix="!", intents=intents)
client.remove_command("help") #I have my own help command
if __name__ == "__main__":
client.run(TOKEN)

The issue is caused by the way you created the command:
#client refers to the bot
#client.command(aliases=["s"], help="Skips the current song that is being played")
The decorator doesn't expect this to be a method in a class. As such, it's not invoked as a method either, so self won't be passed automatically and the Context will be the first argument (self). As a result, the library won't pass a value for ctx, which results in your error.
In cogs, commands should be created using #commands.command instead of #<Bot_instance>.command, the reason being that you shouldn't have your Bot instance available to you in that scope.
You could've also gotten this by reading the manual for Cogs...
Every command is marked with the commands.command() decorator.

Related

Delete messages from specifc user using Discord py

I wanted to create a clear command where if example .clear #user#0000 100 it will clear the last 100 messages #user#0000 sent. Here is my code:
#commands.command()
#commands.has_permissions(manage_messages=True)
async def clear(self, ctx, amount=1):
await ctx.channel.purge(limit=amount + 1)
#clear.error
async def clear_error(self, ctx, error):
if isinstance(error, commands.MissingPermissions):
await ctx.send('Sorry, you are missing the ``MANAGE_MESSAGES`` permission to use this command.')
This code i provided only deletes depeneding how many messages will be deleted in a channel. I want to make a code where the bot will delete messages from a specific user. If this is possible, please let me know. I will use the code as future reference. Much appreciated.
You can use check kwarg to purge messages from specific user:
from typing import Optional
#commands.command()
#commands.has_permissions(manage_messages=True)
async def clear(self, ctx, member: Optional[discord.Member], amount: int = 1):
def _check(message):
if member is None:
return True
else:
if message.author != member:
return False
_check.count += 1
return _check.count <= amount
_check.count = 0
await ctx.channel.purge(limit=amount + 1 if member is None else 1000, check=_check)

on_member_update nickname change doesn't work out as intended

I was trying to set events for my bot, however, it always returns this error after trying to edit the nickname on my server:
#commands.Cog.listener()
async def on_member_update(self, ctx, member_before, member_after):
nickname_after = f"{member_after.nickname}" if not member_after.nickname else member_after.nickname
nickname_before = f"{member_before.nickname}" if not member_before.nickname else member_before.nickname
embed = discord.Embed(timestamp = ctx.message.created_at , color = discord.Colour.random() ,
title = "Member Update")
embed.add_field(name = "Nickname before:" , value = f"{nickname_before}" , inline = True)
embed.add_field(name = "Nickname after:" , value = f"{nickname_after}" , inline = True)
embed.set_thumbnail(url = f"{member_after.avatar.url}")
embed.set_author(name = f"{self.osamu.user}" , icon_url = f"{self.osamu.avatar.url}")
channel = self.osamu.get_channel(898199584726188042)
return await channel.send(embed = embed)
The following traceback has been detected:
Ignoring exception in on_member_update
Traceback (most recent call last):
File "/home/ohnezahn/.local/lib/python3.9/site-packages/discord/client.py", line 352, in _run_event
await coro(*args, **kwargs)
TypeError: on_member_update() missing 1 required positional argument: 'member_after'
What I'm trying to achieve: if the user is editing his nickname the bot shall send the embed in the Channel which I've set (channel = self.osamu.get_channel(898199584726188042)). However, the response is the traceback, no embed whatsoever...
According to the docs, on_member_update only takes 2 parameters: before and after. Since your listener seems to be in a class you also need to use self as the first argument.
So if you remove the ctx from your arguments it should work properly.
#commands.Cog.listener()
async def on_member_update(self, member_before, member_after):
https://discordpy.readthedocs.io/en/master/api.html?highlight=on_member_update#discord.on_member_update

How to check if a user has permission to use a command? Discordpy

I am creating a custom help command and I have a problem. The default help command can somehow hide commands that are not available for a user that invoked the command. I was looking everywhere and I can't find any solution. I tried using hidden attribute of a command object but it is not working. The best solution would be a method like Member.can_use(command) that would return True or False but there is nothing like that in docs. Or at least I cannot find it.
Help would be appreciated.
Use the Command.can_run method, an example would be:
#bot.command()
async def can_it_run(ctx, command_name: str):
command = bot.get_command(command_name)
can_use = await command.can_run(ctx)
if can_use:
await ctx.send(f"You can use {command_name}")
else:
await ctx.send(f"You can't use {command_name}")
Sadly if a check raises an exception (like commands.guild_only) the exception(s) won't be suppressed, also if you want to check if someone else (not the invoker) can use the command you have to overwrite Context.author, a handy function would be:
async def can_run(ctx, command_name: str, member: discord.Member=None) -> bool:
command = bot.get_command(command_name)
if command is None: # Command doesn't exist / invalid command name
return False # Or raise an exception
if member is not None: # If a member is passed overwrite the attribute
ctx.author = member
try:
return await command.can_run(ctx)
except Exception:
return False
#bot.command()
async def can_it_run(ctx, command_name: str, member: discord.Member=None):
resp = await can_run(ctx, command_name, member)
await ctx.send(resp)
You could just check if the user countains a certain id by doing the following:
if (message.author.id == "WHoever ID1" || message.author.id == "Whoever ID2"):
#Do Something
If you want to put a bunch of ID's in then just do the following:
bool = false;
arr = ["ID1", "ID2", "ID3"]
for x in arr:
if(x == message.author.id):
bool = true;
if(bool):
#Do stuff
bool = false;
This is very vague code but it should be something similar to this

Bot saying only one word in string

As in title. I'm trying to get my bot to send an announcement, without having to use "" to capture the whole sentence. What the hell do you mean?
Here's my code:
#bot.command(pass_context=True)
#discord.ext.commands.has_permissions(administrator=True)
async def announce(ctx, message : str):
if message == None:
return
else:
embed = discord.Embed(title='yBot | ANNOUNCEMENT', description='', color= 0xFF0000)
embed.add_field(name="ANNOUNCEMENT: ", value="{}".format(message))
embed.set_footer(text="© 2020 - Powered by yanuu ;k#2137")
await ctx.send("||#everyone||")
await ctx.send(embed=embed)
Your issue is in the way you defined the command it self. It should be with a * so that it will take everything after
async def announce(ctx,*, message : str):
Have a look at this doc

How do functions work in cogs (discord.py)?

An error occurs using this code it says bot is not defined. I don't understand cogs that much but I understand a bit of classes too. I wanted to know how functions work in a cog and how variables are assigned like guildstats = ... below.
This is my code: (I am trying to make a database using the discord bot in a guild. The code works without using cogs but I wanted it to be easier to debug any errors so I went for cogs.)
class Boot(commands.Cog):
def __init__(self, bot):
self.bot = bot
guildstats = pd.read_excel('DiscordStats.xlsx',sheet_name=0)
userstats = pd.read_excel('DiscordStats.xlsx',sheet_name=1)
def dataframe_to_excel(df1 = guildstats, df2 = userstats):
with pd.ExcelWriter('DiscordStats.xlsx', mode = 'w') as writer:
df1.to_excel(writer, index=False, sheet_name = 'GuildStats')
df2.to_excel(writer, index=False, sheet_name = 'UserStats')
def guildstats_writer():
guild_row_data = []
for guild in self.bot.guilds:
if '\''+str(guild.id) not in guildstats['GuildID'].to_list():
guild_row_data.append([guild.name,'\''+str(guild.id),'','',False])
else:
pass
guild_row = pd.DataFrame(guild_row_data,columns = guildstats.columns.to_list())
guildstats1 = guildstats.append(guild_row,ignore_index=True)
Boot.dataframe_to_excel(df1=guildstats1)
#commands.Cog.listener()
async def on_ready(self):
Boot.guildstats_writer(self)
await sbot.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name="for MeepMop ~help"))
print(f'{bot.user} is connected to the following guild:')
for guild in bot.guilds:
print(f'{guild.name} (id: {guild.id})')
def setup(bot):
bot.add_cog(Boot(bot))
You're trying to call the variable bot when you never defined it, your program has no idea what the variable "bot" is, try changing all the times you called bot to instead call self.bot instead
For example your on_ready function should look like this:
#commands.Cog.listener()
async def on_ready(self):
Boot.guildstats_writer(self)
await self.bot.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name="for MeepMop ~help"))
print(f'{self.bot.user} is connected to the following guild:')
for guild in self.bot.guilds:
print(f'{guild.name} (id: {guild.id})')

Resources