Discord Python cog_check issue - python

New to coding. I want to prevent my bot from responding to itself/other bots, and I'm trying to invoke a cog_check but can't make it work properly. Help would be greatly appreciated.
class CommandsMisc(commands.Cog):
def __init__(self, client):
self.client = client
async def cog_check(self, ctx):
if ctx.author.bot: # also tried: if ctx.author == self.client.user:
await exit()
#commands.Cog.listener("on_message") # example cog command
async def print(self, message):
if '!guide' in message.content:
await message.reply(Guide_Text)
else:
pass
Since some people seem to lack reading comprehension skills, my question is how can I best utilize "cog_check" in Discord.py so that all subsequent commands/listeners in the cog will check whether the message.author is the bot and then won't execute if it is?

It seems like what you want is a custom command decorator. You can do this as follows:
def is_not_bot():
async def predicate(ctx):
return not ctx.author.bot
return commands.check(predicate)
You place this in your cog class, then on any command inside the cog you can use the decorator #is_not_bot()
What does this do, exactly? Well, we create a function which can be used as a command decorator (#is_not_bot()). Inside this, we create a function called predicate. This predicate will return True if the user is not a bot. We then call return commands.check(predicate) inside the parent function. This will allow us to use this as a decorator.
discord.py docs reference

what you can have is a on_message event, which can do checks on the message before processing the command, something like this
bot = commands.Bot(command_prefix="!", intents=discord.Intents.all())
# or if you made your subclass of Bot or its variants
class CustomBot(commands.Bot):
def __init__(self):
...
bot = CustomBot()
#bot.event
async def on_message(message):
if message.author.bot:
return # this is a bot, so we can early exit
# any other checks
await bot.process_commands(message)

exit() will just make your entire program close. cog_check should return a Boolean that indicates if the command should run or not. In your case, you don't return anything if it's okay, so it returns None which is falsey, so your check always fails.
Make it return True if it should succeed, and False if it should fail.
Edit
The ext.commands framework already prevents bots from running your commands, so this is not something you should even have to worry about.
The reason your cog_check still doesn't do anything is because you don't have any commands. You're using an on_message listener to manually parse messages, instead of creating commands. Your "example cog command" is not a command, it's just an event listener. Those are two very different concepts.
cog_check is only invoked when actual commands are run, not before dispatching events like on_message, so this doesn't have any effect here. If you parse commands in on_message, checks aren't used at all.
See the docs for ext.commands for how to create commands: https://discordpy.readthedocs.io/en/stable/ext/commands/index.html

Related

How do I create multiple commands using command_prefix=‘!’?

I am creating a Discord bot and am not experiencing any success creating custom commands with a command_prefix=‘!’.
E.g. I want to create, using the !-sign as the prefix, multiple commands that can even accept other arguments if you know exactly what you want within that command. Something like this:
!creeds
-or if you know what creed, you could use “!creeds Nicene” or “!creeds Athanasian” or if you want a list just use the base command “!creeds” and it will provide a list.
I have been told to use cogs and have not had any success with those, yet. Though I know I will need a database (using SQLite3) which I am also working on.
#jh316, all I have is this so far:
#client.event #basic event using a !command.
async def on_message(msg):
if msg.author != client.user:
if msg.content.lower().startswith("!hi"):
await msg.channel.send(f"Hi, {msg.author.display_name}!")
Something like this? https://onecompiler.com/python/3yc5xaqce
def purify(string):
return string.lower().strip()
def handleMessage(msg):
prefix = "!"
delimiter = ":"
if msg[0:1] != "!":
print("This is not a command")
#exit
splitMessage = msg[1:len(msg)].split(":")
cmd = purify(splitMessage[0])
param = "" if len(splitMessage) <= 1 else purify(splitMessage[1])
response = executeCommand(cmd, param)
#Now use the response
print(response)
def executeCommand(command, parameter):
#Actually use command here...
response = "Sorry, we don't have that one!"
if command == "creed":
if parameter == "nicene":
response = "We believe in one God, the Father almighty, maker of heaven and earth, of all things visible and invisible..."
elif parameter == "apostles":
response = "I believe in God, the Father almighty, creator of heaven and earth."
return response
#Examples
handleMessage("!creed: Nicene")
handleMessage("!creed: Apostles")
handleMessage("!creed: Athanasian")
Obviously you would want to remove the examples and put a single handleMessage reference in your discord client event message handler. Does this help?
Anyway, I love the idea!
Are you using a Client or a Bot? You should be importing from discord.ext.commands import Bot. The Bot class has a decorator to create commands:
from discord import Intents
from discord.ext.commands import Bot
TOKEN = "your secret bot token from the Discord developer page"
bot = Bot(command_prefix="!", intents=Intents.all())
#bot.command()
async def settings(context):
await context.reply("You executed command `settings`!")
#bot.command()
async def hello(context):
await context.reply("You executed command `hello`!")
bot.run(TOKEN)
Now your users will be able to type !settings and !hello in Discord to trigger these commands.
Be careful though, because if you define your own on_message like you did in your code, these commands will not automatically be called. You have to invoke them yourself:
#bot.event
async def on_message(message):
context = await bot.get_context(message)
await bot.invoke(context)
There is plenty of information online about how these commands work and how you can pass in arguments and cast them to the correct type automatically. This tutorial is one of the first Google Search results when I search "discord.py commands", and it looks like a decent tutorial that you could read.

rotating status and command usage

so, im trying to make a multiple status for my discord bot, but i cant find anything.
also im trying to make the status show how many times two commands have been used globally
this is what ive tried for multiple status
async def change_playing():
threading.Timer(10, change_playing).start()
await test_bot.change_presence(game=discord.Game(name='Currently on ' + str(len(client.servers)) +
' servers'))
threading.Timer(10, change_playing).start()
await test_bot.change_presence(game=discord.Game(name='Say shelp'))```
i found it, but thanks for the help
guilds = len(client.guilds)
users = len(client.users)
await client.wait_until_ready()
statuses = ["Shelp", f"im in {guilds} guilds| watching {users} users", "with knives"]
while True:
game = discord.Game(random.choice(statuses))
await client.change_presence(status=discord.Status, activity=game)
await asyncio.sleep(10)
client.loop.create_task(roll_presence())```
You could add this to your bot with the discord.ext.tasks decorator that was made for the very purpose of repeating an action every n seconds.
The docs for it are here.
You could do something like
import discord
from discord.ext import tasks
SAYHELP = discord.Game(name="Say shelp")
SERVER_COUNT = discord.Game(name=f'Currently on {len(client.servers)} servers'))
#tasks.loop(seconds=10)
async def change_playing():
if test_bot.activity is SAYHELP:
test_bot.change_presence(game=SERVER_COUNT)
else
test_bot.change_presence(game=SAYHELP)
async def on_ready(self, *args, **kwargs):
change_playing.start()
super().__on_ready__(*args, **kwargs)
Just don't forget to update the SERVER_COUNT activity whenever the bot gets added to another server. There's this event that gets fired then.
As for the command usage counter, there's a couple approaches to this that depend entirely on how you're instantiating your bot.
If you're just using the discord.ext.commands.Bot class as-is, you'll want to define an integer for that in your top-level code and increment that every time the relevant command is called.
If you're subclassing the bot, however, then you can define self.commandcounter in your bot and have the commands update that instead.
If you're also using cogs, then make sure the cog has a reference back to the bot so you can increment self.bot.commandcounter.
You could then have the #task decorated function set that as presence as well. Might need to add another check for the current activity though.
The docs for dpy are a good place to start looking if you can't get something to work.

(discord.py) functions in a cog

I've been trying to develop a discord bot in python, and I want to make a bunch of "hidden" commands that don't show up in help. I want to make it so that whenever someone activates a hidden command, the bot sends them a pm. I tried to make a function to do it, but so far it doesn't work. Here is the code in the cog file:
import discord
from discord.ext import commands
class Hidden(commands.Cog):
def __init__(self, client):
self.client = client
def hidden_message(ctx):
ctx.author.send('You have found one of several hidden commands! :shushing_face:\nCan you find them all? :thinking:')
#commands.command()
async def example(self, ctx):
await ctx.send('yes')
hidden_message(ctx)
def setup(client):
client.add_cog(Hidden(client))
When the example command is run, the bot responds normally, but the function isn't called. There are no error messages in the console. I'm still pretty new to python so could someone tell me what I'm doing wrong?
You need to use await when calling async functions like ctx.author.send, and so the function you're wrapping that with needs to be async too
async def hidden_message(self, ctx):
await ctx.author.send('You have found one of several hidden commands! :shushing_face:\nCan you find them all? :thinking:')
And then
#commands.command()
async def example(self, ctx):
await ctx.send('yes')
await self.hidden_message(ctx)
Finally, to make a command hidden from the default help command, you can do
#commands.command(hidden=True)
In order to send a message, hidden_message would have to be a courotine, i.e. it uses async def instead of just def.
However, there is a second issue that arises because of how hidden_message is called. Calling hidden_message as hidden_message(ctx) would require the function to be defined in the global scope. Since it is a method of class Hidden, it needs to be called as such.
Highlighting the edits:
class Hidden(commands.Cog):
...
async def hidden_message(self, ctx):
...
#commands.command()
async def example(self, ctx):
await ctx.send("yes")
await self.hidden_message(ctx)

Discord.py: Multiple multipage messages

EDIT:
The solution was to add an additional condition in the check function, I have posted the details in a follow-up answer.
Main Question:
I am new python and decided to learn it by creating a discord bot.
On user command, I was able to get the bot to send a message that can change pages on react following this design:
I want to make a multi-page help command using discord.py
I can change the pages of one message just fine, however, if the user calls the command multiple times, reacting to one of the bot sent messages change the pages for all messages that haven't timed out. What can I change in the code so that reacting to one message won't trigger page changes in the other ones?
Additional questions:
def check(reaction, user):
return user == ctx.author and str(reaction.emoji) in ["◀️", "▶️"]
# This makes sure nobody except the command sender can interact with the "menu"
I understand that wait_for will use this function as a check, but where and how are the check parameters being passed in? How could I add more parameters if possible?
reaction, user = await bot.wait_for("reaction_add", timeout=60, check=check)
In the documentation, I couldn't pin down the return value of the wait_for method, it looks like it changes under different circumstances.
We aren't passing in a message instance into the wait_for method, so how does the method know which message to wait for a reaction on?
What happens to the flow of the program if the check fails? It appears that statements under this line are executed only if check() returns true. But what happens when the check fails?
A better alternative than the method one you provided is the use of ext.menus (It's in beta, so it doesn't have any docs yet, to install it python -m pip install -U git+https://github.com/Rapptz/discord-ext-menus)
Example
from discord.ext import menus
class MyMenu(menus.Menu):
async def send_initial_message(self, ctx, channel):
return await channel.send(f'Hello {ctx.author}')
#menus.button('\N{THUMBS UP SIGN}')
async def on_thumbs_up(self, payload):
await self.message.edit(content=f'Thanks {self.ctx.author}!')
#menus.button('\N{THUMBS DOWN SIGN}')
async def on_thumbs_down(self, payload):
await self.message.edit(content=f"That's not nice {self.ctx.author}...")
#menus.button('\N{BLACK SQUARE FOR STOP}\ufe0f')
async def on_stop(self, payload):
self.stop()
# later
#bot.command()
async def menu_example(ctx):
m = MyMenu()
await m.start(ctx)
Unfornatelly I can't answer your first question, I'm not sure why's that happening, sorry.
Answering your additional questions:
wait_for takes the same arguments as the event, on_message takes message so the check will take message as the single argument (the method will also only return message). Adding more arguments it's pretty similar to the basics of decorators, we wrap the check in another outer function
def check(func): # The ADDITIONAL arguments we want to pass, in this example another function
def inner_check(reaction, user): # The actual check
return user == ctx.author and func(reaction, user)
return inner_check
# To use it Note how I'm calling the check an additional argument
reaction, user = await bot.wait_for('reaction_add', check=check(lambda r, u: return True), timeout=60.0)
Python decorator basics
Same as the first question
The bot waits for any reaction to be added, if the check function returns True it will return the values
If the check returns other than True the bot will wait till the function returns True or the timeout is over
Thanks to Łukasz Kwieciński for answering my additional questions. Because I learned that wait_for waits for any reaction to any message, I've added an additional condition to the check() function. Now each message is independent of one another.
message = await ctx.send(f"Page {cur_page}/{pages}:\n{contents[cur_page-1]}")
# getting the message object for editing and reacting
def check(reaction, user):
if reaction.message != message:
return false
# SOLUTION: Checks if the message reacted on is the same as the one the bot sent
return user == ctx.author and str(reaction.emoji) in ["◀️", "▶️"]
# This makes sure nobody except the command sender can interact with the "menu"
However, this fix creates a possible performance problem. For every bot message that hasn't timed out yet, reacting to any message on the server causes it to run check() that many times.

Can an event execute a command? If so, how can I make my one do so?

So I am trying to have an event where, once the user types a specific word (not a command, literally a word/string), the event will trigger an existing command. Yeah you may wonder "Why not have the user type the command itself?" well, the reason why this isn't the case it's kinda hard to explain. Check this out:
My event will work only if the person types "nothing" (literally the word nothing). Eventually, the person won't expect the bot to actually take this as a command, and so he/she won't type it as command (with the prefix and all that) This is my code:
#client.command()
async def menu(ctx)
#here, well, goes what I want the command to do but it's not the issue
#client.event
async def on_message(message):
if message.content.startswith("nothing"):
#here idk how to execute the command up there. That's my question
I hope I am being clear with my issue. Don't worry about what the command exectues, or why the message for the event is "nothing". I just really want to know how to make this work.
Some friends suggested me to invoke the command, but I didn't really know how to do that, and everytime I would try it wouldn't work. Others suggested to call the function, but I also tried that and it wouldn't work. I don't know if I typed it correctly or if it simply won't work. I hope someone helps me out here.
Thanks in advance.
get_context, this takes a message object. Then invoke. Keep in mind, there are 3 downsides to using this method.
The converters (the type hints) won't be triggered. You need to pass the correct types into the arguments.
Checks will be bypassed. You could invoke an owner only command with a non-owner and it would still work.
If you still want to run all the checks, see can_run, which will run all the checks and raise an error if any checks fail.
If ctx.invoke was invoked outside of a command (eg eval), the error handler won't fire.
#client.command()
async def menu(ctx):
await ctx.send("Hello")
#client.event
async def on_message(message):
if message.content.startswith("nothing"):
ctx = await client.get_context(message)
await ctx.invoke(menu)
await client.process_commands(message)
If your client is a Bot instance you can use Bot.get_context() to create your own context and invoke the command from there:
import discord
from discord.ext import commands
bot = commands.Bot(command_prefix='!')
#bot.command()
async def menu(ctx):
await ctx.send('bar')
#bot.event
async def on_message(message):
if message.content.startswith('foo'):
ctx = await bot.get_context(message, cls=commands.Context)
ctx.command = bot.get_command('menu')
await bot.invoke(ctx)
await bot.process_commands(message)

Categories

Resources