From 7256344726c1270d873cd19401348cbadc9e7698 Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Mon, 10 Apr 2023 16:47:43 +0300 Subject: [PATCH 1/2] Redesign infractions embed --- bot/exts/info/help.py | 2 +- bot/exts/moderation/infraction/management.py | 155 +++++++++++-------- 2 files changed, 93 insertions(+), 64 deletions(-) diff --git a/bot/exts/info/help.py b/bot/exts/info/help.py index 69d7de584e..d4e0a133f6 100644 --- a/bot/exts/info/help.py +++ b/bot/exts/info/help.py @@ -310,7 +310,7 @@ async def command_formatting(self, command: Command) -> tuple[Embed, CommandView # Remove line breaks from docstrings, if not used to separate paragraphs. # Allow overriding this behaviour via putting \u2003 at the start of a line. - formatted_doc = re.sub("(? ModLog: """Get currently loaded ModLog cog instance.""" @@ -58,10 +81,10 @@ async def infraction_group(self, ctx: Context, infraction: Infraction = None) -> return embed = discord.Embed( - title=f"Infraction #{infraction['id']}", + title=f"{self.format_infraction_title(infraction)}", colour=discord.Colour.orange() ) - await self.send_infraction_list(ctx, embed, [infraction]) + await self.send_infraction_list(ctx, embed, [infraction], ignore_fields=("id",)) @infraction_group.command(name="resend", aliases=("send", "rs", "dm")) async def infraction_resend(self, ctx: Context, infraction: Infraction) -> None: @@ -287,7 +310,8 @@ async def search_user(self, ctx: Context, user: MemberOrUser | discord.Object) - title=f"Infractions for {user_str} ({formatted_infraction_count} total)", colour=discord.Colour.orange() ) - await self.send_infraction_list(ctx, embed, infraction_list) + prefix = f"{user.mention} - {user.id}" + await self.send_infraction_list(ctx, embed, infraction_list, prefix, ("user",)) @infraction_search_group.command(name="reason", aliases=("match", "regex", "re")) async def search_reason(self, ctx: Context, reason: str) -> None: @@ -304,10 +328,12 @@ async def search_reason(self, ctx: Context, reason: str) -> None: formatted_infraction_count = self.format_infraction_count(len(infraction_list)) embed = discord.Embed( - title=f"Infractions matching `{reason}` ({formatted_infraction_count} total)", + title=f"Infractions with matching context ({formatted_infraction_count} total)", colour=discord.Colour.orange() ) - await self.send_infraction_list(ctx, embed, infraction_list) + if len(reason) > 500: + reason = reason[:500] + "..." + await self.send_infraction_list(ctx, embed, infraction_list, reason) # endregion # region: Search for infractions by given actor @@ -348,7 +374,8 @@ async def search_by_actor( colour=discord.Colour.orange() ) - await self.send_infraction_list(ctx, embed, infraction_list) + prefix = f"{actor.mention} - {actor.id}" + await self.send_infraction_list(ctx, embed, infraction_list, prefix, ("actor",)) # endregion # region: Utility functions @@ -369,94 +396,96 @@ async def send_infraction_list( self, ctx: Context, embed: discord.Embed, - infractions: t.Iterable[dict[str, t.Any]] + infractions: t.Iterable[dict[str, t.Any]], + prefix: str = "", + ignore_fields: tuple[str, ...] = () ) -> None: """Send a paginated embed of infractions for the specified user.""" if not infractions: await ctx.send(":warning: No infractions could be found for that query.") return - lines = tuple( - self.infraction_to_string(infraction) - for infraction in infractions - ) + lines = [self.infraction_to_string(infraction, ignore_fields) for infraction in infractions] await LinePaginator.paginate( lines, ctx=ctx, embed=embed, + prefix=f"{prefix}\n", empty=True, max_lines=3, max_size=1000 ) - def infraction_to_string(self, infraction: dict[str, t.Any]) -> str: + def infraction_to_string(self, infraction: dict[str, t.Any], ignore_fields: tuple[str, ...]) -> str: """Convert the infraction object to a string representation.""" - active = infraction["active"] - user = infraction["user"] expires_at = infraction["expires_at"] inserted_at = infraction["inserted_at"] last_applied = infraction["last_applied"] - created = time.discord_timestamp(inserted_at) - applied = time.discord_timestamp(last_applied) - duration_edited = arrow.get(last_applied) > arrow.get(inserted_at) - dm_sent = infraction["dm_sent"] jump_url = infraction["jump_url"] - # Format the user string. - if user_obj := self.bot.get_user(user["id"]): - # The user is in the cache. - user_str = messages.format_user(user_obj) - else: - # Use the user data retrieved from the DB. - name = escape_markdown(user["name"]) - user_str = f"<@{user['id']}> ({name}#{user['discriminator']:04})" + title = "" + if "id" not in ignore_fields: + title = f"**{self.format_infraction_title(infraction)}**" - if active: - remaining = time.until_expiration(expires_at) - else: - remaining = "Inactive" + symbols = [] + if not infraction["hidden"] and infraction["dm_sent"] is False: + symbols.append(FAILED_DM_SYMBOL) + if infraction["hidden"]: + symbols.append(HIDDEN_INFRACTION_SYMBOL) + if inserted_at != infraction["last_applied"]: + symbols.append(EDITED_DURATION_SYMBOL) + symbols = " ".join(symbols) - if expires_at is None: - duration = "*Permanent*" - else: - duration = time.humanize_delta(last_applied, expires_at) + user_str = "" + if "user" not in ignore_fields: + user_str = "For " + self.format_user_from_record(infraction["user"]) - # Notice if infraction expiry was edited. - if duration_edited: - duration += f" (edited {applied})" + actor_str = "" + if "actor" not in ignore_fields: + actor_str = f"By <@{infraction['actor']['id']}>" - # Format `dm_sent` - if dm_sent is None: - dm_sent_text = "N/A" - else: - dm_sent_text = "Yes" if dm_sent else "No" + issued = "Issued " + time.discord_timestamp(inserted_at) + + duration = "" + if infraction["type"] not in NO_DURATION_INFRACTIONS: + if expires_at is None: + duration = "*Permanent*" + else: + duration = time.humanize_delta(last_applied, expires_at) + expiry_timestamp = time.format_relative(expires_at) + if arrow.get(expires_at) > arrow.utcnow(): + duration = f"{duration} (Expires {expiry_timestamp})" + else: + duration = f"{duration} (Expired {expiry_timestamp})" + duration = f"Duration: {duration}" if jump_url is None: # Infraction was issued prior to jump urls being stored in the database # or infraction was issued in ModMail category. - jump_url = "N/A" + context = f"**Context**: {infraction['reason'] or '*None*'}" else: - jump_url = f"[Click here.]({jump_url})" - - lines = textwrap.dedent(f""" - {"**===============**" if active else "==============="} - Status: {"__**Active**__" if active else "Inactive"} - User: {user_str} - Type: **{infraction["type"]}** - DM Sent: {dm_sent_text} - Shadow: {infraction["hidden"]} - Created: {created} - Expires: {remaining} - Duration: {duration} - Actor: <@{infraction["actor"]["id"]}> - ID: `{infraction["id"]}` - Jump URL: {jump_url} - Reason: {infraction["reason"] or "*None*"} - {"**===============**" if active else "==============="} - """) - - return lines.strip() + context = f"**[Context]({jump_url})**: {infraction['reason'] or '*None*'}" + + return "\n".join(part for part in (title, symbols, user_str, actor_str, issued, duration, context) if part) + + def format_user_from_record(self, user: dict) -> str: + """Create a formatted user string from its DB record.""" + if user_obj := self.bot.get_user(user["id"]): + # The user is in the cache. + return messages.format_user(user_obj) + + # Use the user data retrieved from the DB. + name = escape_markdown(user['name']) + return f"<@{user['id']}> ({name}#{user['discriminator']:04})" + + @staticmethod + def format_infraction_title(infraction: Infraction) -> str: + """Format the infraction title.""" + title = infraction["type"].replace("_", " ").title() + if infraction["active"]: + title = f"__Active__ {title}" + return f"{title} #{infraction['id']}" # endregion From 6e843a0f458d1e29ad5bddf10b3df62a62e0767e Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Wed, 12 Apr 2023 21:24:30 +0300 Subject: [PATCH 2/2] Don't show expiry for inactive infractions --- bot/exts/moderation/infraction/management.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/bot/exts/moderation/infraction/management.py b/bot/exts/moderation/infraction/management.py index 6aef7b5b46..f9a4787dd5 100644 --- a/bot/exts/moderation/infraction/management.py +++ b/bot/exts/moderation/infraction/management.py @@ -2,7 +2,6 @@ import textwrap import typing as t -import arrow import discord from discord.ext import commands from discord.ext.commands import Context @@ -453,11 +452,8 @@ def infraction_to_string(self, infraction: dict[str, t.Any], ignore_fields: tupl duration = "*Permanent*" else: duration = time.humanize_delta(last_applied, expires_at) - expiry_timestamp = time.format_relative(expires_at) - if arrow.get(expires_at) > arrow.utcnow(): - duration = f"{duration} (Expires {expiry_timestamp})" - else: - duration = f"{duration} (Expired {expiry_timestamp})" + if infraction["active"]: + duration = f"{duration} (Expires {time.format_relative(expires_at)})" duration = f"Duration: {duration}" if jump_url is None: @@ -476,7 +472,7 @@ def format_user_from_record(self, user: dict) -> str: return messages.format_user(user_obj) # Use the user data retrieved from the DB. - name = escape_markdown(user['name']) + name = escape_markdown(user["name"]) return f"<@{user['id']}> ({name}#{user['discriminator']:04})" @staticmethod