Ignoring exception in command None: discord.ext.commands.errors.CommandNotFound: Command "memechannel" is not found error (discord.py) - python

Alright, so I have made a Discord Bot that takes memes from reddit and sends it to a user specified channel every 5 minutes, to do this i had to make a cog
#imports
import discord
...
...
#Automemer
class Automemer(commands.Cog):
def __init__(self, client):
....
#used to loop the task of posting memes
#tasks.loop(minutes=5.0)
async def automemer(self):
all_subs = []
...
#start of formating embed for task loop
name = random_sub.title
url = random_sub.url
em = discord.Embed(title = name)
em.set_image(url = url)
em.color = 0xff0000
em.set_footer(text=f'Memes from r/AmongUsMemes')
#used to post the memes where the user does the command am.memechannel
#commands.command()
async def memechannel(self, ctx):
channel_id = ctx.channel.id
await ctx.send('Memes will now be sent in this channel')
memeChannel = ctx.guild.get_channel(channel_id)
if memeChannel:
emoji1 = ':arrow_up:'
emoji2 = ':arrow_down:'
msg = await ctx.send(embed=em)
await msg.add_reaction(emoji1)
await msg.add_reaction(emoji2)
await ctx.send(embed=em)
Error = Ignoring exception in command None: discord.ext.commands.errors.CommandNotFound: Command "memechannel" is not found
The error happens whenever I run the command am.memechannel.
It would be great if somebody told me how to fix this error.
This is the last feature of the bot, and it will be my first ever Discord Bot released to the public! :)

You defined the commands inside of the task loop, your indentation is messed up. You're supposed to put the command underneath the loop, not inside of it.
#tasks.loop(minutes=5.0)
...
#commands.command()
...
Keep in mind that your em variable is only visible within your task so you'll have to find a way to work around that, otherwise you can't use it in your command. An example is adding it as a class variable instead.
def __init__(self, client):
self.em = discord.Embed()
And then just overwriting that variable as you go along.
self.em = discord.Embed(title=name)
...

Related

Discord.py slash command

I made a function that echoes a text with classic commands. I wanted to make it with the slash command, but it didn't work. Honestly, I was confused, and the discord.py documentation wasn't precise about many things.
In my code, the first two arguments are reserved for channels and time.
The bot will check if the two arguments are provided. If one or both are not, it will ignore them. This is for the channel:
async def echo(ctx, *args):
#If a command is not followed with a text
if not args:
await ctx.send("Please provide some text for me to echo dummy.")
return
#Channel mention
if args[0].startswith("<#") and args[0].endswith(">"):
channel_id = int(args[0][2:-1])
channel = client.get_channel(channel_id)
#If the channel is not valid or doesn't exist
if not channel:
await ctx.send("Invalid channel mention.")
return
args = args[1:]
else:
channel = ctx.channel
The rest of the code is for the time argument:
#Check if there is a time argument
time_match = re.match(r"(\d+)(h|m)", args[0])
if time_match:
time_delta = timedelta(hours=int(time_match.group(1))) if time_match.group(2) == "h" else timedelta(minutes=int(time_match.group(1)))
await ctx.send(f"I will echo the message in {time_delta}.")
await asyncio.sleep(time_delta.total_seconds())
args = args[1:]
else:
time_delta = timedelta()
message = " ".join(args)
await channel.send(message)
I found that converting those will require a lot of understanding, and I am a beginner at python.
I dont know if i understood your problem correct, that creating slash commands is not working for you? I know 2 main ways of how to create slash commands with discord.py:
Use discord.Client and manage the commandTree on your own
Use discord.ext.commands.Bot and use cogs to create commands
Also i want to point out the difference between the commands and the app_commands.
app_commands are the commands you know as slash commands. They are supported by the discord api and will show up in the text channel after typing "/". The normal commands are unknown to disocrd. Discord only sends all text messages to discord.py. discord.py works out if a command was texted or not and will call the coreesponding method.
I highly recommend using commands.Bot and cogs. Also this example is based on it.
So first of all your class for a bot should inherits from commands.Bot. Also we add the mehtod setup_hook to load all cogs you are going to implement.
class MyBot(commands.Bot):
def __init__(self):
super().__init__(intents=discord.Intents.all(), command_prefix='/')
async def setup_hook(self):
for file in os.listdir(f'./cogs'):
if file.endswith('.py'):
await self.load_extension(f'cogs.{file[:-3]}')
Now you can create you first cog in a subfolder named "cogs". I recomment to create one file for every Cog. So for this exmaple we create a file and put the following code inside(note that the name inside #app_commands.command defines how the command will be shown in discord):
class MyCog(commands.Cog):
def __init__(self, client):
self.client = client
#app_commands.command(name='test_command')
async def test_command_method(self, ctx: discord.Interaction, param1: str = None, param2: str = None):
if param1 is None:
param1 = ctx.channel_id # set default value
if param2 is None:
await ctx.response.send_message(f'param2 missing!')
return
await ctx.response.send_message(f'Called with parameters param1: "{param1}" and param2: "{param2}"')
async def setup(client):
await client.add_cog(MyCog(client))
The last thing you need to do is to add another Cog, which will sync your created commands with the discord api, so you can see them in discord after typing "/". Note, that this is using commands and not app_commands! So the sync command will not appear after typing "/", but any other app_command does (after syncing).
class Sync(commands.Cog):
def __init__(self, client):
self.client = client
#commands.command()
async def sync(self, ctx: Context, spec: Optional[Literal["~", "*", "^"]] = None) -> None:
ctx.bot.tree.copy_global_to(guild=ctx.guild)
synced = await ctx.bot.tree.sync(guild=ctx.guild)
await ctx.send(f"Synced {len(synced)} commands {'globally' if spec is None else 'to the current guild.'}")
async def setup(client):
await client.add_cog(Sync(client))
If you have all this things set up correctly and your bot starts without errors you can type "/sync" in any textchannel on any discord server the bot is connected to. After this you should be able to see your commands after typing "/" in a text channel.

How to snipe messages from a specific channel (discord.py)

Outcome
To snipe messages sent in X channel instead of all the channels within the Discord guild. That is, it should only track message deletions in that one channel (identified by its ID), and only respond to the !snipe command in that same channel. The current code I have here snipes every message sent within the Discord guild.
Question
How can I snipe messages sent in X channel instead of the entire guild?
I mostly intend to run this bot in one guild. However, it would be nice if it could scale to multiple guilds if needed.
The code I have so far is below.
import discord
from discord.ext import commands
from tokens import token
client = commands.Bot(command_prefix="!", self_bot=False)
client.sniped_messages = {}
#client.event
async def on_ready():
print("Your bot is ready.")
#client.event
async def on_message_delete(message):
print(f'sniped message {message}')
client.sniped_messages[message.guild.id] = (
message.content, message.author, message.channel.name, message.created_at)
#client.command()
async def snipe(ctx):
try:
contents, author, channel_name, time = client.sniped_messages[ctx.guild.id]
except:
await ctx.channel.send("Couldn't find a message to snipe!")
return
embed = discord.Embed(description=contents,
color=discord.Color.purple(), timestamp=time)
embed.set_author(
name=f"{author.name}#{author.discriminator}", icon_url=author.avatar_url)
embed.set_footer(text=f"Deleted in : #{channel_name}")
await ctx.channel.send(embed=embed)
client.run(token, bot=True)
I'm going to suggest two slightly different solutions, because the code can be simpler if you're only running this bot on one guild. What's common to both is that you need to check in what channel messages are deleted, and in what channel the !snipe command is sent.
Single-Guild Version
If you're only monitoring/sniping one channel on one guild, then you can only ever have one deleted message to keep track of. Thus, you don't need a dictionary like in your posted code; you can just keep a single message or None.
You're already importing your token from a separate file, so you might as well put the channel ID (which is an int, unlike the bot token) there too for convenience. Note that, by convention, constants (variables you don't intend to change) are usually named in all caps in Python. tokens.py would look something like this:
TOKEN = 'string of characters here'
CHANNEL_ID = 123456789 # actually a 17- or 18-digit integer
And the bot itself:
import discord
from discord.ext import commands
from tokens import TOKEN, CHANNEL_ID
client = commands.Bot(command_prefix='!')
client.sniped_message = None
#client.event
async def on_ready():
print("Your bot is ready.")
#client.event
async def on_message_delete(message):
# Make sure it's in the watched channel, and not one of the bot's own
# messages.
if message.channel.id == CHANNEL_ID and message.author != client.user:
print(f'sniped message: {message}')
client.sniped_message = message
#client.command()
async def snipe(ctx):
# Only respond to the command in the watched channel.
if ctx.channel.id != CHANNEL_ID:
return
if client.sniped_message is None:
await ctx.channel.send("Couldn't find a message to snipe!")
return
message = client.sniped_message
embed = discord.Embed(
description=message.content,
color=discord.Color.purple(),
timestamp=message.created_at
)
embed.set_author(
name=f"{message.author.name}#{message.author.discriminator}",
icon_url=message.author.avatar_url
)
embed.set_footer(text=f"Deleted in: #{message.channel.name}")
await ctx.channel.send(embed=embed)
client.run(TOKEN)
Multi-Guild Version
If you're monitoring one channel each in multiple guilds, then you need to keep track of them separately. Handily, channel IDs are globally unique, not just within a single guild. So you can keep track of them by ID alone, without having to include the guild ID as well.
You could keep them in a list, but I recommend a set, because checking whether something is in a set or not is faster. Comments to help yourself remember which one is which are probably also a good idea.
TOKEN = 'string of characters here'
# Not a dictionary, even though it uses {}
CHANNEL_IDS = {
# That one guild
123456789,
# The other guild
987654322,
}
Then instead of checking against the single channel ID, you check if it's in the set of multiple IDs.
import discord
from discord.ext import commands
from tokens import TOKEN, CHANNEL_IDS
client = commands.Bot(command_prefix='!')
client.sniped_messages = {}
#client.event
async def on_ready():
print("Your bot is ready.")
#client.event
async def on_message_delete(message):
# Make sure it's in a watched channel, and not one of the bot's own
# messages.
if message.channel.id in CHANNEL_IDS and message.author != client.user:
print(f'sniped message: {message}')
client.sniped_messages[message.channel.id] = message
#client.command()
async def snipe(ctx):
# Only respond to the command in a watched channel.
if ctx.channel.id not in CHANNEL_IDS:
return
try:
message = client.sniped_messages[ctx.channel.id]
# See note below
except KeyError:
await ctx.channel.send("Couldn't find a message to snipe!")
return
embed = discord.Embed(
description=message.content,
color=discord.Color.purple(),
timestamp=message.created_at
)
embed.set_author(
name=f"{message.author.name}#{message.author.discriminator}",
icon_url=message.author.avatar_url
)
embed.set_footer(text=f"Deleted in: #{message.channel.name}")
await ctx.channel.send(embed=embed)
client.run(TOKEN)
Note: bare except clauses, like in your original code, are not generally a good idea. Here we only want to catch KeyError, which is what is raised if the requested key isn't in the dictionary.
You could, optionally, implement the same logic in a different way:
message = client.sniped_messages.get(ctx.channel.id)
if message is None:
await ctx.channel.send("Couldn't find a message to snipe!")
return
A dictionary's .get() method returns the corresponding item just like normal indexing. However, if there is no such key, instead of raising an exception, it returns a default value (which is None if you don't specify one in the call to get).
If you're using Python 3.8+, the first two lines could also be combined using an assignment expression (using the "walrus operator"), which assigns and checks all at once:
if (message := client.sniped_messages.get(ctx.channel.id)) is None:
await ctx.channel.send("Couldn't find a message to snipe!")
return
These alternative options are mentioned for completeness; all of these ways of doing it are perfectly fine.

how to run a command in tasks.loop discord.py bot

so im trying to get a command to run every 5 minutes on my dsicrod.py bot and need ctx to get guild members and certain details like that so I need it in a bot.command, but I cant have that properly do that without tasks.loop(minutes=5) so I tried getting it to send the command with tasks.loop but it wouldn't work so I went to the pythin discord and got help they got me to this point
#bot.command(pass_context=True)
async def update_member_count(ctx):
await ctx.send(ctx.guild.member_count)
channel = discord.utils.get(ctx.guild.channels, id=829355122122424330)
await channel.edit(name = f'Member Count: {ctx.guild.member_count}')
#tasks.loop(minutes=5)
async def update_member_count2(ctx):
await update_member_count(ctx)
and It still gives errors saying that ctx arg is missing in update_member_count2. pls help
You can create your loop-function in another way. Your approach is a bit unclear to me.
Try out the following:
async def update_member_count(ctx):
while True:
await ctx.send(ctx.guild.member_count)
channel = discord.utils.get(ctx.guild.channels, id=YourID)
await channel.edit(name=f'Member Count: {ctx.guild.member_count}')
await asyncio.sleep(TimeInSeconds)
#bot.command()
async def updatem(ctx):
bot.loop.create_task(update_member_count(ctx)) # Create loop/task
await ctx.send("Loop started, changed member count.") # Optional
(Or you simply create the task in your on_ready event, depends on you)
What did we do?
Created a loop out of a function (async def update_member_count(ctx):)
Exectued updatem one time to activate the loop
Changed your actual command to a "function"
You could go with #Dominik's answer but its near impossible to stop a running While loop. Going ahead with discord.py tasks.
If you want to start the task with a command:
async def start(ctx):
update_member_count2.start(ctx)
If you want to start it when the bot starts, you have to slightly modify the code
#tasks.loop(minutes=5)
async def update_members():
guild = bot.get_guild(guildID)
channel = guild.get_channel(channelID)
#update channel, don't sleep
# inside init or on_ready
if not update_members.is_running():
update_members.start()
Use this in a on_member_join event
#bot.event
async def on_member_join(member):
channel = discord.utils.get(member.guild.channels, id=962732335386746910)
await channel.edit(name = f'⚫┃・Members: {member.guild.member_count}')

Trying to use discord.py to make the bot send a message as the command originator

I'm trying to create a bot on discord to tag things with tone indicators (i.e., /s for sarcasm) This project started off as just a library but then I realized I could have it append the tone indicator to the end of the message by using it as an argument. However, I can only get the bot to send it as itself, which is disappointing because it interrupts the flow of conversations. I'm writing it using python. Here's my code:
import discord
from discord_slash import SlashCommand, SlashContext # Importing the newly installed library.
client = discord.Client(intents=discord.Intents.all())
slash = SlashCommand(client, sync_commands=True) # Declares slash commands through the client.
guild_ids = [guild id] # Put your server ID in this array.
#client.event
async def on_ready():
print("Ready!")
#slash.slash(name="j", description="joking", guild_ids=guild_ids)
async def j(ctx:SlashContext, message:str): # Defines a new "context" (ctx) command called "ping."
await ctx.respond(eat=True)
send_message = await ctx.send(f"{message} /j")
def check(message):
return message.author.id == ctx.author_id
try:
answer = await bot.wait_for('message', check=check, timeout = 60.0)
except asyncio.TimeoutError:
await ctx.send("You took to long to answer...", hidden=True)
else:
# do something with answer (convert to tone indicator)
pass
client.run("bot token")
I know I don't actually need any of the last part, but it was suggested to me as a work around to make it ID the user. It returns nothing. Anyone know how to make it send as the user who inputted the command, or mask itself as such?
You can use webhooks for this
#inside your command
guild = client.get_channel(ctx.channel_id)
if channel is None:
#error here
return
webhooks = filter(lambda x: x.user == client.user, await channel.webhooks()) #gets only webhooks created by us
if len(webhooks) == 0:
webhook = await channel.create_webhook(name='something')
else: webhook = webhooks[0]
await webhook.send(content, username=ctx.author.username, avatar = ctx.author.avatar_url
References:
Webhooks
getting webhooks
sending messages with webhooks
discord-slash get channel id

Discord.py Cog cannot take in content param

So I'm in discord.py right now, and whenever I directly edit bot.py (my main file) I can implement a version of my suggest command.
#bot.command(name = 'suggest', help = 'Use this command to give a suggestion to the server')
async def suggest(ctx, content):
channel = discord.utils.get(bot.guilds[0].channels, name = 'suggestions')
print('Suggest command triggered, content is {}'.format(content))
await channel.send(content)
^ bot is just my version of client
This works perfectly fine (except the fact that I only get the 1st word of content, so if someone could solve that, that would also be nice
But when I copy paste into my cog
#commands.command(name = 'suggest', help = 'Use this command to give a suggestion to the server')
async def suggest(self, bot, ctx, content):
print('Suggest command triggered')
channel = discord.utils.get(bot.guilds[0].channels, name = 'suggestions')
print('Content is {}'.format(content))
await channel.send(content)
It doesn't work, can someone help me?
For a cog, that bot parameter is unnecessary, so you just put
#commands.command(name = 'suggest', help = 'Use this command to give a suggestion to the server')
async def suggest(self, ctx, content):
print('Suggest command triggered')
channel = discord.utils.get(bot.guilds[0].channels, name = 'suggestions')
print('Content is {}'.format(content))
await channel.send(content)
As for the only having the first word, discord.py separates arguments by words, so you can just group them all with a * like so:
async def suggest(self, ctx, *content): # gets all the words in a tuple

Categories