aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/exts/moderation/clean.py4
-rw-r--r--bot/exts/moderation/defcon.py13
-rw-r--r--bot/exts/moderation/infraction/_scheduler.py10
-rw-r--r--bot/exts/moderation/infraction/management.py10
-rw-r--r--bot/exts/moderation/modlog.py211
-rw-r--r--bot/exts/moderation/watchchannels/_watchchannel.py13
-rw-r--r--bot/utils/modlog.py69
-rw-r--r--tests/bot/exts/moderation/test_modlog.py4
8 files changed, 203 insertions, 131 deletions
diff --git a/bot/exts/moderation/clean.py b/bot/exts/moderation/clean.py
index 1c73736d7..d38de15e0 100644
--- a/bot/exts/moderation/clean.py
+++ b/bot/exts/moderation/clean.py
@@ -21,6 +21,7 @@ from bot.exts.moderation.modlog import ModLog
from bot.log import get_logger
from bot.utils.channel import is_mod_channel
from bot.utils.messages import upload_log
+from bot.utils.modlog import send_log_message
log = get_logger(__name__)
@@ -367,7 +368,8 @@ class Clean(Cog):
f"A log of the deleted messages can be found [here]({log_url})."
)
- await self.mod_log.send_log_message(
+ await send_log_message(
+ self.bot,
icon_url=Icons.message_bulk_delete,
colour=Colour(Colours.soft_red),
title="Bulk message delete",
diff --git a/bot/exts/moderation/defcon.py b/bot/exts/moderation/defcon.py
index 3c16f8e0e..3196c4a1a 100644
--- a/bot/exts/moderation/defcon.py
+++ b/bot/exts/moderation/defcon.py
@@ -21,6 +21,7 @@ from bot.exts.moderation.modlog import ModLog
from bot.log import get_logger
from bot.utils import time
from bot.utils.messages import format_user
+from bot.utils.modlog import send_log_message
log = get_logger(__name__)
@@ -136,9 +137,13 @@ class Defcon(Cog):
if not message_sent:
message = f"{message}\n\nUnable to send rejection message via DM; they probably have DMs disabled."
- await (await self.get_mod_log()).send_log_message(
- Icons.defcon_denied, Colours.soft_red, "Entry denied",
- message, member.display_avatar.url
+ await send_log_message(
+ self.bot,
+ Icons.defcon_denied,
+ Colours.soft_red,
+ "Entry denied",
+ message,
+ thumbnail=member.display_avatar.url
)
@group(name="defcon", aliases=("dc",), invoke_without_command=True)
@@ -304,7 +309,7 @@ class Defcon(Cog):
)
status_msg = f"DEFCON {action.name.lower()}"
- await (await self.get_mod_log()).send_log_message(info.icon, info.color, status_msg, log_msg)
+ await send_log_message(self.bot, info.icon, info.color, status_msg, log_msg)
def _update_notifier(self) -> None:
"""Start or stop the notifier according to the DEFCON status."""
diff --git a/bot/exts/moderation/infraction/_scheduler.py b/bot/exts/moderation/infraction/_scheduler.py
index a079f775e..aed6210a2 100644
--- a/bot/exts/moderation/infraction/_scheduler.py
+++ b/bot/exts/moderation/infraction/_scheduler.py
@@ -20,6 +20,7 @@ from bot.exts.moderation.modlog import ModLog
from bot.log import get_logger
from bot.utils import messages, time
from bot.utils.channel import is_mod_channel
+from bot.utils.modlog import send_log_message
log = get_logger(__name__)
@@ -270,7 +271,8 @@ class InfractionScheduler:
# Send a log message to the mod log.
# Don't use ctx.message.author for the actor; antispam only patches ctx.author.
log.trace(f"Sending apply mod log for infraction #{id_}.")
- await self.mod_log.send_log_message(
+ await send_log_message(
+ self.bot,
icon_url=icon,
colour=Colours.soft_red,
title=f"Infraction {log_title}: {' '.join(infr_type.split('_'))}",
@@ -369,7 +371,8 @@ class InfractionScheduler:
log_text["Reason"] = log_text.pop("Reason")
# Send a log message to the mod log.
- await self.mod_log.send_log_message(
+ await send_log_message(
+ self.bot,
icon_url=_utils.INFRACTION_ICONS[infr_type][1],
colour=Colours.soft_green,
title=f"Infraction {log_title}: {' '.join(infr_type.split('_'))}",
@@ -507,7 +510,8 @@ class InfractionScheduler:
log_text["Reason"] = log_text.pop("Reason")
log.trace(f"Sending deactivation mod log for infraction #{id_}.")
- await self.mod_log.send_log_message(
+ await send_log_message(
+ self.bot,
icon_url=_utils.INFRACTION_ICONS[type_][1],
colour=Colours.soft_green,
title=f"Infraction {log_title}: {type_}",
diff --git a/bot/exts/moderation/infraction/management.py b/bot/exts/moderation/infraction/management.py
index 9bf3a4dbb..93959042b 100644
--- a/bot/exts/moderation/infraction/management.py
+++ b/bot/exts/moderation/infraction/management.py
@@ -16,11 +16,11 @@ from bot.decorators import ensure_future_timestamp
from bot.errors import InvalidInfractionError
from bot.exts.moderation.infraction import _utils
from bot.exts.moderation.infraction.infractions import Infractions
-from bot.exts.moderation.modlog import ModLog
from bot.log import get_logger
from bot.pagination import LinePaginator
from bot.utils import messages, time
from bot.utils.channel import is_in_category, is_mod_channel
+from bot.utils.modlog import send_log_message
from bot.utils.time import unpack_duration
log = get_logger(__name__)
@@ -58,11 +58,6 @@ class ModManagement(commands.Cog):
command.help += f"\n{SYMBOLS_GUIDE}"
@property
- def mod_log(self) -> ModLog:
- """Get currently loaded ModLog cog instance."""
- return self.bot.get_cog("ModLog")
-
- @property
def infractions_cog(self) -> Infractions:
"""Get currently loaded Infractions cog instance."""
return self.bot.get_cog("Infractions")
@@ -260,7 +255,8 @@ class ModManagement(commands.Cog):
else:
jump_url = f"[Click here.]({ctx.message.jump_url})"
- await self.mod_log.send_log_message(
+ await send_log_message(
+ self.bot,
icon_url=constants.Icons.pencil,
colour=discord.Colour.og_blurple(),
title="Infraction edited",
diff --git a/bot/exts/moderation/modlog.py b/bot/exts/moderation/modlog.py
index b349f4d5d..3c256396a 100644
--- a/bot/exts/moderation/modlog.py
+++ b/bot/exts/moderation/modlog.py
@@ -8,7 +8,7 @@ from dateutil.relativedelta import relativedelta
from deepdiff import DeepDiff
from discord import Colour, Message, Thread
from discord.abc import GuildChannel
-from discord.ext.commands import Cog, Context
+from discord.ext.commands import Cog
from discord.utils import escape_markdown, format_dt, snowflake_time
from bot.bot import Bot
@@ -16,6 +16,7 @@ from bot.constants import Channels, Colours, Emojis, Event, Guild as GuildConsta
from bot.log import get_logger
from bot.utils import time
from bot.utils.messages import format_user, upload_log
+from bot.utils.modlog import send_log_message
log = get_logger(__name__)
@@ -47,63 +48,6 @@ class ModLog(Cog, name="ModLog"):
if item not in self._ignored[event]:
self._ignored[event].append(item)
- async def send_log_message(
- self,
- icon_url: str | None,
- colour: discord.Colour | int,
- title: str | None,
- text: str,
- thumbnail: str | discord.Asset | None = None,
- channel_id: int = Channels.mod_log,
- ping_everyone: bool = False,
- files: list[discord.File] | None = None,
- content: str | None = None,
- additional_embeds: list[discord.Embed] | None = None,
- timestamp_override: datetime | None = None,
- footer: str | None = None,
- ) -> Context:
- """Generate log embed and send to logging channel."""
- await self.bot.wait_until_guild_available()
- # Truncate string directly here to avoid removing newlines
- embed = discord.Embed(
- description=text[:4093] + "..." if len(text) > 4096 else text
- )
-
- if title and icon_url:
- embed.set_author(name=title, icon_url=icon_url)
-
- embed.colour = colour
- embed.timestamp = timestamp_override or datetime.now(tz=UTC)
-
- if footer:
- embed.set_footer(text=footer)
-
- if thumbnail:
- embed.set_thumbnail(url=thumbnail)
-
- if ping_everyone:
- if content:
- content = f"<@&{Roles.moderators}> {content}"
- else:
- content = f"<@&{Roles.moderators}>"
-
- # Truncate content to 2000 characters and append an ellipsis.
- if content and len(content) > 2000:
- content = content[:2000 - 3] + "..."
-
- channel = self.bot.get_channel(channel_id)
- log_message = await channel.send(
- content=content,
- embed=embed,
- files=files
- )
-
- if additional_embeds:
- for additional_embed in additional_embeds:
- await channel.send(embed=additional_embed)
-
- return await self.bot.get_context(log_message) # Optionally return for use with antispam
-
@Cog.listener()
async def on_guild_channel_create(self, channel: GUILD_CHANNEL) -> None:
"""Log channel create event to mod log."""
@@ -128,7 +72,7 @@ class ModLog(Cog, name="ModLog"):
else:
message = f"{channel.name} (`{channel.id}`)"
- await self.send_log_message(Icons.hash_green, Colours.soft_green, title, message)
+ await send_log_message(self.bot, Icons.hash_green, Colours.soft_green, title, message)
@Cog.listener()
async def on_guild_channel_delete(self, channel: GUILD_CHANNEL) -> None:
@@ -148,9 +92,12 @@ class ModLog(Cog, name="ModLog"):
else:
message = f"{channel.name} (`{channel.id}`)"
- await self.send_log_message(
- Icons.hash_red, Colours.soft_red,
- title, message
+ await send_log_message(
+ self.bot,
+ Icons.hash_red,
+ Colours.soft_red,
+ title,
+ message
)
@Cog.listener()
@@ -211,9 +158,12 @@ class ModLog(Cog, name="ModLog"):
else:
message = f"**#{after.name}** (`{after.id}`)\n{message}"
- await self.send_log_message(
- Icons.hash_blurple, Colour.og_blurple(),
- "Channel updated", message
+ await send_log_message(
+ self.bot,
+ Icons.hash_blurple,
+ Colour.og_blurple(),
+ "Channel updated",
+ message
)
@Cog.listener()
@@ -222,9 +172,12 @@ class ModLog(Cog, name="ModLog"):
if role.guild.id != GuildConstant.id:
return
- await self.send_log_message(
- Icons.crown_green, Colours.soft_green,
- "Role created", f"`{role.id}`"
+ await send_log_message(
+ self.bot,
+ Icons.crown_green,
+ Colours.soft_green,
+ "Role created",
+ f"`{role.id}`"
)
@Cog.listener()
@@ -233,9 +186,12 @@ class ModLog(Cog, name="ModLog"):
if role.guild.id != GuildConstant.id:
return
- await self.send_log_message(
- Icons.crown_red, Colours.soft_red,
- "Role removed", f"{role.name} (`{role.id}`)"
+ await send_log_message(
+ self.bot,
+ Icons.crown_red,
+ Colours.soft_red,
+ "Role removed",
+ f"{role.name} (`{role.id}`)"
)
@Cog.listener()
@@ -286,9 +242,12 @@ class ModLog(Cog, name="ModLog"):
message = f"**{after.name}** (`{after.id}`)\n{message}"
- await self.send_log_message(
- Icons.crown_blurple, Colour.og_blurple(),
- "Role updated", message
+ await send_log_message(
+ self.bot,
+ Icons.crown_blurple,
+ Colour.og_blurple(),
+ "Role updated",
+ message
)
@Cog.listener()
@@ -336,9 +295,12 @@ class ModLog(Cog, name="ModLog"):
message = f"**{after.name}** (`{after.id}`)\n{message}"
- await self.send_log_message(
- Icons.guild_update, Colour.og_blurple(),
- "Guild updated", message,
+ await send_log_message(
+ self.bot,
+ Icons.guild_update,
+ Colour.og_blurple(),
+ "Guild updated",
+ message,
thumbnail=after.icon.with_static_format("png")
)
@@ -352,9 +314,12 @@ class ModLog(Cog, name="ModLog"):
self._ignored[Event.member_ban].remove(member.id)
return
- await self.send_log_message(
- Icons.user_ban, Colours.soft_red,
- "User banned", format_user(member),
+ await send_log_message(
+ self.bot,
+ Icons.user_ban,
+ Colours.soft_red,
+ "User banned",
+ format_user(member),
thumbnail=member.display_avatar.url,
channel_id=Channels.user_log
)
@@ -373,9 +338,12 @@ class ModLog(Cog, name="ModLog"):
if difference.days < 1 and difference.months < 1 and difference.years < 1: # New user account!
message = f"{Emojis.new} {message}"
- await self.send_log_message(
- Icons.sign_in, Colours.soft_green,
- "User joined", message,
+ await send_log_message(
+ self.bot,
+ Icons.sign_in,
+ Colours.soft_green,
+ "User joined",
+ message,
thumbnail=member.display_avatar.url,
channel_id=Channels.user_log
)
@@ -390,9 +358,12 @@ class ModLog(Cog, name="ModLog"):
self._ignored[Event.member_remove].remove(member.id)
return
- await self.send_log_message(
- Icons.sign_out, Colours.soft_red,
- "User left", format_user(member),
+ await send_log_message(
+ self.bot,
+ Icons.sign_out,
+ Colours.soft_red,
+ "User left",
+ format_user(member),
thumbnail=member.display_avatar.url,
channel_id=Channels.user_log
)
@@ -407,9 +378,12 @@ class ModLog(Cog, name="ModLog"):
self._ignored[Event.member_unban].remove(member.id)
return
- await self.send_log_message(
- Icons.user_unban, Colour.og_blurple(),
- "User unbanned", format_user(member),
+ await send_log_message(
+ self.bot,
+ Icons.user_unban,
+ Colour.og_blurple(),
+ "User unbanned",
+ format_user(member),
thumbnail=member.display_avatar.url,
channel_id=Channels.mod_log
)
@@ -471,7 +445,8 @@ class ModLog(Cog, name="ModLog"):
message = f"{format_user(after)}\n{message}"
- await self.send_log_message(
+ await send_log_message(
+ self.bot,
icon_url=Icons.user_update,
colour=Colour.og_blurple(),
title="Member updated",
@@ -565,8 +540,10 @@ class ModLog(Cog, name="ModLog"):
response += f"{content}"
- await self.send_log_message(
- Icons.message_delete, Colours.soft_red,
+ await send_log_message(
+ self.bot,
+ Icons.message_delete,
+ Colours.soft_red,
"Message deleted",
response,
channel_id=Channels.message_log
@@ -606,8 +583,10 @@ class ModLog(Cog, name="ModLog"):
"This message was not cached, so the message content cannot be displayed."
)
- await self.send_log_message(
- Icons.message_delete, Colours.soft_red,
+ await send_log_message(
+ self.bot,
+ Icons.message_delete,
+ Colours.soft_red,
"Message deleted",
response,
channel_id=Channels.message_log
@@ -687,9 +666,15 @@ class ModLog(Cog, name="ModLog"):
timestamp = msg_before.created_at
footer = None
- await self.send_log_message(
- Icons.message_edit, Colour.og_blurple(), "Message edited", response,
- channel_id=Channels.message_log, timestamp_override=timestamp, footer=footer
+ await send_log_message(
+ self.bot,
+ Icons.message_edit,
+ Colour.og_blurple(),
+ "Message edited",
+ response,
+ channel_id=Channels.message_log,
+ timestamp_override=timestamp,
+ footer=footer
)
@Cog.listener()
@@ -734,14 +719,22 @@ class ModLog(Cog, name="ModLog"):
f"{message.clean_content}"
)
- await self.send_log_message(
- Icons.message_edit, Colour.og_blurple(), "Message edited (Before)",
- before_response, channel_id=Channels.message_log
+ await send_log_message(
+ self.bot,
+ Icons.message_edit,
+ Colour.og_blurple(),
+ "Message edited (Before)",
+ before_response,
+ channel_id=Channels.message_log
)
- await self.send_log_message(
- Icons.message_edit, Colour.og_blurple(), "Message edited (After)",
- after_response, channel_id=Channels.message_log
+ await send_log_message(
+ self.bot,
+ Icons.message_edit,
+ Colour.og_blurple(),
+ "Message edited (After)",
+ after_response,
+ channel_id=Channels.message_log
)
@Cog.listener()
@@ -752,7 +745,8 @@ class ModLog(Cog, name="ModLog"):
return
if before.name != after.name:
- await self.send_log_message(
+ await send_log_message(
+ self.bot,
Icons.hash_blurple,
Colour.og_blurple(),
"Thread name edited",
@@ -774,7 +768,8 @@ class ModLog(Cog, name="ModLog"):
else:
return
- await self.send_log_message(
+ await send_log_message(
+ self.bot,
icon,
colour,
f"Thread {action}",
@@ -792,7 +787,8 @@ class ModLog(Cog, name="ModLog"):
log.trace("Ignoring deletion of thread %s (%d)", thread.mention, thread.id)
return
- await self.send_log_message(
+ await send_log_message(
+ self.bot,
Icons.hash_red,
Colours.soft_red,
"Thread deleted",
@@ -868,7 +864,8 @@ class ModLog(Cog, name="ModLog"):
message = "\n".join(f"{Emojis.bullet} {item}" for item in sorted(changes))
message = f"{format_user(member)}\n{message}"
- await self.send_log_message(
+ await send_log_message(
+ self.bot,
icon_url=icon,
colour=colour,
title="Voice state updated",
diff --git a/bot/exts/moderation/watchchannels/_watchchannel.py b/bot/exts/moderation/watchchannels/_watchchannel.py
index d0fc0de44..71600e9df 100644
--- a/bot/exts/moderation/watchchannels/_watchchannel.py
+++ b/bot/exts/moderation/watchchannels/_watchchannel.py
@@ -19,10 +19,10 @@ from bot.bot import Bot
from bot.constants import BigBrother as BigBrotherConfig, Guild as GuildConfig, Icons
from bot.exts.filtering._filters.unique.discord_token import DiscordTokenFilter
from bot.exts.filtering._filters.unique.webhook import WEBHOOK_URL_RE
-from bot.exts.moderation.modlog import ModLog
from bot.log import get_logger
from bot.pagination import LinePaginator
from bot.utils import CogABCMeta, messages, time
+from bot.utils.modlog import send_log_message
log = get_logger(__name__)
@@ -73,11 +73,6 @@ class WatchChannel(metaclass=CogABCMeta):
self.disable_header = disable_header
@property
- def modlog(self) -> ModLog:
- """Provides access to the ModLog cog for alert purposes."""
- return self.bot.get_cog("ModLog")
-
- @property
def consuming_messages(self) -> bool:
"""Checks if a consumption task is currently running."""
if self._consume_task is None:
@@ -122,7 +117,8 @@ class WatchChannel(metaclass=CogABCMeta):
"""
)
- await self.modlog.send_log_message(
+ await send_log_message(
+ self.bot,
title=f"Error: Failed to initialize the {self.__class__.__name__} watch channel",
text=message,
ping_everyone=True,
@@ -134,7 +130,8 @@ class WatchChannel(metaclass=CogABCMeta):
return
if not await self.fetch_user_cache():
- await self.modlog.send_log_message(
+ await send_log_message(
+ self.bot,
title=f"Warning: Failed to retrieve user cache for the {self.__class__.__name__} watch channel",
text=(
"Could not retrieve the list of watched users from the API. "
diff --git a/bot/utils/modlog.py b/bot/utils/modlog.py
new file mode 100644
index 000000000..6a432e65e
--- /dev/null
+++ b/bot/utils/modlog.py
@@ -0,0 +1,69 @@
+from datetime import UTC, datetime
+
+import discord
+
+from bot.bot import Bot
+from bot.constants import Channels, Roles
+
+
+async def send_log_message(
+ bot: Bot,
+ icon_url: str | None,
+ colour: discord.Colour | int,
+ title: str | None,
+ text: str,
+ *,
+ thumbnail: str | discord.Asset | None = None,
+ channel_id: int = Channels.mod_log,
+ ping_everyone: bool = False,
+ files: list[discord.File] | None = None,
+ content: str | None = None,
+ additional_embeds: list[discord.Embed] | None = None,
+ timestamp_override: datetime | None = None,
+ footer: str | None = None,
+) -> discord.Message:
+ """Generate log embed and send to logging channel."""
+ await bot.wait_until_guild_available()
+ # Truncate string directly here to avoid removing newlines
+ embed = discord.Embed(
+ description=text[:4093] + "..." if len(text) > 4096 else text
+ )
+
+ if title and icon_url:
+ embed.set_author(name=title, icon_url=icon_url)
+ elif title:
+ raise ValueError("title cannot be set without icon_url")
+ elif icon_url:
+ raise ValueError("icon_url cannot be set without title")
+
+ embed.colour = colour
+ embed.timestamp = timestamp_override or datetime.now(tz=UTC)
+
+ if footer:
+ embed.set_footer(text=footer)
+
+ if thumbnail:
+ embed.set_thumbnail(url=thumbnail)
+
+ if ping_everyone:
+ if content:
+ content = f"<@&{Roles.moderators}> {content}"
+ else:
+ content = f"<@&{Roles.moderators}>"
+
+ # Truncate content to 2000 characters and append an ellipsis.
+ if content and len(content) > 2000:
+ content = content[:2000 - 3] + "..."
+
+ channel = bot.get_channel(channel_id)
+ log_message = await channel.send(
+ content=content,
+ embed=embed,
+ files=files
+ )
+
+ if additional_embeds:
+ for additional_embed in additional_embeds:
+ await channel.send(embed=additional_embed)
+
+ return log_message
diff --git a/tests/bot/exts/moderation/test_modlog.py b/tests/bot/exts/moderation/test_modlog.py
index 79e04837d..f2b02bd1b 100644
--- a/tests/bot/exts/moderation/test_modlog.py
+++ b/tests/bot/exts/moderation/test_modlog.py
@@ -3,6 +3,7 @@ import unittest
import discord
from bot.exts.moderation.modlog import ModLog
+from bot.utils.modlog import send_log_message
from tests.helpers import MockBot, MockTextChannel
@@ -17,7 +18,8 @@ class ModLogTests(unittest.IsolatedAsyncioTestCase):
async def test_log_entry_description_truncation(self):
"""Test that embed description for ModLog entry is truncated."""
self.bot.get_channel.return_value = self.channel
- await self.cog.send_log_message(
+ await send_log_message(
+ self.bot,
icon_url="foo",
colour=discord.Colour.blue(),
title="bar",