From 3c78fb420b1fc9d7d1d8b3adddf868c38aba2b1e Mon Sep 17 00:00:00 2001 From: Michael H Date: Wed, 15 May 2019 02:52:06 -0400 Subject: [PATCH 01/20] [Help] Fixes some issues with fuzzy help (#2674) * Fixes some issues with fuzzy help - also cleans up some name shadowing which wasn't causing issues (curently) * ver * style --- redbot/__init__.py | 2 +- redbot/core/commands/help.py | 44 +++++++++++++++++------------------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/redbot/__init__.py b/redbot/__init__.py index 42dbf140e..dcc031cac 100644 --- a/redbot/__init__.py +++ b/redbot/__init__.py @@ -174,7 +174,7 @@ class VersionInfo: ) -__version__ = "3.1.0" +__version__ = "3.1.1" version_info = VersionInfo.from_str(__version__) # Filter fuzzywuzzy slow sequence matcher warning diff --git a/redbot/core/commands/help.py b/redbot/core/commands/help.py index 10fc576fe..29127b3c7 100644 --- a/redbot/core/commands/help.py +++ b/redbot/core/commands/help.py @@ -271,8 +271,8 @@ class RedHelpFormatter: async def format_cog_help(self, ctx: Context, obj: commands.Cog): - commands = await self.get_cog_help_mapping(ctx, obj) - if not (commands or self.CONFIRM_UNAVAILABLE_COMMAND_EXISTENCES): + coms = await self.get_cog_help_mapping(ctx, obj) + if not (coms or self.CONFIRM_UNAVAILABLE_COMMAND_EXISTENCES): return description = obj.help @@ -285,9 +285,9 @@ class RedHelpFormatter: if description: emb["embed"]["title"] = f"*{description[:2044]}*" - if commands: + if coms: command_text = "\n".join( - f"**{name}** {command.short_doc}" for name, command in sorted(commands.items()) + 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)): if i == 0: @@ -300,11 +300,11 @@ class RedHelpFormatter: await self.make_and_send_embeds(ctx, emb) else: - commands_text = None - commands_header = None - if commands: + subtext = None + subtext_header = None + if coms: subtext_header = "Commands:" - max_width = max(discord.utils._string_width(name) for name in commands.keys()) + max_width = max(discord.utils._string_width(name) for name in coms.keys()) def width_maker(cmds): doc_max_width = 80 - max_width @@ -316,20 +316,17 @@ class RedHelpFormatter: yield nm, doc, max_width - width_gap subtext = "\n".join( - f" {name:<{width}} {doc}" - for name, doc, width in width_maker(commands.items()) + f" {name:<{width}} {doc}" for name, doc, width in width_maker(coms.items()) ) - to_page = "\n\n".join( - filter(None, (description, signature[1:-1], subtext_header, subtext)) - ) + to_page = "\n\n".join(filter(None, (description, subtext_header, subtext))) pages = [box(p) for p in pagify(to_page)] await self.send_pages(ctx, pages, embed=False) async def format_bot_help(self, ctx: Context): - commands = await self.get_bot_help_mapping(ctx) - if not commands: + coms = await self.get_bot_help_mapping(ctx) + if not coms: return description = ctx.bot.description or "" @@ -343,7 +340,7 @@ class RedHelpFormatter: if description: emb["embed"]["title"] = f"*{description[:2044]}*" - for cog_name, data in commands: + for cog_name, data in coms: if cog_name: title = f"**__{cog_name}:__**" @@ -362,11 +359,12 @@ class RedHelpFormatter: await self.make_and_send_embeds(ctx, emb) else: + to_join = [] if description: - to_join = [f"{description}\n"] + to_join.append(f"{description}\n") names = [] - for k, v in commands: + for k, v in coms: names.extend(list(v.name for v in v.values())) max_width = max( @@ -382,7 +380,7 @@ class RedHelpFormatter: doc = doc[: doc_max_width - 3] + "..." yield nm, doc, max_width - width_gap - for cog_name, data in commands: + for cog_name, data in coms: title = f"{cog_name}:" if cog_name else "No Category:" to_join.append(title) @@ -426,17 +424,17 @@ class RedHelpFormatter: if fuzzy_commands: ret = await format_fuzzy_results(ctx, fuzzy_commands, embed=use_embeds) if use_embeds: - ret.set_author() + 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) elif self.CONFIRM_UNAVAILABLE_COMMAND_EXISTENCES: - ret = T_("Command *{command_name}* not found.").format(command_name=command_name) + ret = T_("Command *{command_name}* not found.").format(command_name=help_for) if use_embeds: - emb = discord.Embed(color=(await ctx.embed_color()), description=ret) - emb.set_author(name=f"{ctx.me.display_name} Help Menu", icon_url=ctx.me.avatar_url) + 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) From f2858ea48cd7993d3c7f126ed586e6dda5410052 Mon Sep 17 00:00:00 2001 From: Michael H Date: Wed, 15 May 2019 10:31:00 -0400 Subject: [PATCH 02/20] [Core] Fix update notification (#2677) removes a problematic await --- redbot/core/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redbot/core/events.py b/redbot/core/events.py index 165d51aa7..dc60c4028 100644 --- a/redbot/core/events.py +++ b/redbot/core/events.py @@ -126,7 +126,7 @@ def init_events(bot, cli_flags): owners.append(owner) for co_owner in bot._co_owners: - co_owner = await bot.get_user(co_owner) + co_owner = bot.get_user(co_owner) if co_owner is not None: owners.append(co_owner) From 3a62d392b4f3dfd81276caf06ff0ecd743303e16 Mon Sep 17 00:00:00 2001 From: Fixator10 Date: Wed, 15 May 2019 18:33:10 +0400 Subject: [PATCH 03/20] [Mod] [p]names utilize consume-rest (#2675) Currently, `[p]names` requires quotes, if "user" arg contains spaces --- redbot/cogs/mod/names.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redbot/cogs/mod/names.py b/redbot/cogs/mod/names.py index d072aa233..ad509c60b 100644 --- a/redbot/cogs/mod/names.py +++ b/redbot/cogs/mod/names.py @@ -164,7 +164,7 @@ class ModInfo(MixinMeta): await ctx.send(embed=data) @commands.command() - async def names(self, ctx: commands.Context, user: discord.Member): + async def names(self, ctx: commands.Context, *, user: discord.Member): """Show previous names and nicknames of a user.""" names, nicks = await self.get_names_and_nicks(user) msg = "" From 79e5d2c9d7d152ff0e5df2e1b19e13075f2a7261 Mon Sep 17 00:00:00 2001 From: DevilXD Date: Thu, 16 May 2019 02:01:02 +0200 Subject: [PATCH 04/20] Fixed command doc formatting in code blocks (#2678) --- redbot/core/commands/help.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redbot/core/commands/help.py b/redbot/core/commands/help.py index 29127b3c7..a447bcab1 100644 --- a/redbot/core/commands/help.py +++ b/redbot/core/commands/help.py @@ -208,7 +208,7 @@ class RedHelpFormatter: doc_max_width = 80 - max_width for nm, com in sorted(cmds): width_gap = discord.utils._string_width(nm) - len(nm) - doc = command.short_doc + doc = com.short_doc if len(doc) > doc_max_width: doc = doc[: doc_max_width - 3] + "..." yield nm, doc, max_width - width_gap From 9d0ca00f8976bc320e5a1b26bf9384b8121a8378 Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Thu, 16 May 2019 08:06:46 +0200 Subject: [PATCH 05/20] [General]: shorten descriptions properly with disabled embeds in urban (#2684) fix #2683 --- redbot/cogs/general/general.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/redbot/cogs/general/general.py b/redbot/cogs/general/general.py index ec7a35ac6..133ca58ba 100644 --- a/redbot/cogs/general/general.py +++ b/redbot/cogs/general/general.py @@ -307,14 +307,17 @@ class General(commands.Cog): messages = [] for ud in data["list"]: ud.setdefault("example", "N/A") - description = _("{definition}\n\n**Example:** {example}").format(**ud) - if len(description) > 2048: - description = "{}...".format(description[:2045]) - message = _( "<{permalink}>\n {word} by {author}\n\n{description}\n\n" "{thumbs_down} Down / {thumbs_up} Up, Powered by Urban Dictionary." - ).format(word=ud.pop("word").capitalize(), description=description, **ud) + ).format(word=ud.pop("word").capitalize(), description="{description}", **ud) + max_desc_len = 2000 - len(message) + + description = _("{definition}\n\n**Example:** {example}").format(**ud) + if len(description) > max_desc_len: + description = "{}...".format(description[: max_desc_len - 3]) + + message = message.format(description=description) messages.append(message) if messages is not None and len(messages) > 0: From c5d2ae583187a836cfc4aaaf2149ed6990c35ba6 Mon Sep 17 00:00:00 2001 From: Neuro Assassin <42872277+NeuroAssassin@users.noreply.github.com> Date: Thu, 16 May 2019 09:12:09 -0400 Subject: [PATCH 06/20] Mention voice channel in `[p]userinfo` (#2680) * Mention voice channels, due to new discord update * Add ID part back in, as Discord doesn't have copy ID for it yet --- redbot/cogs/mod/names.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redbot/cogs/mod/names.py b/redbot/cogs/mod/names.py index ad509c60b..4a4bb4edf 100644 --- a/redbot/cogs/mod/names.py +++ b/redbot/cogs/mod/names.py @@ -145,7 +145,7 @@ class ModInfo(MixinMeta): if voice_state and voice_state.channel: data.add_field( name=_("Current voice channel"), - value="{0.name} (ID {0.id})".format(voice_state.channel), + value="{0.mention} ID: {0.id}".format(voice_state.channel), inline=False, ) data.set_footer(text=_("Member #{} | User ID: {}").format(member_number, user.id)) From db3fb29b304a28f91e7b49fbbf316f887bffd976 Mon Sep 17 00:00:00 2001 From: El Laggron Date: Thu, 16 May 2019 23:03:09 +0200 Subject: [PATCH 07/20] [docs] Fix typo (#2691) --- docs/changelog_3_1_0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog_3_1_0.rst b/docs/changelog_3_1_0.rst index 109a11e08..d56af23ee 100644 --- a/docs/changelog_3_1_0.rst +++ b/docs/changelog_3_1_0.rst @@ -169,7 +169,7 @@ Trivia Utility Functions ----------------- - * New: ``chat_formatting.humaize_timedelta`` (`#2412`_) + * New: ``chat_formatting.humanize_timedelta`` (`#2412`_) * ``Tunnel`` - Spelling correction of method name - changed ``files_from_attatch`` to ``files_from_attach`` (old name is left for backwards compatibility) (`#2496`_) * ``Tunnel`` - fixed behavior of ``react_close()``, now when tunnel closes message will be sent to other end (`#2507`_) * ``chat_formatting.humanize_list`` - Improved error handling of empty lists (`#2597`_) From 21a253103e5099365564379a1757302a7f2a359d Mon Sep 17 00:00:00 2001 From: palmtree5 <3577255+palmtree5@users.noreply.github.com> Date: Thu, 16 May 2019 18:28:26 -0800 Subject: [PATCH 08/20] [V3 Streams] fix an issue with stream commands not dealing with reruns (#2679) --- redbot/cogs/streams/streams.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/redbot/cogs/streams/streams.py b/redbot/cogs/streams/streams.py index 9ba5c398e..52c95ef1d 100644 --- a/redbot/cogs/streams/streams.py +++ b/redbot/cogs/streams/streams.py @@ -131,7 +131,7 @@ class Streams(commands.Cog): @staticmethod async def check_online(ctx: commands.Context, stream): try: - embed = await stream.is_online() + info = await stream.is_online() except OfflineStream: await ctx.send(_("That user is offline.")) except StreamNotFound: @@ -155,6 +155,14 @@ class Streams(commands.Cog): _("Something went wrong whilst trying to contact the stream service's API.") ) else: + if isinstance(info, tuple): + embed, is_rerun = info + ignore_reruns = await self.db.guild(channel.guild).ignore_reruns() + if ignore_reruns and is_rerun: + await ctx.send(_("That user is offline.")) + return + else: + embed = info await ctx.send(embed=embed) @commands.group() From 7dd3ff7c8dc2d17b774ddeeb3fa73591dd852cb6 Mon Sep 17 00:00:00 2001 From: zephyrkul Date: Thu, 16 May 2019 21:54:17 -0600 Subject: [PATCH 09/20] [Core] Strip commas in user input for load, reload, unload (#2693) rstrip commas, closes #2695 --- redbot/core/core_commands.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/redbot/core/core_commands.py b/redbot/core/core_commands.py index 97f1bbcc3..6552bd4ee 100644 --- a/redbot/core/core_commands.py +++ b/redbot/core/core_commands.py @@ -529,6 +529,7 @@ class Core(commands.Cog, CoreLogic): """Loads packages""" if not cogs: return await ctx.send_help() + cogs = tuple(map(lambda cog: cog.rstrip(","), cogs)) async with ctx.typing(): loaded, failed, not_found, already_loaded, failed_with_reason = await self._load(cogs) @@ -571,6 +572,7 @@ class Core(commands.Cog, CoreLogic): """Unloads packages""" if not cogs: return await ctx.send_help() + cogs = tuple(map(lambda cog: cog.rstrip(","), cogs)) unloaded, failed = await self._unload(cogs) if unloaded: @@ -589,6 +591,7 @@ class Core(commands.Cog, CoreLogic): """Reloads packages""" if not cogs: return await ctx.send_help() + cogs = tuple(map(lambda cog: cog.rstrip(","), cogs)) async with ctx.typing(): loaded, failed, not_found, already_loaded, failed_with_reason = await self._reload( cogs From b190e7417e273a3e825782fe7a10c0f54159ee10 Mon Sep 17 00:00:00 2001 From: Flame442 <34169552+Flame442@users.noreply.github.com> Date: Fri, 17 May 2019 21:40:42 -0400 Subject: [PATCH 10/20] [Downloader] Adds ctx.typing() to `[p]pipinstall` (#2700) --- redbot/cogs/downloader/downloader.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/redbot/cogs/downloader/downloader.py b/redbot/cogs/downloader/downloader.py index adce10923..f609e58a6 100644 --- a/redbot/cogs/downloader/downloader.py +++ b/redbot/cogs/downloader/downloader.py @@ -199,7 +199,8 @@ class Downloader(commands.Cog): if not deps: return await ctx.send_help() repo = Repo("", "", "", Path.cwd(), loop=ctx.bot.loop) - success = await repo.install_raw_requirements(deps, self.LIB_PATH) + async with ctx.typing(): + success = await repo.install_raw_requirements(deps, self.LIB_PATH) if success: await ctx.send(_("Libraries installed.")) From cdea03792d6680e4cafdce6f525eaa2e94ed157d Mon Sep 17 00:00:00 2001 From: Michael H Date: Sat, 18 May 2019 04:59:26 -0400 Subject: [PATCH 11/20] [Streams] Fix NameError (#2699) - fixes #2696 - My fault for just looking at the github diff and seeing the logic fix on this one, without verifying the name validity in surrounding context. (see my review in #2679) --- redbot/cogs/streams/streams.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/redbot/cogs/streams/streams.py b/redbot/cogs/streams/streams.py index 52c95ef1d..3893dcf18 100644 --- a/redbot/cogs/streams/streams.py +++ b/redbot/cogs/streams/streams.py @@ -128,8 +128,7 @@ class Streams(commands.Cog): stream = PicartoStream(name=channel_name) await self.check_online(ctx, stream) - @staticmethod - async def check_online(ctx: commands.Context, stream): + async def check_online(self, ctx: commands.Context, stream): try: info = await stream.is_online() except OfflineStream: @@ -157,7 +156,7 @@ class Streams(commands.Cog): else: if isinstance(info, tuple): embed, is_rerun = info - ignore_reruns = await self.db.guild(channel.guild).ignore_reruns() + ignore_reruns = await self.db.guild(ctx.channel.guild).ignore_reruns() if ignore_reruns and is_rerun: await ctx.send(_("That user is offline.")) return From 644aaf0c0e7c46441886062b68d932e342ab3fbf Mon Sep 17 00:00:00 2001 From: Michael H Date: Sat, 18 May 2019 06:54:02 -0400 Subject: [PATCH 12/20] [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 --- redbot/core/bot.py | 4 ++ redbot/core/commands/help.py | 103 +++++++++++++++++++++++++---------- redbot/core/core_commands.py | 76 +++++++++++++++++++++++++- 3 files changed, 154 insertions(+), 29 deletions(-) diff --git a/redbot/core/bot.py b/redbot/core/bot.py index f122ee5ff..877c54eb7 100644 --- a/redbot/core/bot.py +++ b/redbot/core/bot.py @@ -52,6 +52,10 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): custom_info=None, help__page_char_limit=1000, help__max_pages_in_guild=2, + help__use_menus=False, + help__show_hidden=False, + help__verify_checks=True, + help__verify_exists=False, help__tagline="", disabled_commands=[], disabled_command_msg="That command is disabled.", diff --git a/redbot/core/commands/help.py b/redbot/core/commands/help.py index a447bcab1..f1f590e21 100644 --- a/redbot/core/commands/help.py +++ b/redbot/core/commands/help.py @@ -77,14 +77,6 @@ class RedHelpFormatter: 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): """ This delegates to other functions. @@ -102,7 +94,7 @@ class RedHelpFormatter: await self.command_not_found(ctx, help_for) return 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) return help_for = exc.last @@ -138,7 +130,7 @@ class RedHelpFormatter: 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: async for _ in self.help_filter_func(ctx, (obj,), bypass_hidden=True): # This is a really lazy option for not @@ -182,8 +174,14 @@ class RedHelpFormatter: emb["fields"].append(field) 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( - f"**{name}** {command.short_doc}" + shorten_line(f"**{name}** {command.short_doc}") for name, command in sorted(subcommands.items()) ) 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): 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 description = obj.help @@ -286,8 +284,15 @@ class RedHelpFormatter: emb["embed"]["title"] = f"*{description[:2044]}*" 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( - 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)): if i == 0: @@ -347,8 +352,14 @@ class RedHelpFormatter: else: 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( - 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)): @@ -399,17 +410,25 @@ class RedHelpFormatter: """ 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 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. if await obj.can_see(ctx): yield obj - elif self.VERIFY_CHECKS: - if await obj.can_run(ctx): + elif verify_checks: + try: + can_run = await obj.can_run(ctx) + except discord.DiscordException: + can_run = False + if can_run: yield obj - elif not (self.SHOW_HIDDEN or bypass_hidden): - if getattr(obj, "hidden", False): # Cog compatibility + elif not show_hidden: + if not getattr(obj, "hidden", False): # Cog compatibility yield obj else: yield obj @@ -430,8 +449,8 @@ class RedHelpFormatter: await ctx.send(embed=ret) else: await ctx.send(ret) - elif self.CONFIRM_UNAVAILABLE_COMMAND_EXISTENCES: - ret = T_("Command *{command_name}* not found.").format(command_name=help_for) + elif await ctx.bot.db.help.verify_exists(): + ret = T_("Help topic for *{command_name}* not found.").format(command_name=help_for) if use_embeds: 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) @@ -445,10 +464,17 @@ class RedHelpFormatter: """ Sends an error """ - ret = T_("Command *{command_name}* has no subcommands.").format( - command_name=command.qualified_name + ret = T_("Command *{command_name}* has no subcommand named *{not_found}*.").format( + command_name=command.qualified_name, not_found=not_found[0] ) - await ctx.send(ret) + 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) @staticmethod def parse_command(ctx, help_for: str): @@ -487,19 +513,40 @@ class RedHelpFormatter: 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() destination = ctx.author if len(pages) > max_pages_in_guild else ctx if embed: for page in pages: - await destination.send(embed=page) + try: + 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: for page in pages: - await destination.send(page) + try: + 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: - await menus.menu(ctx, pages, menus.DEFAULT_CONTROLS) + if len(pages) > 1: + 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_) diff --git a/redbot/core/core_commands.py b/redbot/core/core_commands.py index 6552bd4ee..6adc88b74 100644 --- a/redbot/core/core_commands.py +++ b/redbot/core/core_commands.py @@ -1083,6 +1083,80 @@ class Core(commands.Cog, CoreLogic): """Manage settings for the help command.""" 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") async def helpset_pagecharlimt(self, ctx: commands.Context, limit: int): """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) - 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!")) return From 1ffb79f8524e8f4e1492c908b6fcb5f137cbd9e3 Mon Sep 17 00:00:00 2001 From: Fixator10 Date: Sun, 19 May 2019 14:14:12 +0400 Subject: [PATCH 13/20] [events] send help on BadUnionArgument exception (#2707) * [events] send help on BadUnionArgument exception * Update redbot/core/events.py Co-Authored-By: Michael H --- redbot/core/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redbot/core/events.py b/redbot/core/events.py index dc60c4028..aacc88017 100644 --- a/redbot/core/events.py +++ b/redbot/core/events.py @@ -191,7 +191,7 @@ def init_events(bot, cli_flags): await ctx.send(error.args[0]) else: await ctx.send_help() - elif isinstance(error, commands.BadArgument): + elif isinstance(error, commands.UserInputError): await ctx.send_help() elif isinstance(error, commands.DisabledCommand): disabled_message = await bot.db.disabled_command_msg() From cdcde26dfcad0f58bd621aa337c2c89a5b247ebc Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Sun, 19 May 2019 16:13:01 +0200 Subject: [PATCH 14/20] [Setup] Fix: wrong var used for instance data in `remove_instance` (#2709) --- redbot/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redbot/setup.py b/redbot/setup.py index be6b75d8a..3814e8b5d 100644 --- a/redbot/setup.py +++ b/redbot/setup.py @@ -440,7 +440,7 @@ async def remove_instance(instance): collection = await db.get_collection(name) await collection.drop() else: - pth = Path(instance_data["DATA_PATH"]) + pth = Path(instance_vals["DATA_PATH"]) safe_delete(pth) save_config(instance, {}, remove=True) print("The instance {} has been removed\n".format(instance)) From 1cfce8b72cecc8d3f03b97d280726e63d50a9266 Mon Sep 17 00:00:00 2001 From: Brenden Campbell Date: Mon, 20 May 2019 13:24:05 -0400 Subject: [PATCH 15/20] Fix minor typo (#2713) --- redbot/launcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redbot/launcher.py b/redbot/launcher.py index ffe935d41..602e8528e 100644 --- a/redbot/launcher.py +++ b/redbot/launcher.py @@ -254,7 +254,7 @@ async def reset_red(): "please select option 5 in the launcher." ) await asyncio.sleep(2) - print("\nIf you continue you will remove these instanes.\n") + print("\nIf you continue you will remove these instances.\n") for instance in list(instances.keys()): print(" - {}".format(instance)) await asyncio.sleep(3) From 0a832cee9cd6f44afc3fec38bc63ea63cdf0acb2 Mon Sep 17 00:00:00 2001 From: DiscordLiz <47602820+DiscordLiz@users.noreply.github.com> Date: Tue, 21 May 2019 16:52:44 -0400 Subject: [PATCH 16/20] [Utils] Allow functools.partial use with menu (#2720) --- redbot/core/utils/menus.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/redbot/core/utils/menus.py b/redbot/core/utils/menus.py index b60929e09..6305b19ff 100644 --- a/redbot/core/utils/menus.py +++ b/redbot/core/utils/menus.py @@ -4,6 +4,7 @@ # Ported to Red V3 by Palm\_\_ (https://github.com/palmtree5) import asyncio import contextlib +import functools from typing import Union, Iterable, Optional import discord @@ -60,7 +61,10 @@ async def menu( ): raise RuntimeError("All pages must be of the same type") for key, value in controls.items(): - if not asyncio.iscoroutinefunction(value): + maybe_coro = value + if isinstance(value, functools.partial): + maybe_coro = value.func + if not asyncio.iscoroutinefunction(maybe_coro): raise RuntimeError("Function must be a coroutine") current_page = pages[page] From ced5bb4631858b7d22c5f18337dace8697d5d0a9 Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Tue, 21 May 2019 23:49:25 +0200 Subject: [PATCH 17/20] docs(install): remove information about voice extra (#2717) --- docs/install_linux_mac.rst | 12 +++--------- docs/install_windows.rst | 12 +++--------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/docs/install_linux_mac.rst b/docs/install_linux_mac.rst index edab2a99b..22d0c492e 100644 --- a/docs/install_linux_mac.rst +++ b/docs/install_linux_mac.rst @@ -188,23 +188,17 @@ Choose one of the following commands to install Red. If you're not inside an activated virtual environment, include the ``--user`` flag with all ``python3.7 -m pip`` commands. -To install without audio support: +To install without MongoDB support: .. code-block:: none python3.7 -m pip install -U Red-DiscordBot -Or, to install with audio support: +Or, to install with MongoDB support: .. code-block:: none - python3.7 -m pip install -U Red-DiscordBot[voice] - -Or, install with audio and MongoDB support: - -.. code-block:: none - - python3.7 -m pip install -U Red-DiscordBot[voice,mongo] + python3.7 -m pip install -U Red-DiscordBot[mongo] .. note:: diff --git a/docs/install_windows.rst b/docs/install_windows.rst index 076f83abd..d913cdb48 100644 --- a/docs/install_windows.rst +++ b/docs/install_windows.rst @@ -62,23 +62,17 @@ Installing Red If you're not inside an activated virtual environment, include the ``--user`` flag with all ``pip`` commands. - * No audio: + * No MongoDB support: .. code-block:: none python -m pip install -U Red-DiscordBot - * With audio: + * With MongoDB support: .. code-block:: none - python -m pip install -U Red-DiscordBot[voice] - - * With audio and MongoDB support: - - .. code-block:: none - - python -m pip install -U Red-DiscordBot[voice,mongo] + python -m pip install -U Red-DiscordBot[mongo] .. note:: From 342935de49eedd94c6bdb65ff5999e7befab0055 Mon Sep 17 00:00:00 2001 From: PredaaA <46051820+PredaaA@users.noreply.github.com> Date: Tue, 21 May 2019 23:50:51 +0200 Subject: [PATCH 18/20] [Trivia] Remove bold on a box (#2716) --- redbot/cogs/trivia/trivia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redbot/cogs/trivia/trivia.py b/redbot/cogs/trivia/trivia.py index 14709c8ae..8a9dd8970 100644 --- a/redbot/cogs/trivia/trivia.py +++ b/redbot/cogs/trivia/trivia.py @@ -57,7 +57,7 @@ class Trivia(commands.Cog): settings_dict = await settings.all() msg = box( _( - "**Current settings**\n" + "Current settings\n" "Bot gains points: {bot_plays}\n" "Answer time limit: {delay} seconds\n" "Lack of response timeout: {timeout} seconds\n" From c6c016521470f5c1637a7a4674803bdcc20b98b9 Mon Sep 17 00:00:00 2001 From: Michael H Date: Wed, 22 May 2019 04:22:31 -0400 Subject: [PATCH 19/20] [Help] Special case fixing for empty docstring (#2722) Resolves #2415 --- redbot/core/commands/help.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/redbot/core/commands/help.py b/redbot/core/commands/help.py index f1f590e21..3f671df7e 100644 --- a/redbot/core/commands/help.py +++ b/redbot/core/commands/help.py @@ -249,6 +249,12 @@ class RedHelpFormatter: author_info = {"name": f"{ctx.me.display_name} Help Menu", "icon_url": ctx.me.avatar_url} + if not field_groups: # This can happen on single command without a docstring + embed = discord.Embed(color=color, **embed_dict["embed"]) + embed.set_author(**author_info) + embed.set_footer(**embed_dict["footer"]) + pages.append(embed) + for i, group in enumerate(field_groups, 1): embed = discord.Embed(color=color, **embed_dict["embed"]) From 51dcf65fd75aca17ea32c9c55548dc249a56ec65 Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Thu, 23 May 2019 09:08:14 +0200 Subject: [PATCH 20/20] [Downloader] Fix problem with copying directory tree. (#2690) * fix(downloader): clear paths in `distutils.dir_util._path_created` before copying tree fix #2685 * style(downloader): add comment about PR --- redbot/cogs/downloader/installable.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/redbot/cogs/downloader/installable.py b/redbot/cogs/downloader/installable.py index 143701a6a..772aec4a9 100644 --- a/redbot/cogs/downloader/installable.py +++ b/redbot/cogs/downloader/installable.py @@ -114,6 +114,8 @@ class Installable(RepoJSONMixin): if self._location.is_file(): copy_func = shutil.copy2 else: + # clear copy_tree's cache to make sure missing directories are created (GH-2690) + distutils.dir_util._path_created = {} copy_func = distutils.dir_util.copy_tree # noinspection PyBroadException