diff options
author | 2023-04-14 01:04:28 +0300 | |
---|---|---|
committer | 2023-04-14 01:04:28 +0300 | |
commit | 074ec33a6b62f636943313284b9c06bf50243a0f (patch) | |
tree | 42246b8b9776ff92f6c4b185693292277c6ce2d2 | |
parent | Upload weekly autoban report to pastebin (diff) | |
parent | Redesign infractions embed (#2526) (diff) |
Merge branch 'main' into use-paste-service-for-long-autoban-filters
-rw-r--r-- | bot/exts/info/help.py | 2 | ||||
-rw-r--r-- | bot/exts/moderation/infraction/management.py | 153 |
2 files changed, 90 insertions, 65 deletions
diff --git a/bot/exts/info/help.py b/bot/exts/info/help.py index 69d7de584..d4e0a133f 100644 --- a/bot/exts/info/help.py +++ b/bot/exts/info/help.py @@ -310,7 +310,7 @@ class CustomHelpCommand(HelpCommand): # 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("(?<!\n)\n(?![\n\u2003])", " ", command.help) + formatted_doc = re.sub("(?<!\n)\n(?![\n\u2003])", " ", command.help) if command.help else None command_details += f"{formatted_doc or 'No details provided.'}\n" embed.description = command_details diff --git a/bot/exts/moderation/infraction/management.py b/bot/exts/moderation/infraction/management.py index 8c51eb3bb..f9a4787dd 100644 --- a/bot/exts/moderation/infraction/management.py +++ b/bot/exts/moderation/infraction/management.py @@ -2,7 +2,6 @@ import re import textwrap import typing as t -import arrow import discord from discord.ext import commands from discord.ext.commands import Context @@ -26,6 +25,19 @@ from bot.utils.time import unpack_duration log = get_logger(__name__) +NO_DURATION_INFRACTIONS = ("note", "warning", "kick") + +FAILED_DM_SYMBOL = constants.Emojis.failmail +HIDDEN_INFRACTION_SYMBOL = "🕵️" +EDITED_DURATION_SYMBOL = "✏️" + +SYMBOLS_GUIDE = f""" +Symbols guide: +\u2003{FAILED_DM_SYMBOL} - The infraction DM failed to deliver. +\u2003{HIDDEN_INFRACTION_SYMBOL} - The infraction is hidden. +\u2003{EDITED_DURATION_SYMBOL}- The duration was edited. +""" + class ModManagement(commands.Cog): """Management of infractions.""" @@ -35,6 +47,16 @@ class ModManagement(commands.Cog): def __init__(self, bot: Bot): self.bot = bot + # Add the symbols guide to the help embeds of the appropriate commands. + for command in ( + self.infraction_group, + self.infraction_search_group, + self.search_reason, + self.search_user, + self.search_by_actor + ): + command.help += f"\n{SYMBOLS_GUIDE}" + @property def mod_log(self) -> ModLog: """Get currently loaded ModLog cog instance.""" @@ -58,10 +80,10 @@ class ModManagement(commands.Cog): 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 +309,8 @@ class ModManagement(commands.Cog): 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 +327,12 @@ class ModManagement(commands.Cog): 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 +373,8 @@ class ModManagement(commands.Cog): 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 +395,93 @@ class ModManagement(commands.Cog): 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) + if infraction["active"]: + duration = f"{duration} (Expires {time.format_relative(expires_at)})" + 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 |