Skip to content

Instantly share code, notes, and snippets.

@nonchris
Last active August 13, 2024 03:40
Show Gist options
  • Save nonchris/1c7060a14a9d94e7929aa2ef14c41bc2 to your computer and use it in GitHub Desktop.
Save nonchris/1c7060a14a9d94e7929aa2ef14c41bc2 to your computer and use it in GitHub Desktop.
A kinda advanced custom "help" command for your Discord.py bots!
import discord
from discord.ext import commands
from discord.errors import Forbidden
"""This custom help command is a perfect replacement for the default one on any Discord Bot written in Discord.py!
However, you must put "bot.remove_command('help')" in your bot, and the command must be in a cog for it to work.
Original concept by Jared Newsom (AKA Jared M.F.)
[Deleted] https://gist.github.com/StudioMFTechnologies/ad41bfd32b2379ccffe90b0e34128b8b
Rewritten and optimized by github.com/nonchris
https://gist.github.com/nonchris/1c7060a14a9d94e7929aa2ef14c41bc2
You need to set three variables to make that cog run.
Have a look at line 51 to 57
"""
async def send_embed(ctx, embed):
"""
Function that handles the sending of embeds
-> Takes context and embed to send
- tries to send embed in channel
- tries to send normal message when that fails
- tries to send embed private with information abot missing permissions
If this all fails: https://youtu.be/dQw4w9WgXcQ
"""
try:
await ctx.send(embed=embed)
except Forbidden:
try:
await ctx.send("Hey, seems like I can't send embeds. Please check my permissions :)")
except Forbidden:
await ctx.author.send(
f"Hey, seems like I can't send any message in {ctx.channel.name} on {ctx.guild.name}\n"
f"May you inform the server team about this issue? :slight_smile: ", embed=embed)
class Help(commands.Cog):
"""
Sends this help message
"""
def __init__(self, bot):
self.bot = bot
@commands.command()
# @commands.bot_has_permissions(add_reactions=True,embed_links=True)
async def help(self, ctx, *input):
"""Shows all modules of that bot"""
# !SET THOSE VARIABLES TO MAKE THE COG FUNCTIONAL!
prefix = # ENTER YOUR PREFIX - loaded from config, as string or how ever you want!
version = # enter version of your code
# setting owner name - if you don't wanna be mentioned remove line 49-60 and adjust help text (line 88)
owner = # ENTER YOU DISCORD-ID
owner_name = # ENTER YOUR USERNAME#1234
# checks if cog parameter was given
# if not: sending all modules and commands not associated with a cog
if not input:
# checks if owner is on this server - used to 'tag' owner
try:
owner = ctx.guild.get_member(owner).mention
except AttributeError as e:
owner = owner
# starting to build embed
emb = discord.Embed(title='Commands and modules', color=discord.Color.blue(),
description=f'Use `{prefix}help <module>` to gain more information about that module '
f':smiley:\n')
# iterating trough cogs, gathering descriptions
cogs_desc = ''
for cog in self.bot.cogs:
cogs_desc += f'`{cog}` {self.bot.cogs[cog].__doc__}\n'
# adding 'list' of cogs to embed
emb.add_field(name='Modules', value=cogs_desc, inline=False)
# integrating trough uncategorized commands
commands_desc = ''
for command in self.bot.walk_commands():
# if cog not in a cog
# listing command if cog name is None and command isn't hidden
if not command.cog_name and not command.hidden:
commands_desc += f'{command.name} - {command.help}\n'
# adding those commands to embed
if commands_desc:
emb.add_field(name='Not belonging to a module', value=commands_desc, inline=False)
# setting information about author
emb.add_field(name="About", value=f"The Bots is developed by Chriѕ#0001, based on discord.py.\n\
This version of it is maintained by {owner}\n\
Please visit https://github.com/nonchris/discord-fury to submit ideas or bugs.")
emb.set_footer(text=f"Bot is running {version}")
# block called when one cog-name is given
# trying to find matching cog and it's commands
elif len(input) == 1:
# iterating trough cogs
for cog in self.bot.cogs:
# check if cog is the matching one
if cog.lower() == input[0].lower():
# making title - getting description from doc-string below class
emb = discord.Embed(title=f'{cog} - Commands', description=self.bot.cogs[cog].__doc__,
color=discord.Color.green())
# getting commands from cog
for command in self.bot.get_cog(cog).get_commands():
# if cog is not hidden
if not command.hidden:
emb.add_field(name=f"`{prefix}{command.name}`", value=command.help, inline=False)
# found cog - breaking loop
break
# if input not found
# yes, for-loops have an else statement, it's called when no 'break' was issued
else:
emb = discord.Embed(title="What's that?!",
description=f"I've never heard from a module called `{input[0]}` before :scream:",
color=discord.Color.orange())
# too many cogs requested - only one at a time allowed
elif len(input) > 1:
emb = discord.Embed(title="That's too much.",
description="Please request only one module at once :sweat_smile:",
color=discord.Color.orange())
else:
emb = discord.Embed(title="It's a magical place.",
description="I don't know how you got here. But I didn't see this coming at all.\n"
"Would you please be so kind to report that issue to me on github?\n"
"https://github.com/nonchris/discord-fury/issues\n"
"Thank you! ~Chris",
color=discord.Color.red())
# sending reply embed using our own function defined above
await send_embed(ctx, emb)
def setup(bot):
bot.add_cog(Help(bot))

Hey, I just wanted to let you know that I've made a discord bot template on github, which offers some more utility funtions.
It's a solid base to begin with, also containing this help command :)

Have a look, if you're interested!
https://github.com/nonchris/discord-bot

@nonchris
Copy link
Author

Ah - did miss that.
Just replace those vars with the owner/ owner_name variables you had to define in line 55/56
@235baron

im using an extra config files for those variables... seems like I missed some occurrences 😉

@stolenvw
Copy link

Made changes to add a list of cogs to ignore and not show in help command.
Made changes to add a list of cogs that contain bot owner only commands and only show them in the list if owner used help command

owner cogs need to be in both list to show when owner runs help command and not when a regular user runs help command

https://gist.github.com/stolenvw/1d12aa417a471c2f29f983255e635d22

@nonchris
Copy link
Author

nonchris commented Apr 11, 2021

Hey, cool idea!
I really don’t like the way those cogs are kinda hardcoded since the whole command can be setup and never touched again.

What would you say if we’d implement this function with a keyword at the beginning of a cogs description?

like: if cog.dec().startswith(„ignore“) - pass

that would make the help command stateless again.

@stolenvw
Copy link

stolenvw commented Apr 11, 2021

im fine with that, but would have to add 2 key words to the owner cogs so it gets placed in both the ignore and owner section.

i have commands to load/unload/reload a cog so hard codded dont bother me much seeing how only time it would need to be changed is if your adding another cog

i have a few cogs that the commands are all set to hidden, they where showing in the help list but show empty if help cog, thats why i did the 2 list.

@stolenvw
Copy link

stolenvw commented Apr 11, 2021

@nonchris
Redid it no more ignore or owner cog list,.
Now checks if user can run a command if they cant does not show that command, if they cant run any of the commands in the cog does not show that cog in main help.

@ozeitis
Copy link

ozeitis commented Apr 13, 2021

How do I add info to commands? I read through code but cant follow that...?

@stolenvw
Copy link

stolenvw commented Apr 13, 2021

it pulls the help info from the @commands.command()

@commands.command(name="stats", help="Plots a graph of connected players over the last X hours.\n Available args: 24, 12, w (default: 24)")

it works same way the default help works in discord.py just with a nicer output

@ozeitis
Copy link

ozeitis commented Apr 13, 2021

i pulls the help info from the @commands.command()

@commands.command(name="stats", help="Plots a graph of connected players over the last X hours.\n Available args: 24, 12, w (default: 24)")

Thanks!

@ozeitis
Copy link

ozeitis commented Apr 14, 2021

How do I add command CATEGORY text? Like in the .help it first lists the category of commands, how do I add text to that?

@stolenvw
Copy link

How do you hide a cog with this help command? - To where it doesn't show on the modules list.

@commands.command(hidden=True)
or
@commands.command(name='commandname', hidden=True)

@nonchris
Copy link
Author

nonchris commented Apr 28, 2021

@stolenvw
Hey I just had a brief look over your fork and I like it!
Especially that you tried to use more d.py functions instead of the built in solutions like
self.bot.cogs[cog].__doc__ to self.bot.get_cog(cog).get_commands() and the await can_run(ctx) function which was new to me ^^

Well done! :)

@stolenvw
Copy link

@nonchris
Thank you, only thing i didn't like about the can_run command is it returns true or an error, by the name of it at first i thought it would return true or false.

@pvcodes-zz
Copy link

my all description are showing none, how can I fix that

@nonchris
Copy link
Author

nonchris commented May 1, 2021

You’d need to share some code if you’re expecting help.

@snare392
Copy link

snare392 commented May 1, 2021

image
I am trying to add descriptions to each cogs.

        # iterating trough cogs, gathering descriptions
        cogs_desc = ''
        for cog in self.client.cogs:
            cogs_desc += f'`{cog}` {self.client.cogs[cog].__doc__}\n'

        # adding 'list' of cogs to embed
        embed.add_field(name='Modules', value=cogs_desc, inline=False)

I believe this is the code which adds the descs to the embed however None is being passed when i have a description as seen in below.

class bedwars(commands.Cog, name="bedwars", description="bedwars related commands"):
def init(self, client):
self.client = client
Am i doing something wrong or is there an error in the code.

@stolenvw
Copy link

stolenvw commented May 1, 2021

@snare392 try this

class bedwars(commands.Cog, name="bedwars", description="bedwars related commands"):
    """
    bedwars related commands
    """
    def init(self, client):
    self.client = client

Copy link

ghost commented May 1, 2021

How do you hide a cog with this help command? - To where it doesn't show on the modules list.

@commands.command(hidden=True)
or
@commands.command(name='commandname', hidden=True)

No, not to hide a command. Hide A COG from the HELP COMMAND.

Copy link

ghost commented May 1, 2021

How do you hide a cog with this help command? - To where it doesn't show on the modules list.

@commands.command(hidden=True)
or
@commands.command(name='commandname', hidden=True)

And I tried the way you would hide a COG on the NORMAL help command, and it DIDNT work.

@greed-d
Copy link

greed-d commented May 19, 2021

I just got Rick Rolled --
Was curious what that link lead to -
-

@pvcodes-zz
Copy link

pvcodes-zz commented May 19, 2021

How do you hide a cog with this help command? - To where it doesn't show on the modules list.

@commands.command(hidden=True)
or
@commands.command(name='commandname', hidden=True)

And I tried the way you would hide a COG on the NORMAL help command, and it DIDNT work.

What's ur help cog look like

@nonchris
Copy link
Author

I just got Rick Rolled -- Was curious what that link lead to --

I'm sorry :D

@eternal-will
Copy link

eternal-will commented Jun 17, 2021

This guy fukin rickrolled me -_-
Thanks for the help though

@nonchris
Copy link
Author

Haha, you're welcome ^^

@Dr-Insanity
Copy link

Love your help command bro. I really needed it. I credit this change to my discord bot to you

@nonchris
Copy link
Author

Love your help command bro. I really needed it. I credit this change to my discord bot to you

Happy to hear that!

@rockwillck
Copy link

rockwillck commented Jun 28, 2021

I ran into a couple of issues while using the help command, specifically getting the module because of the module[0] line, which should have just been module. I did a little reformatting and a little adding, and this is my finished code:

async def send_embed(ctx, embed):
    """
    Function that handles the sending of embeds
    -> Takes context and embed to send
    - tries to send embed in channel
    - tries to send normal message when that fails
    - tries to send embed private with information abot missing permissions
    If this all fails: https://youtu.be/dQw4w9WgXcQ
    """
    try:
        await ctx.send(embed=embed)
    except Forbidden:
        try:
            await ctx.send("Hey, seems like I can't send embeds. Please check my permissions :)")
        except Forbidden:
            await ctx.author.send(
                f"Hey, seems like I can't send any message in {ctx.channel.name} on {ctx.guild.name}\n"
                f"May you inform the server team about this issue? :slight_smile: ", embed=embed)
class Help(commands.Cog):
    """Sends this help message"""

    def __init__(self, bot):
        self.bot = bot

    @commands.command()
    # @commands.bot_has_permissions(add_reactions=True,embed_links=True)
    async def help(self, ctx, *module):
        module = " ".join(module)
        """Shows all modules of that bot"""
	
	    # !SET THOSE VARIABLES TO MAKE THE COG FUNCTIONAL!
        prefix = "-" # ENTER YOUR PREFIX - loaded from config, as string or how ever you want!
        version =  1.0 # enter version of your code
        
        # setting owner name - if you don't wanna be mentioned remove line 49-60 and adjust help text (line 88) 
        owner =  	# ENTER YOUR DISCORD-ID
        owner_name = ""	# ENTER YOUR USERNAME#1234
        bot_name = ""   # ENTER YOUR BOT'S NAME HERE
        general_color =     # THE HELP COMMAND'S COLOR WHEN SHOWING MAIN MENU
        specific_color =     # THE HELP COMMAND'S COLOR WHEN SHOWING MODULES
        error_color =  # THE HELP COMMAND'S COLOR FOR ERRORS
        issues_site = ""    # WHERE SHOULD USERS SEND ISSUES?

        # checks if cog parameter was given
        # if not: sending all modules and commands not associated with a cog
        if len(module) == 0:
            # checks if owner is on this server - used to 'tag' owner
            try:
                owner = ctx.guild.get_member(owner).mention

            except AttributeError as e:
                owner = owner

            # starting to build embed
            emb = discord.Embed(title='Commands and modules', color=general_color,
                                description=f'Use `{prefix}help <module>` to gain more information about that module '
                                            f':smiley:\n')

            # iterating trough cogs, gathering descriptions
            cogs_desc = ''
            for cog in self.bot.cogs:
                cogs_desc += f'`{cog}` {self.bot.cogs[cog].__doc__}\n'

            # adding 'list' of cogs to embed
            emb.add_field(name='Modules', value=cogs_desc, inline=False)

            # integrating trough uncategorized commands
            commands_desc = ''
            for command in self.bot.walk_commands():
                # if cog not in a cog
                # listing command if cog name is None and command isn't hidden
                if not command.cog_name and not command.hidden:
                    commands_desc += f'{command.name} - {command.help}\n'

            # adding those commands to embed
            if commands_desc:
                emb.add_field(name='Not belonging to a module', value=commands_desc, inline=False)

            # setting information about author
            emb.add_field(name="About", value=f"\n\
                                    {bot_name} is maintained by {owner}\n\
                                    Please visit {issues_site} to submit ideas or bugs.")
            emb.set_footer(text=f"{bot_name} is running {version}")
        else:
            # block called when one cog-name is given
            # trying to find matching cog and it's commands
            # iterating trough cogs
            for cog in self.bot.cogs:
                # check if cog is the matching one
                if cog.lower() == module.lower():

                    # making title - getting description from doc-string below class
                    emb = discord.Embed(title=f'{cog} - Commands', description=self.bot.cogs[cog].__doc__,
                                        color=specific_color)

                    # getting commands from cog
                    for command in self.bot.get_cog(cog).get_commands():
                        # if cog is not hidden
                        if not command.hidden:
                            emb.add_field(name=f"`{prefix}{command.name}`", value=command.help, inline=False)
                    # found cog - breaking loop
                    break

            # if input not found
            # yes, for-loops have an else statement, it's called when no 'break' was issued
            else:
                emb = discord.Embed(title="What's that?!",
                                    description=f"I've never heard from a module called `{module}` before :scream:",
                                    color=error_color)

        # sending reply embed using our own function defined above
        await send_embed(ctx, emb)

bot.add_cog(Help(bot))

@nonchris
Copy link
Author

hey @rockwillck may you highlight the changes you made?
And could you also tell me the Exceptions that were raised?

Thank you ✌️

@rockwillck
Copy link

rockwillck commented Jun 28, 2021

I added a join statement:

module = " ".join(module)

and then added:

if len(module) == 0:

but most importantly:
I got rid of

elif len(input) > 1:
emb = discord.Embed(title="That's too much.",
description="Please request only one module at once 😅",
color=discord.Color.orange())

because that was if the input was more than 1 character long, and that wasn't working.

And then I just added some more variables to make setup easier.

@shahriyardx
Copy link

Just subclass the HelpCommand lol

@Fabiii05
Copy link

The cmd is nice but it doesn't ignores the classes where no commands are in. For example the my profile save class. The cmd shows it up with typing the help cmd to discord but it's actually empty.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment