mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 11:18:54 -05:00
[Help] Continuing work and bug-fixes (#2676)
* [Help] Add settings for various things - Fixes a small issue in a previously not-exposed logic path - Fixes an issue with denied commands help invocation - Adds some global usage settings * remove outdated comment * improve intent of strings * added punctuation * Add DM forbidden handling * use a slightly different method for shortening embed width specifically
This commit is contained in:
parent
cdea03792d
commit
644aaf0c0e
@ -52,6 +52,10 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
|||||||
custom_info=None,
|
custom_info=None,
|
||||||
help__page_char_limit=1000,
|
help__page_char_limit=1000,
|
||||||
help__max_pages_in_guild=2,
|
help__max_pages_in_guild=2,
|
||||||
|
help__use_menus=False,
|
||||||
|
help__show_hidden=False,
|
||||||
|
help__verify_checks=True,
|
||||||
|
help__verify_exists=False,
|
||||||
help__tagline="",
|
help__tagline="",
|
||||||
disabled_commands=[],
|
disabled_commands=[],
|
||||||
disabled_command_msg="That command is disabled.",
|
disabled_command_msg="That command is disabled.",
|
||||||
|
|||||||
@ -77,14 +77,6 @@ class RedHelpFormatter:
|
|||||||
should not need or want a shared state.
|
should not need or want a shared state.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Class vars for things which should be configurable at a later date but aren't now
|
|
||||||
# Technically, someone can just use a cog to switch these in real time for now.
|
|
||||||
|
|
||||||
USE_MENU = False
|
|
||||||
CONFIRM_UNAVAILABLE_COMMAND_EXISTENCES = False
|
|
||||||
SHOW_HIDDEN = False
|
|
||||||
VERIFY_CHECKS = True
|
|
||||||
|
|
||||||
async def send_help(self, ctx: Context, help_for: HelpTarget = None):
|
async def send_help(self, ctx: Context, help_for: HelpTarget = None):
|
||||||
"""
|
"""
|
||||||
This delegates to other functions.
|
This delegates to other functions.
|
||||||
@ -102,7 +94,7 @@ class RedHelpFormatter:
|
|||||||
await self.command_not_found(ctx, help_for)
|
await self.command_not_found(ctx, help_for)
|
||||||
return
|
return
|
||||||
except NoSubCommand as exc:
|
except NoSubCommand as exc:
|
||||||
if self.CONFIRM_UNAVAILABLE_COMMAND_EXISTENCES:
|
if await ctx.bot.db.help.verify_exists():
|
||||||
await self.subcommand_not_found(ctx, exc.last, exc.not_found)
|
await self.subcommand_not_found(ctx, exc.last, exc.not_found)
|
||||||
return
|
return
|
||||||
help_for = exc.last
|
help_for = exc.last
|
||||||
@ -138,7 +130,7 @@ class RedHelpFormatter:
|
|||||||
|
|
||||||
async def format_command_help(self, ctx: Context, obj: commands.Command):
|
async def format_command_help(self, ctx: Context, obj: commands.Command):
|
||||||
|
|
||||||
send = self.CONFIRM_UNAVAILABLE_COMMAND_EXISTENCES
|
send = await ctx.bot.db.help.verify_exists()
|
||||||
if not send:
|
if not send:
|
||||||
async for _ in self.help_filter_func(ctx, (obj,), bypass_hidden=True):
|
async for _ in self.help_filter_func(ctx, (obj,), bypass_hidden=True):
|
||||||
# This is a really lazy option for not
|
# This is a really lazy option for not
|
||||||
@ -182,8 +174,14 @@ class RedHelpFormatter:
|
|||||||
emb["fields"].append(field)
|
emb["fields"].append(field)
|
||||||
|
|
||||||
if subcommands:
|
if subcommands:
|
||||||
|
|
||||||
|
def shorten_line(a_line: str) -> str:
|
||||||
|
if len(a_line) < 70: # embed max width needs to be lower
|
||||||
|
return a_line
|
||||||
|
return a_line[:67] + "..."
|
||||||
|
|
||||||
subtext = "\n".join(
|
subtext = "\n".join(
|
||||||
f"**{name}** {command.short_doc}"
|
shorten_line(f"**{name}** {command.short_doc}")
|
||||||
for name, command in sorted(subcommands.items())
|
for name, command in sorted(subcommands.items())
|
||||||
)
|
)
|
||||||
for i, page in enumerate(pagify(subtext, page_length=1000, shorten_by=0)):
|
for i, page in enumerate(pagify(subtext, page_length=1000, shorten_by=0)):
|
||||||
@ -272,7 +270,7 @@ class RedHelpFormatter:
|
|||||||
async def format_cog_help(self, ctx: Context, obj: commands.Cog):
|
async def format_cog_help(self, ctx: Context, obj: commands.Cog):
|
||||||
|
|
||||||
coms = await self.get_cog_help_mapping(ctx, obj)
|
coms = await self.get_cog_help_mapping(ctx, obj)
|
||||||
if not (coms or self.CONFIRM_UNAVAILABLE_COMMAND_EXISTENCES):
|
if not (coms or await ctx.bot.db.help.verify_exists()):
|
||||||
return
|
return
|
||||||
|
|
||||||
description = obj.help
|
description = obj.help
|
||||||
@ -286,8 +284,15 @@ class RedHelpFormatter:
|
|||||||
emb["embed"]["title"] = f"*{description[:2044]}*"
|
emb["embed"]["title"] = f"*{description[:2044]}*"
|
||||||
|
|
||||||
if coms:
|
if coms:
|
||||||
|
|
||||||
|
def shorten_line(a_line: str) -> str:
|
||||||
|
if len(a_line) < 70: # embed max width needs to be lower
|
||||||
|
return a_line
|
||||||
|
return a_line[:67] + "..."
|
||||||
|
|
||||||
command_text = "\n".join(
|
command_text = "\n".join(
|
||||||
f"**{name}** {command.short_doc}" for name, command in sorted(coms.items())
|
shorten_line(f"**{name}** {command.short_doc}")
|
||||||
|
for name, command in sorted(coms.items())
|
||||||
)
|
)
|
||||||
for i, page in enumerate(pagify(command_text, page_length=1000, shorten_by=0)):
|
for i, page in enumerate(pagify(command_text, page_length=1000, shorten_by=0)):
|
||||||
if i == 0:
|
if i == 0:
|
||||||
@ -347,8 +352,14 @@ class RedHelpFormatter:
|
|||||||
else:
|
else:
|
||||||
title = f"**__No Category:__**"
|
title = f"**__No Category:__**"
|
||||||
|
|
||||||
|
def shorten_line(a_line: str) -> str:
|
||||||
|
if len(a_line) < 70: # embed max width needs to be lower
|
||||||
|
return a_line
|
||||||
|
return a_line[:67] + "..."
|
||||||
|
|
||||||
cog_text = "\n".join(
|
cog_text = "\n".join(
|
||||||
f"**{name}** {command.short_doc}" for name, command in sorted(data.items())
|
shorten_line(f"**{name}** {command.short_doc}")
|
||||||
|
for name, command in sorted(data.items())
|
||||||
)
|
)
|
||||||
|
|
||||||
for i, page in enumerate(pagify(cog_text, page_length=1000, shorten_by=0)):
|
for i, page in enumerate(pagify(cog_text, page_length=1000, shorten_by=0)):
|
||||||
@ -399,17 +410,25 @@ class RedHelpFormatter:
|
|||||||
"""
|
"""
|
||||||
This does most of actual filtering.
|
This does most of actual filtering.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
show_hidden = bypass_hidden or await ctx.bot.db.help.show_hidden()
|
||||||
|
verify_checks = await ctx.bot.db.help.verify_checks()
|
||||||
|
|
||||||
# TODO: Settings for this in core bot db
|
# TODO: Settings for this in core bot db
|
||||||
for obj in objects:
|
for obj in objects:
|
||||||
if self.VERIFY_CHECKS and not (self.SHOW_HIDDEN or bypass_hidden):
|
if verify_checks and not show_hidden:
|
||||||
# Default Red behavior, can_see includes a can_run check.
|
# Default Red behavior, can_see includes a can_run check.
|
||||||
if await obj.can_see(ctx):
|
if await obj.can_see(ctx):
|
||||||
yield obj
|
yield obj
|
||||||
elif self.VERIFY_CHECKS:
|
elif verify_checks:
|
||||||
if await obj.can_run(ctx):
|
try:
|
||||||
|
can_run = await obj.can_run(ctx)
|
||||||
|
except discord.DiscordException:
|
||||||
|
can_run = False
|
||||||
|
if can_run:
|
||||||
yield obj
|
yield obj
|
||||||
elif not (self.SHOW_HIDDEN or bypass_hidden):
|
elif not show_hidden:
|
||||||
if getattr(obj, "hidden", False): # Cog compatibility
|
if not getattr(obj, "hidden", False): # Cog compatibility
|
||||||
yield obj
|
yield obj
|
||||||
else:
|
else:
|
||||||
yield obj
|
yield obj
|
||||||
@ -430,8 +449,8 @@ class RedHelpFormatter:
|
|||||||
await ctx.send(embed=ret)
|
await ctx.send(embed=ret)
|
||||||
else:
|
else:
|
||||||
await ctx.send(ret)
|
await ctx.send(ret)
|
||||||
elif self.CONFIRM_UNAVAILABLE_COMMAND_EXISTENCES:
|
elif await ctx.bot.db.help.verify_exists():
|
||||||
ret = T_("Command *{command_name}* not found.").format(command_name=help_for)
|
ret = T_("Help topic for *{command_name}* not found.").format(command_name=help_for)
|
||||||
if use_embeds:
|
if use_embeds:
|
||||||
ret = discord.Embed(color=(await ctx.embed_color()), description=ret)
|
ret = discord.Embed(color=(await ctx.embed_color()), description=ret)
|
||||||
ret.set_author(name=f"{ctx.me.display_name} Help Menu", icon_url=ctx.me.avatar_url)
|
ret.set_author(name=f"{ctx.me.display_name} Help Menu", icon_url=ctx.me.avatar_url)
|
||||||
@ -445,9 +464,16 @@ class RedHelpFormatter:
|
|||||||
"""
|
"""
|
||||||
Sends an error
|
Sends an error
|
||||||
"""
|
"""
|
||||||
ret = T_("Command *{command_name}* has no subcommands.").format(
|
ret = T_("Command *{command_name}* has no subcommand named *{not_found}*.").format(
|
||||||
command_name=command.qualified_name
|
command_name=command.qualified_name, not_found=not_found[0]
|
||||||
)
|
)
|
||||||
|
if await ctx.embed_requested():
|
||||||
|
ret = discord.Embed(color=(await ctx.embed_color()), description=ret)
|
||||||
|
ret.set_author(name=f"{ctx.me.display_name} Help Menu", icon_url=ctx.me.avatar_url)
|
||||||
|
tagline = (await ctx.bot.db.help.tagline()) or self.get_default_tagline(ctx)
|
||||||
|
ret.set_footer(text=tagline)
|
||||||
|
await ctx.send(embed=ret)
|
||||||
|
else:
|
||||||
await ctx.send(ret)
|
await ctx.send(ret)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -487,19 +513,40 @@ class RedHelpFormatter:
|
|||||||
Sends pages based on settings.
|
Sends pages based on settings.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not self.USE_MENU:
|
if not (
|
||||||
|
ctx.channel.permissions_for(ctx.me).add_reactions and await ctx.bot.db.help.use_menus()
|
||||||
|
):
|
||||||
|
|
||||||
max_pages_in_guild = await ctx.bot.db.help.max_pages_in_guild()
|
max_pages_in_guild = await ctx.bot.db.help.max_pages_in_guild()
|
||||||
destination = ctx.author if len(pages) > max_pages_in_guild else ctx
|
destination = ctx.author if len(pages) > max_pages_in_guild else ctx
|
||||||
|
|
||||||
if embed:
|
if embed:
|
||||||
for page in pages:
|
for page in pages:
|
||||||
|
try:
|
||||||
await destination.send(embed=page)
|
await destination.send(embed=page)
|
||||||
|
except discord.Forbidden:
|
||||||
|
await ctx.send(
|
||||||
|
T_(
|
||||||
|
"I couldn't send the help message to you in DM. "
|
||||||
|
"Either you blocked me or you disabled DMs in this server."
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
for page in pages:
|
for page in pages:
|
||||||
|
try:
|
||||||
await destination.send(page)
|
await destination.send(page)
|
||||||
|
except discord.Forbidden:
|
||||||
|
await ctx.send(
|
||||||
|
T_(
|
||||||
|
"I couldn't send the help message to you in DM. "
|
||||||
|
"Either you blocked me or you disabled DMs in this server."
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
|
if len(pages) > 1:
|
||||||
await menus.menu(ctx, pages, menus.DEFAULT_CONTROLS)
|
await menus.menu(ctx, pages, menus.DEFAULT_CONTROLS)
|
||||||
|
else:
|
||||||
|
await menus.menu(ctx, pages, {"\N{CROSS MARK}": menus.close_menu})
|
||||||
|
|
||||||
|
|
||||||
@commands.command(name="help", hidden=True, i18n=T_)
|
@commands.command(name="help", hidden=True, i18n=T_)
|
||||||
|
|||||||
@ -1083,6 +1083,80 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
"""Manage settings for the help command."""
|
"""Manage settings for the help command."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@helpset.command(name="usemenus")
|
||||||
|
async def helpset_usemenus(self, ctx: commands.Context, use_menus: bool = None):
|
||||||
|
"""
|
||||||
|
Allows the help command to be sent as a paginated menu instead of seperate
|
||||||
|
messages.
|
||||||
|
|
||||||
|
This defaults to False.
|
||||||
|
Using this without a setting will toggle.
|
||||||
|
"""
|
||||||
|
if use_menus is None:
|
||||||
|
use_menus = not await ctx.bot.db.help.use_menus()
|
||||||
|
await ctx.bot.db.help.use_menus.set(use_menus)
|
||||||
|
if use_menus:
|
||||||
|
await ctx.send(_("Help will use menus."))
|
||||||
|
else:
|
||||||
|
await ctx.send(_("Help will not use menus."))
|
||||||
|
|
||||||
|
@helpset.command(name="showhidden")
|
||||||
|
async def helpset_showhidden(self, ctx: commands.Context, show_hidden: bool = None):
|
||||||
|
"""
|
||||||
|
This allows the help command to show hidden commands
|
||||||
|
|
||||||
|
This defaults to False.
|
||||||
|
Using this without a setting will toggle.
|
||||||
|
"""
|
||||||
|
if show_hidden is None:
|
||||||
|
show_hidden = not await ctx.bot.db.help.show_hidden()
|
||||||
|
await ctx.bot.db.help.show_hidden.set(show_hidden)
|
||||||
|
if show_hidden:
|
||||||
|
await ctx.send(_("Help will not filter hidden commands"))
|
||||||
|
else:
|
||||||
|
await ctx.send(_("Help will filter hidden commands."))
|
||||||
|
|
||||||
|
@helpset.command(name="verifychecks")
|
||||||
|
async def helpset_permfilter(self, ctx: commands.Context, verify: bool = None):
|
||||||
|
"""
|
||||||
|
Sets if commands which can't be run in the current context should be
|
||||||
|
filtered from help
|
||||||
|
|
||||||
|
Defaults to True.
|
||||||
|
Using this without a setting will toggle.
|
||||||
|
"""
|
||||||
|
if verify is None:
|
||||||
|
verify = not await ctx.bot.db.help.verify_checks()
|
||||||
|
await ctx.bot.db.help.verify_checks.set(verify)
|
||||||
|
if verify:
|
||||||
|
await ctx.send(_("Help will only show for commands which can be run."))
|
||||||
|
else:
|
||||||
|
await ctx.send(_("Help will show up without checking if the commands can be run."))
|
||||||
|
|
||||||
|
@helpset.command(name="verifyexists")
|
||||||
|
async def helpset_verifyexists(self, ctx: commands.Context, verify: bool = None):
|
||||||
|
"""
|
||||||
|
This allows the bot to respond indicating the existence of a specific
|
||||||
|
help topic even if the user can't use it.
|
||||||
|
|
||||||
|
Note: This setting on it's own does not fully prevent command enumeration.
|
||||||
|
|
||||||
|
Defaults to False.
|
||||||
|
Using this without a setting will toggle.
|
||||||
|
"""
|
||||||
|
if verify is None:
|
||||||
|
verify = not await ctx.bot.db.help.verify_exists()
|
||||||
|
await ctx.bot.db.help.verify_exists.set(verify)
|
||||||
|
if verify:
|
||||||
|
await ctx.send(_("Help will verify the existence of help topics."))
|
||||||
|
else:
|
||||||
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"Help will only verify the existence of "
|
||||||
|
"help topics via fuzzy help (if enabled)."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@helpset.command(name="pagecharlimit")
|
@helpset.command(name="pagecharlimit")
|
||||||
async def helpset_pagecharlimt(self, ctx: commands.Context, limit: int):
|
async def helpset_pagecharlimt(self, ctx: commands.Context, limit: int):
|
||||||
"""Set the character limit for each page in the help message.
|
"""Set the character limit for each page in the help message.
|
||||||
@ -1605,7 +1679,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
"""
|
"""
|
||||||
user = isinstance(user_or_role, discord.Member)
|
user = isinstance(user_or_role, discord.Member)
|
||||||
|
|
||||||
if user and await ctx.bot.is_owner(obj):
|
if user and await ctx.bot.is_owner(user_or_role):
|
||||||
await ctx.send(_("You cannot blacklist an owner!"))
|
await ctx.send(_("You cannot blacklist an owner!"))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user