From 4e27059209473118142a5a220b002353dc82d3e4 Mon Sep 17 00:00:00 2001 From: sravan Date: Mon, 21 Oct 2024 01:50:29 +0530 Subject: [PATCH 01/17] [Core] Add error handling when [p]load tries to load "locales" (#6466) --- redbot/core/_cog_manager.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/redbot/core/_cog_manager.py b/redbot/core/_cog_manager.py index 9f195aa65c2..bd5f77fd01a 100644 --- a/redbot/core/_cog_manager.py +++ b/redbot/core/_cog_manager.py @@ -258,6 +258,12 @@ async def _find_core_cog(name: str) -> ModuleSpec: try: mod = import_module(real_name, package=package) + if mod.__spec__.name == "redbot.cogs.locales": + raise NoSuchCog( + "No core cog by the name of '{}' could be found.".format(name), + path=mod.__spec__.origin, + name=name, + ) except ImportError as e: if e.name == package + real_name: raise NoSuchCog( From 43963232052651d56e58efe3903d4acf5fe65457 Mon Sep 17 00:00:00 2001 From: Lemon Rose <78662983+japandotorg@users.noreply.github.com> Date: Tue, 29 Oct 2024 23:16:58 +0530 Subject: [PATCH 02/17] [Trivia] Add non-punctuated alias (#5889) Co-authored-by: Kreusada <67752638+Kreusada@users.noreply.github.com> --- redbot/cogs/trivia/data/lists/harrypotter.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/redbot/cogs/trivia/data/lists/harrypotter.yaml b/redbot/cogs/trivia/data/lists/harrypotter.yaml index de377e32d3f..e83b28e9e84 100644 --- a/redbot/cogs/trivia/data/lists/harrypotter.yaml +++ b/redbot/cogs/trivia/data/lists/harrypotter.yaml @@ -98,6 +98,7 @@ What are draco's parents' names?: - Narcissa and Lucious What book does Hermione insist Ron and Harry read?: - Hogwarts, A History +- Hogwarts A History What breed was Hagrid's pet dragon?: - Norwegian Ridgeback What color are unicorn foals?: @@ -194,7 +195,7 @@ What is Severus Snape's mother's first name?: What is Tonks's first name?: - Nymphadora What is considered Harry's 'trademark spell'?: -- Expelliarmus. +- Expelliarmus What is the Hogwarts School motto in English?: - Never Tickle a Sleeping Dragon What is the age requirement for an Apparation License?: @@ -222,6 +223,7 @@ What is the name of Dumbledore's phoenix?: - fawkes What is the name of Filch's cat?: - Mrs. Norris +- Mrs Norris What is the name of Harry Potter’s pet owl?: - Hedwig What is the name of Harry's aunt?: @@ -609,4 +611,4 @@ How many hours into the past do Harry and Hermione travel in an attempt to rescu - Three How many muggles did Peter Pettigrew kill when he faked his own death?: - 12 -- Twelve \ No newline at end of file +- Twelve From 4134881fae838e5a6de7c3f392a3cb775c623a90 Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Tue, 29 Oct 2024 18:58:28 +0100 Subject: [PATCH 03/17] Optimize Trivia list loading (#6336) Co-authored-by: Kreusada <67752638+Kreusada@users.noreply.github.com> --- redbot/cogs/trivia/trivia.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/redbot/cogs/trivia/trivia.py b/redbot/cogs/trivia/trivia.py index ea319b06005..b4ab7e5aaed 100644 --- a/redbot/cogs/trivia/trivia.py +++ b/redbot/cogs/trivia/trivia.py @@ -29,6 +29,7 @@ UNIQUE_ID = 0xB3C0E453 _ = Translator("Trivia", __file__) +YAMLSafeLoader = getattr(yaml, "CSafeLoader", yaml.SafeLoader) class InvalidListError(Exception): @@ -759,7 +760,7 @@ async def _save_trivia_list( return buffer = io.BytesIO(await attachment.read()) - trivia_dict = yaml.safe_load(buffer) + trivia_dict = yaml.load(buffer, YAMLSafeLoader) TRIVIA_LIST_SCHEMA.validate(trivia_dict) buffer.seek(0) @@ -803,7 +804,7 @@ def get_core_lists() -> List[pathlib.Path]: return list(core_lists_path.glob("*.yaml")) -def get_list(path: pathlib.Path) -> Dict[str, Any]: +def get_list(path: pathlib.Path, *, validate_schema: bool = True) -> Dict[str, Any]: """ Returns a trivia list dictionary from the given path. @@ -814,12 +815,14 @@ def get_list(path: pathlib.Path) -> Dict[str, Any]: """ with path.open(encoding="utf-8") as file: try: - trivia_dict = yaml.safe_load(file) + trivia_dict = yaml.load(file, YAMLSafeLoader) except yaml.error.YAMLError as exc: raise InvalidListError("YAML parsing failed.") from exc - try: - TRIVIA_LIST_SCHEMA.validate(trivia_dict) - except schema.SchemaError as exc: - raise InvalidListError("The list does not adhere to the schema.") from exc + if validate_schema: + try: + TRIVIA_LIST_SCHEMA.validate(trivia_dict) + except schema.SchemaError as exc: + raise InvalidListError("The list does not adhere to the schema.") from exc + return trivia_dict From 30058c0f732ffcf82124bab8781f8b33d1353de4 Mon Sep 17 00:00:00 2001 From: Seaswimmer <102361830+cswimr@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:10:02 -0400 Subject: [PATCH 04/17] Add links to the output of [p]repo list (#6284) Co-authored-by: Seaswimmer <102361830+SeaswimmerTheFsh@users.noreply.github.com> Co-authored-by: Kreusada <67752638+Kreusada@users.noreply.github.com> --- redbot/cogs/downloader/downloader.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/redbot/cogs/downloader/downloader.py b/redbot/cogs/downloader/downloader.py index fc496f0c7ed..2f171b7284a 100644 --- a/redbot/cogs/downloader/downloader.py +++ b/redbot/cogs/downloader/downloader.py @@ -648,14 +648,18 @@ async def _repo_list(self, ctx: commands.Context) -> None: joined = _("There are no repos installed.") else: if len(repos) > 1: - joined = _("# Installed Repos\n") + joined = _("## Installed Repos\n") else: - joined = _("# Installed Repo\n") + joined = _("## Installed Repo\n") for repo in sorted_repos: - joined += "+ {}: {}\n".format(repo.name, repo.short or "") + joined += "- **{}:** {}\n - {}\n".format( + repo.name, + repo.short or "", + "<{}>".format(repo.url), + ) for page in pagify(joined, ["\n"], shorten_by=16): - await ctx.send(box(page.lstrip(" "), lang="markdown")) + await ctx.send(page) @repo.command(name="info") async def _repo_info(self, ctx: commands.Context, repo: Repo) -> None: From 2871992772347f2b95aec214cab113953010f461 Mon Sep 17 00:00:00 2001 From: Kyla Date: Sun, 17 Nov 2024 19:54:30 +0330 Subject: [PATCH 05/17] Define view once inside `menus.menu` for button menus (#6472) --- redbot/core/utils/menus.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/redbot/core/utils/menus.py b/redbot/core/utils/menus.py index b5ee86a01cf..7176188d7cd 100644 --- a/redbot/core/utils/menus.py +++ b/redbot/core/utils/menus.py @@ -212,13 +212,12 @@ async def control_no(*args, **kwargs): # where the original message is already sent prior to starting. # This is not normally the way we recommend sending this because # internally we already include the emojis we expect. + view = SimpleMenu(pages, timeout=timeout) if controls == DEFAULT_CONTROLS: - view = SimpleMenu(pages, timeout=timeout) await view.start(ctx, user=user) await view.wait() return else: - view = SimpleMenu(pages, timeout=timeout) view.remove_item(view.last_button) view.remove_item(view.first_button) has_next = False From 33e0eac741955ce5b7e89d9b8f2f2712727af770 Mon Sep 17 00:00:00 2001 From: palmtree5 <3577255+palmtree5@users.noreply.github.com> Date: Sun, 17 Nov 2024 08:01:31 -0900 Subject: [PATCH 06/17] Remove deprecated options from `.pylintrc` (#6462) Co-authored-by: Kreusada <67752638+Kreusada@users.noreply.github.com> --- .pylintrc | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.pylintrc b/.pylintrc index 1e870ede169..84b81da7930 100644 --- a/.pylintrc +++ b/.pylintrc @@ -26,14 +26,6 @@ unsafe-load-any-extension=no # run arbitrary code extension-pkg-whitelist= -# Allow optimization of some AST trees. This will activate a peephole AST -# optimizer, which will apply various small optimizations. For instance, it can -# be used to obtain the result of joining multiple strings with the addition -# operator. Joining a lot of strings can lead to a maximum recursion error in -# Pylint and this flag can prevent that. It has one side effect, the resulting -# AST will be different than the one from reality. -optimize-ast=no - [MESSAGES CONTROL] @@ -66,7 +58,6 @@ disable=C, # black is enforcing this for us already, incompatibly [REPORTS] output-format=parseable -files-output=no reports=no From 48b2fe77c03a5b687fb319dadb74532a80f219ba Mon Sep 17 00:00:00 2001 From: Ascensionn <87586499+Ascensionn@users.noreply.github.com> Date: Sun, 8 Dec 2024 13:42:22 -0500 Subject: [PATCH 07/17] `[p]warn` ask to ban when user not in server (#6481) Co-authored-by: Michael Oliveira <34169552+Flame442@users.noreply.github.com> --- redbot/cogs/warnings/warnings.py | 48 ++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/redbot/cogs/warnings/warnings.py b/redbot/cogs/warnings/warnings.py index acf84255485..ba22417a28d 100644 --- a/redbot/cogs/warnings/warnings.py +++ b/redbot/cogs/warnings/warnings.py @@ -15,9 +15,10 @@ ) from redbot.core import Config, commands, modlog from redbot.core.bot import Red -from redbot.core.commands import UserInputOptional +from redbot.core.commands import UserInputOptional, RawUserIdConverter from redbot.core.i18n import Translator, cog_i18n from redbot.core.utils import AsyncIter +from redbot.core.utils.views import ConfirmView from redbot.core.utils.chat_formatting import warning, pagify from redbot.core.utils.menus import menu @@ -373,7 +374,7 @@ async def actionlist(self, ctx: commands.Context): async def warn( self, ctx: commands.Context, - member: discord.Member, + user: Union[discord.Member, RawUserIdConverter], points: UserInputOptional[int] = 1, *, reason: str, @@ -386,6 +387,49 @@ async def warn( or a custom reason if ``[p]warningset allowcustomreasons`` is set. """ guild = ctx.guild + member = None + if isinstance(user, discord.Member): + member = user + elif isinstance(user, int): + if not ctx.channel.permissions_for(ctx.guild.me).ban_members: + await ctx.send(_("User `{user}` is not in the server.").format(user=user)) + return + user_obj = self.bot.get_user(user) or discord.Object(id=user) + + confirm = ConfirmView(ctx.author, timeout=30) + confirm.message = await ctx.send( + _( + "User `{user}` is not in the server. Would you like to ban them instead?" + ).format(user=user), + view=confirm, + ) + await confirm.wait() + if confirm.result: + try: + await ctx.guild.ban(user_obj, reason=reason) + await modlog.create_case( + self.bot, + guild, + ctx.message.created_at, + "hackban", + user, + ctx.author, + reason, + until=None, + channel=None, + ) + except discord.HTTPException as error: + await ctx.send( + _("An error occurred while trying to ban the user. Error: {error}").format( + error=error + ) + ) + else: + confirm.message = await ctx.send(_("No action taken.")) + + await ctx.tick() + return + if member == ctx.author: return await ctx.send(_("You cannot warn yourself.")) if member.bot: From 99206289488dbdca8ac4af3a6aae7587bff6420b Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Wed, 18 Dec 2024 06:28:05 +0100 Subject: [PATCH 08/17] Bump YT plugin version and update LL server config (#6488) --- .../audio/managed_node/ll_server_config.py | 30 +++++++++++++++---- .../cogs/audio/managed_node/version_pins.py | 2 +- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/redbot/cogs/audio/managed_node/ll_server_config.py b/redbot/cogs/audio/managed_node/ll_server_config.py index 3e9faec8471..e37946e7a85 100644 --- a/redbot/cogs/audio/managed_node/ll_server_config.py +++ b/redbot/cogs/audio/managed_node/ll_server_config.py @@ -50,16 +50,36 @@ "yaml__plugins__youtube__clients": [ "MUSIC", "WEB", - "ANDROID_TESTSUITE", + "WEBEMBEDDED", + "MWEB", "TVHTML5EMBEDDED", - "ANDROID_LITE", - "MEDIA_CONNECT", + "TV", "IOS", ], - "yaml__plugins__youtube__WEB__playback": True, + "yaml__plugins__youtube__MUSIC__playback": False, + "yaml__plugins__youtube__MUSIC__playlistLoading": False, + "yaml__plugins__youtube__MUSIC__searching": True, + "yaml__plugins__youtube__MUSIC__videoLoading": False, + "yaml__plugins__youtube__MWEB__playback": True, + "yaml__plugins__youtube__MWEB__playlistLoading": True, + "yaml__plugins__youtube__MWEB__searching": False, + "yaml__plugins__youtube__MWEB__videoLoading": False, + "yaml__plugins__youtube__TV__playback": True, + "yaml__plugins__youtube__TV__playlistLoading": False, + "yaml__plugins__youtube__TV__searching": False, + "yaml__plugins__youtube__TV__videoLoading": False, + "yaml__plugins__youtube__TVHTML5EMBEDDED__playback": True, "yaml__plugins__youtube__TVHTML5EMBEDDED__playlistLoading": False, - "yaml__plugins__youtube__TVHTML5EMBEDDED__videoLoading": False, "yaml__plugins__youtube__TVHTML5EMBEDDED__searching": False, + "yaml__plugins__youtube__TVHTML5EMBEDDED__videoLoading": False, + "yaml__plugins__youtube__WEB__playback": True, + "yaml__plugins__youtube__WEB__playlistLoading": True, + "yaml__plugins__youtube__WEB__searching": False, + "yaml__plugins__youtube__WEB__videoLoading": False, + "yaml__plugins__youtube__WEBEMBEDDED__playback": True, + "yaml__plugins__youtube__WEBEMBEDDED__playlistLoading": False, + "yaml__plugins__youtube__WEBEMBEDDED__searching": False, + "yaml__plugins__youtube__WEBEMBEDDED__videoLoading": False, } diff --git a/redbot/cogs/audio/managed_node/version_pins.py b/redbot/cogs/audio/managed_node/version_pins.py index 728e7634611..558fb627207 100644 --- a/redbot/cogs/audio/managed_node/version_pins.py +++ b/redbot/cogs/audio/managed_node/version_pins.py @@ -6,4 +6,4 @@ JAR_VERSION: Final[LavalinkVersion] = LavalinkVersion(3, 7, 12, red=1) -YT_PLUGIN_VERSION: Final[str] = "1.7.2" +YT_PLUGIN_VERSION: Final[str] = "1.11.1" From d29ae723c140de40971633025c72ddc4df0a50ac Mon Sep 17 00:00:00 2001 From: aikaterna <20862007+aikaterna@users.noreply.github.com> Date: Sun, 22 Dec 2024 14:15:43 -0800 Subject: [PATCH 09/17] Bump YT plugin version (#6490) --- redbot/cogs/audio/managed_node/version_pins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redbot/cogs/audio/managed_node/version_pins.py b/redbot/cogs/audio/managed_node/version_pins.py index 558fb627207..aa4003fadc6 100644 --- a/redbot/cogs/audio/managed_node/version_pins.py +++ b/redbot/cogs/audio/managed_node/version_pins.py @@ -6,4 +6,4 @@ JAR_VERSION: Final[LavalinkVersion] = LavalinkVersion(3, 7, 12, red=1) -YT_PLUGIN_VERSION: Final[str] = "1.11.1" +YT_PLUGIN_VERSION: Final[str] = "1.11.2" From f0a29e9815a38215d61d173107528a40d8f0e0a1 Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Tue, 24 Dec 2024 01:18:36 +0100 Subject: [PATCH 10/17] Support [botname] substitutions in cog install messages too (#6491) --- redbot/cogs/downloader/downloader.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/redbot/cogs/downloader/downloader.py b/redbot/cogs/downloader/downloader.py index 2f171b7284a..9df5d8ada94 100644 --- a/redbot/cogs/downloader/downloader.py +++ b/redbot/cogs/downloader/downloader.py @@ -936,7 +936,11 @@ async def _cog_installrev( await self.send_pagified(ctx, f"{message}{deprecation_notice}\n---") for cog in installed_cogs: if cog.install_msg: - await ctx.send(cog.install_msg.replace("[p]", ctx.clean_prefix)) + await ctx.send( + cog.install_msg.replace("[p]", ctx.clean_prefix).replace( + "[botname]", ctx.me.display_name + ) + ) @cog.command(name="uninstall", require_var_positional=True) async def _cog_uninstall(self, ctx: commands.Context, *cogs: InstalledCog) -> None: From 9419f2642a9ce2b406d5828aca49294403153762 Mon Sep 17 00:00:00 2001 From: Kevin Wang <38193800+kevin1015wang@users.noreply.github.com> Date: Mon, 23 Dec 2024 19:58:57 -0500 Subject: [PATCH 11/17] Allow enforcing reason to be filled in Mod cog commands (#6477) Co-authored-by: Jakub Kuczys --- docs/cog_guides/mod.rst | 22 ++++++++++++++++++++++ redbot/cogs/mod/kickban.py | 35 +++++++++++++++++++++++++++++++++++ redbot/cogs/mod/mod.py | 1 + redbot/cogs/mod/settings.py | 23 +++++++++++++++++++++++ 4 files changed, 81 insertions(+) diff --git a/docs/cog_guides/mod.rst b/docs/cog_guides/mod.rst index c8dc7da91dc..f412b1500d2 100644 --- a/docs/cog_guides/mod.rst +++ b/docs/cog_guides/mod.rst @@ -266,6 +266,28 @@ and reason as to why they were kicked/banned. * ``[enabled]``: Whether a message should be sent to a user when they are kicked/banned. |bool-input| +.. _mod-command-modset-requirereason: + +"""""""""""""""""""" +modset requirereason +"""""""""""""""""""" + +**Syntax** + +.. code-block:: none + + [p]modset requirereason [enabled] + +**Description** + +Toggle whether a reason is required for mod actions. + +If this is enabled, the bot will require a reason to be provided for all mod actions. + +**Arguments** + +* ``[enabled]``: Whether a reason should be required when performing mod actions. |bool-input| + .. _mod-command-modset-hierarchy: """""""""""""""" diff --git a/redbot/cogs/mod/kickban.py b/redbot/cogs/mod/kickban.py index 28e7f13fd9c..583acb2a8d1 100644 --- a/redbot/cogs/mod/kickban.py +++ b/redbot/cogs/mod/kickban.py @@ -117,6 +117,9 @@ async def ban_user( removed_temp = False + if reason is None and await self.config.guild(guild).require_reason(): + return False, _("You must provide a reason for the ban.") + if not (0 <= days <= 7): return False, _("Invalid days. Must be between 0 and 7.") @@ -303,6 +306,10 @@ async def kick(self, ctx: commands.Context, member: discord.Member, *, reason: s author = ctx.author guild = ctx.guild + if reason is None and await self.config.guild(guild).require_reason(): + await ctx.send(_("You must provide a reason for the kick.")) + return + if author == member: await ctx.send( _("I cannot let you do that. Self-harm is bad {emoji}").format( @@ -428,6 +435,10 @@ async def massban( errors = {} upgrades = [] + if reason is None and await self.config.guild(ctx.guild).require_reason(): + await ctx.send(_("You must provide a reason for the massban.")) + return + async def show_results(): text = _("Banned {num} users from the server.").format( num=humanize_number(len(banned)) @@ -605,6 +616,10 @@ async def tempban( guild = ctx.guild author = ctx.author + if reason is None and await self.config.guild(guild).require_reason(): + await ctx.send(_("You must provide a reason for the temporary ban.")) + return + if author == member: await ctx.send( _("I cannot let you do that. Self-harm is bad {}").format("\N{PENSIVE FACE}") @@ -684,6 +699,10 @@ async def softban(self, ctx: commands.Context, member: discord.Member, *, reason guild = ctx.guild author = ctx.author + if reason is None and await self.config.guild(guild).require_reason(): + await ctx.send(_("You must provide a reason for the softban.")) + return + if author == member: await ctx.send( _("I cannot let you do that. Self-harm is bad {emoji}").format( @@ -771,6 +790,10 @@ async def voicekick( self, ctx: commands.Context, member: discord.Member, *, reason: str = None ): """Kick a member from a voice channel.""" + if reason is None and await self.config.guild(ctx.guild).require_reason(): + await ctx.send(_("You must provide a reason for the voice kick.")) + return + author = ctx.author guild = ctx.guild user_voice_state: discord.VoiceState = member.voice @@ -818,6 +841,10 @@ async def voiceunban( self, ctx: commands.Context, member: discord.Member, *, reason: str = None ): """Unban a user from speaking and listening in the server's voice channels.""" + if reason is None and await self.config.guild(ctx.guild).require_reason(): + await ctx.send(_("You must provide a reason for the voice unban.")) + return + user_voice_state = member.voice if ( await self._voice_perm_check( @@ -859,6 +886,10 @@ async def voiceunban( @commands.admin_or_permissions(mute_members=True, deafen_members=True) async def voiceban(self, ctx: commands.Context, member: discord.Member, *, reason: str = None): """Ban a user from speaking and listening in the server's voice channels.""" + if reason is None and await self.config.guild(ctx.guild).require_reason(): + await ctx.send(_("You must provide a reason for the voice ban.")) + return + user_voice_state: discord.VoiceState = member.voice if ( await self._voice_perm_check( @@ -908,6 +939,10 @@ async def unban( 1. Copy it from the mod log case (if one was created), or 2. Enable Developer Mode, go to Bans in this server's settings, right-click the user and select 'Copy ID'. """ + if reason is None and await self.config.guild(ctx.guild).require_reason(): + await ctx.send(_("You must provide a reason for the unban.")) + return + guild = ctx.guild author = ctx.author audit_reason = get_audit_reason(ctx.author, reason, shorten=True) diff --git a/redbot/cogs/mod/mod.py b/redbot/cogs/mod/mod.py index 3915ce0ce46..17aeb0e7caa 100644 --- a/redbot/cogs/mod/mod.py +++ b/redbot/cogs/mod/mod.py @@ -57,6 +57,7 @@ class Mod( "reinvite_on_unban": False, "current_tempbans": [], "dm_on_kickban": False, + "require_reason": False, "default_days": 0, "default_tempban_duration": 60 * 60 * 24, "track_nicknames": True, diff --git a/redbot/cogs/mod/settings.py b/redbot/cogs/mod/settings.py index 298ed10a590..cd5fc294143 100644 --- a/redbot/cogs/mod/settings.py +++ b/redbot/cogs/mod/settings.py @@ -370,6 +370,29 @@ async def dm(self, ctx: commands.Context, enabled: bool = None): _("Bot will no longer attempt to send a DM to user before kick and ban.") ) + @modset.command() + @commands.guild_only() + async def requirereason(self, ctx: commands.Context, enabled: bool = None): + """ + Toggle whether a reason is required for mod actions. + + If this is enabled, the bot will require a reason to be provided for all mod actions. + """ + guild = ctx.guild + if enabled is None: + setting = await self.config.guild(guild).require_reason() + await ctx.send( + _("Mod action reason requirement is currently set to: {setting}").format( + setting=setting + ) + ) + return + await self.config.guild(guild).require_reason.set(enabled) + if enabled: + await ctx.send(_("Bot will now require a reason for all mod actions.")) + else: + await ctx.send(_("Bot will no longer require a reason for all mod actions.")) + @modset.command() @commands.guild_only() async def defaultdays(self, ctx: commands.Context, days: int = 0): From f4ffc6bc8032ff0a07e035883a86963209ee89f3 Mon Sep 17 00:00:00 2001 From: TrustyJAID Date: Mon, 23 Dec 2024 18:00:41 -0700 Subject: [PATCH 12/17] Add support for SimpleMenu to use custom select options (#6480) Co-authored-by: Jakub Kuczys --- redbot/core/utils/views.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/redbot/core/utils/views.py b/redbot/core/utils/views.py index aff3f11e1ef..f70c5565014 100644 --- a/redbot/core/utils/views.py +++ b/redbot/core/utils/views.py @@ -107,6 +107,17 @@ class SimpleMenu(discord.ui.View): under the select menu in this instance. Defaults to False. + Attributes + ---------- + select_menu: `discord.ui.Select` + A select menu with a list of pages. The usage of this attribute is discouraged + as it may store different instances throughout the menu's lifetime. + + .. deprecated-removed:: 3.5.14 60 + Any behaviour enabled by the usage of this attribute should no longer be depended on. + If you need this for something and cannot replace it with the other functionality, + create an issue on Red's issue tracker. + Examples -------- You can provide a list of strings:: @@ -269,6 +280,11 @@ async def start( Send the message ephemerally. This only works if the context is from a slash command interaction. """ + if self.use_select_menu and self.source.is_paginating(): + self.remove_item(self.select_menu) + # we added a default one in init so we want to remove it and add any changes here + self.select_menu = self._get_select_menu() + self.add_item(self.select_menu) self._fallback_author_to_ctx = True if user is not None: self.author = user @@ -285,6 +301,11 @@ async def start_dm(self, user: discord.User): user: `discord.User` The user that will be direct messaged by the bot. """ + if self.use_select_menu and self.source.is_paginating(): + self.remove_item(self.select_menu) + # we added a default one in init so we want to remove it and add any changes here + self.select_menu = self._get_select_menu() + self.add_item(self.select_menu) self.author = user kwargs = await self.get_page(self.current_page) self.message = await user.send(**kwargs) From 150692538f417310801dc90c1916d83be9321d2a Mon Sep 17 00:00:00 2001 From: cdaman3141 <46515032+cdaman3141@users.noreply.github.com> Date: Mon, 23 Dec 2024 20:12:33 -0500 Subject: [PATCH 13/17] Fixed [p]ban raising an unhandled error if an ID too large is provided (#6486) Co-authored-by: Jakub Kuczys --- redbot/core/commands/converter.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/redbot/core/commands/converter.py b/redbot/core/commands/converter.py index 015a4c079ca..ca69a5a4485 100644 --- a/redbot/core/commands/converter.py +++ b/redbot/core/commands/converter.py @@ -51,8 +51,11 @@ _ = Translator("commands.converter", __file__) -ID_REGEX = re.compile(r"([0-9]{15,20})") -USER_MENTION_REGEX = re.compile(r"<@!?([0-9]{15,21})>$") +# You'd think that Discord's documentation showing an example of 2 ** 64 - 1 snowflake would mean that +# this is going to be accepted by everything in their API but nope... Let's assume 2 ** 63 - 1 as the max instead. +ID_REGEX = re.compile(r"([0-9]{15,19})") +USER_MENTION_REGEX = re.compile(r"<@!?([0-9]{15,19})>$") +_MAX_ID = 2**63 - 1 # Taken with permission from @@ -239,8 +242,16 @@ async def convert(self, ctx: "Context", argument: str) -> int: # are most likely not in the guild. # Mentions are supported, but most likely won't ever be in cache. - if match := ID_REGEX.match(argument) or USER_MENTION_REGEX.match(argument): - return int(match.group(1)) + if match := ID_REGEX.fullmatch(argument) or USER_MENTION_REGEX.fullmatch(argument): + user_id = int(match.group(1)) + + # Validate user ID range + if user_id > _MAX_ID: + raise BadArgument( + f"The ID '{argument}' is too large to be a valid Discord user ID." + ) + + return user_id raise BadArgument(_("'{input}' doesn't look like a valid user ID.").format(input=argument)) From 5cfb8edab840ca04abbc37691c0693e9c3b91684 Mon Sep 17 00:00:00 2001 From: Kowlin <10947836+Kowlin@users.noreply.github.com> Date: Tue, 24 Dec 2024 02:20:42 +0100 Subject: [PATCH 14/17] ANSI formatting support (#5538) Co-authored-by: Kowlin Co-authored-by: Vexed Co-authored-by: Jack Tealey <67752638+Kreusada@users.noreply.github.com> --- redbot/core/utils/chat_formatting.py | 68 +++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/redbot/core/utils/chat_formatting.py b/redbot/core/utils/chat_formatting.py index 9a96ae380c6..9c1f6684953 100644 --- a/redbot/core/utils/chat_formatting.py +++ b/redbot/core/utils/chat_formatting.py @@ -4,12 +4,13 @@ import itertools import math import textwrap -from io import BytesIO -from typing import Iterator, List, Literal, Optional, Sequence, SupportsInt, Union +from io import BytesIO, StringIO +from typing import Any, Iterator, List, Literal, Optional, Sequence, SupportsInt, Union import discord from babel.lists import format_list as babel_list from babel.numbers import format_decimal +from rich.console import Console from redbot.core.i18n import Translator, get_babel_locale, get_babel_regional_format @@ -729,3 +730,66 @@ def text_to_file( """ file = BytesIO(text.encode(encoding)) return discord.File(file, filename, spoiler=spoiler) + + +def rich_markup( + *objects: Any, + crop: Optional[bool] = True, + emoji: Optional[bool] = True, + highlight: Optional[bool] = True, + justify: Optional[str] = None, + markup: Optional[bool] = True, + no_wrap: Optional[bool] = None, + overflow: Optional[str] = None, + width: Optional[int] = None, +) -> str: + """Returns a codeblock with ANSI formatting for colour support. + + This supports a limited set of Rich markup, and rich helper functions. (https://rich.readthedocs.io/en/stable/index.html) + + Parameters + ---------- + *objects: Any + The text to convert to ANSI formatting. + crop: Optional[bool] + Crop output to width of virtual terminal. Defaults to ``True``. + emoji: Optional[bool] + Enable emoji code. Defaults to ``True``. + highlight: Optional[bool] + Enable automated highlighting. Defaults to ``True``. + justify: Optional[str] + Justify method: "default", "left", "right", "center", or "full". Defaults to ``None``. + markup: Optional[bool] + Boolean to enable Console Markup. Defaults to ``True``. + no_wrap: Optional[bool] + Disables word wrapping. Defaults to ``None``. + overflow: Optional[str] + Overflow method: "ignore", "crop", "fold", or "ellipsis". Defaults to None. + width: Optional[int] + The width of the virtual terminal. Defaults to ``80`` characters long. + + + Returns + ------- + str: + The ANSI formatted text in a codeblock. + """ + temp_console = Console( # Prevent messing with STDOUT's console + color_system="standard", # Discord only supports 8-bit in colors + emoji=emoji, + file=StringIO(), + force_terminal=True, + force_interactive=False, + highlight=highlight, + markup=markup, + width=width if width is not None else 80, + ) + + temp_console.print( + *objects, + crop=crop, + justify=justify, + no_wrap=no_wrap, + overflow=overflow, + ) + return box(temp_console.file.getvalue(), lang="ansi") From 016684bcce817ce9bd54ff76199a08d866b7e0c6 Mon Sep 17 00:00:00 2001 From: Michael Oliveira <34169552+Flame442@users.noreply.github.com> Date: Mon, 23 Dec 2024 21:02:16 -0500 Subject: [PATCH 15/17] Add methods for getting app command IDs/mentions from cache (#6278) Co-authored-by: Jakub Kuczys --- redbot/core/bot.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/redbot/core/bot.py b/redbot/core/bot.py index 89f711ef009..6cba99d8891 100644 --- a/redbot/core/bot.py +++ b/redbot/core/bot.py @@ -1791,6 +1791,82 @@ async def list_enabled_app_commands(self) -> Dict[str, Dict[str, Optional[int]]] "user": curr_user_commands, } + async def get_app_command_id( + self, + command_name: str, + command_type: discord.AppCommandType = discord.AppCommandType.chat_input, + ) -> Optional[int]: + """ + Get the cached ID for a particular app command. + + Pulls from Red's internal cache of app command IDs, which is updated + when the ``[p]slash sync`` command is ran on this instance + or `bot.tree.sync() ` is called. + Does not keep track of guild-specific app commands. + + Parameters + ---------- + command_name : str + Name of the command to get the ID of. + command_type : `discord.AppCommandType` + Type of the command to get the ID of. + + Returns + ------- + Optional[int] + The cached of the specified app command or ``None``, + if the command does not exist or Red does not know the ID + for that app command. + """ + if command_type is discord.AppCommandType.chat_input: + cfg = self._config.enabled_slash_commands() + elif command_type is discord.AppCommandType.message: + cfg = self._config.enabled_message_commands() + elif command_type is discord.AppCommandType.user: + cfg = self._config.enabled_user_commands() + else: + raise TypeError("command type must be one of chat_input, message, user") + + curr_commands = await cfg + return curr_commands.get(command_name, None) + + async def get_app_command_mention( + self, + command_name: str, + command_type: discord.AppCommandType = discord.AppCommandType.chat_input, + ) -> Optional[str]: + """ + Get the string that allows you to mention a particular app command. + + Pulls from Red's internal cache of app command IDs, which is updated + when the ``[p]slash sync`` command is ran on this instance + or `bot.tree.sync() ` is called. + Does not keep track of guild-specific app commands. + + Parameters + ---------- + command_name : str + Name of the command to get the mention for. + command_type : `discord.AppCommandType` + Type of the command to get the mention for. + + Returns + ------- + Optional[str] + The string that allows you to mention the specified app command + or ``None``, if the command does not exist or Red does not know the ID + for that app command. + """ + # Empty string names will break later code and can't exist as commands, exit early + if not command_name: + raise ValueError("command name must be a non-empty string") + # Account for mentioning subcommands by fetching from the cache based on the base command + base_command = command_name.split(" ")[0] + command_id = await self.get_app_command_id(base_command, command_type) + if command_id is None: + return None + return f"" + async def is_automod_immune( self, to_check: Union[discord.Message, commands.Context, discord.abc.User, discord.Role] ) -> bool: From 18614b16045847543c08a69e366f93ef331d3dd0 Mon Sep 17 00:00:00 2001 From: Michael Oliveira <34169552+Flame442@users.noreply.github.com> Date: Mon, 23 Dec 2024 21:04:11 -0500 Subject: [PATCH 16/17] Respect `[p]bypasscooldowns` in `[p]slash sync` (#6465) --- redbot/core/core_commands.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/redbot/core/core_commands.py b/redbot/core/core_commands.py index 42aab5221bb..521009060ba 100644 --- a/redbot/core/core_commands.py +++ b/redbot/core/core_commands.py @@ -2375,6 +2375,11 @@ async def slash_sync_error(self, ctx: commands.Context, error: commands.CommandE """Custom cooldown error message.""" if not isinstance(error, commands.CommandOnCooldown): return await ctx.bot.on_command_error(ctx, error, unhandled_by_cog=True) + if ctx.bot._bypass_cooldowns and ctx.author.id in ctx.bot.owner_ids: + ctx.command.reset_cooldown(ctx) + new_ctx = await ctx.bot.get_context(ctx.message) + await ctx.bot.invoke(new_ctx) + return await ctx.send( _( "You seem to be attempting to sync after recently syncing. Discord does not like it " From fdaa8691306eee5779e28f993944212e29c424ed Mon Sep 17 00:00:00 2001 From: TrustyJAID Date: Mon, 23 Dec 2024 19:23:26 -0700 Subject: [PATCH 17/17] [Help command] Utilize copy to clipboard (#6244) Co-authored-by: Kreusada Ignad Amredes <67752638+Kreusada@users.noreply.github.com> --- redbot/core/commands/help.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/redbot/core/commands/help.py b/redbot/core/commands/help.py index a86b16bb5ab..8ad7f4c5f3e 100644 --- a/redbot/core/commands/help.py +++ b/redbot/core/commands/help.py @@ -352,11 +352,10 @@ async def format_command_help( description = command.description or "" tagline = self.format_tagline(ctx, help_settings.tagline) or self.get_default_tagline(ctx) - signature = _("Syntax: {command_signature}").format( - command_signature=self.get_command_signature(ctx, command) - ) + signature = self.get_command_signature(ctx, command) aliases = command.aliases + sig_description = bold(_("Syntax:\n")) + box(signature) if help_settings.show_aliases and aliases: alias_fmt = _("Aliases") if len(command.aliases) > 1 else _("Alias") aliases = sorted(aliases, key=len) @@ -386,7 +385,7 @@ async def format_command_help( aliases_content = _("{aliases} and one more alias.").format( aliases=aliases_formatted_list ) - signature += f"\n{alias_fmt}: {aliases_content}" + sig_description += bold(f"{alias_fmt}:") + box(f"{aliases_content}") subcommands = None if hasattr(command, "all_commands"): @@ -400,7 +399,7 @@ async def format_command_help( emb["embed"]["title"] = f"*{description[:250]}*" emb["footer"]["text"] = tagline - emb["embed"]["description"] = box(signature) + emb["embed"]["description"] = sig_description command_help = command.format_help_for_context(ctx) if command_help: