Skip to content

Commit 074ec33

Browse files
authored
Merge branch 'main' into use-paste-service-for-long-autoban-filters
2 parents e75a94a + e8ef3a2 commit 074ec33

File tree

2 files changed

+90
-65
lines changed

2 files changed

+90
-65
lines changed

bot/exts/info/help.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ async def command_formatting(self, command: Command) -> tuple[Embed, CommandView
310310

311311
# Remove line breaks from docstrings, if not used to separate paragraphs.
312312
# Allow overriding this behaviour via putting \u2003 at the start of a line.
313-
formatted_doc = re.sub("(?<!\n)\n(?![\n\u2003])", " ", command.help)
313+
formatted_doc = re.sub("(?<!\n)\n(?![\n\u2003])", " ", command.help) if command.help else None
314314
command_details += f"{formatted_doc or 'No details provided.'}\n"
315315
embed.description = command_details
316316

bot/exts/moderation/infraction/management.py

Lines changed: 89 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import textwrap
33
import typing as t
44

5-
import arrow
65
import discord
76
from discord.ext import commands
87
from discord.ext.commands import Context
@@ -26,6 +25,19 @@
2625

2726
log = get_logger(__name__)
2827

28+
NO_DURATION_INFRACTIONS = ("note", "warning", "kick")
29+
30+
FAILED_DM_SYMBOL = constants.Emojis.failmail
31+
HIDDEN_INFRACTION_SYMBOL = "🕵️"
32+
EDITED_DURATION_SYMBOL = "✏️"
33+
34+
SYMBOLS_GUIDE = f"""
35+
Symbols guide:
36+
\u2003{FAILED_DM_SYMBOL} - The infraction DM failed to deliver.
37+
\u2003{HIDDEN_INFRACTION_SYMBOL} - The infraction is hidden.
38+
\u2003{EDITED_DURATION_SYMBOL}- The duration was edited.
39+
"""
40+
2941

3042
class ModManagement(commands.Cog):
3143
"""Management of infractions."""
@@ -35,6 +47,16 @@ class ModManagement(commands.Cog):
3547
def __init__(self, bot: Bot):
3648
self.bot = bot
3749

50+
# Add the symbols guide to the help embeds of the appropriate commands.
51+
for command in (
52+
self.infraction_group,
53+
self.infraction_search_group,
54+
self.search_reason,
55+
self.search_user,
56+
self.search_by_actor
57+
):
58+
command.help += f"\n{SYMBOLS_GUIDE}"
59+
3860
@property
3961
def mod_log(self) -> ModLog:
4062
"""Get currently loaded ModLog cog instance."""
@@ -58,10 +80,10 @@ async def infraction_group(self, ctx: Context, infraction: Infraction = None) ->
5880
return
5981

6082
embed = discord.Embed(
61-
title=f"Infraction #{infraction['id']}",
83+
title=f"{self.format_infraction_title(infraction)}",
6284
colour=discord.Colour.orange()
6385
)
64-
await self.send_infraction_list(ctx, embed, [infraction])
86+
await self.send_infraction_list(ctx, embed, [infraction], ignore_fields=("id",))
6587

6688
@infraction_group.command(name="resend", aliases=("send", "rs", "dm"))
6789
async def infraction_resend(self, ctx: Context, infraction: Infraction) -> None:
@@ -287,7 +309,8 @@ async def search_user(self, ctx: Context, user: MemberOrUser | discord.Object) -
287309
title=f"Infractions for {user_str} ({formatted_infraction_count} total)",
288310
colour=discord.Colour.orange()
289311
)
290-
await self.send_infraction_list(ctx, embed, infraction_list)
312+
prefix = f"{user.mention} - {user.id}"
313+
await self.send_infraction_list(ctx, embed, infraction_list, prefix, ("user",))
291314

292315
@infraction_search_group.command(name="reason", aliases=("match", "regex", "re"))
293316
async def search_reason(self, ctx: Context, reason: str) -> None:
@@ -304,10 +327,12 @@ async def search_reason(self, ctx: Context, reason: str) -> None:
304327

305328
formatted_infraction_count = self.format_infraction_count(len(infraction_list))
306329
embed = discord.Embed(
307-
title=f"Infractions matching `{reason}` ({formatted_infraction_count} total)",
330+
title=f"Infractions with matching context ({formatted_infraction_count} total)",
308331
colour=discord.Colour.orange()
309332
)
310-
await self.send_infraction_list(ctx, embed, infraction_list)
333+
if len(reason) > 500:
334+
reason = reason[:500] + "..."
335+
await self.send_infraction_list(ctx, embed, infraction_list, reason)
311336

312337
# endregion
313338
# region: Search for infractions by given actor
@@ -348,7 +373,8 @@ async def search_by_actor(
348373
colour=discord.Colour.orange()
349374
)
350375

351-
await self.send_infraction_list(ctx, embed, infraction_list)
376+
prefix = f"{actor.mention} - {actor.id}"
377+
await self.send_infraction_list(ctx, embed, infraction_list, prefix, ("actor",))
352378

353379
# endregion
354380
# region: Utility functions
@@ -369,94 +395,93 @@ async def send_infraction_list(
369395
self,
370396
ctx: Context,
371397
embed: discord.Embed,
372-
infractions: t.Iterable[dict[str, t.Any]]
398+
infractions: t.Iterable[dict[str, t.Any]],
399+
prefix: str = "",
400+
ignore_fields: tuple[str, ...] = ()
373401
) -> None:
374402
"""Send a paginated embed of infractions for the specified user."""
375403
if not infractions:
376404
await ctx.send(":warning: No infractions could be found for that query.")
377405
return
378406

379-
lines = tuple(
380-
self.infraction_to_string(infraction)
381-
for infraction in infractions
382-
)
407+
lines = [self.infraction_to_string(infraction, ignore_fields) for infraction in infractions]
383408

384409
await LinePaginator.paginate(
385410
lines,
386411
ctx=ctx,
387412
embed=embed,
413+
prefix=f"{prefix}\n",
388414
empty=True,
389415
max_lines=3,
390416
max_size=1000
391417
)
392418

393-
def infraction_to_string(self, infraction: dict[str, t.Any]) -> str:
419+
def infraction_to_string(self, infraction: dict[str, t.Any], ignore_fields: tuple[str, ...]) -> str:
394420
"""Convert the infraction object to a string representation."""
395-
active = infraction["active"]
396-
user = infraction["user"]
397421
expires_at = infraction["expires_at"]
398422
inserted_at = infraction["inserted_at"]
399423
last_applied = infraction["last_applied"]
400-
created = time.discord_timestamp(inserted_at)
401-
applied = time.discord_timestamp(last_applied)
402-
duration_edited = arrow.get(last_applied) > arrow.get(inserted_at)
403-
dm_sent = infraction["dm_sent"]
404424
jump_url = infraction["jump_url"]
405425

406-
# Format the user string.
407-
if user_obj := self.bot.get_user(user["id"]):
408-
# The user is in the cache.
409-
user_str = messages.format_user(user_obj)
410-
else:
411-
# Use the user data retrieved from the DB.
412-
name = escape_markdown(user["name"])
413-
user_str = f"<@{user['id']}> ({name}#{user['discriminator']:04})"
426+
title = ""
427+
if "id" not in ignore_fields:
428+
title = f"**{self.format_infraction_title(infraction)}**"
414429

415-
if active:
416-
remaining = time.until_expiration(expires_at)
417-
else:
418-
remaining = "Inactive"
430+
symbols = []
431+
if not infraction["hidden"] and infraction["dm_sent"] is False:
432+
symbols.append(FAILED_DM_SYMBOL)
433+
if infraction["hidden"]:
434+
symbols.append(HIDDEN_INFRACTION_SYMBOL)
435+
if inserted_at != infraction["last_applied"]:
436+
symbols.append(EDITED_DURATION_SYMBOL)
437+
symbols = " ".join(symbols)
419438

420-
if expires_at is None:
421-
duration = "*Permanent*"
422-
else:
423-
duration = time.humanize_delta(last_applied, expires_at)
439+
user_str = ""
440+
if "user" not in ignore_fields:
441+
user_str = "For " + self.format_user_from_record(infraction["user"])
424442

425-
# Notice if infraction expiry was edited.
426-
if duration_edited:
427-
duration += f" (edited {applied})"
443+
actor_str = ""
444+
if "actor" not in ignore_fields:
445+
actor_str = f"By <@{infraction['actor']['id']}>"
428446

429-
# Format `dm_sent`
430-
if dm_sent is None:
431-
dm_sent_text = "N/A"
432-
else:
433-
dm_sent_text = "Yes" if dm_sent else "No"
447+
issued = "Issued " + time.discord_timestamp(inserted_at)
448+
449+
duration = ""
450+
if infraction["type"] not in NO_DURATION_INFRACTIONS:
451+
if expires_at is None:
452+
duration = "*Permanent*"
453+
else:
454+
duration = time.humanize_delta(last_applied, expires_at)
455+
if infraction["active"]:
456+
duration = f"{duration} (Expires {time.format_relative(expires_at)})"
457+
duration = f"Duration: {duration}"
434458

435459
if jump_url is None:
436460
# Infraction was issued prior to jump urls being stored in the database
437461
# or infraction was issued in ModMail category.
438-
jump_url = "N/A"
462+
context = f"**Context**: {infraction['reason'] or '*None*'}"
439463
else:
440-
jump_url = f"[Click here.]({jump_url})"
441-
442-
lines = textwrap.dedent(f"""
443-
{"**===============**" if active else "==============="}
444-
Status: {"__**Active**__" if active else "Inactive"}
445-
User: {user_str}
446-
Type: **{infraction["type"]}**
447-
DM Sent: {dm_sent_text}
448-
Shadow: {infraction["hidden"]}
449-
Created: {created}
450-
Expires: {remaining}
451-
Duration: {duration}
452-
Actor: <@{infraction["actor"]["id"]}>
453-
ID: `{infraction["id"]}`
454-
Jump URL: {jump_url}
455-
Reason: {infraction["reason"] or "*None*"}
456-
{"**===============**" if active else "==============="}
457-
""")
458-
459-
return lines.strip()
464+
context = f"**[Context]({jump_url})**: {infraction['reason'] or '*None*'}"
465+
466+
return "\n".join(part for part in (title, symbols, user_str, actor_str, issued, duration, context) if part)
467+
468+
def format_user_from_record(self, user: dict) -> str:
469+
"""Create a formatted user string from its DB record."""
470+
if user_obj := self.bot.get_user(user["id"]):
471+
# The user is in the cache.
472+
return messages.format_user(user_obj)
473+
474+
# Use the user data retrieved from the DB.
475+
name = escape_markdown(user["name"])
476+
return f"<@{user['id']}> ({name}#{user['discriminator']:04})"
477+
478+
@staticmethod
479+
def format_infraction_title(infraction: Infraction) -> str:
480+
"""Format the infraction title."""
481+
title = infraction["type"].replace("_", " ").title()
482+
if infraction["active"]:
483+
title = f"__Active__ {title}"
484+
return f"{title} #{infraction['id']}"
460485

461486
# endregion
462487

0 commit comments

Comments
 (0)