discord.py "sub help command" - python

I was wondering if it's possible to make a somewhat "sub help command" basically if I were to do ;help mute it would show how to use the mute command and so on for each command. Kinda like dyno how you can do ?help (command name) and it shows you the usage of the command. I have my own help command already finished but I was thinking about adding to it so if someone did ;help commandname it would show them the usage such as arguments I tried at the bottom but I don't think that will work. If you know how please let me know
#client.hybrid_command(name = "help", with_app_command=True, description="Get a list of commands")
#commands.guild_only()
async def help(ctx, arg = None):
pages = 3
cur_page = 1
roleplayembed = discord.Embed(color=embedcolor, title="Roleplay Commands")
roleplayembed.add_field(name=f"{client.command_prefix}Cuddle", value="Cuddle a user and add a message(Optional)",inline=False)
roleplayembed.add_field(name=f"{client.command_prefix}Hug", value="Hug a user and add a message(Optional)",inline=False)
roleplayembed.add_field(name=f"{client.command_prefix}Kiss", value="Kiss a user and add a message(Optional)",inline=False)
roleplayembed.add_field(name=f"{client.command_prefix}Slap", value="Slap a user and add a message(Optional)",inline=False)
roleplayembed.add_field(name=f"{client.command_prefix}Pat", value="Pat a user and add a message(Optional)",inline=False)
roleplayembed.set_footer(text=f"Page {cur_page+1} of {pages}")
roleplayembed.timestamp = datetime.datetime.utcnow()
basicembed = discord.Embed(color=embedcolor, title="Basic Commands")
basicembed.add_field(name=f"{client.command_prefix}Waifu", value="Posts a random AI Generated Image of a waifu",inline=False)
basicembed.add_field(name=f"{client.command_prefix}8ball", value="Works as an 8 ball",inline=False)
basicembed.add_field(name=f"{client.command_prefix}Ara", value="Gives you a random ara ara from Kurumi Tokisaki",inline=False)
basicembed.add_field(name=f"{client.command_prefix}Wikipedia", value="Search something up on the wiki",inline=False)
basicembed.add_field(name=f"{client.command_prefix}Userinfo", value="Look up info about a user",inline=False)
basicembed.add_field(name=f"{client.command_prefix}Ask", value="Ask the bot a question",inline=False)
basicembed.add_field(name=f"{client.command_prefix}Askwhy", value="Ask the boy a question beginning with 'why'",inline=False)
basicembed.add_field(name=f"{client.command_prefix}Avatar", value="Get a user's avatar or your own avatar",inline=False)
basicembed.set_footer(text=f"Page {cur_page} of {pages}")
basicembed.timestamp = datetime.datetime.utcnow()
moderationembed = discord.Embed(color=embedcolor, title="Moderation Commands")
moderationembed.add_field(name=f"{client.command_prefix}Kick", value="Kick a member",inline=False)
moderationembed.add_field(name=f"{client.command_prefix}Ban", value="Ban a member",inline=False)
moderationembed.add_field(name=f"{client.command_prefix}Slowmode", value="Set the slowmode of a channel",inline=False)
moderationembed.add_field(name=f"{client.command_prefix}Purge", value="Purge an amount of messages in a channel",inline=False)
moderationembed.add_field(name=f"{client.command_prefix}Mute", value="Mute a member for a time and reason",inline=False)
moderationembed.add_field(name=f"{client.command_prefix}Unmute", value="Unmute a member for a time and reason",inline=False)
moderationembed.set_footer(text=f"Page {cur_page+2} of {pages}")
moderationembed.timestamp = datetime.datetime.utcnow()
contents = [basicembed, roleplayembed, moderationembed]
if arg == None:
message = await ctx.send(embed=contents[cur_page-1])
await message.add_reaction("◀️")
await message.add_reaction("▶️")
def check(reaction, user):
return user == ctx.author and str(reaction.emoji) in ["◀️", "▶️"]
while True:
try:
reaction, user = await client.wait_for("reaction_add", timeout=60, check=check)
if str(reaction.emoji) == "▶️":
cur_page += 1
elif str(reaction.emoji) == "◀️":
cur_page -= 1
if cur_page > pages: #check if forward on last page
cur_page = 1
elif cur_page < 1: #check if back on first page
cur_page = pages
await message.edit(embed=contents[cur_page-1])
await message.remove_reaction(reaction, user)
except asyncio.TimeoutError:
await message.delete()
break
if arg.lower() == client.command_name:
await ctx.reply(f"{client.command_prefix}{client.command_name}{client.command_argument}")

There are several ways you can do this.
When you're using slash commands (which you are currently not,) there is a really elegant way to do this in the form of SlashCommandGroups. This would get the commands as [command name] help instead, but I don't think that is a downside.
This would work like this, an example I thought of was blocking:
class Block(discord.ext.commands.Cog):
block = SlashCommandGroup("block")
def __init__(self, bot):
self.bot = bot
#block.command(name="add")
async def add(args):
# Something here
#block.command(name="remove")
async def remove(args):
# Something here
#block.command(name="help")
async def help(args):
# Get help message for this command
This would expose the commands block add [args], block remove [args] and block help each of which calls their own sub-command in the cog, and I think this is the cleanest way to get a consistent help system.
You can add this cog to your bot with bot.add_cog(Block(bot)) somewhere in your code. Specifically, I'd look into extensions
Then, for what you want to do, You're not using slash commands, so you don't need to provide autocomplete. If you want to, you can do something really hacky, using this helper function, which will work as long as you have the function in your current scope:
def help(function_name):
return globals()[function_name].__doc__
Now, you can define the help of every function individually using docstrings, and the help command will simply get those doc strings and presumably do something with it.
The way Dyno would do it is more complex, using slash commands again, but really similar to the first version. You simply add a slash command group again, but this time it is for helping specifically. I personally don't like this as much, as I think the code is a lot less clean, but if you really want the help [function] syntax instead of [function] help, this is how to do that:
class Help(discord.ext.commands.Cog):
help = SlashCommandGroup("help")
def __init__(self, bot):
self.bot = bot
#help.command(name="block")
async def block(args):
# Send the user the help response for blocking
#help.command(name="ask")
async def ask(args):
# Send the user the help response for asking
I hope that helps! :-)

Related

Sending an Embed in channel with permission using Discord.py raises exceptions

First of all I am sorry if I'm doing something wrong. This is my first question.
I am currently trying to create a Discord Bot using Python.
Edit: Though the answer of one person helped me a lot, my question remains, because I still have the "await coro" exception and another one was thrown after I corrected the old mistake. I've updated the code and the exceptions. Thanks for your help!
When I'm trying to send an embed when the bot joins the server, I get two exceptions. Since I don't have 50 servers, I replaced the on_member_join(self) with a simple function call when something is written in a channel:
File "...\Python\Python39\lib\site-packages\discord\client.py"
await coro(*args, **kwargs)
TypeError: on_message() missing 1 required positional argument: 'ctx'
Though I watched videos and searched on stackoverflow, I still don't understand ctx completely. That's probably the reason I'm making this mistake. If you could help me correct the code or even explain what ctx is (is it like "this" in java?), that'd be great!
Here's the code of the two functions trying to send an embed:
import discord
from discord.utils import get
from discord.ext import commands
Bot_prefix = "<" #Later used before every command
class MyClient(discord.Client):
async def Joining_Server(ctx, self):
#Get channel by name:
channel = get(ctx.guild.text_channels, name="Channel Name")
#Get channel by ID:
channels_Ids = get(ctx.guild.text_channels, id=discord.channel_id)
embed = discord.Embed(title="Thanks for adding me!", description="Try")
fields = [("Prefix", "My prefix is <. Just write it in front of every command!", True),
("Changing prefix", "Wanna change my prefix? Just write \"<change_prefix\" and the prefix you want, such as: \"<change_prefix !\"", True),
("Commands", "For a list of the most important commands type \"<help\"", False),
("Help", "For more help, type \"<help_All\" or visit:", True)]
for channels in self.channel_Ids:
if(commands.has_permissions(write=True)):
channel_verified = channels.id
await ctx.channel_verified.send(embed)
async def on_message(message, self, ctx):
if message.author == client.user:
return
if message.content == "test":
await MyClient.Joining_Server(ctx, self)
Thank you for helping me! Again: I'm sorry if I'm doing something wrong, it's my first question. Please ask if you need something. Feedback would also be very helpful.
I think you simply want to compare the content of the message to the "test" string
if message.author == client.user:
return
if message.content == "test":
await MyClient.Joining_Server()

Discord bot adding and waiting for reactions for a quiz game

I am writing a discord quiz bot using discordpy.
The bot sends a message that contains the questions and the 4 possible answers.
The bot also adds reactions to his message with the emojis 1️⃣, 2️⃣, 3️⃣ and 4️⃣.
The idea is, that the bot waits 30 seconds for people to click on one fo the reactions. If the clicked reaction is the correct/wrong answer, the bot replies with either correct or wrong. The bot should also stop waiting for new reaction once one person answered. Aka: Once a person clicks on one of the 4 reaction emojis, the bot should reply, and not process any future reactions to this message.
Currently, I got the bot to send the message (an embed) and add the reaction emojis to it. However, obtaining the results from the people is where I have problems with.
For one, the bot still seems to get triggered by his own reactions for some reason, even I excluded that in the check function. (Or so I thought).
In general, I'd like to have a very well structured approach for this. I am familiar with all the api calls/events, such as on_message() and on_reaction_add(), but I have trouble putting everything together correctly.
This is what I have so far:
#commands.command(name="quiz")
async def on_command_quiz(ctx):
#if ctx.message.author.bot:
# return
print("Quiz command!")
quiz = QuizGame()
# Send quiz
reply = await ctx.message.channel.send(embed=quiz.format())
# Add reply emojis
for x in range(0, len(quiz.quiz_answers)):
await reply.add_reaction(Utils.get_number_emoji_by_number(x + 1))
print("Correct Answer:", quiz.quiz_correct_answer)
# Evaluate replies
async def check_answer(reaction, user):
emojis = ["1️⃣","2️⃣","3️⃣","4️⃣"]
return user != ctx.message.author and str(reaction.emoji) in emojis
# Wait for replies
try:
reaction, user = await bot.wait_for('reaction_add', timeout=30.0, check=check_answer)
except asyncio.TimeoutError:
print("Timeout")
else:
if user != ctx.message.author:
if str(reaction.emoji) == "1️⃣":
print("1")
elif str(reaction.emoji) == "2️⃣":
print("2")
elif str(reaction.emoji) == "3️⃣":
print("3")
elif str(reaction.emoji) == "4️⃣":
print("4")
else:
print("Unknown reaction")
How can I get this right?
There are several errors and some inaccuracies in your code; first I'll list them and then I'll show you what I think is the best way to set up this type of commands.
Please note that some of the following are not actual fixes but more efficient ways to organize your code.
-You should use decorators to define bot commands, instead of using functions like on_command:
#bot.command()
async def quiz(ctx)
-The ctx class provides the channel attribute already, so ctx.message.channel is kind of redundant, use ctx.channel instead.
Same applies for ctx.message.author.
-If the number of answers is always the same, then you can add the numeric emojis with a very simple for loop (also, there is no need to call Utils to get the relevant emojis):
for emoji in ["1️⃣","2️⃣","3️⃣","4️⃣"]:
reply.add_reaction(emoji)
-The check_answer function is redundant as well, and logically wrong too.
It is redundant because there is no need to verify that the reaction emoji is one of the 4 available, since it will be determined later in the try block anyway.
It is logically wrong because it should return True if the user who added the reaction matches the author of the command, and not the opposite (you will notice that this will also prevent the bot from being triggered by its own reactions).
Then, there is no need for the function to be asynchronous.
def check_answer(reaction, user):
return user == ctx.author
-Finally, the whole try-except-else block is not really functional here. In order for the bot to remain responsive until the first reaction of the specific user or until the 30 seconds timeout expires, you should integrate the try-except block into an infinite while loop:
while True:
try:
reaction, user = await bot.wait_for("reaction_add", timeout=30, check=check_answer)
# The following line is optional: it removes the reaction added by the user
# to let them react again with the same emoji; not really necessary in your case,
# but very helpful if your bot would still be responsive after the first reaction.
await reply.remove_reaction(reaction, user)
# Here goes the if-else block of reactions.
except asyncio.TimeoutError:
print("Timeout")
Remember that somewhere in the try block you will have to stop the loop with a break statement when the operation is finished, otherwise it will continue indefinitely.
I am developing a Discord bot too and am still a beginner, so I hope I've been able to explain well.
Anyway, to sum it up, here is an example of how I would personally implement that command:
#bot.command()
async def quiz(ctx):
print("Quiz command!")
quiz = QuizGame()
reply = await ctx.send(embed=quiz.format())
emojis = ["1️⃣","2️⃣","3️⃣","4️⃣"]
for emoji in emojis:
await reply.add_reaction(emoji)
def check_answer(reaction, user):
return user == ctx.author
while True:
try:
reaction, user = await bot.wait_for("reaction_add", timeout=30, check=check_answer)
await reply.remove_reaction(reaction, user)
# Shorter representation of that if-else block.
if reaction.emoji in emojis:
print(emojis.index(reaction.emoji) + 1)
break
else:
print("Unknown reaction")
except asyncio.TimeoutError:
print("Timeout")
Then of course you should define how to recognize the correct answer and how to notify the user.
If you need some clarification on what I wrote, feel free to comment on this answer and I will be happy to answer you.
You didn't really ignore the bots reaction, at least from what I can see in the code.
You can try the following method:
try:
reaction, user = await self.bot.wait_for('reaction_add', timeout=15)
while user == self.bot.user:
reaction, user = await self.bot.wait_for('reaction_add', timeout=15)
if str(reaction.emoji) == "YourEmoji":
A check function could be:
reactions = "YourReactions"
def check1(reaction, user):
return user == ctx.author and str(reaction.emoji) in [reactions]
Here we check if the reaction comes from the author of the command and also check if the emoji is in the reactions "list".

discord.py command cooldown for ban command

I've got this code for my bot, which allows banning users for certain people. However, is there a way to make it so that a staff member can only use the ban command once every 2 hours. I want every other command to have no cooldown, but just for the ban command, have a way to only allow it to be used once per 2 hours per user. Here's the code I've got so far:
#commands.has_permissions(administrator=True)
async def pogoban (ctx, member:discord.User=None, *, reason =None):
if member == None or member == ctx.message.author:
await ctx.channel.send("You cannot ban yourself")
return
if reason == None:
reason = "For breaking the rules."
message = f"You have been banned from {ctx.guild.name} for {reason}"
await member.send(message)
await ctx.guild.ban(member, reason=reason)
await ctx.channel.send(f"{member} is banned!")
What would I need to add/change to add a command cooldown of 2 hours per user for this command. I've tried looking around, but I've only found ways to make all commands have a cooldown, instead of this one specific command. Thanks!
How about something like this:
cooldown = []
#client.command()
async def pogoban(ctx, member: discord.Member = None, *, reason = None):
author = str(ctx.author)
if author in cooldown:
await ctx.send('Calm down! You\'ve already banned someone less that two hours ago.')
return
try:
if reason == None:
reason = 'breaking the rules.'
await member.send(f'You have been banned from **{ctx.guild.name}** for **{reason}**')
await member.ban(reason = reason)
await ctx.send(f'{member.mention} has been banned.')
cooldown.append(author)
await asyncio.sleep(2 * 60 * 60) #The argument is in seconds. 2hr = 7200s
cooldown.remove(author)
except:
await ctx.send('Sorry, you aren\'t allowed to do that.')
Note: Remember to import asyncio. Also remember that once your bot goes offline, all the users stored in the list will be erased.
Please Read
A better approach would be to store the time of banning along with the the authors name and check if the current time is at least an hour more than the saved time. And to make it a lot safer, the authors name along with the time can be saved in a database or an external text file, this way your data won't get erased if the bot goes offline.

Check if a specific user did a command discord.py

Im trying to make a command that I could use in every server, but when others use "-help" it doesn't show in the list of commands. I know the normal way to do this would be:
if ctx.author.id == myID:
#do some code
But if I do it like this, the user can still see that the command exists when using "-help". Is there any way to get around this? Is there a
#commands.command
#commands.is_user(myID)
?
There are three options, you can either make a check, make a decorator, or make a global check:
1.
def is_user(ctx):
return not ctx.author.id == myID
#bot.command()
#commands.check(is_user)
async def foo(ctx):
...
decorator
def is_user(func):
def wrapper(ctx):
return not ctx.author.id == myID
return commands.check(wrapper)
#bot.command()
#is_user
async def foo(ctx):
...
global check
#bot.check
async def is_user(ctx):
return not ctx.author.id == myID
If a check returns False, commands.CheckFailure is going to be raised, you can make an error handler and send something like "You can't use this command" or whatever.
Reference:
commands.check
commands.Bot.check
commands.CheckFailure
https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#help-commands
Help commands can have a hidden attribute, which makes them not show up in the help command.
#commands.command(..., hidden=True)
def my_command(...):
...

How can I make my discord.py bot mentions someone mentioned in my message?

So I know that there is a similar question on stack overflow but it is for discord.js and I use discord.py so can someone tell me (also this is my first question on stack overflow)
(This is my first reply aswell!)
so lets say
import discord
from discord.ext import commands #or command I can't remember)
#setup setting(on_ready.. etc)
#client.command(ctx, target:discord.Member == None) #CTX represents your command, target means your mentioned member
if target == None:
await ctx.send("You didn't mention anyone!")
else:
await ctx.send(target.mention)
#client run token
Okay so the above one is almost correct.
The solution is:
#client.command()
async def something(ctx, target:discord.Member = None):
if target == None:
await ctx.send("You didn't mention anyone!")
else:
await ctx.send(target.mention)
#whatever other code
so it's target:discord.Member = None instead of target:discord.Member == None and that async def thingy ofcourse :)

Categories