aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Boris Muratov <[email protected]>2023-04-14 01:04:28 +0300
committerGravatar GitHub <[email protected]>2023-04-14 01:04:28 +0300
commit074ec33a6b62f636943313284b9c06bf50243a0f (patch)
tree42246b8b9776ff92f6c4b185693292277c6ce2d2
parentUpload weekly autoban report to pastebin (diff)
parentRedesign infractions embed (#2526) (diff)
Merge branch 'main' into use-paste-service-for-long-autoban-filters
-rw-r--r--bot/exts/info/help.py2
-rw-r--r--bot/exts/moderation/infraction/management.py153
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