From 66ec8673a16ec6a2afe7d72d045600d18e882fc8 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Thu, 15 Jul 2021 22:07:25 -0700 Subject: Allow manual mutes to override auto mutes If a moderator mutes a user, allow them to override any active mute that was automatically applied by the bot (e.g. from antispam). Resolve #1665 --- bot/exts/moderation/infraction/_utils.py | 13 +++++++++---- bot/exts/moderation/infraction/infractions.py | 9 +++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/bot/exts/moderation/infraction/_utils.py b/bot/exts/moderation/infraction/_utils.py index adbc641fa..dd427e413 100644 --- a/bot/exts/moderation/infraction/_utils.py +++ b/bot/exts/moderation/infraction/_utils.py @@ -143,15 +143,20 @@ async def get_active_infraction( # Checks to see if the moderator should be told there is an active infraction if send_msg: log.trace(f"{user} has active infractions of type {infr_type}.") - await ctx.send( - f":x: According to my records, this user already has a {infr_type} infraction. " - f"See infraction **#{active_infractions[0]['id']}**." - ) + await send_active_infraction_message(ctx, active_infractions[0]) return active_infractions[0] else: log.trace(f"{user} does not have active infractions of type {infr_type}.") +async def send_active_infraction_message(ctx: Context, infraction: Infraction) -> None: + """Send a message stating that the given infraction is active.""" + await ctx.send( + f":x: According to my records, this user already has a {infraction['type']} infraction. " + f"See infraction **#{infraction['id']}**." + ) + + async def notify_infraction( user: UserObject, infr_type: str, diff --git a/bot/exts/moderation/infraction/infractions.py b/bot/exts/moderation/infraction/infractions.py index f19323c7c..1b1414ec7 100644 --- a/bot/exts/moderation/infraction/infractions.py +++ b/bot/exts/moderation/infraction/infractions.py @@ -280,8 +280,13 @@ class Infractions(InfractionScheduler, commands.Cog): async def apply_mute(self, ctx: Context, user: Member, reason: t.Optional[str], **kwargs) -> None: """Apply a mute infraction with kwargs passed to `post_infraction`.""" - if await _utils.get_active_infraction(ctx, user, "mute"): - return + if active := await _utils.get_active_infraction(ctx, user, "mute", send_msg=False): + if active["actor"] != self.bot.user.id: + await _utils.send_active_infraction_message(ctx, active) + return + + # Let the current mute attempt override an automatically triggered mute. + await self.deactivate_infraction(active) infraction = await _utils.post_infraction(ctx, user, "mute", reason, active=True, **kwargs) if infraction is None: -- cgit v1.2.3 From 786b690fab9812e0bda5296d5ea64e2c3cb446d3 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Thu, 15 Jul 2021 22:25:16 -0700 Subject: Display error if overriding mute fails --- bot/exts/moderation/infraction/infractions.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bot/exts/moderation/infraction/infractions.py b/bot/exts/moderation/infraction/infractions.py index 1b1414ec7..eaf718af4 100644 --- a/bot/exts/moderation/infraction/infractions.py +++ b/bot/exts/moderation/infraction/infractions.py @@ -286,7 +286,13 @@ class Infractions(InfractionScheduler, commands.Cog): return # Let the current mute attempt override an automatically triggered mute. - await self.deactivate_infraction(active) + log_text = await self.deactivate_infraction(active) + if "Failure" in log_text: + await ctx.send( + f":x: can't override infraction **mute** for {user.mention}: " + f"failed to deactivate. {log_text['Failure']}" + ) + return infraction = await _utils.post_infraction(ctx, user, "mute", reason, active=True, **kwargs) if infraction is None: -- cgit v1.2.3 From 8ec6f0880be0ca876b9a255a0fd5e0090695d5b8 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Thu, 15 Jul 2021 22:31:01 -0700 Subject: Fix get_active_infraction test --- tests/bot/exts/moderation/infraction/test_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/bot/exts/moderation/infraction/test_utils.py b/tests/bot/exts/moderation/infraction/test_utils.py index 50a717bb5..f3af7bea9 100644 --- a/tests/bot/exts/moderation/infraction/test_utils.py +++ b/tests/bot/exts/moderation/infraction/test_utils.py @@ -94,8 +94,8 @@ class ModerationUtilsTests(unittest.IsolatedAsyncioTestCase): test_case = namedtuple("test_case", ["get_return_value", "expected_output", "infraction_nr", "send_msg"]) test_cases = [ test_case([], None, None, True), - test_case([{"id": 123987}], {"id": 123987}, "123987", False), - test_case([{"id": 123987}], {"id": 123987}, "123987", True) + test_case([{"id": 123987, "type": "ban"}], {"id": 123987, "type": "ban"}, "123987", False), + test_case([{"id": 123987, "type": "ban"}], {"id": 123987, "type": "ban"}, "123987", True) ] for case in test_cases: -- cgit v1.2.3 From 24e4a9d2f4c037f7652b7300772ec0c6c6ab8d3c Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Thu, 15 Jul 2021 22:44:42 -0700 Subject: Remove redundant parameter from pardon_voice_ban --- bot/exts/moderation/infraction/infractions.py | 4 ++-- tests/bot/exts/moderation/infraction/test_infractions.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bot/exts/moderation/infraction/infractions.py b/bot/exts/moderation/infraction/infractions.py index eaf718af4..dbf56d6bb 100644 --- a/bot/exts/moderation/infraction/infractions.py +++ b/bot/exts/moderation/infraction/infractions.py @@ -455,7 +455,7 @@ class Infractions(InfractionScheduler, commands.Cog): return log_text - async def pardon_voice_ban(self, user_id: int, guild: discord.Guild, reason: t.Optional[str]) -> t.Dict[str, str]: + async def pardon_voice_ban(self, user_id: int, guild: discord.Guild) -> t.Dict[str, str]: """Add Voice Verified role back to user, DM them a notification, and return a log dict.""" user = guild.get_member(user_id) log_text = {} @@ -491,7 +491,7 @@ class Infractions(InfractionScheduler, commands.Cog): elif infraction["type"] == "ban": return await self.pardon_ban(user_id, guild, reason) elif infraction["type"] == "voice_ban": - return await self.pardon_voice_ban(user_id, guild, reason) + return await self.pardon_voice_ban(user_id, guild) # endregion diff --git a/tests/bot/exts/moderation/infraction/test_infractions.py b/tests/bot/exts/moderation/infraction/test_infractions.py index b9d527770..f844a9181 100644 --- a/tests/bot/exts/moderation/infraction/test_infractions.py +++ b/tests/bot/exts/moderation/infraction/test_infractions.py @@ -195,7 +195,7 @@ class VoiceBanTests(unittest.IsolatedAsyncioTestCase): async def test_voice_unban_user_not_found(self): """Should include info to return dict when user was not found from guild.""" self.guild.get_member.return_value = None - result = await self.cog.pardon_voice_ban(self.user.id, self.guild, "foobar") + result = await self.cog.pardon_voice_ban(self.user.id, self.guild) self.assertEqual(result, {"Info": "User was not found in the guild."}) @patch("bot.exts.moderation.infraction.infractions._utils.notify_pardon") @@ -206,7 +206,7 @@ class VoiceBanTests(unittest.IsolatedAsyncioTestCase): notify_pardon_mock.return_value = True format_user_mock.return_value = "my-user" - result = await self.cog.pardon_voice_ban(self.user.id, self.guild, "foobar") + result = await self.cog.pardon_voice_ban(self.user.id, self.guild) self.assertEqual(result, { "Member": "my-user", "DM": "Sent" @@ -221,7 +221,7 @@ class VoiceBanTests(unittest.IsolatedAsyncioTestCase): notify_pardon_mock.return_value = False format_user_mock.return_value = "my-user" - result = await self.cog.pardon_voice_ban(self.user.id, self.guild, "foobar") + result = await self.cog.pardon_voice_ban(self.user.id, self.guild) self.assertEqual(result, { "Member": "my-user", "DM": "**Failed**" -- cgit v1.2.3 From c84ed0fa6d647116b333327a29525719000f4c29 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Thu, 15 Jul 2021 23:02:30 -0700 Subject: Avoid sending pardon DM when overriding a mute --- bot/exts/moderation/infraction/_scheduler.py | 25 ++++++++--- bot/exts/moderation/infraction/infractions.py | 64 +++++++++++++++++---------- 2 files changed, 59 insertions(+), 30 deletions(-) diff --git a/bot/exts/moderation/infraction/_scheduler.py b/bot/exts/moderation/infraction/_scheduler.py index 8286d3635..c2fd959f7 100644 --- a/bot/exts/moderation/infraction/_scheduler.py +++ b/bot/exts/moderation/infraction/_scheduler.py @@ -265,13 +265,17 @@ class InfractionScheduler: ctx: Context, infr_type: str, user: UserSnowflake, - send_msg: bool = True + *, + send_msg: bool = True, + notify: bool = True ) -> None: """ Prematurely end an infraction for a user and log the action in the mod log. If `send_msg` is True, then a pardoning confirmation message will be sent to - the context channel. Otherwise, no such message will be sent. + the context channel. Otherwise, no such message will be sent. + + If `notify` is True, notify the user of the pardon via DM where applicable. """ log.trace(f"Pardoning {infr_type} infraction for {user}.") @@ -292,7 +296,7 @@ class InfractionScheduler: return # Deactivate the infraction and cancel its scheduled expiration task. - log_text = await self.deactivate_infraction(response[0], send_log=False) + log_text = await self.deactivate_infraction(response[0], send_log=False, notify=notify) log_text["Member"] = messages.format_user(user) log_text["Actor"] = ctx.author.mention @@ -345,7 +349,9 @@ class InfractionScheduler: async def deactivate_infraction( self, infraction: _utils.Infraction, - send_log: bool = True + *, + send_log: bool = True, + notify: bool = True ) -> t.Dict[str, str]: """ Deactivate an active infraction and return a dictionary of lines to send in a mod log. @@ -354,6 +360,8 @@ class InfractionScheduler: expiration task cancelled. If `send_log` is True, a mod log is sent for the deactivation of the infraction. + If `notify` is True, notify the user of the pardon via DM where applicable. + Infractions of unsupported types will raise a ValueError. """ guild = self.bot.get_guild(constants.Guild.id) @@ -380,7 +388,7 @@ class InfractionScheduler: try: log.trace("Awaiting the pardon action coroutine.") - returned_log = await self._pardon_action(infraction) + returned_log = await self._pardon_action(infraction, notify) if returned_log is not None: log_text = {**log_text, **returned_log} # Merge the logs together @@ -468,10 +476,15 @@ class InfractionScheduler: return log_text @abstractmethod - async def _pardon_action(self, infraction: _utils.Infraction) -> t.Optional[t.Dict[str, str]]: + async def _pardon_action( + self, + infraction: _utils.Infraction, + notify: bool + ) -> t.Optional[t.Dict[str, str]]: """ Execute deactivation steps specific to the infraction's type and return a log dict. + If `notify` is True, notify the user of the pardon via DM where applicable. If an infraction type is unsupported, return None instead. """ raise NotImplementedError diff --git a/bot/exts/moderation/infraction/infractions.py b/bot/exts/moderation/infraction/infractions.py index dbf56d6bb..9d5b049e1 100644 --- a/bot/exts/moderation/infraction/infractions.py +++ b/bot/exts/moderation/infraction/infractions.py @@ -286,7 +286,7 @@ class Infractions(InfractionScheduler, commands.Cog): return # Let the current mute attempt override an automatically triggered mute. - log_text = await self.deactivate_infraction(active) + log_text = await self.deactivate_infraction(active, notify=False) if "Failure" in log_text: await ctx.send( f":x: can't override infraction **mute** for {user.mention}: " @@ -414,8 +414,15 @@ class Infractions(InfractionScheduler, commands.Cog): # endregion # region: Base pardon functions - async def pardon_mute(self, user_id: int, guild: discord.Guild, reason: t.Optional[str]) -> t.Dict[str, str]: - """Remove a user's muted role, DM them a notification, and return a log dict.""" + async def pardon_mute( + self, + user_id: int, + guild: discord.Guild, + reason: t.Optional[str], + *, + notify: bool = True + ) -> t.Dict[str, str]: + """Remove a user's muted role, optionally DM them a notification, and return a log dict.""" user = guild.get_member(user_id) log_text = {} @@ -424,16 +431,17 @@ class Infractions(InfractionScheduler, commands.Cog): self.mod_log.ignore(Event.member_update, user.id) await user.remove_roles(self._muted_role, reason=reason) - # DM the user about the expiration. - notified = await _utils.notify_pardon( - user=user, - title="You have been unmuted", - content="You may now send messages in the server.", - icon_url=_utils.INFRACTION_ICONS["mute"][1] - ) + if notify: + # DM the user about the expiration. + notified = await _utils.notify_pardon( + user=user, + title="You have been unmuted", + content="You may now send messages in the server.", + icon_url=_utils.INFRACTION_ICONS["mute"][1] + ) + log_text["DM"] = "Sent" if notified else "**Failed**" log_text["Member"] = format_user(user) - log_text["DM"] = "Sent" if notified else "**Failed**" else: log.info(f"Failed to unmute user {user_id}: user not found") log_text["Failure"] = "User was not found in the guild." @@ -455,31 +463,39 @@ class Infractions(InfractionScheduler, commands.Cog): return log_text - async def pardon_voice_ban(self, user_id: int, guild: discord.Guild) -> t.Dict[str, str]: - """Add Voice Verified role back to user, DM them a notification, and return a log dict.""" + async def pardon_voice_ban( + self, + user_id: int, + guild: discord.Guild, + *, + notify: bool = True + ) -> t.Dict[str, str]: + """Optionally DM the user a pardon notification and return a log dict.""" user = guild.get_member(user_id) log_text = {} if user: - # DM user about infraction expiration - notified = await _utils.notify_pardon( - user=user, - title="Voice ban ended", - content="You have been unbanned and can verify yourself again in the server.", - icon_url=_utils.INFRACTION_ICONS["voice_ban"][1] - ) + if notify: + # DM user about infraction expiration + notified = await _utils.notify_pardon( + user=user, + title="Voice ban ended", + content="You have been unbanned and can verify yourself again in the server.", + icon_url=_utils.INFRACTION_ICONS["voice_ban"][1] + ) + log_text["DM"] = "Sent" if notified else "**Failed**" log_text["Member"] = format_user(user) - log_text["DM"] = "Sent" if notified else "**Failed**" else: log_text["Info"] = "User was not found in the guild." return log_text - async def _pardon_action(self, infraction: _utils.Infraction) -> t.Optional[t.Dict[str, str]]: + async def _pardon_action(self, infraction: _utils.Infraction, notify: bool) -> t.Optional[t.Dict[str, str]]: """ Execute deactivation steps specific to the infraction's type and return a log dict. + If `notify` is True, notify the user of the pardon via DM where applicable. If an infraction type is unsupported, return None instead. """ guild = self.bot.get_guild(constants.Guild.id) @@ -487,11 +503,11 @@ class Infractions(InfractionScheduler, commands.Cog): reason = f"Infraction #{infraction['id']} expired or was pardoned." if infraction["type"] == "mute": - return await self.pardon_mute(user_id, guild, reason) + return await self.pardon_mute(user_id, guild, reason, notify=notify) elif infraction["type"] == "ban": return await self.pardon_ban(user_id, guild, reason) elif infraction["type"] == "voice_ban": - return await self.pardon_voice_ban(user_id, guild) + return await self.pardon_voice_ban(user_id, guild, notify=notify) # endregion -- cgit v1.2.3 From a0c013c95b664eef9df7dd10c987ed51865c378a Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Fri, 23 Jul 2021 16:42:52 -0700 Subject: Add missing notify param to _pardon_action for superstarify --- bot/exts/moderation/infraction/superstarify.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index 07e79b9fe..05a2bbe10 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -192,8 +192,8 @@ class Superstarify(InfractionScheduler, Cog): """Remove the superstarify infraction and allow the user to change their nickname.""" await self.pardon_infraction(ctx, "superstar", member) - async def _pardon_action(self, infraction: _utils.Infraction) -> t.Optional[t.Dict[str, str]]: - """Pardon a superstar infraction and return a log dict.""" + async def _pardon_action(self, infraction: _utils.Infraction, notify: bool) -> t.Optional[t.Dict[str, str]]: + """Pardon a superstar infraction, optionally notify the user via DM, and return a log dict.""" if infraction["type"] != "superstar": return @@ -208,18 +208,19 @@ class Superstarify(InfractionScheduler, Cog): ) return {} + log_text = {"Member": format_user(user)} + # DM the user about the expiration. - notified = await _utils.notify_pardon( - user=user, - title="You are no longer superstarified", - content="You may now change your nickname on the server.", - icon_url=_utils.INFRACTION_ICONS["superstar"][1] - ) + if notify: + notified = await _utils.notify_pardon( + user=user, + title="You are no longer superstarified", + content="You may now change your nickname on the server.", + icon_url=_utils.INFRACTION_ICONS["superstar"][1] + ) + log_text["DM"] = "Sent" if notified else "**Failed**" - return { - "Member": format_user(user), - "DM": "Sent" if notified else "**Failed**" - } + return log_text @staticmethod def get_nick(infraction_id: int, member_id: int) -> str: -- cgit v1.2.3 From 400f821d203222e4bc2de91bf70207b9401c017f Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Fri, 23 Jul 2021 16:45:30 -0700 Subject: Fix pardon_infraction call for permanent bans --- bot/exts/moderation/infraction/infractions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/infraction/infractions.py b/bot/exts/moderation/infraction/infractions.py index 9d5b049e1..ce19bcfdc 100644 --- a/bot/exts/moderation/infraction/infractions.py +++ b/bot/exts/moderation/infraction/infractions.py @@ -356,7 +356,7 @@ class Infractions(InfractionScheduler, commands.Cog): return log.trace("Old tempban is being replaced by new permaban.") - await self.pardon_infraction(ctx, "ban", user, is_temporary) + await self.pardon_infraction(ctx, "ban", user, send_msg=is_temporary) infraction = await _utils.post_infraction(ctx, user, "ban", reason, active=True, **kwargs) if infraction is None: -- cgit v1.2.3 From 693573412dcf726426271c66f832e626fa77a5d4 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Fri, 23 Jul 2021 19:26:03 -0700 Subject: Clarify a comment --- bot/exts/moderation/infraction/infractions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/infraction/infractions.py b/bot/exts/moderation/infraction/infractions.py index ce19bcfdc..0df5fb60b 100644 --- a/bot/exts/moderation/infraction/infractions.py +++ b/bot/exts/moderation/infraction/infractions.py @@ -285,7 +285,7 @@ class Infractions(InfractionScheduler, commands.Cog): await _utils.send_active_infraction_message(ctx, active) return - # Let the current mute attempt override an automatically triggered mute. + # Allow the current mute attempt to override an automatically triggered mute. log_text = await self.deactivate_infraction(active, notify=False) if "Failure" in log_text: await ctx.send( -- cgit v1.2.3 From 1a952752de4b2d9d472f385d3598eb357be0abcd Mon Sep 17 00:00:00 2001 From: Ryu18 Date: Sat, 7 Aug 2021 00:52:52 +0200 Subject: added escape markdown in PythonNews --- bot/exts/info/python_news.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/bot/exts/info/python_news.py b/bot/exts/info/python_news.py index a7837c93a..651a33d02 100644 --- a/bot/exts/info/python_news.py +++ b/bot/exts/info/python_news.py @@ -1,6 +1,7 @@ import logging import typing as t from datetime import date, datetime +import re import discord import feedparser @@ -72,6 +73,12 @@ class PythonNews(Cog): if mail["name"].split("@")[0] in constants.PythonNews.mail_lists: self.webhook_names[mail["name"].split("@")[0]] = mail["display_name"] + @staticmethod + def escape_markdown(content: str) -> str: + """Escape the markdown underlines""" + # taken from discord.utils.escape_markdown + return re.sub(r'[_\\~|]', lambda match: '\\' + match[0], content, 0, re.MULTILINE) + async def post_pep_news(self) -> None: """Fetch new PEPs and when they don't have announcement in #python-news, create it.""" # Wait until everything is ready and http_session available @@ -103,7 +110,7 @@ class PythonNews(Cog): # Build an embed and send a webhook embed = discord.Embed( title=new["title"], - description=new["summary"], + description=self.escape_markdown(new["summary"]), timestamp=new_datetime, url=new["link"], colour=constants.Colours.soft_green @@ -167,7 +174,7 @@ class PythonNews(Cog): ): continue - content = email_information["content"] + content = self.escape_markdown(email_information["content"]) link = THREAD_URL.format(id=thread["href"].split("/")[-2], list=maillist) # Build an embed and send a message to the webhook -- cgit v1.2.3 From 93456475548ff883bd534ab5e0de62a29d9dc936 Mon Sep 17 00:00:00 2001 From: Ryu18 Date: Sat, 7 Aug 2021 01:00:29 +0200 Subject: fix linting issues --- bot/exts/info/python_news.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/exts/info/python_news.py b/bot/exts/info/python_news.py index 651a33d02..0b6e230b4 100644 --- a/bot/exts/info/python_news.py +++ b/bot/exts/info/python_news.py @@ -1,7 +1,7 @@ import logging +import re import typing as t from datetime import date, datetime -import re import discord import feedparser @@ -75,7 +75,7 @@ class PythonNews(Cog): @staticmethod def escape_markdown(content: str) -> str: - """Escape the markdown underlines""" + """Escape the markdown underlines.""" # taken from discord.utils.escape_markdown return re.sub(r'[_\\~|]', lambda match: '\\' + match[0], content, 0, re.MULTILINE) -- cgit v1.2.3 From 357c7181d8b0b11ab38ac63c96bf6667b55b0524 Mon Sep 17 00:00:00 2001 From: Ryu1845 <77058942+Ryu1845@users.noreply.github.com> Date: Sun, 8 Aug 2021 00:47:49 +0200 Subject: Removed comment The code is now almost completely different from discord.py. --- bot/exts/info/python_news.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bot/exts/info/python_news.py b/bot/exts/info/python_news.py index 0b6e230b4..8d7ffec88 100644 --- a/bot/exts/info/python_news.py +++ b/bot/exts/info/python_news.py @@ -76,7 +76,6 @@ class PythonNews(Cog): @staticmethod def escape_markdown(content: str) -> str: """Escape the markdown underlines.""" - # taken from discord.utils.escape_markdown return re.sub(r'[_\\~|]', lambda match: '\\' + match[0], content, 0, re.MULTILINE) async def post_pep_news(self) -> None: -- cgit v1.2.3 From 939535d5c7c597741e5b1c5b6f7209aed152d4bc Mon Sep 17 00:00:00 2001 From: dawnofmidnight Date: Wed, 11 Aug 2021 17:34:15 -0400 Subject: fix: update urls in site cog --- bot/exts/info/site.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bot/exts/info/site.py b/bot/exts/info/site.py index fb5b99086..28eb558a6 100644 --- a/bot/exts/info/site.py +++ b/bot/exts/info/site.py @@ -9,7 +9,7 @@ from bot.pagination import LinePaginator log = logging.getLogger(__name__) -PAGES_URL = f"{URLs.site_schema}{URLs.site}/pages" +BASE_URL = f"{URLs.site_schema}{URLs.site}" class Site(Cog): @@ -43,7 +43,7 @@ class Site(Cog): @site_group.command(name="resources", root_aliases=("resources", "resource")) async def site_resources(self, ctx: Context) -> None: """Info about the site's Resources page.""" - learning_url = f"{PAGES_URL}/resources" + learning_url = f"{BASE_URL}/resources" embed = Embed(title="Resources") embed.set_footer(text=f"{learning_url}") @@ -59,7 +59,7 @@ class Site(Cog): @site_group.command(name="tools", root_aliases=("tools",)) async def site_tools(self, ctx: Context) -> None: """Info about the site's Tools page.""" - tools_url = f"{PAGES_URL}/resources/tools" + tools_url = f"{BASE_URL}/resources/tools" embed = Embed(title="Tools") embed.set_footer(text=f"{tools_url}") @@ -74,7 +74,7 @@ class Site(Cog): @site_group.command(name="help") async def site_help(self, ctx: Context) -> None: """Info about the site's Getting Help page.""" - url = f"{PAGES_URL}/resources/guides/asking-good-questions" + url = f"{BASE_URL}/pages/guides/pydis-guides/asking-good-questions/" embed = Embed(title="Asking Good Questions") embed.set_footer(text=url) @@ -90,7 +90,7 @@ class Site(Cog): @site_group.command(name="faq", root_aliases=("faq",)) async def site_faq(self, ctx: Context) -> None: """Info about the site's FAQ page.""" - url = f"{PAGES_URL}/frequently-asked-questions" + url = f"{BASE_URL}/pages/frequently-asked-questions" embed = Embed(title="FAQ") embed.set_footer(text=url) @@ -107,13 +107,13 @@ class Site(Cog): @site_group.command(name="rules", aliases=("r", "rule"), root_aliases=("rules", "rule")) async def site_rules(self, ctx: Context, rules: Greedy[int]) -> None: """Provides a link to all rules or, if specified, displays specific rule(s).""" - rules_embed = Embed(title='Rules', color=Colour.blurple(), url=f'{PAGES_URL}/rules') + rules_embed = Embed(title='Rules', color=Colour.blurple(), url=f'{BASE_URL}/pages/rules') if not rules: # Rules were not submitted. Return the default description. rules_embed.description = ( "The rules and guidelines that apply to this community can be found on" - f" our [rules page]({PAGES_URL}/rules). We expect" + f" our [rules page]({BASE_URL}/pages/rules). We expect" " all members of the community to have read and understood these." ) -- cgit v1.2.3 From 2ea95a090253b7383f0d8be195ffe809dbfe4d53 Mon Sep 17 00:00:00 2001 From: D0rs4n <41237606+D0rs4n@users.noreply.github.com> Date: Fri, 13 Aug 2021 23:34:38 +0200 Subject: Update reminders to reply instead of using a jump url to the origin message --- bot/exts/utils/reminders.py | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 441b0353f..32e00bee1 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -8,7 +8,7 @@ from operator import itemgetter import discord from dateutil.parser import isoparse -from discord.ext.commands import Cog, Context, Greedy, group +from discord.ext.commands import Cog, Context, Greedy, HTTPException, group from bot.bot import Bot from bot.constants import Guild, Icons, MODERATION_ROLES, POSITIVE_REPLIES, Roles, STAFF_ROLES @@ -172,32 +172,39 @@ class Reminders(Cog): if not is_valid: # No need to cancel the task too; it'll simply be done once this coroutine returns. return - embed = discord.Embed() - embed.colour = discord.Colour.blurple() - embed.set_author( - icon_url=Icons.remind_blurple, - name="It has arrived!" - ) - - # Let's not use a codeblock to keep emojis and mentions working. Embeds are safe anyway. - embed.description = f"Here's your reminder: {reminder['content']}" - - if reminder.get("jump_url"): # keep backward compatibility - embed.description += f"\n[Jump back to when you created the reminder]({reminder['jump_url']})" - if expected_time: embed.colour = discord.Colour.red() embed.set_author( icon_url=Icons.remind_red, name=f"Sorry it should have arrived {time_since(expected_time)} !" ) + else: + embed.colour = discord.Colour.blurple() + embed.set_author( + icon_url=Icons.remind_blurple, + name="It has arrived!" + ) + + # Let's not use a codeblock to keep emojis and mentions working. Embeds are safe anyway. + embed.description = f"Here's your reminder: {reminder['content']}" + # Here the jump URL is in the format of base_url/guild_id/channel_id/message_id additional_mentions = ' '.join( mentionable.mention for mentionable in self.get_mentionables(reminder["mentions"]) ) - await channel.send(content=f"{user.mention} {additional_mentions}", embed=embed) + jump_url = reminder.get("jump_url") + partial_message = channel.get_partial_message(int(jump_url.split("/")[-1])) + try: + await partial_message.reply(content=f"{additional_mentions}", embed=embed) + except HTTPException as e: + log.error( + f"There was an error when trying to reply to a reminder invocation message, {e}, " + "fall back to using jump_url" + ) + embed.description += f"\n[Jump back to when you created the reminder]({reminder['jump_url']})" + await channel.send(content=f"{user.mention} {additional_mentions}", embed=embed) log.debug(f"Deleting reminder #{reminder['id']} (the user has been reminded).") await self.bot.api_client.delete(f"bot/reminders/{reminder['id']}") -- cgit v1.2.3 From d6817258a59cdb2d9d3ab31a6e7f394cb0793b93 Mon Sep 17 00:00:00 2001 From: D0rs4n <41237606+D0rs4n@users.noreply.github.com> Date: Fri, 13 Aug 2021 23:48:53 +0200 Subject: Improve code consitency in remainders --- bot/exts/utils/reminders.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 32e00bee1..66a0e6e92 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -8,7 +8,7 @@ from operator import itemgetter import discord from dateutil.parser import isoparse -from discord.ext.commands import Cog, Context, Greedy, HTTPException, group +from discord.ext.commands import Cog, Context, Greedy, group from bot.bot import Bot from bot.constants import Guild, Icons, MODERATION_ROLES, POSITIVE_REPLIES, Roles, STAFF_ROLES @@ -198,7 +198,7 @@ class Reminders(Cog): partial_message = channel.get_partial_message(int(jump_url.split("/")[-1])) try: await partial_message.reply(content=f"{additional_mentions}", embed=embed) - except HTTPException as e: + except discord.HTTPException as e: log.error( f"There was an error when trying to reply to a reminder invocation message, {e}, " "fall back to using jump_url" -- cgit v1.2.3 From 054d5da2af3e75a8bb29f7088272f8222f2d0b33 Mon Sep 17 00:00:00 2001 From: Ryu18 Date: Mon, 16 Aug 2021 00:53:58 +0200 Subject: apply changes for review https://github.com/python-discord/bot/pull/1725\#pullrequestreview-730223166 --- bot/exts/info/python_news.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/info/python_news.py b/bot/exts/info/python_news.py index 8d7ffec88..045f9e6f9 100644 --- a/bot/exts/info/python_news.py +++ b/bot/exts/info/python_news.py @@ -76,7 +76,7 @@ class PythonNews(Cog): @staticmethod def escape_markdown(content: str) -> str: """Escape the markdown underlines.""" - return re.sub(r'[_\\~|]', lambda match: '\\' + match[0], content, 0, re.MULTILINE) + return re.sub(r"[_|]", lambda match: "\\" + match[0], content) async def post_pep_news(self) -> None: """Fetch new PEPs and when they don't have announcement in #python-news, create it.""" -- cgit v1.2.3 From c5d26a9358099660d18f9409e8d5a9c3fe9fd344 Mon Sep 17 00:00:00 2001 From: Ryu18 Date: Mon, 16 Aug 2021 00:56:31 +0200 Subject: change docstring in escape markdown to reflect actual behavior --- bot/exts/info/python_news.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/info/python_news.py b/bot/exts/info/python_news.py index 045f9e6f9..63eb4ac17 100644 --- a/bot/exts/info/python_news.py +++ b/bot/exts/info/python_news.py @@ -75,7 +75,7 @@ class PythonNews(Cog): @staticmethod def escape_markdown(content: str) -> str: - """Escape the markdown underlines.""" + """Escape the markdown underlines and spoilers.""" return re.sub(r"[_|]", lambda match: "\\" + match[0], content) async def post_pep_news(self) -> None: -- cgit v1.2.3 From d697860f22d13063a064ebc5982dea2dc127b0f0 Mon Sep 17 00:00:00 2001 From: D0rs4n <41237606+D0rs4n@users.noreply.github.com> Date: Tue, 17 Aug 2021 11:51:01 +0200 Subject: Provide jump_url in reminders even when the bot replies When the message is in the same channel and the bot replies, from now on it will also provide a jump_url for the sake of consistency --- bot/exts/utils/reminders.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 66a0e6e92..03c7d0323 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -195,6 +195,7 @@ class Reminders(Cog): ) jump_url = reminder.get("jump_url") + embed.description += f"\n[Jump back to when you created the reminder]({jump_url})" partial_message = channel.get_partial_message(int(jump_url.split("/")[-1])) try: await partial_message.reply(content=f"{additional_mentions}", embed=embed) @@ -203,7 +204,6 @@ class Reminders(Cog): f"There was an error when trying to reply to a reminder invocation message, {e}, " "fall back to using jump_url" ) - embed.description += f"\n[Jump back to when you created the reminder]({reminder['jump_url']})" await channel.send(content=f"{user.mention} {additional_mentions}", embed=embed) log.debug(f"Deleting reminder #{reminder['id']} (the user has been reminded).") -- cgit v1.2.3 From fe5c6a57222b5d5a6fab974dde41fc42c66f2173 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Thu, 19 Aug 2021 01:25:42 +0100 Subject: Fix linebreak formatting on server command The features part of the embed isn't included in some channels, this leads to there not being a linebreak between voice regions and roles in the embed. By changing it to this, rather than dedent, we specify exact where we want the linebreaks to be. --- bot/exts/info/information.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bot/exts/info/information.py b/bot/exts/info/information.py index 167731e64..54c03e139 100644 --- a/bot/exts/info/information.py +++ b/bot/exts/info/information.py @@ -190,13 +190,13 @@ class Information(Cog): f"{constants.Emojis.status_offline} {offline_presences}" ) - embed.description = textwrap.dedent(f""" - Created: {created} - Voice region: {region}\ - {features} - Roles: {num_roles} - Member status: {member_status} - """) + embed.description = ( + f"Created: {created}" + f"\nVoice region: {region}" + f"{features}" + f"\nRoles: {num_roles}" + f"\nMember status: {member_status}" + ) embed.set_thumbnail(url=ctx.guild.icon_url) # Members -- cgit v1.2.3 From 480d26d0959f78d4990a83e46ee0c481e74ea62b Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Thu, 19 Aug 2021 01:26:10 +0100 Subject: Add comma separators to member counts in !server --- bot/exts/info/information.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bot/exts/info/information.py b/bot/exts/info/information.py index 54c03e139..83ca59bea 100644 --- a/bot/exts/info/information.py +++ b/bot/exts/info/information.py @@ -186,8 +186,8 @@ class Information(Cog): online_presences = py_invite.approximate_presence_count offline_presences = py_invite.approximate_member_count - online_presences member_status = ( - f"{constants.Emojis.status_online} {online_presences} " - f"{constants.Emojis.status_offline} {offline_presences}" + f"{constants.Emojis.status_online} {online_presences:,} " + f"{constants.Emojis.status_offline} {offline_presences:,}" ) embed.description = ( @@ -200,7 +200,7 @@ class Information(Cog): embed.set_thumbnail(url=ctx.guild.icon_url) # Members - total_members = ctx.guild.member_count + total_members = f"{ctx.guild.member_count:,}" member_counts = self.get_member_counts(ctx.guild) member_info = "\n".join(f"{role}: {count}" for role, count in member_counts.items()) embed.add_field(name=f"Members: {total_members}", value=member_info) -- cgit v1.2.3 From 7e8d24f88eb199dc6ba790816d4abaa3269144e0 Mon Sep 17 00:00:00 2001 From: D0rs4n <41237606+D0rs4n@users.noreply.github.com> Date: Sun, 22 Aug 2021 11:06:38 +0200 Subject: Modify reminder response messages to be in the correct format --- bot/exts/utils/reminders.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 03c7d0323..f0aa280b6 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -18,7 +18,7 @@ from bot.utils.checks import has_any_role_check, has_no_roles_check from bot.utils.lock import lock_arg from bot.utils.messages import send_denial from bot.utils.scheduling import Scheduler -from bot.utils.time import TimestampFormats, discord_timestamp, time_since +from bot.utils.time import TimestampFormats, discord_timestamp log = logging.getLogger(__name__) @@ -177,7 +177,7 @@ class Reminders(Cog): embed.colour = discord.Colour.red() embed.set_author( icon_url=Icons.remind_red, - name=f"Sorry it should have arrived {time_since(expected_time)} !" + name="Sorry, your reminder should have arrived earlier!" ) else: embed.colour = discord.Colour.blurple() -- cgit v1.2.3 From 2a00be51f64a3980be3e174778b82329378abdc5 Mon Sep 17 00:00:00 2001 From: Xithrius Date: Sun, 22 Aug 2021 02:45:56 -0700 Subject: Error to info log level on missing reminder message. --- bot/exts/utils/reminders.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index f0aa280b6..cc13f6ebe 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -200,7 +200,7 @@ class Reminders(Cog): try: await partial_message.reply(content=f"{additional_mentions}", embed=embed) except discord.HTTPException as e: - log.error( + log.info( f"There was an error when trying to reply to a reminder invocation message, {e}, " "fall back to using jump_url" ) -- cgit v1.2.3 From 9ad34a8f22fc19dba28498174da7f14df14a78c2 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Sun, 22 Aug 2021 11:32:54 +0100 Subject: Move metabase error handling to a cog error handler --- bot/exts/moderation/metabase.py | 59 +++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/bot/exts/moderation/metabase.py b/bot/exts/moderation/metabase.py index e9faf7240..80dabbbb0 100644 --- a/bot/exts/moderation/metabase.py +++ b/bot/exts/moderation/metabase.py @@ -42,6 +42,25 @@ class Metabase(Cog): self.init_task = self.bot.loop.create_task(self.init_cog()) + async def cog_command_error(self, ctx: Context, error: Exception) -> None: + """Handle ClientResponseError errors locally to invalidate token if needed.""" + if not isinstance(error.original, ClientResponseError): + return + + if error.original.status == 403: + # User doesn't have access to the given question + log.warning(f"Failed to auth with Metabase for {error.original.url}.") + await ctx.send(f":x: {ctx.author.mention} Failed to auth with Metabase for that question.") + elif error.original.status == 404: + await ctx.send(f":x: {ctx.author.mention} That question could not be found.") + else: + # User credentials are invalid, or the refresh failed. + # Delete the expiry time, to force a refresh on next startup. + await self.session_info.delete("session_expiry") + log.exception("Session token is invalid or refresh failed.") + await ctx.send(f":x: {ctx.author.mention} Session token is invalid or refresh failed.") + error.handled = True + async def init_cog(self) -> None: """Initialise the metabase session.""" expiry_time = await self.session_info.get("session_expiry") @@ -112,32 +131,20 @@ class Metabase(Cog): await self.init_task url = f"{MetabaseConfig.url}/card/{question_id}/query/{extension}" - try: - async with self.bot.http_session.post(url, headers=self.headers, raise_for_status=True) as resp: - if extension == "csv": - out = await resp.text(encoding="utf-8") - # Save the output for use with int e - self.exports[question_id] = list(csv.DictReader(StringIO(out))) - - elif extension == "json": - out = await resp.json(encoding="utf-8") - # Save the output for use with int e - self.exports[question_id] = out - - # Format it nicely for human eyes - out = json.dumps(out, indent=4, sort_keys=True) - except ClientResponseError as e: - if e.status == 403: - # User doesn't have access to the given question - log.warning(f"Failed to auth with Metabase for question {question_id}.") - await ctx.send(f":x: {ctx.author.mention} Failed to auth with Metabase for that question.") - else: - # User credentials are invalid, or the refresh failed. - # Delete the expiry time, to force a refresh on next startup. - await self.session_info.delete("session_expiry") - log.exception("Session token is invalid or refresh failed.") - await ctx.send(f":x: {ctx.author.mention} Session token is invalid or refresh failed.") - return + + async with self.bot.http_session.post(url, headers=self.headers, raise_for_status=True) as resp: + if extension == "csv": + out = await resp.text(encoding="utf-8") + # Save the output for use with int e + self.exports[question_id] = list(csv.DictReader(StringIO(out))) + + elif extension == "json": + out = await resp.json(encoding="utf-8") + # Save the output for use with int e + self.exports[question_id] = out + + # Format it nicely for human eyes + out = json.dumps(out, indent=4, sort_keys=True) paste_link = await send_to_paste_service(out, extension=extension) if paste_link: -- cgit v1.2.3 From 30bcc28b2caf90ac6ed0e2f5df530c617ba03b4a Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Sun, 22 Aug 2021 11:38:14 +0100 Subject: Change metabase config to base url, rather than api url --- bot/constants.py | 2 +- bot/exts/moderation/metabase.py | 4 ++-- config-default.yml | 10 ++++------ 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/bot/constants.py b/bot/constants.py index 500803f33..12b5c02e5 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -568,7 +568,7 @@ class Metabase(metaclass=YAMLGetter): username: Optional[str] password: Optional[str] - url: str + base_url: str max_session_age: int diff --git a/bot/exts/moderation/metabase.py b/bot/exts/moderation/metabase.py index 80dabbbb0..bfa94b7c1 100644 --- a/bot/exts/moderation/metabase.py +++ b/bot/exts/moderation/metabase.py @@ -84,7 +84,7 @@ class Metabase(Cog): "username": MetabaseConfig.username, "password": MetabaseConfig.password } - async with self.bot.http_session.post(f"{MetabaseConfig.url}/session", json=data) as resp: + async with self.bot.http_session.post(f"{MetabaseConfig.base_url}/api/session", json=data) as resp: json_data = await resp.json() self.session_token = json_data.get("id") @@ -130,7 +130,7 @@ class Metabase(Cog): # Make sure we have a session token before running anything await self.init_task - url = f"{MetabaseConfig.url}/card/{question_id}/query/{extension}" + url = f"{MetabaseConfig.base_url}/api/card/{question_id}/query/{extension}" async with self.bot.http_session.post(url, headers=self.headers, raise_for_status=True) as resp: if extension == "csv": diff --git a/config-default.yml b/config-default.yml index 881a7df76..79828dd77 100644 --- a/config-default.yml +++ b/config-default.yml @@ -432,14 +432,12 @@ anti_spam: max: 3 - metabase: - username: !ENV "METABASE_USERNAME" - password: !ENV "METABASE_PASSWORD" - url: "http://metabase.default.svc.cluster.local/api" + username: !ENV "METABASE_USERNAME" + password: !ENV "METABASE_PASSWORD" + base_url: "http://metabase.default.svc.cluster.local" # 14 days, see https://www.metabase.com/docs/latest/operations-guide/environment-variables.html#max_session_age - max_session_age: 20160 - + max_session_age: 20160 big_brother: -- cgit v1.2.3 From ce80b92199f7b81d49845a212dabf2e02b3d4684 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Sun, 22 Aug 2021 11:38:28 +0100 Subject: Add alias for metabase export --- bot/exts/moderation/metabase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/metabase.py b/bot/exts/moderation/metabase.py index bfa94b7c1..fa5f60ca6 100644 --- a/bot/exts/moderation/metabase.py +++ b/bot/exts/moderation/metabase.py @@ -105,7 +105,7 @@ class Metabase(Cog): """A group of commands for interacting with metabase.""" await ctx.send_help(ctx.command) - @metabase_group.command(name="extract") + @metabase_group.command(name="extract", aliases=("export",)) async def metabase_extract( self, ctx: Context, -- cgit v1.2.3 From eb5cedea9a432b6b98d5ef831a016f9e33d72cbb Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Sun, 22 Aug 2021 11:42:04 +0100 Subject: Trigger typing, to avoid wrapping whole func in a context manager --- bot/exts/moderation/metabase.py | 60 ++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/bot/exts/moderation/metabase.py b/bot/exts/moderation/metabase.py index fa5f60ca6..d97b355a5 100644 --- a/bot/exts/moderation/metabase.py +++ b/bot/exts/moderation/metabase.py @@ -125,36 +125,36 @@ class Metabase(Cog): Valid extensions are: csv and json. """ - async with ctx.typing(): - - # Make sure we have a session token before running anything - await self.init_task - - url = f"{MetabaseConfig.base_url}/api/card/{question_id}/query/{extension}" - - async with self.bot.http_session.post(url, headers=self.headers, raise_for_status=True) as resp: - if extension == "csv": - out = await resp.text(encoding="utf-8") - # Save the output for use with int e - self.exports[question_id] = list(csv.DictReader(StringIO(out))) - - elif extension == "json": - out = await resp.json(encoding="utf-8") - # Save the output for use with int e - self.exports[question_id] = out - - # Format it nicely for human eyes - out = json.dumps(out, indent=4, sort_keys=True) - - paste_link = await send_to_paste_service(out, extension=extension) - if paste_link: - message = f":+1: {ctx.author.mention} Here's your link: {paste_link}" - else: - message = f":x: {ctx.author.mention} Link service is unavailible." - await ctx.send( - f"{message}\nYou can also access this data within internal eval by doing: " - f"`bot.get_cog('Metabase').exports[{question_id}]`" - ) + await ctx.trigger_typing() + + # Make sure we have a session token before running anything + await self.init_task + + url = f"{MetabaseConfig.base_url}/api/card/{question_id}/query/{extension}" + + async with self.bot.http_session.post(url, headers=self.headers, raise_for_status=True) as resp: + if extension == "csv": + out = await resp.text(encoding="utf-8") + # Save the output for use with int e + self.exports[question_id] = list(csv.DictReader(StringIO(out))) + + elif extension == "json": + out = await resp.json(encoding="utf-8") + # Save the output for use with int e + self.exports[question_id] = out + + # Format it nicely for human eyes + out = json.dumps(out, indent=4, sort_keys=True) + + paste_link = await send_to_paste_service(out, extension=extension) + if paste_link: + message = f":+1: {ctx.author.mention} Here's your link: {paste_link}" + else: + message = f":x: {ctx.author.mention} Link service is unavailible." + await ctx.send( + f"{message}\nYou can also access this data within internal eval by doing: " + f"`bot.get_cog('Metabase').exports[{question_id}]`" + ) # This cannot be static (must have a __func__ attribute). async def cog_check(self, ctx: Context) -> bool: -- cgit v1.2.3 From 4a0d6196a1f56433191a2a71b349209f6291fc22 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Sun, 22 Aug 2021 11:38:46 +0100 Subject: Add ability to publish metabase questions --- bot/exts/moderation/metabase.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/bot/exts/moderation/metabase.py b/bot/exts/moderation/metabase.py index d97b355a5..3b454ab18 100644 --- a/bot/exts/moderation/metabase.py +++ b/bot/exts/moderation/metabase.py @@ -156,6 +156,20 @@ class Metabase(Cog): f"`bot.get_cog('Metabase').exports[{question_id}]`" ) + @metabase_group.command(name="publish", aliases=("share",)) + async def metabase_publish(self, ctx: Context, question_id: int) -> None: + """Publically shares the given question and posts the link.""" + await ctx.trigger_typing() + # Make sure we have a session token before running anything + await self.init_task + + url = f"{MetabaseConfig.base_url}/api/card/{question_id}/public_link" + + async with self.bot.http_session.post(url, headers=self.headers, raise_for_status=True) as resp: + response_json = await resp.json(encoding="utf-8") + sharing_url = f"{MetabaseConfig.base_url}/public/question/{response_json['uuid']}" + await ctx.send(f":+1: {ctx.author.mention} Here's your sharing link: {sharing_url}") + # This cannot be static (must have a __func__ attribute). async def cog_check(self, ctx: Context) -> bool: """Only allow admins inside moderator channels to invoke the commands in this cog.""" -- cgit v1.2.3 From 9c199dbbb8683584ef01955197232314eb0cf372 Mon Sep 17 00:00:00 2001 From: TizzySaurus <47674925+TizzySaurus@users.noreply.github.com> Date: Sun, 22 Aug 2021 18:55:45 +0100 Subject: Fix edge-case of `user.joined_at` being `None` in userinfo command. --- bot/exts/info/information.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bot/exts/info/information.py b/bot/exts/info/information.py index 83ca59bea..85d3c0e73 100644 --- a/bot/exts/info/information.py +++ b/bot/exts/info/information.py @@ -257,7 +257,11 @@ class Information(Cog): badges.append(emoji) if on_server: - joined = discord_timestamp(user.joined_at, TimestampFormats.RELATIVE) + if user.joined_at: + joined = discord_timestamp(user.joined_at, TimestampFormats.RELATIVE) + else: + joined = "Unable to get join date" + # The 0 is for excluding the default @everyone role, # and the -1 is for reversing the order of the roles to highest to lowest in hierarchy. roles = ", ".join(role.mention for role in user.roles[:0:-1]) -- cgit v1.2.3 From 73bc01123efb8dbc1e3efb64545b028d21c044d2 Mon Sep 17 00:00:00 2001 From: TizzySaurus <47674925+TizzySaurus@users.noreply.github.com> Date: Wed, 11 Aug 2021 16:41:45 +0100 Subject: Remove converters made redundant by dpy V1.6's UserConverter update --- bot/converters.py | 71 +------------------------------- bot/exts/moderation/infraction/_utils.py | 2 - 2 files changed, 1 insertion(+), 72 deletions(-) diff --git a/bot/converters.py b/bot/converters.py index 595809517..3df613379 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -439,29 +439,6 @@ class HushDurationConverter(Converter): return duration -def proxy_user(user_id: str) -> discord.Object: - """ - Create a proxy user object from the given id. - - Used when a Member or User object cannot be resolved. - """ - log.trace(f"Attempting to create a proxy user for the user id {user_id}.") - - try: - user_id = int(user_id) - except ValueError: - log.debug(f"Failed to create proxy user {user_id}: could not convert to int.") - raise BadArgument(f"User ID `{user_id}` is invalid - could not convert to an integer.") - - user = discord.Object(user_id) - user.mention = user.id - user.display_name = f"<@{user.id}>" - user.avatar_url_as = lambda static_format: None - user.bot = False - - return user - - class UserMentionOrID(UserConverter): """ Converts to a `discord.User`, but only if a mention or userID is provided. @@ -480,51 +457,6 @@ class UserMentionOrID(UserConverter): raise BadArgument(f"`{argument}` is not a User mention or a User ID.") -class FetchedUser(UserConverter): - """ - Converts to a `discord.User` or, if it fails, a `discord.Object`. - - Unlike the default `UserConverter`, which only does lookups via the global user cache, this - converter attempts to fetch the user via an API call to Discord when the using the cache is - unsuccessful. - - If the fetch also fails and the error doesn't imply the user doesn't exist, then a - `discord.Object` is returned via the `user_proxy` converter. - - The lookup strategy is as follows (in order): - - 1. Lookup by ID. - 2. Lookup by mention. - 3. Lookup by name#discrim - 4. Lookup by name - 5. Lookup via API - 6. Create a proxy user with discord.Object - """ - - async def convert(self, ctx: Context, arg: str) -> t.Union[discord.User, discord.Object]: - """Convert the `arg` to a `discord.User` or `discord.Object`.""" - try: - return await super().convert(ctx, arg) - except BadArgument: - pass - - try: - user_id = int(arg) - log.trace(f"Fetching user {user_id}...") - return await ctx.bot.fetch_user(user_id) - except ValueError: - log.debug(f"Failed to fetch user {arg}: could not convert to int.") - raise BadArgument(f"The provided argument can't be turned into integer: `{arg}`") - except discord.HTTPException as e: - # If the Discord error isn't `Unknown user`, return a proxy instead - if e.code != 10013: - log.info(f"Failed to fetch user, returning a proxy instead: status {e.status}") - return proxy_user(arg) - - log.debug(f"Failed to fetch user {arg}: user does not exist.") - raise BadArgument(f"User `{arg}` does not exist") - - def _snowflake_from_regex(pattern: t.Pattern, arg: str) -> int: """ Extract the snowflake from `arg` using a regex `pattern` and return it as an int. @@ -568,5 +500,4 @@ class Infraction(Converter): Expiry = t.Union[Duration, ISODateTime] -FetchedMember = t.Union[discord.Member, FetchedUser] -UserMention = partial(_snowflake_from_regex, RE_USER_MENTION) +FetchedMember = t.Union[discord.Member, discord.User] diff --git a/bot/exts/moderation/infraction/_utils.py b/bot/exts/moderation/infraction/_utils.py index a4059a6e9..e3fcda730 100644 --- a/bot/exts/moderation/infraction/_utils.py +++ b/bot/exts/moderation/infraction/_utils.py @@ -24,8 +24,6 @@ INFRACTION_ICONS = { RULES_URL = "https://pythondiscord.com/pages/rules" # Type aliases -UserObject = t.Union[discord.Member, discord.User] -UserSnowflake = t.Union[UserObject, discord.Object] Infraction = t.Dict[str, t.Union[str, int, bool]] APPEAL_EMAIL = "appeals@pythondiscord.com" -- cgit v1.2.3 From 17f3750cbe53eeb740795d77c192b5822d086a4b Mon Sep 17 00:00:00 2001 From: TizzySaurus <47674925+TizzySaurus@users.noreply.github.com> Date: Wed, 11 Aug 2021 16:51:28 +0100 Subject: Update to new converters --- bot/exts/fun/duck_pond.py | 3 ++- bot/exts/moderation/infraction/_scheduler.py | 6 +++--- bot/exts/moderation/infraction/_utils.py | 18 ++++++++---------- bot/exts/moderation/infraction/infractions.py | 5 ++--- bot/exts/moderation/infraction/management.py | 16 +++++----------- bot/exts/recruitment/talentpool/_cog.py | 2 +- 6 files changed, 21 insertions(+), 29 deletions(-) diff --git a/bot/exts/fun/duck_pond.py b/bot/exts/fun/duck_pond.py index c78b9c141..a8d927353 100644 --- a/bot/exts/fun/duck_pond.py +++ b/bot/exts/fun/duck_pond.py @@ -8,6 +8,7 @@ from discord.ext.commands import Cog, Context, command from bot import constants from bot.bot import Bot +from bot.converters import FetchedMember from bot.utils.checks import has_any_role from bot.utils.messages import count_unique_users_reaction, send_attachments from bot.utils.webhooks import send_webhook @@ -36,7 +37,7 @@ class DuckPond(Cog): log.exception(f"Failed to fetch webhook with id `{self.webhook_id}`") @staticmethod - def is_staff(member: Union[User, Member]) -> bool: + def is_staff(member: FetchedMember) -> bool: """Check if a specific member or user is staff.""" if hasattr(member, "roles"): for role in member.roles: diff --git a/bot/exts/moderation/infraction/_scheduler.py b/bot/exts/moderation/infraction/_scheduler.py index 8286d3635..89869820c 100644 --- a/bot/exts/moderation/infraction/_scheduler.py +++ b/bot/exts/moderation/infraction/_scheduler.py @@ -13,8 +13,8 @@ from bot import constants from bot.api import ResponseCodeError from bot.bot import Bot from bot.constants import Colours +from bot.converters import FetchedMember from bot.exts.moderation.infraction import _utils -from bot.exts.moderation.infraction._utils import UserSnowflake from bot.exts.moderation.modlog import ModLog from bot.utils import messages, scheduling, time from bot.utils.channel import is_mod_channel @@ -115,7 +115,7 @@ class InfractionScheduler: self, ctx: Context, infraction: _utils.Infraction, - user: UserSnowflake, + user: FetchedMember, action_coro: t.Optional[t.Awaitable] = None, user_reason: t.Optional[str] = None, additional_info: str = "", @@ -264,7 +264,7 @@ class InfractionScheduler: self, ctx: Context, infr_type: str, - user: UserSnowflake, + user: FetchedMember, send_msg: bool = True ) -> None: """ diff --git a/bot/exts/moderation/infraction/_utils.py b/bot/exts/moderation/infraction/_utils.py index e3fcda730..9c3f9d804 100644 --- a/bot/exts/moderation/infraction/_utils.py +++ b/bot/exts/moderation/infraction/_utils.py @@ -7,6 +7,7 @@ from discord.ext.commands import Context from bot.api import ResponseCodeError from bot.constants import Colours, Icons +from bot.converters import FetchedMember from bot.errors import InvalidInfractedUserError log = logging.getLogger(__name__) @@ -43,7 +44,7 @@ INFRACTION_DESCRIPTION_TEMPLATE = ( ) -async def post_user(ctx: Context, user: UserSnowflake) -> t.Optional[dict]: +async def post_user(ctx: Context, user: FetchedMember) -> t.Optional[dict]: """ Create a new user in the database. @@ -51,9 +52,6 @@ async def post_user(ctx: Context, user: UserSnowflake) -> t.Optional[dict]: """ log.trace(f"Attempting to add user {user.id} to the database.") - if not isinstance(user, (discord.Member, discord.User)): - log.debug("The user being added to the DB is not a Member or User object.") - payload = { 'discriminator': int(getattr(user, 'discriminator', 0)), 'id': user.id, @@ -73,7 +71,7 @@ async def post_user(ctx: Context, user: UserSnowflake) -> t.Optional[dict]: async def post_infraction( ctx: Context, - user: UserSnowflake, + user: FetchedMember, infr_type: str, reason: str, expires_at: datetime = None, @@ -81,7 +79,7 @@ async def post_infraction( active: bool = True ) -> t.Optional[dict]: """Posts an infraction to the API.""" - if isinstance(user, (discord.Member, discord.User)) and user.bot: + if isinstance(user, FetchedMember) and user.bot: log.trace(f"Posting of {infr_type} infraction for {user} to the API aborted. User is a bot.") raise InvalidInfractedUserError(user) @@ -116,7 +114,7 @@ async def post_infraction( async def get_active_infraction( ctx: Context, - user: UserSnowflake, + user: FetchedMember, infr_type: str, send_msg: bool = True ) -> t.Optional[dict]: @@ -151,7 +149,7 @@ async def get_active_infraction( async def notify_infraction( - user: UserObject, + user: FetchedMember, infr_type: str, expires_at: t.Optional[str] = None, reason: t.Optional[str] = None, @@ -187,7 +185,7 @@ async def notify_infraction( async def notify_pardon( - user: UserObject, + user: FetchedMember, title: str, content: str, icon_url: str = Icons.user_verified @@ -205,7 +203,7 @@ async def notify_pardon( return await send_private_embed(user, embed) -async def send_private_embed(user: UserObject, embed: discord.Embed) -> bool: +async def send_private_embed(user: FetchedMember, embed: discord.Embed) -> bool: """ A helper method for sending an embed to a user's DMs. diff --git a/bot/exts/moderation/infraction/infractions.py b/bot/exts/moderation/infraction/infractions.py index f19323c7c..dfffb2b20 100644 --- a/bot/exts/moderation/infraction/infractions.py +++ b/bot/exts/moderation/infraction/infractions.py @@ -14,7 +14,6 @@ from bot.converters import Duration, Expiry, FetchedMember from bot.decorators import respect_role_hierarchy from bot.exts.moderation.infraction import _utils from bot.exts.moderation.infraction._scheduler import InfractionScheduler -from bot.exts.moderation.infraction._utils import UserSnowflake from bot.utils.messages import format_user log = logging.getLogger(__name__) @@ -320,7 +319,7 @@ class Infractions(InfractionScheduler, commands.Cog): async def apply_ban( self, ctx: Context, - user: UserSnowflake, + user: FetchedMember, reason: t.Optional[str], purge_days: t.Optional[int] = 0, **kwargs @@ -376,7 +375,7 @@ class Infractions(InfractionScheduler, commands.Cog): await bb_cog.apply_unwatch(ctx, user, bb_reason, send_message=False) @respect_role_hierarchy(member_arg=2) - async def apply_voice_ban(self, ctx: Context, user: UserSnowflake, reason: t.Optional[str], **kwargs) -> None: + async def apply_voice_ban(self, ctx: Context, user: FetchedMember, reason: t.Optional[str], **kwargs) -> None: """Apply a voice ban infraction with kwargs passed to `post_infraction`.""" if await _utils.get_active_infraction(ctx, user, "voice_ban"): return diff --git a/bot/exts/moderation/infraction/management.py b/bot/exts/moderation/infraction/management.py index 3094159cd..d1437c635 100644 --- a/bot/exts/moderation/infraction/management.py +++ b/bot/exts/moderation/infraction/management.py @@ -12,7 +12,7 @@ from discord.utils import escape_markdown from bot import constants from bot.bot import Bot -from bot.converters import Expiry, Infraction, Snowflake, UserMention, allowed_strings, proxy_user +from bot.converters import Expiry, FetchedMember, Infraction, allowed_strings from bot.exts.moderation.infraction.infractions import Infractions from bot.exts.moderation.modlog import ModLog from bot.pagination import LinePaginator @@ -201,27 +201,21 @@ class ModManagement(commands.Cog): # region: Search infractions @infraction_group.group(name="search", aliases=('s',), invoke_without_command=True) - async def infraction_search_group(self, ctx: Context, query: t.Union[UserMention, Snowflake, str]) -> None: + async def infraction_search_group(self, ctx: Context, query: t.Union[FetchedMember, str]) -> None: """Searches for infractions in the database.""" - if isinstance(query, int): - await self.search_user(ctx, discord.Object(query)) + if isinstance(query, FetchedMember): + await self.search_user(ctx, query) else: await self.search_reason(ctx, query) @infraction_search_group.command(name="user", aliases=("member", "id")) - async def search_user(self, ctx: Context, user: t.Union[discord.User, proxy_user]) -> None: + async def search_user(self, ctx: Context, user: FetchedMember) -> None: """Search for infractions by member.""" infraction_list = await self.bot.api_client.get( 'bot/infractions/expanded', params={'user__id': str(user.id)} ) - user = self.bot.get_user(user.id) - if not user and infraction_list: - # Use the user data retrieved from the DB for the username. - user = infraction_list[0]["user"] - user = escape_markdown(user["name"]) + f"#{user['discriminator']:04}" - embed = discord.Embed( title=f"Infractions for {user} ({len(infraction_list)} total)", colour=discord.Colour.orange() diff --git a/bot/exts/recruitment/talentpool/_cog.py b/bot/exts/recruitment/talentpool/_cog.py index 80bd48534..9b09c4f7b 100644 --- a/bot/exts/recruitment/talentpool/_cog.py +++ b/bot/exts/recruitment/talentpool/_cog.py @@ -417,7 +417,7 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"): await ctx.message.add_reaction(Emojis.check_mark) @Cog.listener() - async def on_member_ban(self, guild: Guild, user: Union[User, Member]) -> None: + async def on_member_ban(self, guild: Guild, user: Union[FetchedMember]) -> None: """Remove `user` from the talent pool after they are banned.""" await self.unwatch(user.id, "User was banned.") -- cgit v1.2.3 From bb2c20fd5acf998d440c835345132bdaee34fb64 Mon Sep 17 00:00:00 2001 From: TizzySaurus <47674925+TizzySaurus@users.noreply.github.com> Date: Wed, 11 Aug 2021 17:28:13 +0100 Subject: Remove unused imports --- bot/converters.py | 1 - bot/errors.py | 2 +- bot/exts/fun/duck_pond.py | 2 +- bot/exts/recruitment/talentpool/_cog.py | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/bot/converters.py b/bot/converters.py index 3df613379..1cfbd29b8 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -2,7 +2,6 @@ import logging import re import typing as t from datetime import datetime -from functools import partial from ssl import CertificateError import dateutil.parser diff --git a/bot/errors.py b/bot/errors.py index 5785faa44..d472334d4 100644 --- a/bot/errors.py +++ b/bot/errors.py @@ -1,4 +1,4 @@ -from typing import Hashable, Union +from typing import Hashable from discord import Member, User diff --git a/bot/exts/fun/duck_pond.py b/bot/exts/fun/duck_pond.py index a8d927353..7c09e27f5 100644 --- a/bot/exts/fun/duck_pond.py +++ b/bot/exts/fun/duck_pond.py @@ -3,7 +3,7 @@ import logging from typing import Union import discord -from discord import Color, Embed, Member, Message, RawReactionActionEvent, TextChannel, User, errors +from discord import Color, Embed, Message, RawReactionActionEvent, TextChannel, errors from discord.ext.commands import Cog, Context, command from bot import constants diff --git a/bot/exts/recruitment/talentpool/_cog.py b/bot/exts/recruitment/talentpool/_cog.py index 9b09c4f7b..304bdafba 100644 --- a/bot/exts/recruitment/talentpool/_cog.py +++ b/bot/exts/recruitment/talentpool/_cog.py @@ -6,7 +6,7 @@ from typing import Union import discord from async_rediscache import RedisCache -from discord import Color, Embed, Member, PartialMessage, RawReactionActionEvent, User +from discord import Color, Embed, Member, PartialMessage, RawReactionActionEvent from discord.ext.commands import Cog, Context, group, has_any_role from bot.api import ResponseCodeError -- cgit v1.2.3 From ad873f91aadb7698c3fd73e1e263ad7bc0ca21e9 Mon Sep 17 00:00:00 2001 From: Izan Date: Wed, 11 Aug 2021 18:53:05 +0100 Subject: Rename converter from FetchedMember to MemberOrUser --- bot/converters.py | 2 +- bot/errors.py | 5 ++-- bot/exts/fun/duck_pond.py | 4 +-- bot/exts/info/information.py | 14 +++++----- bot/exts/moderation/infraction/_scheduler.py | 6 ++--- bot/exts/moderation/infraction/_utils.py | 16 ++++++------ bot/exts/moderation/infraction/infractions.py | 34 ++++++++++++------------- bot/exts/moderation/infraction/management.py | 8 +++--- bot/exts/moderation/watchchannels/bigbrother.py | 10 ++++---- bot/exts/recruitment/talentpool/_cog.py | 16 ++++++------ 10 files changed, 58 insertions(+), 57 deletions(-) diff --git a/bot/converters.py b/bot/converters.py index 1cfbd29b8..ff9f6e5d9 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -499,4 +499,4 @@ class Infraction(Converter): Expiry = t.Union[Duration, ISODateTime] -FetchedMember = t.Union[discord.Member, discord.User] +MemberOrUser = t.Union[discord.Member, discord.User] diff --git a/bot/errors.py b/bot/errors.py index d472334d4..5186aa3bb 100644 --- a/bot/errors.py +++ b/bot/errors.py @@ -1,6 +1,6 @@ from typing import Hashable -from discord import Member, User +from converters import MemberOrUser class LockedResourceError(RuntimeError): @@ -30,7 +30,8 @@ class InvalidInfractedUserError(Exception): `user` -- User or Member which is invalid """ - def __init__(self, user: Union[Member, User], reason: str = "User infracted is a bot."): + def __init__(self, user: MemberOrUser, reason: str = "User infracted is a bot."): + self.user = user self.reason = reason diff --git a/bot/exts/fun/duck_pond.py b/bot/exts/fun/duck_pond.py index 7c09e27f5..0ac985139 100644 --- a/bot/exts/fun/duck_pond.py +++ b/bot/exts/fun/duck_pond.py @@ -8,7 +8,7 @@ from discord.ext.commands import Cog, Context, command from bot import constants from bot.bot import Bot -from bot.converters import FetchedMember +from bot.converters import MemberOrUser from bot.utils.checks import has_any_role from bot.utils.messages import count_unique_users_reaction, send_attachments from bot.utils.webhooks import send_webhook @@ -37,7 +37,7 @@ class DuckPond(Cog): log.exception(f"Failed to fetch webhook with id `{self.webhook_id}`") @staticmethod - def is_staff(member: FetchedMember) -> bool: + def is_staff(member: MemberOrUser) -> bool: """Check if a specific member or user is staff.""" if hasattr(member, "roles"): for role in member.roles: diff --git a/bot/exts/info/information.py b/bot/exts/info/information.py index 167731e64..ff3fb9408 100644 --- a/bot/exts/info/information.py +++ b/bot/exts/info/information.py @@ -12,7 +12,7 @@ from discord.ext.commands import BucketType, Cog, Context, Paginator, command, g from bot import constants from bot.api import ResponseCodeError from bot.bot import Bot -from bot.converters import FetchedMember +from bot.converters import MemberOrUser from bot.decorators import in_whitelist from bot.errors import NonExistentRoleError from bot.pagination import LinePaginator @@ -220,7 +220,7 @@ class Information(Cog): await ctx.send(embed=embed) @command(name="user", aliases=["user_info", "member", "member_info", "u"]) - async def user_info(self, ctx: Context, user: FetchedMember = None) -> None: + async def user_info(self, ctx: Context, user: MemberOrUser = None) -> None: """Returns info about a user.""" if user is None: user = ctx.author @@ -235,7 +235,7 @@ class Information(Cog): embed = await self.create_user_embed(ctx, user) await ctx.send(embed=embed) - async def create_user_embed(self, ctx: Context, user: FetchedMember) -> Embed: + async def create_user_embed(self, ctx: Context, user: MemberOrUser) -> Embed: """Creates an embed containing information on the `user`.""" on_server = bool(ctx.guild.get_member(user.id)) @@ -307,7 +307,7 @@ class Information(Cog): return embed - async def basic_user_infraction_counts(self, user: FetchedMember) -> Tuple[str, str]: + async def basic_user_infraction_counts(self, user: MemberOrUser) -> Tuple[str, str]: """Gets the total and active infraction counts for the given `member`.""" infractions = await self.bot.api_client.get( 'bot/infractions', @@ -324,7 +324,7 @@ class Information(Cog): return "Infractions", infraction_output - async def expanded_user_infraction_counts(self, user: FetchedMember) -> Tuple[str, str]: + async def expanded_user_infraction_counts(self, user: MemberOrUser) -> Tuple[str, str]: """ Gets expanded infraction counts for the given `member`. @@ -365,7 +365,7 @@ class Information(Cog): return "Infractions", "\n".join(infraction_output) - async def user_nomination_counts(self, user: FetchedMember) -> Tuple[str, str]: + async def user_nomination_counts(self, user: MemberOrUser) -> Tuple[str, str]: """Gets the active and historical nomination counts for the given `member`.""" nominations = await self.bot.api_client.get( 'bot/nominations', @@ -390,7 +390,7 @@ class Information(Cog): return "Nominations", "\n".join(output) - async def user_messages(self, user: FetchedMember) -> Tuple[Union[bool, str], Tuple[str, str]]: + async def user_messages(self, user: MemberOrUser) -> Tuple[Union[bool, str], Tuple[str, str]]: """ Gets the amount of messages for `member`. diff --git a/bot/exts/moderation/infraction/_scheduler.py b/bot/exts/moderation/infraction/_scheduler.py index 89869820c..13f59bb76 100644 --- a/bot/exts/moderation/infraction/_scheduler.py +++ b/bot/exts/moderation/infraction/_scheduler.py @@ -13,7 +13,7 @@ from bot import constants from bot.api import ResponseCodeError from bot.bot import Bot from bot.constants import Colours -from bot.converters import FetchedMember +from bot.converters import MemberOrUser from bot.exts.moderation.infraction import _utils from bot.exts.moderation.modlog import ModLog from bot.utils import messages, scheduling, time @@ -115,7 +115,7 @@ class InfractionScheduler: self, ctx: Context, infraction: _utils.Infraction, - user: FetchedMember, + user: MemberOrUser, action_coro: t.Optional[t.Awaitable] = None, user_reason: t.Optional[str] = None, additional_info: str = "", @@ -264,7 +264,7 @@ class InfractionScheduler: self, ctx: Context, infr_type: str, - user: FetchedMember, + user: MemberOrUser, send_msg: bool = True ) -> None: """ diff --git a/bot/exts/moderation/infraction/_utils.py b/bot/exts/moderation/infraction/_utils.py index 9c3f9d804..fe9815600 100644 --- a/bot/exts/moderation/infraction/_utils.py +++ b/bot/exts/moderation/infraction/_utils.py @@ -7,7 +7,7 @@ from discord.ext.commands import Context from bot.api import ResponseCodeError from bot.constants import Colours, Icons -from bot.converters import FetchedMember +from bot.converters import MemberOrUser from bot.errors import InvalidInfractedUserError log = logging.getLogger(__name__) @@ -44,7 +44,7 @@ INFRACTION_DESCRIPTION_TEMPLATE = ( ) -async def post_user(ctx: Context, user: FetchedMember) -> t.Optional[dict]: +async def post_user(ctx: Context, user: MemberOrUser) -> t.Optional[dict]: """ Create a new user in the database. @@ -71,7 +71,7 @@ async def post_user(ctx: Context, user: FetchedMember) -> t.Optional[dict]: async def post_infraction( ctx: Context, - user: FetchedMember, + user: MemberOrUser, infr_type: str, reason: str, expires_at: datetime = None, @@ -79,7 +79,7 @@ async def post_infraction( active: bool = True ) -> t.Optional[dict]: """Posts an infraction to the API.""" - if isinstance(user, FetchedMember) and user.bot: + if isinstance(user, MemberOrUser) and user.bot: log.trace(f"Posting of {infr_type} infraction for {user} to the API aborted. User is a bot.") raise InvalidInfractedUserError(user) @@ -114,7 +114,7 @@ async def post_infraction( async def get_active_infraction( ctx: Context, - user: FetchedMember, + user: MemberOrUser, infr_type: str, send_msg: bool = True ) -> t.Optional[dict]: @@ -149,7 +149,7 @@ async def get_active_infraction( async def notify_infraction( - user: FetchedMember, + user: MemberOrUser, infr_type: str, expires_at: t.Optional[str] = None, reason: t.Optional[str] = None, @@ -185,7 +185,7 @@ async def notify_infraction( async def notify_pardon( - user: FetchedMember, + user: MemberOrUser, title: str, content: str, icon_url: str = Icons.user_verified @@ -203,7 +203,7 @@ async def notify_pardon( return await send_private_embed(user, embed) -async def send_private_embed(user: FetchedMember, embed: discord.Embed) -> bool: +async def send_private_embed(user: MemberOrUser, embed: discord.Embed) -> bool: """ A helper method for sending an embed to a user's DMs. diff --git a/bot/exts/moderation/infraction/infractions.py b/bot/exts/moderation/infraction/infractions.py index dfffb2b20..48ffbd773 100644 --- a/bot/exts/moderation/infraction/infractions.py +++ b/bot/exts/moderation/infraction/infractions.py @@ -10,7 +10,7 @@ from discord.ext.commands import Context, command from bot import constants from bot.bot import Bot from bot.constants import Event -from bot.converters import Duration, Expiry, FetchedMember +from bot.converters import Duration, Expiry, MemberOrUser from bot.decorators import respect_role_hierarchy from bot.exts.moderation.infraction import _utils from bot.exts.moderation.infraction._scheduler import InfractionScheduler @@ -53,7 +53,7 @@ class Infractions(InfractionScheduler, commands.Cog): # region: Permanent infractions @command() - async def warn(self, ctx: Context, user: FetchedMember, *, reason: t.Optional[str] = None) -> None: + async def warn(self, ctx: Context, user: MemberOrUser, *, reason: t.Optional[str] = None) -> None: """Warn a user for the given reason.""" if not isinstance(user, Member): await ctx.send(":x: The user doesn't appear to be on the server.") @@ -66,7 +66,7 @@ class Infractions(InfractionScheduler, commands.Cog): await self.apply_infraction(ctx, infraction, user) @command() - async def kick(self, ctx: Context, user: FetchedMember, *, reason: t.Optional[str] = None) -> None: + async def kick(self, ctx: Context, user: MemberOrUser, *, reason: t.Optional[str] = None) -> None: """Kick a user for the given reason.""" if not isinstance(user, Member): await ctx.send(":x: The user doesn't appear to be on the server.") @@ -78,7 +78,7 @@ class Infractions(InfractionScheduler, commands.Cog): async def ban( self, ctx: Context, - user: FetchedMember, + user: MemberOrUser, duration: t.Optional[Expiry] = None, *, reason: t.Optional[str] = None @@ -94,7 +94,7 @@ class Infractions(InfractionScheduler, commands.Cog): async def purgeban( self, ctx: Context, - user: FetchedMember, + user: MemberOrUser, duration: t.Optional[Expiry] = None, *, reason: t.Optional[str] = None @@ -110,7 +110,7 @@ class Infractions(InfractionScheduler, commands.Cog): async def voiceban( self, ctx: Context, - user: FetchedMember, + user: MemberOrUser, duration: t.Optional[Expiry] = None, *, reason: t.Optional[str] @@ -128,7 +128,7 @@ class Infractions(InfractionScheduler, commands.Cog): @command(aliases=["mute"]) async def tempmute( self, ctx: Context, - user: FetchedMember, + user: MemberOrUser, duration: t.Optional[Expiry] = None, *, reason: t.Optional[str] = None @@ -162,7 +162,7 @@ class Infractions(InfractionScheduler, commands.Cog): async def tempban( self, ctx: Context, - user: FetchedMember, + user: MemberOrUser, duration: Expiry, *, reason: t.Optional[str] = None @@ -188,7 +188,7 @@ class Infractions(InfractionScheduler, commands.Cog): async def tempvoiceban( self, ctx: Context, - user: FetchedMember, + user: MemberOrUser, duration: Expiry, *, reason: t.Optional[str] @@ -214,7 +214,7 @@ class Infractions(InfractionScheduler, commands.Cog): # region: Permanent shadow infractions @command(hidden=True) - async def note(self, ctx: Context, user: FetchedMember, *, reason: t.Optional[str] = None) -> None: + async def note(self, ctx: Context, user: MemberOrUser, *, reason: t.Optional[str] = None) -> None: """Create a private note for a user with the given reason without notifying the user.""" infraction = await _utils.post_infraction(ctx, user, "note", reason, hidden=True, active=False) if infraction is None: @@ -223,7 +223,7 @@ class Infractions(InfractionScheduler, commands.Cog): await self.apply_infraction(ctx, infraction, user) @command(hidden=True, aliases=['shadowban', 'sban']) - async def shadow_ban(self, ctx: Context, user: FetchedMember, *, reason: t.Optional[str] = None) -> None: + async def shadow_ban(self, ctx: Context, user: MemberOrUser, *, reason: t.Optional[str] = None) -> None: """Permanently ban a user for the given reason without notifying the user.""" await self.apply_ban(ctx, user, reason, hidden=True) @@ -234,7 +234,7 @@ class Infractions(InfractionScheduler, commands.Cog): async def shadow_tempban( self, ctx: Context, - user: FetchedMember, + user: MemberOrUser, duration: Expiry, *, reason: t.Optional[str] = None @@ -260,17 +260,17 @@ class Infractions(InfractionScheduler, commands.Cog): # region: Remove infractions (un- commands) @command() - async def unmute(self, ctx: Context, user: FetchedMember) -> None: + async def unmute(self, ctx: Context, user: MemberOrUser) -> None: """Prematurely end the active mute infraction for the user.""" await self.pardon_infraction(ctx, "mute", user) @command() - async def unban(self, ctx: Context, user: FetchedMember) -> None: + async def unban(self, ctx: Context, user: MemberOrUser) -> None: """Prematurely end the active ban infraction for the user.""" await self.pardon_infraction(ctx, "ban", user) @command(aliases=("uvban",)) - async def unvoiceban(self, ctx: Context, user: FetchedMember) -> None: + async def unvoiceban(self, ctx: Context, user: MemberOrUser) -> None: """Prematurely end the active voice ban infraction for the user.""" await self.pardon_infraction(ctx, "voice_ban", user) @@ -319,7 +319,7 @@ class Infractions(InfractionScheduler, commands.Cog): async def apply_ban( self, ctx: Context, - user: FetchedMember, + user: MemberOrUser, reason: t.Optional[str], purge_days: t.Optional[int] = 0, **kwargs @@ -375,7 +375,7 @@ class Infractions(InfractionScheduler, commands.Cog): await bb_cog.apply_unwatch(ctx, user, bb_reason, send_message=False) @respect_role_hierarchy(member_arg=2) - async def apply_voice_ban(self, ctx: Context, user: FetchedMember, reason: t.Optional[str], **kwargs) -> None: + async def apply_voice_ban(self, ctx: Context, user: MemberOrUser, reason: t.Optional[str], **kwargs) -> None: """Apply a voice ban infraction with kwargs passed to `post_infraction`.""" if await _utils.get_active_infraction(ctx, user, "voice_ban"): return diff --git a/bot/exts/moderation/infraction/management.py b/bot/exts/moderation/infraction/management.py index d1437c635..68ebab8ba 100644 --- a/bot/exts/moderation/infraction/management.py +++ b/bot/exts/moderation/infraction/management.py @@ -12,7 +12,7 @@ from discord.utils import escape_markdown from bot import constants from bot.bot import Bot -from bot.converters import Expiry, FetchedMember, Infraction, allowed_strings +from bot.converters import Expiry, MemberOrUser, Infraction, allowed_strings from bot.exts.moderation.infraction.infractions import Infractions from bot.exts.moderation.modlog import ModLog from bot.pagination import LinePaginator @@ -201,15 +201,15 @@ class ModManagement(commands.Cog): # region: Search infractions @infraction_group.group(name="search", aliases=('s',), invoke_without_command=True) - async def infraction_search_group(self, ctx: Context, query: t.Union[FetchedMember, str]) -> None: + async def infraction_search_group(self, ctx: Context, query: t.Union[MemberOrUser, str]) -> None: """Searches for infractions in the database.""" - if isinstance(query, FetchedMember): + if isinstance(query, MemberOrUser): await self.search_user(ctx, query) else: await self.search_reason(ctx, query) @infraction_search_group.command(name="user", aliases=("member", "id")) - async def search_user(self, ctx: Context, user: FetchedMember) -> None: + async def search_user(self, ctx: Context, user: MemberOrUser) -> None: """Search for infractions by member.""" infraction_list = await self.bot.api_client.get( 'bot/infractions/expanded', diff --git a/bot/exts/moderation/watchchannels/bigbrother.py b/bot/exts/moderation/watchchannels/bigbrother.py index c6ee844ef..4ee69ec9b 100644 --- a/bot/exts/moderation/watchchannels/bigbrother.py +++ b/bot/exts/moderation/watchchannels/bigbrother.py @@ -6,7 +6,7 @@ from discord.ext.commands import Cog, Context, group, has_any_role from bot.bot import Bot from bot.constants import Channels, MODERATION_ROLES, Webhooks -from bot.converters import FetchedMember +from bot.converters import MemberOrUser from bot.exts.moderation.infraction._utils import post_infraction from bot.exts.moderation.watchchannels._watchchannel import WatchChannel @@ -60,7 +60,7 @@ class BigBrother(WatchChannel, Cog, name="Big Brother"): @bigbrother_group.command(name='watch', aliases=('w',), root_aliases=('watch',)) @has_any_role(*MODERATION_ROLES) - async def watch_command(self, ctx: Context, user: FetchedMember, *, reason: str) -> None: + async def watch_command(self, ctx: Context, user: MemberOrUser, *, reason: str) -> None: """ Relay messages sent by the given `user` to the `#big-brother` channel. @@ -71,11 +71,11 @@ class BigBrother(WatchChannel, Cog, name="Big Brother"): @bigbrother_group.command(name='unwatch', aliases=('uw',), root_aliases=('unwatch',)) @has_any_role(*MODERATION_ROLES) - async def unwatch_command(self, ctx: Context, user: FetchedMember, *, reason: str) -> None: + async def unwatch_command(self, ctx: Context, user: MemberOrUser, *, reason: str) -> None: """Stop relaying messages by the given `user`.""" await self.apply_unwatch(ctx, user, reason) - async def apply_watch(self, ctx: Context, user: FetchedMember, reason: str) -> None: + async def apply_watch(self, ctx: Context, user: MemberOrUser, reason: str) -> None: """ Add `user` to watched users and apply a watch infraction with `reason`. @@ -125,7 +125,7 @@ class BigBrother(WatchChannel, Cog, name="Big Brother"): await ctx.send(msg) - async def apply_unwatch(self, ctx: Context, user: FetchedMember, reason: str, send_message: bool = True) -> None: + async def apply_unwatch(self, ctx: Context, user: MemberOrUser, reason: str, send_message: bool = True) -> None: """ Remove `user` from watched users and mark their infraction as inactive with `reason`. diff --git a/bot/exts/recruitment/talentpool/_cog.py b/bot/exts/recruitment/talentpool/_cog.py index 304bdafba..5c1a1cd3f 100644 --- a/bot/exts/recruitment/talentpool/_cog.py +++ b/bot/exts/recruitment/talentpool/_cog.py @@ -12,7 +12,7 @@ from discord.ext.commands import Cog, Context, group, has_any_role from bot.api import ResponseCodeError from bot.bot import Bot from bot.constants import Channels, Emojis, Guild, MODERATION_ROLES, Roles, STAFF_ROLES, Webhooks -from bot.converters import FetchedMember +from bot.converters import MemberOrUser from bot.exts.moderation.watchchannels._watchchannel import WatchChannel from bot.exts.recruitment.talentpool._review import Reviewer from bot.pagination import LinePaginator @@ -178,7 +178,7 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"): @nomination_group.command(name='forcewatch', aliases=('fw', 'forceadd', 'fa'), root_aliases=("forcenominate",)) @has_any_role(*MODERATION_ROLES) - async def force_watch_command(self, ctx: Context, user: FetchedMember, *, reason: str = '') -> None: + async def force_watch_command(self, ctx: Context, user: MemberOrUser, *, reason: str = '') -> None: """ Adds the given `user` to the talent pool, from any channel. @@ -188,7 +188,7 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"): @nomination_group.command(name='watch', aliases=('w', 'add', 'a'), root_aliases=("nominate",)) @has_any_role(*STAFF_ROLES) - async def watch_command(self, ctx: Context, user: FetchedMember, *, reason: str = '') -> None: + async def watch_command(self, ctx: Context, user: MemberOrUser, *, reason: str = '') -> None: """ Adds the given `user` to the talent pool. @@ -207,7 +207,7 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"): await self._watch_user(ctx, user, reason) - async def _watch_user(self, ctx: Context, user: FetchedMember, reason: str) -> None: + async def _watch_user(self, ctx: Context, user: MemberOrUser, reason: str) -> None: """Adds the given user to the talent pool.""" if user.bot: await ctx.send(f":x: I'm sorry {ctx.author}, I'm afraid I can't do that. I only watch humans.") @@ -271,7 +271,7 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"): @nomination_group.command(name='history', aliases=('info', 'search')) @has_any_role(*MODERATION_ROLES) - async def history_command(self, ctx: Context, user: FetchedMember) -> None: + async def history_command(self, ctx: Context, user: MemberOrUser) -> None: """Shows the specified user's nomination history.""" result = await self.bot.api_client.get( self.api_endpoint, @@ -300,7 +300,7 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"): @nomination_group.command(name='unwatch', aliases=('end', ), root_aliases=("unnominate",)) @has_any_role(*MODERATION_ROLES) - async def unwatch_command(self, ctx: Context, user: FetchedMember, *, reason: str) -> None: + async def unwatch_command(self, ctx: Context, user: MemberOrUser, *, reason: str) -> None: """ Ends the active nomination of the specified user with the given reason. @@ -323,7 +323,7 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"): @nomination_edit_group.command(name='reason') @has_any_role(*MODERATION_ROLES) - async def edit_reason_command(self, ctx: Context, nomination_id: int, actor: FetchedMember, *, reason: str) -> None: + async def edit_reason_command(self, ctx: Context, nomination_id: int, actor: MemberOrUser, *, reason: str) -> None: """Edits the reason of a specific nominator in a specific active nomination.""" if len(reason) > REASON_MAX_CHARS: await ctx.send(f":x: Maxiumum allowed characters for the reason is {REASON_MAX_CHARS}.") @@ -417,7 +417,7 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"): await ctx.message.add_reaction(Emojis.check_mark) @Cog.listener() - async def on_member_ban(self, guild: Guild, user: Union[FetchedMember]) -> None: + async def on_member_ban(self, guild: Guild, user: Union[MemberOrUser]) -> None: """Remove `user` from the talent pool after they are banned.""" await self.unwatch(user.id, "User was banned.") -- cgit v1.2.3 From 4c8d7754ec1879acf43925f3bc2c998d2890385f Mon Sep 17 00:00:00 2001 From: Izan Date: Wed, 11 Aug 2021 19:08:20 +0100 Subject: Fix import order --- bot/exts/moderation/infraction/management.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/infraction/management.py b/bot/exts/moderation/infraction/management.py index 68ebab8ba..e2755efc5 100644 --- a/bot/exts/moderation/infraction/management.py +++ b/bot/exts/moderation/infraction/management.py @@ -12,7 +12,7 @@ from discord.utils import escape_markdown from bot import constants from bot.bot import Bot -from bot.converters import Expiry, MemberOrUser, Infraction, allowed_strings +from bot.converters import Expiry, Infraction, MemberOrUser, allowed_strings from bot.exts.moderation.infraction.infractions import Infractions from bot.exts.moderation.modlog import ModLog from bot.pagination import LinePaginator -- cgit v1.2.3 From 22268276556d568f6d254fe2715f509c213119e3 Mon Sep 17 00:00:00 2001 From: Izan Date: Wed, 11 Aug 2021 19:14:36 +0100 Subject: Fix import --- bot/errors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/errors.py b/bot/errors.py index 5186aa3bb..08396ec3e 100644 --- a/bot/errors.py +++ b/bot/errors.py @@ -1,6 +1,6 @@ from typing import Hashable -from converters import MemberOrUser +from bot.converters import MemberOrUser class LockedResourceError(RuntimeError): -- cgit v1.2.3 From aa8c455f9ef919cfc21f0a99cae5a878ec57853f Mon Sep 17 00:00:00 2001 From: Izan Date: Wed, 11 Aug 2021 19:56:10 +0100 Subject: Fix isinstance check --- bot/exts/moderation/infraction/_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/infraction/_utils.py b/bot/exts/moderation/infraction/_utils.py index fe9815600..827623936 100644 --- a/bot/exts/moderation/infraction/_utils.py +++ b/bot/exts/moderation/infraction/_utils.py @@ -79,7 +79,7 @@ async def post_infraction( active: bool = True ) -> t.Optional[dict]: """Posts an infraction to the API.""" - if isinstance(user, MemberOrUser) and user.bot: + if isinstance(user, (discord.Member, discord.User)) and user.bot: log.trace(f"Posting of {infr_type} infraction for {user} to the API aborted. User is a bot.") raise InvalidInfractedUserError(user) -- cgit v1.2.3 From 4e6af7be36da47cdd083ef27e4757f809b09aafd Mon Sep 17 00:00:00 2001 From: Izan Date: Wed, 11 Aug 2021 23:34:19 +0100 Subject: Remove redundant function --- bot/converters.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/bot/converters.py b/bot/converters.py index ff9f6e5d9..37eb91c7f 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -456,19 +456,6 @@ class UserMentionOrID(UserConverter): raise BadArgument(f"`{argument}` is not a User mention or a User ID.") -def _snowflake_from_regex(pattern: t.Pattern, arg: str) -> int: - """ - Extract the snowflake from `arg` using a regex `pattern` and return it as an int. - - The snowflake is expected to be within the first capture group in `pattern`. - """ - match = pattern.match(arg) - if not match: - raise BadArgument(f"Mention {str!r} is invalid.") - - return int(match.group(1)) - - class Infraction(Converter): """ Attempts to convert a given infraction ID into an infraction. -- cgit v1.2.3 From c90908effae17aba4f656516b306f40c8844bd40 Mon Sep 17 00:00:00 2001 From: Izan Date: Thu, 12 Aug 2021 00:07:11 +0100 Subject: Re-add ability to search infractions of deleted account --- bot/exts/moderation/infraction/management.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/bot/exts/moderation/infraction/management.py b/bot/exts/moderation/infraction/management.py index e2755efc5..058bc9db0 100644 --- a/bot/exts/moderation/infraction/management.py +++ b/bot/exts/moderation/infraction/management.py @@ -12,7 +12,7 @@ from discord.utils import escape_markdown from bot import constants from bot.bot import Bot -from bot.converters import Expiry, Infraction, MemberOrUser, allowed_strings +from bot.converters import Expiry, Infraction, MemberOrUser, Snowflake, allowed_strings from bot.exts.moderation.infraction.infractions import Infractions from bot.exts.moderation.modlog import ModLog from bot.pagination import LinePaginator @@ -201,21 +201,24 @@ class ModManagement(commands.Cog): # region: Search infractions @infraction_group.group(name="search", aliases=('s',), invoke_without_command=True) - async def infraction_search_group(self, ctx: Context, query: t.Union[MemberOrUser, str]) -> None: + async def infraction_search_group(self, ctx: Context, query: t.Union[MemberOrUser, Snowflake, str]) -> None: """Searches for infractions in the database.""" - if isinstance(query, MemberOrUser): - await self.search_user(ctx, query) - else: + if isinstance(query, int): + await self.search_user(ctx, discord.Object(query)) + elif isinstance(query, str): await self.search_reason(ctx, query) + else: + await self.search_user(ctx, query) @infraction_search_group.command(name="user", aliases=("member", "id")) - async def search_user(self, ctx: Context, user: MemberOrUser) -> None: + async def search_user(self, ctx: Context, user: t.Union[MemberOrUser, discord.Object]) -> None: """Search for infractions by member.""" infraction_list = await self.bot.api_client.get( 'bot/infractions/expanded', params={'user__id': str(user.id)} ) + user_string = str(user if isinstance(user, (discord.Member, discord.User)) else user.id) embed = discord.Embed( title=f"Infractions for {user} ({len(infraction_list)} total)", colour=discord.Colour.orange() -- cgit v1.2.3 From 9770d2ed7d9b0ce92e0284f9c96d0944e331509c Mon Sep 17 00:00:00 2001 From: Izan Date: Fri, 13 Aug 2021 10:16:17 +0100 Subject: Add missing call to `escape_markdown` --- bot/exts/moderation/infraction/management.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bot/exts/moderation/infraction/management.py b/bot/exts/moderation/infraction/management.py index 058bc9db0..725e8798c 100644 --- a/bot/exts/moderation/infraction/management.py +++ b/bot/exts/moderation/infraction/management.py @@ -218,9 +218,13 @@ class ModManagement(commands.Cog): params={'user__id': str(user.id)} ) - user_string = str(user if isinstance(user, (discord.Member, discord.User)) else user.id) + if isinstance(user, (discord.Member, discord.User)): + user_str = escape_markdown(user.name) + user.discriminator + else: + user_str = str(user.id) + embed = discord.Embed( - title=f"Infractions for {user} ({len(infraction_list)} total)", + title=f"Infractions for {user_str} ({len(infraction_list)} total)", colour=discord.Colour.orange() ) await self.send_infraction_list(ctx, embed, infraction_list) -- cgit v1.2.3 From 39818408bcd9ca2a65d2459a216c81db39c80d18 Mon Sep 17 00:00:00 2001 From: Izan Date: Fri, 13 Aug 2021 10:18:31 +0100 Subject: Remove redundant fetching of user code --- bot/exts/moderation/infraction/_scheduler.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/bot/exts/moderation/infraction/_scheduler.py b/bot/exts/moderation/infraction/_scheduler.py index 13f59bb76..3c5e5d3bf 100644 --- a/bot/exts/moderation/infraction/_scheduler.py +++ b/bot/exts/moderation/infraction/_scheduler.py @@ -165,17 +165,10 @@ class InfractionScheduler: dm_result = f"{constants.Emojis.failmail} " dm_log_text = "\nDM: **Failed**" - # Sometimes user is a discord.Object; make it a proper user. - try: - if not isinstance(user, (discord.Member, discord.User)): - user = await self.bot.fetch_user(user.id) - except discord.HTTPException as e: - log.error(f"Failed to DM {user.id}: could not fetch user (status {e.status})") - else: - # Accordingly display whether the user was successfully notified via DM. - if await _utils.notify_infraction(user, infr_type.replace("_", " ").title(), expiry, user_reason, icon): - dm_result = ":incoming_envelope: " - dm_log_text = "\nDM: Sent" + # Accordingly display whether the user was successfully notified via DM. + if await _utils.notify_infraction(user, infr_type.replace("_", " ").title(), expiry, user_reason, icon): + dm_result = ":incoming_envelope: " + dm_log_text = "\nDM: Sent" end_msg = "" if infraction["actor"] == self.bot.user.id: -- cgit v1.2.3 From 59aeaab40655a3bc91264e24802d2bc6e02d3372 Mon Sep 17 00:00:00 2001 From: Izan Date: Fri, 13 Aug 2021 10:22:02 +0100 Subject: Remove redundant getattr protection --- bot/exts/moderation/infraction/_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/exts/moderation/infraction/_utils.py b/bot/exts/moderation/infraction/_utils.py index 827623936..9d94bca2d 100644 --- a/bot/exts/moderation/infraction/_utils.py +++ b/bot/exts/moderation/infraction/_utils.py @@ -53,10 +53,10 @@ async def post_user(ctx: Context, user: MemberOrUser) -> t.Optional[dict]: log.trace(f"Attempting to add user {user.id} to the database.") payload = { - 'discriminator': int(getattr(user, 'discriminator', 0)), + 'discriminator': int(user.discriminator), 'id': user.id, 'in_guild': False, - 'name': getattr(user, 'name', 'Name unknown'), + 'name': user.name, 'roles': [] } -- cgit v1.2.3 From 36d0ea88dc4ac071778355de948e7f06420f1c41 Mon Sep 17 00:00:00 2001 From: Izan Date: Sat, 14 Aug 2021 09:53:53 +0100 Subject: Attempt to get user from DB when discord.Object passed to search_user --- bot/exts/moderation/infraction/management.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bot/exts/moderation/infraction/management.py b/bot/exts/moderation/infraction/management.py index 725e8798c..b05b051cc 100644 --- a/bot/exts/moderation/infraction/management.py +++ b/bot/exts/moderation/infraction/management.py @@ -219,9 +219,13 @@ class ModManagement(commands.Cog): ) if isinstance(user, (discord.Member, discord.User)): - user_str = escape_markdown(user.name) + user.discriminator + user_str = escape_markdown(str(user)) else: - user_str = str(user.id) + if infraction_list: + user = infraction_list[0]["user"] + user_str = escape_markdown(user["name"]) + f"#{user['discriminator']:04}" + else: + user_str = str(user.id) embed = discord.Embed( title=f"Infractions for {user_str} ({len(infraction_list)} total)", -- cgit v1.2.3 From df23edb837ac1d50821976605208dc51c54f5c6c Mon Sep 17 00:00:00 2001 From: Izan Date: Fri, 20 Aug 2021 09:12:54 +0100 Subject: Update `infraction_search_group` typehint to `UserMentionOrId` --- bot/exts/moderation/infraction/management.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bot/exts/moderation/infraction/management.py b/bot/exts/moderation/infraction/management.py index b05b051cc..9ff30da59 100644 --- a/bot/exts/moderation/infraction/management.py +++ b/bot/exts/moderation/infraction/management.py @@ -12,7 +12,7 @@ from discord.utils import escape_markdown from bot import constants from bot.bot import Bot -from bot.converters import Expiry, Infraction, MemberOrUser, Snowflake, allowed_strings +from bot.converters import Expiry, Infraction, Snowflake, UserMentionOrID, allowed_strings from bot.exts.moderation.infraction.infractions import Infractions from bot.exts.moderation.modlog import ModLog from bot.pagination import LinePaginator @@ -201,7 +201,7 @@ class ModManagement(commands.Cog): # region: Search infractions @infraction_group.group(name="search", aliases=('s',), invoke_without_command=True) - async def infraction_search_group(self, ctx: Context, query: t.Union[MemberOrUser, Snowflake, str]) -> None: + async def infraction_search_group(self, ctx: Context, query: t.Union[UserMentionOrID, Snowflake, str]) -> None: """Searches for infractions in the database.""" if isinstance(query, int): await self.search_user(ctx, discord.Object(query)) @@ -211,7 +211,7 @@ class ModManagement(commands.Cog): await self.search_user(ctx, query) @infraction_search_group.command(name="user", aliases=("member", "id")) - async def search_user(self, ctx: Context, user: t.Union[MemberOrUser, discord.Object]) -> None: + async def search_user(self, ctx: Context, user: t.Union[UserMentionOrID, discord.Object]) -> None: """Search for infractions by member.""" infraction_list = await self.bot.api_client.get( 'bot/infractions/expanded', -- cgit v1.2.3 From 4e158b2a64e6f640847f0f7d2f229c96cecb2c46 Mon Sep 17 00:00:00 2001 From: Izan Date: Sat, 21 Aug 2021 22:07:29 +0100 Subject: Revert `search_user` typehint back to `MemberOrUser` --- bot/exts/moderation/infraction/management.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/exts/moderation/infraction/management.py b/bot/exts/moderation/infraction/management.py index 9ff30da59..641ad0410 100644 --- a/bot/exts/moderation/infraction/management.py +++ b/bot/exts/moderation/infraction/management.py @@ -12,7 +12,7 @@ from discord.utils import escape_markdown from bot import constants from bot.bot import Bot -from bot.converters import Expiry, Infraction, Snowflake, UserMentionOrID, allowed_strings +from bot.converters import Expiry, Infraction, MemberOrUser, Snowflake, UserMentionOrID, allowed_strings from bot.exts.moderation.infraction.infractions import Infractions from bot.exts.moderation.modlog import ModLog from bot.pagination import LinePaginator @@ -211,7 +211,7 @@ class ModManagement(commands.Cog): await self.search_user(ctx, query) @infraction_search_group.command(name="user", aliases=("member", "id")) - async def search_user(self, ctx: Context, user: t.Union[UserMentionOrID, discord.Object]) -> None: + async def search_user(self, ctx: Context, user: t.Union[MemberOrUser, discord.Object]) -> None: """Search for infractions by member.""" infraction_list = await self.bot.api_client.get( 'bot/infractions/expanded', -- cgit v1.2.3 From 9c41f0bf3c9487a01f7b5aca32c8b288b99ac382 Mon Sep 17 00:00:00 2001 From: Izan Date: Sun, 22 Aug 2021 21:48:34 +0100 Subject: Update outdated comment --- bot/exts/moderation/watchchannels/bigbrother.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/watchchannels/bigbrother.py b/bot/exts/moderation/watchchannels/bigbrother.py index 4ee69ec9b..3aa253fea 100644 --- a/bot/exts/moderation/watchchannels/bigbrother.py +++ b/bot/exts/moderation/watchchannels/bigbrother.py @@ -94,7 +94,7 @@ class BigBrother(WatchChannel, Cog, name="Big Brother"): await ctx.send(f":x: {user} is already being watched.") return - # FetchedUser instances don't have a roles attribute + # discord.User instances don't have a roles attribute if hasattr(user, "roles") and any(role.id in MODERATION_ROLES for role in user.roles): await ctx.send(f":x: I'm sorry {ctx.author}, I'm afraid I can't do that. I must be kind to my masters.") return -- cgit v1.2.3 From a831aa011821254387bf079795ecd21b3439051a Mon Sep 17 00:00:00 2001 From: TizzySaurus <47674925+TizzySaurus@users.noreply.github.com> Date: Mon, 23 Aug 2021 08:25:18 +0100 Subject: Update time format in reminder message to DAY_TIME from RELATIVE (#1766) * Update time format in reminder message to DAY_TIME from RELATIVE Co-authored-by: Xithrius <15021300+Xithrius@users.noreply.github.com> --- bot/exts/utils/reminders.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index cc13f6ebe..2e60f5030 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -270,7 +270,7 @@ class Reminders(Cog): } ) - mention_string = f"Your reminder will arrive {discord_timestamp(expiration, TimestampFormats.RELATIVE)}" + mention_string = f"Your reminder will arrive on {discord_timestamp(expiration, TimestampFormats.DAY_TIME)}" if mentions: mention_string += f" and will mention {len(mentions)} other(s)" -- cgit v1.2.3 From 26073a9a445bb17408884fb862e91ae5e45dd3c7 Mon Sep 17 00:00:00 2001 From: TizzySaurus <47674925+TizzySaurus@users.noreply.github.com> Date: Mon, 23 Aug 2021 10:49:16 +0100 Subject: Update reminders command to use `UserMentionOrID` instead of `discord.Member` to fix greediness issues. (#1768) --- bot/exts/utils/reminders.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 2e60f5030..144f7b537 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -12,7 +12,7 @@ from discord.ext.commands import Cog, Context, Greedy, group from bot.bot import Bot from bot.constants import Guild, Icons, MODERATION_ROLES, POSITIVE_REPLIES, Roles, STAFF_ROLES -from bot.converters import Duration +from bot.converters import Duration, UserMentionOrID from bot.pagination import LinePaginator from bot.utils.checks import has_any_role_check, has_no_roles_check from bot.utils.lock import lock_arg @@ -27,6 +27,7 @@ WHITELISTED_CHANNELS = Guild.reminder_whitelist MAXIMUM_REMINDERS = 5 Mentionable = t.Union[discord.Member, discord.Role] +ReminderMention = t.Union[UserMentionOrID, discord.Role] class Reminders(Cog): @@ -211,14 +212,14 @@ class Reminders(Cog): @group(name="remind", aliases=("reminder", "reminders", "remindme"), invoke_without_command=True) async def remind_group( - self, ctx: Context, mentions: Greedy[Mentionable], expiration: Duration, *, content: str + self, ctx: Context, mentions: Greedy[ReminderMention], expiration: Duration, *, content: str ) -> None: """Commands for managing your reminders.""" await self.new_reminder(ctx, mentions=mentions, expiration=expiration, content=content) @remind_group.command(name="new", aliases=("add", "create")) async def new_reminder( - self, ctx: Context, mentions: Greedy[Mentionable], expiration: Duration, *, content: str + self, ctx: Context, mentions: Greedy[ReminderMention], expiration: Duration, *, content: str ) -> None: """ Set yourself a simple reminder. @@ -363,7 +364,7 @@ class Reminders(Cog): await self.edit_reminder(ctx, id_, {"content": content}) @edit_reminder_group.command(name="mentions", aliases=("pings",)) - async def edit_reminder_mentions(self, ctx: Context, id_: int, mentions: Greedy[Mentionable]) -> None: + async def edit_reminder_mentions(self, ctx: Context, id_: int, mentions: Greedy[ReminderMention]) -> None: """Edit one of your reminder's mentions.""" # Remove duplicate mentions mentions = set(mentions) -- cgit v1.2.3 From eb4aab50132b8f26530160b0ced3791f1e42993f Mon Sep 17 00:00:00 2001 From: TizzySaurus <47674925+TizzySaurus@users.noreply.github.com> Date: Mon, 23 Aug 2021 17:59:59 +0100 Subject: Escape markdown in user's name for `!user` command --- bot/exts/info/information.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bot/exts/info/information.py b/bot/exts/info/information.py index a9ea403f7..8bef6a8cd 100644 --- a/bot/exts/info/information.py +++ b/bot/exts/info/information.py @@ -8,6 +8,7 @@ from typing import Any, DefaultDict, Mapping, Optional, Tuple, Union import rapidfuzz from discord import AllowedMentions, Colour, Embed, Guild, Message, Role from discord.ext.commands import BucketType, Cog, Context, Paginator, command, group, has_any_role +from discord.utils import escape_markdown from bot import constants from bot.api import ResponseCodeError @@ -244,6 +245,7 @@ class Information(Cog): name = str(user) if on_server and user.nick: name = f"{user.nick} ({name})" + name = escape_markdown(name) if user.public_flags.verified_bot: name += f" {constants.Emojis.verified_bot}" -- cgit v1.2.3