From 5315e1102d1d2a2e536aa2ef8000ece15910b0fc Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Tue, 9 Jun 2026 09:42:07 -0700 Subject: [PATCH 01/24] Rewrite Message Edit Logging --- botlogging/logger.py | 18 ++-- configuration/config.default.json | 1 + configuration/config.meta.json | 6 +- modules/moderation/events.py | 135 ++++++++++++++++++++++++------ 4 files changed, 127 insertions(+), 33 deletions(-) diff --git a/botlogging/logger.py b/botlogging/logger.py index 6331cc47..41eee000 100644 --- a/botlogging/logger.py +++ b/botlogging/logger.py @@ -199,6 +199,7 @@ async def send_log( console_only: bool = False, embed: discord.Embed = None, exception: Exception = None, + embed_as_is: bool = False, ) -> None: """A comprehensive logging system This will log a message, embed, and/or exception to the console and discord @@ -238,17 +239,18 @@ async def send_log( if console_only or not self.send: return - # Ensure message is never too long - if len(message) > 4000: - message = f"{message[:4000]}..." - # Get the appropriate target to send to on discord log_channel = await self.get_discord_target(channel) - if embed: - embed = log_level.embed(message).modify_embed(embed) - else: - embed = log_level.embed(message) + if not embed_as_is: + # Ensure message is never too long + if len(message) > 4000: + message = f"{message[:4000]}..." + + if embed: + embed = log_level.embed(message).modify_embed(embed) + else: + embed = log_level.embed(message) try: await log_channel.send(embed=embed) diff --git a/configuration/config.default.json b/configuration/config.default.json index 91cd2ba6..82af4605 100644 --- a/configuration/config.default.json +++ b/configuration/config.default.json @@ -23,6 +23,7 @@ "core_guild_id": "", "core_logging_channel": "", "core_member_events_channel": "", + "core_message_events_channel": "", "core_nickname_filter": false, "core_private_channels": [], "duck_allow_manipulation": true, diff --git a/configuration/config.meta.json b/configuration/config.meta.json index c543e9a6..7f4fa682 100644 --- a/configuration/config.meta.json +++ b/configuration/config.meta.json @@ -81,7 +81,7 @@ }, "core_guild_events_channel": { "datatype": "discord.TextChannel", - "description": "The channel to log guild events to. This includes message events" + "description": "The channel to log guild events to." }, "core_guild_id": { "datatype": "discord.Guild", @@ -95,6 +95,10 @@ "datatype": "discord.TextChannel", "description": "The channel to log member events to." }, + "core_message_events_channel": { + "datatype": "discord.TextChannel", + "description": "The channel to log message events to." + }, "core_nickname_filter": { "datatype": "bool", "description": "Whether to run the nickname filter or not" diff --git a/modules/moderation/events.py b/modules/moderation/events.py index 6d58df65..227b8ecc 100644 --- a/modules/moderation/events.py +++ b/modules/moderation/events.py @@ -32,48 +32,135 @@ class EventLogger(cogs.BaseCog): For the explicit purpose of logging, not taking further action """ + CONFIG_MAP: dict[str, str] = { + "guild": "core_guild_events_channel", + "member": "core_member_events_channel", + "message": "core_message_events_channel", + } + + async def send_event_log( + self: Self, + guild: discord.Guild, + log_location: str, + string_message: str, + embed_message: discord.Embed, + channel_location: discord.abc.GuildChannel = None, + ) -> None: + context = LogContext(guild=guild, channel=channel_location) + message_header = f"Events for {guild.name} ({guild.id}): " + log_channel = self.CONFIG_MAP[log_location] + log_channel_id = configuration.get_config_entry(guild.id, log_channel) + await self.bot.logger.send_log( + message=message_header + string_message, + level=LogLevel.INFO, + context=context, + channel=log_channel_id, + embed=embed_message, + embed_as_is=True, + ) + + # MESSAGE EVENTS + @commands.Cog.listener() - async def on_message_edit( - self: Self, before: discord.Message, after: discord.Message + async def on_raw_message_edit( + self: Self, payload: discord.RawMessageUpdateEvent ) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_message_edit + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_raw_message_edit Args: before (discord.Message): The previous version of the message after (discord.Message): The current version of the message """ - # this seems to spam, not sure why - if before.content == after.content: + before = payload.cached_message + after = payload.message + + # Ignore message edit events for not content changes + if before and before.content == after.content: return - guild = getattr(before.channel, "guild", None) + guild = getattr(after.channel, "guild", None) + + # Ignore all message edit events in DMs + if not guild: + return # Ignore ephemeral slash command messages - if not guild and before.type == discord.MessageType.chat_input_command: + if after.type == discord.MessageType.chat_input_command: return - attrs = ["content", "embeds"] - diff = auxiliary.get_object_diff(before, after, attrs) - embed = discord.Embed() - embed = auxiliary.add_diff_fields(embed, diff) - embed.add_field(name="Author", value=before.author) - embed.add_field(name="Channel", value=getattr(before.channel, "name", "DM")) + embed = discord.Embed( + title="Message Edited", + description=f"[Jump to Message]({after.jump_url})", + colour=discord.Colour.orange(), + timestamp=discord.utils.utcnow(), + ) + + embed.set_author( + name=str(after.author), + icon_url=after.author.display_avatar.url, + ) + embed.add_field( - name="Server", - value=guild, + name="Author", + value=( + f"**User:** {after.author.mention}\n" + f"**Name:** {after.author}\n" + f"**ID:** {after.author.id}" + ), + inline=True, ) - embed.set_footer(text=f"Author ID: {before.author.id}") - log_channel = configuration.get_config_entry( - before.author.id, "core_guild_events_channel" + embed.add_field( + name="Channel", + value=( + f"**Channel:** {after.channel.mention}\n" + f"**Name:** #{after.channel.name}\n" + f"**ID:** {after.channel.id}" + ), + inline=True, ) - await self.bot.logger.send_log( - message=f"Message edit detected on message with ID {before.id}", - level=LogLevel.INFO, - context=LogContext(guild=before.channel.guild, channel=before.channel), - channel=log_channel, - embed=embed, + embed.add_field( + name="Timestamps", + value=( + f"**Sent:** " + f"()\n" + f"**Edited:** " + f"()" + ), + inline=False, + ) + if before: + old_content = before.clean_content + embed.add_field( + name="Original Content", + value=before.content[:1024] if before.content else "*No content*", + inline=False, + ) + else: + old_content = "**Unknown. Perhaps this message was too old?**" + embed.add_field( + name="Original Content", + value=old_content, + inline=False, + ) + + embed.add_field( + name="New Content", + value=after.content[:1024] if after.content else "*No content*", + inline=False, + ) + + embed.set_footer(text=f"Message ID: {after.id}") + + console_message = f"Message edit: ID: {after.id} in channel ({after.channel.name} ({after.channel.id})). Old: {old_content}, new {after.clean_content}" + + await self.send_event_log( + guild=after.guild, + log_location="message", + string_message=console_message, + embed_message=embed, + channel_location=after.channel, ) @commands.Cog.listener() From 13a4c5357bbcd1d67126c60f6fe3aa7e04f87de3 Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Tue, 9 Jun 2026 09:44:00 -0700 Subject: [PATCH 02/24] Update message edit docstring --- modules/moderation/events.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/moderation/events.py b/modules/moderation/events.py index 227b8ecc..9b24f187 100644 --- a/modules/moderation/events.py +++ b/modules/moderation/events.py @@ -68,8 +68,7 @@ async def on_raw_message_edit( """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_raw_message_edit Args: - before (discord.Message): The previous version of the message - after (discord.Message): The current version of the message + payload (discord.RawMessageUpdateEvent): The raw payload object for the message edit events """ before = payload.cached_message after = payload.message From 6e60142936b36ec453fe191bd6afca79befaea16 Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Tue, 9 Jun 2026 09:47:01 -0700 Subject: [PATCH 03/24] Sort the events.py file a bit --- modules/moderation/events.py | 129 +++++++++++++++++++---------------- 1 file changed, 69 insertions(+), 60 deletions(-) diff --git a/modules/moderation/events.py b/modules/moderation/events.py index 9b24f187..f05aa5df 100644 --- a/modules/moderation/events.py +++ b/modules/moderation/events.py @@ -33,6 +33,7 @@ class EventLogger(cogs.BaseCog): """ CONFIG_MAP: dict[str, str] = { + "bot": "core_logging_channel", "guild": "core_guild_events_channel", "member": "core_member_events_channel", "message": "core_message_events_channel", @@ -374,6 +375,8 @@ async def on_reaction_clear( embed=embed, ) + # Guild Events + @commands.Cog.listener() async def on_guild_channel_delete( self: Self, channel: discord.abc.GuildChannel @@ -553,66 +556,6 @@ async def on_webhooks_update(self: Self, channel: discord.abc.GuildChannel) -> N embed=embed, ) - @commands.Cog.listener() - async def on_member_update( - self: Self, before: discord.Member, after: discord.Member - ) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_update - - Args: - before (discord.Member): The updated member's old info - after (discord.Member): Teh updated member's new info - """ - changed_role = set(before.roles) ^ set(after.roles) - if changed_role: - if len(before.roles) < len(after.roles): - embed = discord.Embed() - embed.add_field(name="Roles added", value=next(iter(changed_role))) - embed.add_field(name="Server", value=before.guild.name) - else: - embed = discord.Embed() - embed.add_field(name="Roles lost", value=next(iter(changed_role))) - embed.add_field(name="Server", value=before.guild.name) - - log_channel = configuration.get_config_entry( - before.guild.id, "core_member_events_channel" - ) - - await self.bot.logger.send_log( - message=( - f"Member with ID {before.id} has changed status in guild with ID" - f" {before.guild.id}" - ), - level=LogLevel.INFO, - context=LogContext(guild=before.guild), - channel=log_channel, - embed=embed, - ) - - @commands.Cog.listener() - async def on_member_remove(self: Self, member: discord.Member) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_remove - - Args: - member (discord.Member): The member who left - """ - embed = discord.Embed() - embed.add_field(name="Member", value=member) - embed.add_field(name="Server", value=member.guild.name) - log_channel = configuration.get_config_entry( - member.guild.id, "core_member_events_channel" - ) - - await self.bot.logger.send_log( - message=( - f"Member with ID {member.id} has left guild with ID {member.guild.id}" - ), - level=LogLevel.INFO, - context=LogContext(guild=member.guild), - channel=log_channel, - embed=embed, - ) - @commands.Cog.listener() async def on_guild_remove(self: Self, guild: discord.Guild) -> None: """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_remove @@ -808,6 +751,68 @@ async def on_guild_emojis_update( embed=embed, ) + # Member Events + + @commands.Cog.listener() + async def on_member_update( + self: Self, before: discord.Member, after: discord.Member + ) -> None: + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_update + + Args: + before (discord.Member): The updated member's old info + after (discord.Member): Teh updated member's new info + """ + changed_role = set(before.roles) ^ set(after.roles) + if changed_role: + if len(before.roles) < len(after.roles): + embed = discord.Embed() + embed.add_field(name="Roles added", value=next(iter(changed_role))) + embed.add_field(name="Server", value=before.guild.name) + else: + embed = discord.Embed() + embed.add_field(name="Roles lost", value=next(iter(changed_role))) + embed.add_field(name="Server", value=before.guild.name) + + log_channel = configuration.get_config_entry( + before.guild.id, "core_member_events_channel" + ) + + await self.bot.logger.send_log( + message=( + f"Member with ID {before.id} has changed status in guild with ID" + f" {before.guild.id}" + ), + level=LogLevel.INFO, + context=LogContext(guild=before.guild), + channel=log_channel, + embed=embed, + ) + + @commands.Cog.listener() + async def on_member_remove(self: Self, member: discord.Member) -> None: + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_remove + + Args: + member (discord.Member): The member who left + """ + embed = discord.Embed() + embed.add_field(name="Member", value=member) + embed.add_field(name="Server", value=member.guild.name) + log_channel = configuration.get_config_entry( + member.guild.id, "core_member_events_channel" + ) + + await self.bot.logger.send_log( + message=( + f"Member with ID {member.id} has left guild with ID {member.guild.id}" + ), + level=LogLevel.INFO, + context=LogContext(guild=member.guild), + channel=log_channel, + embed=embed, + ) + @commands.Cog.listener() async def on_member_ban( self: Self, guild: discord.Guild, user: discord.User | discord.Member @@ -885,6 +890,8 @@ async def on_member_join(self: Self, member: discord.Member) -> None: embed=embed, ) + # Bot Events + @commands.Cog.listener() async def on_command(self: Self, ctx: commands.Context) -> None: """ @@ -915,6 +922,8 @@ async def on_command(self: Self, ctx: commands.Context) -> None: embed=embed, ) + # CONSOLE ONLY STUFF + @commands.Cog.listener() async def on_error(self: Self, event_method: str) -> None: """Catches non-command errors and sends them to the error logger for processing. From ee59a2a84caa70b81e5a44995f09002a087e2645 Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Tue, 9 Jun 2026 11:08:55 -0700 Subject: [PATCH 04/24] Delete message events --- modules/moderation/events.py | 154 +++++++++++++++++++++-------------- 1 file changed, 93 insertions(+), 61 deletions(-) diff --git a/modules/moderation/events.py b/modules/moderation/events.py index f05aa5df..3a7435bc 100644 --- a/modules/moderation/events.py +++ b/modules/moderation/events.py @@ -153,7 +153,7 @@ async def on_raw_message_edit( embed.set_footer(text=f"Message ID: {after.id}") - console_message = f"Message edit: ID: {after.id} in channel ({after.channel.name} ({after.channel.id})). Old: {old_content}, new {after.clean_content}" + console_message = f"Message edit: ID: {after.id} in channel: {after.channel.name} ({after.channel.id}). Old: {old_content}, new {after.clean_content}" await self.send_event_log( guild=after.guild, @@ -170,38 +170,70 @@ async def on_message_delete(self: Self, message: discord.Message) -> None: Args: message (discord.Message): The deleted message """ - guild = getattr(message.channel, "guild", None) + guild = message.guild + channel = message.channel # Ignore ephemeral slash command messages - if not guild and message.type == discord.MessageType.chat_input_command: + if message.type == discord.MessageType.chat_input_command: return - embed = discord.Embed() - embed.add_field(name="Content", value=message.content[:1024] or "None") - if len(message.content) > 1024: - embed.add_field(name="\a", value=message.content[1025:2048]) - if len(message.content) > 2048: - embed.add_field(name="\a", value=message.content[2049:3072]) - if len(message.content) > 3072: - embed.add_field(name="\a", value=message.content[3073:4096]) - embed.add_field(name="Author", value=message.author) + embed = discord.Embed( + title="Message Deleted", + description=f"[Jump to Message]({message.jump_url})", + colour=discord.Colour.orange(), + timestamp=discord.utils.utcnow(), + ) + + embed.set_author( + name=str(message.author), + icon_url=message.author.display_avatar.url, + ) + + embed.add_field( + name="Author", + value=( + f"**User:** {message.author.mention}\n" + f"**Name:** {message.author}\n" + f"**ID:** {message.author.id}" + ), + inline=True, + ) + embed.add_field( name="Channel", - value=getattr(message.channel, "name", "DM"), + value=( + f"**Channel:** {channel.mention}\n" + f"**Name:** #{channel.name}\n" + f"**ID:** {channel.id}" + ), + inline=True, ) - embed.add_field(name="Server", value=getattr(guild, "name", "None")) - embed.set_footer(text=f"Author ID: {message.author.id}") - log_channel = configuration.get_config_entry( - message.author.id, "core_guild_events_channel" + embed.add_field( + name="Timestamps", + value=( + f"**Sent:** " + f"()\n" + ), + inline=False, ) - await self.bot.logger.send_log( - message=f"Message with ID {message.id} deleted", - level=LogLevel.INFO, - context=LogContext(guild=message.channel.guild, channel=message.channel), - channel=log_channel, - embed=embed, + embed.add_field( + name="Message Content", + value=message.content[:1024] if message.content else "*No content*", + inline=False, + ) + + embed.set_footer(text=f"Message ID: {message.id}") + + console_message = f"Message delete: ID: {message.id} in channel: {channel.name} ({channel.id}). Content: {message.clean_content}" + + await self.send_event_log( + guild=guild, + log_location="message", + string_message=console_message, + embed_message=embed, + channel_location=channel, ) @commands.Cog.listener() @@ -556,44 +588,6 @@ async def on_webhooks_update(self: Self, channel: discord.abc.GuildChannel) -> N embed=embed, ) - @commands.Cog.listener() - async def on_guild_remove(self: Self, guild: discord.Guild) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_remove - - Args: - guild (discord.Guild): The guild that got removed - """ - embed = discord.Embed() - embed.add_field(name="Server", value=guild.name) - await self.bot.logger.send_log( - message=f"Left guild with ID {guild.id}", - level=LogLevel.INFO, - context=LogContext(guild=guild), - embed=embed, - ) - - @commands.Cog.listener() - async def on_guild_join(self: Self, guild: discord.Guild) -> None: - """Configures a new guild upon joining. - - Args: - guild (discord.Guild): the guild that was joined - """ - embed = discord.Embed() - embed.add_field(name="Server", value=guild.name) - - log_channel = configuration.get_config_entry( - guild.id, "core_guild_events_channel" - ) - - await self.bot.logger.send_log( - message=f"Joined guild with ID {guild.id}", - level=LogLevel.INFO, - context=LogContext(guild=guild), - channel=log_channel, - embed=embed, - ) - @commands.Cog.listener() async def on_guild_update( self: Self, before: discord.Guild, after: discord.Guild @@ -964,3 +958,41 @@ async def on_disconnect(self: Self) -> None: level=LogLevel.INFO, console_only=True, ) + + @commands.Cog.listener() + async def on_guild_remove(self: Self, guild: discord.Guild) -> None: + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_remove + + Args: + guild (discord.Guild): The guild that got removed + """ + embed = discord.Embed() + embed.add_field(name="Server", value=guild.name) + await self.bot.logger.send_log( + message=f"Left guild with ID {guild.id}", + level=LogLevel.INFO, + context=LogContext(guild=guild), + embed=embed, + ) + + @commands.Cog.listener() + async def on_guild_join(self: Self, guild: discord.Guild) -> None: + """Configures a new guild upon joining. + + Args: + guild (discord.Guild): the guild that was joined + """ + embed = discord.Embed() + embed.add_field(name="Server", value=guild.name) + + log_channel = configuration.get_config_entry( + guild.id, "core_guild_events_channel" + ) + + await self.bot.logger.send_log( + message=f"Joined guild with ID {guild.id}", + level=LogLevel.INFO, + context=LogContext(guild=guild), + channel=log_channel, + embed=embed, + ) From 07695905f0f6b1d72e1ca8c1ae9ca28a5b623e9a Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Tue, 9 Jun 2026 11:39:41 -0700 Subject: [PATCH 05/24] bulk message delete rewritten --- modules/moderation/events.py | 73 ++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 20 deletions(-) diff --git a/modules/moderation/events.py b/modules/moderation/events.py index 3a7435bc..0f10929d 100644 --- a/modules/moderation/events.py +++ b/modules/moderation/events.py @@ -246,31 +246,64 @@ async def on_bulk_message_delete( Args: messages (list[discord.Message]): The messages that have been deleted """ - guild = getattr(messages[0].channel, "guild", None) - - unique_channels = set() - unique_servers = set() - for message in messages: - unique_channels.add(message.channel.name) - unique_servers.add( - f"{message.channel.guild.name} ({message.channel.guild.id})" - ) + channel = messages[0].channel + guild = channel.guild + + # Don't log stuff not in a guild + if not guild: + return embed = discord.Embed() - embed.add_field(name="Channels", value=",".join(unique_channels)) - embed.add_field(name="Servers", value=",".join(unique_servers)) - log_channel = configuration.get_config_entry( - guild.id, "core_guild_events_channel" + embed = discord.Embed( + title="Bulk Message Delete", + colour=discord.Colour.orange(), + timestamp=discord.utils.utcnow(), ) - await self.bot.logger.send_log( - message=f"{len(messages)} messages bulk deleted!", - level=LogLevel.INFO, - context=LogContext( - guild=messages[0].channel.guild, channel=messages[0].channel + + embed.add_field( + name="Channel", + value=( + f"**Channel:** {channel.mention}\n" + f"**Name:** #{channel.name}\n" + f"**ID:** {channel.id}" ), - channel=log_channel, - embed=embed, + inline=True, + ) + + description_prefix = f"{len(messages)} messages were deleted:\n" + + max_embed_length = 4096 + content_limit = 100 + + while True: + lines: list[str] = [] + + for message in messages: + clean_content = message.clean_content + + if len(clean_content) > content_limit: + clean_content = f"{clean_content[:content_limit]}..." + + lines.append(f"{message.id}, {message.author.name}: {clean_content}") + + description = description_prefix + "\n".join(lines) + + if len(description) <= max_embed_length or content_limit <= 0: + break + + content_limit -= 1 + + embed.description = description + + console_message = f"Bulk message delete: Channel: {channel.name} ({channel.id}). Amount: {len(messages)}" + + await self.send_event_log( + guild=guild, + log_location="message", + string_message=console_message, + embed_message=embed, + channel_location=channel, ) @commands.Cog.listener() From 3887b8b1ebd0f559d55bda57d52e8e4ac2569ea0 Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Tue, 9 Jun 2026 14:12:47 -0700 Subject: [PATCH 06/24] Reaction add and remove done --- modules/moderation/events.py | 187 ++++++++++++++++++++++++++--------- 1 file changed, 142 insertions(+), 45 deletions(-) diff --git a/modules/moderation/events.py b/modules/moderation/events.py index 0f10929d..fbd27d28 100644 --- a/modules/moderation/events.py +++ b/modules/moderation/events.py @@ -47,6 +47,21 @@ async def send_event_log( embed_message: discord.Embed, channel_location: discord.abc.GuildChannel = None, ) -> None: + """This sends a log to discord and the console for the event + + Args: + guild (discord.Guild): The guild the event happened in + log_location (str): The location to log, string in CONFIG_MAP + string_message (str): The string message to send to the console + embed_message (discord.Embed): The embed to send to the configured log channel + channel_location (discord.abc.GuildChannel, optional): + The channel the event happened in, if applicable. Defaults to None. + """ + + # Do nothing if events is disabled in current guild + if not self.extension_enabled(guild): + return + context = LogContext(guild=guild, channel=channel_location) message_header = f"Events for {guild.name} ({guild.id}): " log_channel = self.CONFIG_MAP[log_location] @@ -316,43 +331,84 @@ async def on_reaction_add( reaction (discord.Reaction): The current state of the reaction user (discord.Member | discord.User): The user who added the reaction """ - guild = getattr(reaction.message.channel, "guild", None) + message = reaction.message + channel = message.channel + guild = getattr(channel, "guild", None) - if isinstance(reaction.message.channel, discord.DMChannel): + if isinstance(channel, discord.DMChannel): await self.bot.logger.send_log( message=( f"PM from `{user}`: added {reaction.emoji} reaction to message" - f" {reaction.message.content} in DMs" + f" {message.content} in DMs" ), level=LogLevel.INFO, ) return - embed = discord.Embed() - embed.add_field(name="Emoji", value=reaction.emoji) - embed.add_field(name="User", value=user) - embed.add_field(name="Message", value=reaction.message.content or "None") - embed.add_field(name="Message Author", value=reaction.message.author) - embed.add_field( - name="Channel", value=getattr(reaction.message.channel, "name", "DM") + if not guild: + return + + embed = discord.Embed( + title="Reaction Added", + description=f"[Jump to Message]({message.jump_url})", + colour=discord.Colour.orange(), + timestamp=discord.utils.utcnow(), ) - embed.add_field(name="Server", value=guild.name) - log_channel = configuration.get_config_entry( - guild.id, "core_guild_events_channel" + embed.set_author( + name=str(user), + icon_url=user.display_avatar.url, ) - await self.bot.logger.send_log( - message=( - f"Reaction added to message with ID {reaction.message.id} by user with" - f" ID {user.id}" + # Do a better job at handling custom emotes + if isinstance(reaction.emoji, (discord.Emoji, discord.PartialEmoji)): + emoji_value = ( + f"**Emoji:** {reaction.emoji}\n" + f"**Name:** {reaction.emoji.name}\n" + f"**ID:** {reaction.emoji.id}" + ) + else: + emoji_value = reaction.emoji + + embed.add_field(name="Emoji", value=emoji_value) + + embed.add_field( + name="User", + value=( + f"**User:** {user.mention}\n" + f"**Name:** {user.name}\n" + f"**ID:** {user.id}" ), - level=LogLevel.INFO, - context=LogContext( - guild=reaction.message.channel.guild, channel=reaction.message.channel + inline=True, + ) + + embed.add_field( + name="Channel", + value=( + f"**Channel:** {channel.mention}\n" + f"**Name:** #{channel.name}\n" + f"**ID:** {channel.id}" ), - channel=log_channel, - embed=embed, + inline=True, + ) + + embed.add_field( + name="Message Info", + value=( + f"**Message Content:** {message.clean_content[:50]}\n" + f"**Message Author:** {message.author.name} ({message.author.mention})\n" + f"**Message ID:** {message.id}" + ), + ) + + console_message = f"Reaction {reaction.emoji} added to message with ID: {message.id} by user {user.name} ({user.id})" + + await self.send_event_log( + guild=guild, + log_location="message", + string_message=console_message, + embed_message=embed, + channel_location=channel, ) @commands.Cog.listener() @@ -365,43 +421,84 @@ async def on_reaction_remove( reaction (discord.Reaction): The current state of the reaction user (discord.Member | discord.User): The user whose reaction was removed """ - guild = getattr(reaction.message.channel, "guild", None) + message = reaction.message + channel = message.channel + guild = getattr(channel, "guild", None) - if isinstance(reaction.message.channel, discord.DMChannel): + if isinstance(channel, discord.DMChannel): await self.bot.logger.send_log( message=( - f"PM from `{user}`: removed {reaction.emoji} reaction to message" - f" {reaction.message.content} in DMs" + f"PM from `{user}`: added {reaction.emoji} reaction to message" + f" {message.content} in DMs" ), level=LogLevel.INFO, ) return - embed = discord.Embed() - embed.add_field(name="Emoji", value=reaction.emoji) - embed.add_field(name="User", value=user) - embed.add_field(name="Message", value=reaction.message.content or "None") - embed.add_field(name="Message Author", value=reaction.message.author) - embed.add_field( - name="Channel", value=getattr(reaction.message.channel, "name", "DM") + if not guild: + return + + embed = discord.Embed( + title="Reaction Removed", + description=f"[Jump to Message]({message.jump_url})", + colour=discord.Colour.orange(), + timestamp=discord.utils.utcnow(), ) - embed.add_field(name="Server", value=guild.name) - log_channel = configuration.get_config_entry( - guild.id, "core_guild_events_channel" + embed.set_author( + name=str(user), + icon_url=user.display_avatar.url, ) - await self.bot.logger.send_log( - message=( - f"Reaction removed from message with ID {reaction.message.id} by user" - f" with ID {user.id}" + # Do a better job at handling custom emotes + if isinstance(reaction.emoji, (discord.Emoji, discord.PartialEmoji)): + emoji_value = ( + f"**Emoji:** {reaction.emoji}\n" + f"**Name:** {reaction.emoji.name}\n" + f"**ID:** {reaction.emoji.id}" + ) + else: + emoji_value = reaction.emoji + + embed.add_field(name="Emoji", value=emoji_value) + + embed.add_field( + name="User", + value=( + f"**User:** {user.mention}\n" + f"**Name:** {user.name}\n" + f"**ID:** {user.id}" ), - level=LogLevel.INFO, - context=LogContext( - guild=reaction.message.channel.guild, channel=reaction.message.channel + inline=True, + ) + + embed.add_field( + name="Channel", + value=( + f"**Channel:** {channel.mention}\n" + f"**Name:** #{channel.name}\n" + f"**ID:** {channel.id}" ), - channel=log_channel, - embed=embed, + inline=True, + ) + + embed.add_field( + name="Message Info", + value=( + f"**Message Content:** {message.clean_content[:50]}\n" + f"**Message Author:** {message.author.name} ({message.author.mention})\n" + f"**Message ID:** {message.id}" + ), + ) + + console_message = f"Reaction {reaction.emoji} removed from message with ID: {message.id} by user {user.name} ({user.id})" + + await self.send_event_log( + guild=guild, + log_location="message", + string_message=console_message, + embed_message=embed, + channel_location=channel, ) @commands.Cog.listener() From 99263d112f183e9c0dbaa108659eb9e51f29fa61 Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Tue, 9 Jun 2026 16:50:47 -0700 Subject: [PATCH 07/24] Reaction clear done --- modules/moderation/events.py | 61 ++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/modules/moderation/events.py b/modules/moderation/events.py index fbd27d28..1a07eced 100644 --- a/modules/moderation/events.py +++ b/modules/moderation/events.py @@ -89,6 +89,10 @@ async def on_raw_message_edit( before = payload.cached_message after = payload.message + # If for some reason there is no after message, log nothing + if not after: + return + # Ignore message edit events for not content changes if before and before.content == after.content: return @@ -513,28 +517,53 @@ async def on_reaction_clear( reactions (list[discord.Reaction]): The reactions that were removed """ guild = getattr(message.channel, "guild", None) + channel = message.channel + + # Don't log messages without a guild + if not guild: + return - unique_emojis = set() + emoji_str = "" + total_emoji = 0 for reaction in reactions: - unique_emojis.add(reaction.emoji) + emoji_str += f"`{reaction.emoji}`: {reaction.count}\n" + total_emoji += reaction.count - embed = discord.Embed() - embed.add_field(name="Emojis", value=",".join(unique_emojis)) - embed.add_field(name="Message", value=message.content or "None") - embed.add_field(name="Message Author", value=message.author) - embed.add_field(name="Channel", value=getattr(message.channel, "name", "DM")) - embed.add_field(name="Server", value=guild.name) + embed = discord.Embed( + title="Reactions Cleared", + description=f"[Jump to Message]({message.jump_url})", + colour=discord.Colour.orange(), + timestamp=discord.utils.utcnow(), + ) + embed.add_field(name="Emojis", value=emoji_str) - log_channel = configuration.get_config_entry( - guild.id, "core_guild_events_channel" + embed.add_field( + name="Channel", + value=( + f"**Channel:** {channel.mention}\n" + f"**Name:** #{channel.name}\n" + f"**ID:** {channel.id}" + ), + inline=True, ) - await self.bot.logger.send_log( - message=f"{len(reactions)} cleared from message with ID {message.id}", - level=LogLevel.INFO, - context=LogContext(guild=message.channel.guild, channel=message.channel), - channel=log_channel, - embed=embed, + embed.add_field( + name="Message Info", + value=( + f"**Message Content:** {message.clean_content[:50]}\n" + f"**Message Author:** {message.author.name} ({message.author.mention})\n" + f"**Message ID:** {message.id}" + ), + ) + + console_message = f"{total_emoji} reactions cleared from message with ID: {message.id} in channel {channel.name} ({channel.id})" + + await self.send_event_log( + guild=guild, + log_location="message", + string_message=console_message, + embed_message=embed, + channel_location=channel, ) # Guild Events From 6e7370ad168dff5a64b215f0ae8f831cee4ea93a Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Tue, 9 Jun 2026 18:54:33 -0700 Subject: [PATCH 08/24] Work more on better events --- modules/moderation/events.py | 257 ++++++++++++++++++++--------------- 1 file changed, 150 insertions(+), 107 deletions(-) diff --git a/modules/moderation/events.py b/modules/moderation/events.py index 1a07eced..a2235c79 100644 --- a/modules/moderation/events.py +++ b/modules/moderation/events.py @@ -78,23 +78,16 @@ async def send_event_log( # MESSAGE EVENTS @commands.Cog.listener() - async def on_raw_message_edit( - self: Self, payload: discord.RawMessageUpdateEvent + async def on_message_edit( + self: Self, before: discord.Message, after: discord.Message ) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_raw_message_edit + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_message_edit Args: payload (discord.RawMessageUpdateEvent): The raw payload object for the message edit events """ - before = payload.cached_message - after = payload.message - - # If for some reason there is no after message, log nothing - if not after: - return - - # Ignore message edit events for not content changes - if before and before.content == after.content: + # If for some reason there is no message object, log nothing + if not after or not before: return guild = getattr(after.channel, "guild", None) @@ -107,80 +100,136 @@ async def on_raw_message_edit( if after.type == discord.MessageType.chat_input_command: return - embed = discord.Embed( - title="Message Edited", - description=f"[Jump to Message]({after.jump_url})", - colour=discord.Colour.orange(), - timestamp=discord.utils.utcnow(), - ) + # Message edits for content edit: + if before.content != after.content: + embed = discord.Embed( + title="Message Edited", + description=f"[Jump to Message]({after.jump_url})", + colour=discord.Colour.orange(), + timestamp=discord.utils.utcnow(), + ) - embed.set_author( - name=str(after.author), - icon_url=after.author.display_avatar.url, - ) + embed.set_author( + name=str(after.author), + icon_url=after.author.display_avatar.url, + ) - embed.add_field( - name="Author", - value=( - f"**User:** {after.author.mention}\n" - f"**Name:** {after.author}\n" - f"**ID:** {after.author.id}" - ), - inline=True, - ) + embed.add_field( + name="Author", + value=( + f"**User:** {after.author.mention}\n" + f"**Name:** {after.author}\n" + f"**ID:** {after.author.id}" + ), + inline=True, + ) - embed.add_field( - name="Channel", - value=( - f"**Channel:** {after.channel.mention}\n" - f"**Name:** #{after.channel.name}\n" - f"**ID:** {after.channel.id}" - ), - inline=True, - ) + embed.add_field( + name="Channel", + value=( + f"**Channel:** {after.channel.mention}\n" + f"**Name:** #{after.channel.name}\n" + f"**ID:** {after.channel.id}" + ), + inline=True, + ) - embed.add_field( - name="Timestamps", - value=( - f"**Sent:** " - f"()\n" - f"**Edited:** " - f"()" - ), - inline=False, - ) - if before: - old_content = before.clean_content embed.add_field( - name="Original Content", - value=before.content[:1024] if before.content else "*No content*", + name="Timestamps", + value=( + f"**Sent:** " + f"()\n" + f"**Edited:** " + f"()" + ), inline=False, ) - else: - old_content = "**Unknown. Perhaps this message was too old?**" + if before: + old_content = before.clean_content + embed.add_field( + name="Original Content", + value=before.content[:1024] if before.content else "*No content*", + inline=False, + ) + else: + old_content = "**Unknown. Perhaps this message was too old?**" + embed.add_field( + name="Original Content", + value=old_content, + inline=False, + ) + embed.add_field( - name="Original Content", - value=old_content, + name="New Content", + value=after.content[:1024] if after.content else "*No content*", inline=False, ) - embed.add_field( - name="New Content", - value=after.content[:1024] if after.content else "*No content*", - inline=False, - ) + embed.set_footer(text=f"Message ID: {after.id}") - embed.set_footer(text=f"Message ID: {after.id}") + console_message = f"Message edit: ID: {after.id} in channel: {after.channel.name} ({after.channel.id}). Old: {old_content}, new {after.clean_content}" - console_message = f"Message edit: ID: {after.id} in channel: {after.channel.name} ({after.channel.id}). Old: {old_content}, new {after.clean_content}" + await self.send_event_log( + guild=after.guild, + log_location="message", + string_message=console_message, + embed_message=embed, + channel_location=after.channel, + ) - await self.send_event_log( - guild=after.guild, - log_location="message", - string_message=console_message, - embed_message=embed, - channel_location=after.channel, - ) + # Message edits for pin update: + if before.pinned != after.pinned: + + title = "Message pinned" if after.pinned else "Message unpinned" + embed = discord.Embed( + title=title, + description=f"[Jump to Message]({after.jump_url})", + colour=discord.Colour.orange(), + timestamp=discord.utils.utcnow(), + ) + + embed.set_author( + name=str(after.author), + icon_url=after.author.display_avatar.url, + ) + + embed.add_field( + name="Message Author", + value=( + f"**User:** {after.author.mention}\n" + f"**Name:** {after.author.name}\n" + f"**ID:** {after.author.id}" + ), + inline=True, + ) + + embed.add_field( + name="Channel", + value=( + f"**Channel:** {after.channel.mention}\n" + f"**Name:** #{after.channel.name}\n" + f"**ID:** {after.channel.id}" + ), + inline=True, + ) + + embed.add_field( + name="Content", + value=after.content[:1024] if after.content else "*No content*", + inline=False, + ) + + embed.set_footer(text=f"Message ID: {after.id}") + + console_message = f"Message pins changed: ID: {after.id} in channel: {after.channel.name} ({after.channel.id}). Pinned status: {after.pinned}" + + await self.send_event_log( + guild=after.guild, + log_location="message", + string_message=console_message, + embed_message=embed, + channel_location=after.channel, + ) @commands.Cog.listener() async def on_message_delete(self: Self, message: discord.Message) -> None: @@ -568,6 +617,7 @@ async def on_reaction_clear( # Guild Events + # Useful @commands.Cog.listener() async def on_guild_channel_delete( self: Self, channel: discord.abc.GuildChannel @@ -597,6 +647,7 @@ async def on_guild_channel_delete( embed=embed, ) + # Useful @commands.Cog.listener() async def on_guild_channel_create( self: Self, channel: discord.abc.GuildChannel @@ -624,6 +675,7 @@ async def on_guild_channel_create( embed=embed, ) + # Useful @commands.Cog.listener() async def on_guild_channel_update( self: Self, before: discord.abc.GuildChannel, after: discord.abc.GuildChannel @@ -664,41 +716,7 @@ async def on_guild_channel_update( embed=embed, ) - @commands.Cog.listener() - async def on_guild_channel_pins_update( - self: Self, - channel: discord.abc.GuildChannel | discord.Thread, - _last_pin: datetime.datetime | None, - ) -> None: - """ - See: - https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_channel_pins_update - - Args: - channel (discord.abc.GuildChannel | discord.Thread): The guild channel - that had its pins updated. - _last_pin (datetime.datetime | None): The latest message that was pinned as an - aware datetime in UTC. Could be None. - """ - embed = discord.Embed() - embed.add_field(name="Channel Name", value=channel.name) - embed.add_field(name="Server", value=channel.guild) - - log_channel = configuration.get_config_entry( - channel.guild.id, "core_guild_events_channel" - ) - - await self.bot.logger.send_log( - message=( - f"Channel pins updated in channel with ID {channel.id} in guild with ID" - f" {channel.guild.id}" - ), - level=LogLevel.INFO, - context=LogContext(guild=channel.guild, channel=channel), - channel=log_channel, - embed=embed, - ) - + # Useless @commands.Cog.listener() async def on_guild_integrations_update(self: Self, guild: discord.Guild) -> None: """ @@ -721,6 +739,7 @@ async def on_guild_integrations_update(self: Self, guild: discord.Guild) -> None embed=embed, ) + # Useless @commands.Cog.listener() async def on_webhooks_update(self: Self, channel: discord.abc.GuildChannel) -> None: """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_webhooks_update @@ -747,6 +766,7 @@ async def on_webhooks_update(self: Self, channel: discord.abc.GuildChannel) -> N embed=embed, ) + # Useful @commands.Cog.listener() async def on_guild_update( self: Self, before: discord.Guild, after: discord.Guild @@ -800,6 +820,7 @@ async def on_guild_update( embed=embed, ) + # Useful @commands.Cog.listener() async def on_guild_role_create(self: Self, role: discord.Role) -> None: """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_role_create @@ -823,6 +844,7 @@ async def on_guild_role_create(self: Self, role: discord.Role) -> None: embed=embed, ) + # Useful @commands.Cog.listener() async def on_guild_role_delete(self: Self, role: discord.Role) -> None: """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_role_delete @@ -845,6 +867,7 @@ async def on_guild_role_delete(self: Self, role: discord.Role) -> None: embed=embed, ) + # Useful @commands.Cog.listener() async def on_guild_role_update( self: Self, before: discord.Role, after: discord.Role @@ -877,6 +900,7 @@ async def on_guild_role_update( embed=embed, ) + # Useful @commands.Cog.listener() async def on_guild_emojis_update( self: Self, @@ -906,6 +930,8 @@ async def on_guild_emojis_update( # Member Events + # Useful + # This will have a lot of potential things to log in it, such as roles and nickname @commands.Cog.listener() async def on_member_update( self: Self, before: discord.Member, after: discord.Member @@ -942,6 +968,7 @@ async def on_member_update( embed=embed, ) + # Useful. Should probably be turned into raw @commands.Cog.listener() async def on_member_remove(self: Self, member: discord.Member) -> None: """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_remove @@ -966,6 +993,7 @@ async def on_member_remove(self: Self, member: discord.Member) -> None: embed=embed, ) + # Maybe leave this to modlog and member remove? @commands.Cog.listener() async def on_member_ban( self: Self, guild: discord.Guild, user: discord.User | discord.Member @@ -993,6 +1021,7 @@ async def on_member_ban( embed=embed, ) + # Maybe leave this to modlog? @commands.Cog.listener() async def on_member_unban( self: Self, guild: discord.Guild, user: discord.User @@ -1019,6 +1048,7 @@ async def on_member_unban( embed=embed, ) + # Useful @commands.Cog.listener() async def on_member_join(self: Self, member: discord.Member) -> None: """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_join @@ -1155,3 +1185,16 @@ async def on_guild_join(self: Self, guild: discord.Guild) -> None: channel=log_channel, embed=embed, ) + + + +# Should probably log: +""" +Server deafen/mute (Member?) +Polls creation (Message) +Thread creation/delete (guild) - MAYBE +Automod stuff (guild) +Soundboard & stickers (guild) +Integrations (guild) +user_update (modifications to global name/username?) (member) +""" From 793900993223ccbd29da4ef938750e1b3e6a606e Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Wed, 10 Jun 2026 11:25:38 -0700 Subject: [PATCH 09/24] Standardize functions --- modules/moderation/events.py | 393 +++++++++++++---------------------- 1 file changed, 146 insertions(+), 247 deletions(-) diff --git a/modules/moderation/events.py b/modules/moderation/events.py index a2235c79..40c11753 100644 --- a/modules/moderation/events.py +++ b/modules/moderation/events.py @@ -27,6 +27,88 @@ async def setup(bot: bot.TechSupportBot) -> None: await bot.add_cog(EventLogger(bot=bot)) +class EventEmbed(discord.Embed): + """This subclass of embed contains several functions to create consistent fields for displaying various types of data in the event logs""" + + def __init__(self, *, title, description) -> None: + super().__init__( + title=title, + description=description, + colour=discord.Colour.orange(), + timestamp=discord.utils.utcnow(), + ) + + def setEventAuthor(self, author: discord.Member) -> None: + self.set_author( + name=str(author.display_name), + icon_url=author.display_avatar.url, + ) + + def addMemberField(self: Self, title: str, member: discord.Member) -> None: + self.add_field( + name=title, + value=( + f"**User:** {member.mention}\n" + f"**Name:** {member.name}\n" + f"**ID:** {member.id}" + ), + inline=True, + ) + + def addMessageContentField( + self: Self, title: str, message: discord.Message + ) -> None: + if not message.clean_content: + content = "*Non content*" + elif len(message.clean_content) > 1024: + content = message.clean_content[:1021] + "..." + else: + content = message.clean_content + self.add_field( + name=title, + value=content, + inline=True, + ) + + def addMessageInfoField(self: Self, title: str, message: discord.Message) -> None: + self.add_field( + name=title, + value=( + f"**Message Content:** {message.clean_content[:50]}\n" + f"**Message Author:** {message.author.name} ({message.author.mention})\n" + f"**Message ID:** {message.id}" + ), + ) + + def addChannelField( + self: Self, title: str, channel: discord.abc.GuildChannel + ) -> None: + self.add_field( + name=title, + value=( + f"**Channel:** {channel.mention}\n" + f"**Name:** #{channel.name}\n" + f"**ID:** {channel.id}" + ), + inline=True, + ) + + def addEmojiField( + self: Self, title: str, emoji: discord.Emoji | discord.PartialEmoji | str + ) -> None: + # This is to better display custom emotes + if isinstance(emoji, (discord.Emoji, discord.PartialEmoji)): + emoji_value = ( + f"**Emoji:** {emoji}\n" + f"**Name:** {emoji.name}\n" + f"**ID:** {emoji.id}" + ) + else: + emoji_value = f"**Emoji:** {emoji}" + + self.add_field(name=title, value=emoji_value) + + class EventLogger(cogs.BaseCog): """This is the cog that holds all of the discord event listeners For the explicit purpose of logging, not taking further action @@ -102,38 +184,20 @@ async def on_message_edit( # Message edits for content edit: if before.content != after.content: - embed = discord.Embed( + embed = EventEmbed( title="Message Edited", description=f"[Jump to Message]({after.jump_url})", - colour=discord.Colour.orange(), - timestamp=discord.utils.utcnow(), ) - embed.set_author( - name=str(after.author), - icon_url=after.author.display_avatar.url, - ) + embed.setEventAuthor(after.author) + embed.addMemberField("Message Author", after.author) + embed.addChannelField("Channel", after.channel) - embed.add_field( - name="Author", - value=( - f"**User:** {after.author.mention}\n" - f"**Name:** {after.author}\n" - f"**ID:** {after.author.id}" - ), - inline=True, - ) - - embed.add_field( - name="Channel", - value=( - f"**Channel:** {after.channel.mention}\n" - f"**Name:** #{after.channel.name}\n" - f"**ID:** {after.channel.id}" - ), - inline=True, - ) + old_content = before.clean_content + embed.addMessageContentField("Old Content", before) + embed.addMessageContentField("New Content", after) + # Custom field for this event embed.add_field( name="Timestamps", value=( @@ -144,26 +208,6 @@ async def on_message_edit( ), inline=False, ) - if before: - old_content = before.clean_content - embed.add_field( - name="Original Content", - value=before.content[:1024] if before.content else "*No content*", - inline=False, - ) - else: - old_content = "**Unknown. Perhaps this message was too old?**" - embed.add_field( - name="Original Content", - value=old_content, - inline=False, - ) - - embed.add_field( - name="New Content", - value=after.content[:1024] if after.content else "*No content*", - inline=False, - ) embed.set_footer(text=f"Message ID: {after.id}") @@ -181,43 +225,15 @@ async def on_message_edit( if before.pinned != after.pinned: title = "Message pinned" if after.pinned else "Message unpinned" - embed = discord.Embed( + embed = EventEmbed( title=title, description=f"[Jump to Message]({after.jump_url})", - colour=discord.Colour.orange(), - timestamp=discord.utils.utcnow(), - ) - - embed.set_author( - name=str(after.author), - icon_url=after.author.display_avatar.url, - ) - - embed.add_field( - name="Message Author", - value=( - f"**User:** {after.author.mention}\n" - f"**Name:** {after.author.name}\n" - f"**ID:** {after.author.id}" - ), - inline=True, - ) - - embed.add_field( - name="Channel", - value=( - f"**Channel:** {after.channel.mention}\n" - f"**Name:** #{after.channel.name}\n" - f"**ID:** {after.channel.id}" - ), - inline=True, ) - embed.add_field( - name="Content", - value=after.content[:1024] if after.content else "*No content*", - inline=False, - ) + embed.setEventAuthor(after.author) + embed.addMemberField("Message Author", after.author) + embed.addChannelField("Channel", after.channel) + embed.addMessageContentField("Content", after) embed.set_footer(text=f"Message ID: {after.id}") @@ -245,37 +261,15 @@ async def on_message_delete(self: Self, message: discord.Message) -> None: if message.type == discord.MessageType.chat_input_command: return - embed = discord.Embed( + embed = EventEmbed( title="Message Deleted", description=f"[Jump to Message]({message.jump_url})", - colour=discord.Colour.orange(), - timestamp=discord.utils.utcnow(), ) - embed.set_author( - name=str(message.author), - icon_url=message.author.display_avatar.url, - ) - - embed.add_field( - name="Author", - value=( - f"**User:** {message.author.mention}\n" - f"**Name:** {message.author}\n" - f"**ID:** {message.author.id}" - ), - inline=True, - ) - - embed.add_field( - name="Channel", - value=( - f"**Channel:** {channel.mention}\n" - f"**Name:** #{channel.name}\n" - f"**ID:** {channel.id}" - ), - inline=True, - ) + embed.setEventAuthor(message.author) + embed.addMemberField("Message Author", message.author) + embed.addChannelField("Channel", message.channel) + embed.addMessageContentField("Content", message) embed.add_field( name="Timestamps", @@ -286,12 +280,6 @@ async def on_message_delete(self: Self, message: discord.Message) -> None: inline=False, ) - embed.add_field( - name="Message Content", - value=message.content[:1024] if message.content else "*No content*", - inline=False, - ) - embed.set_footer(text=f"Message ID: {message.id}") console_message = f"Message delete: ID: {message.id} in channel: {channel.name} ({channel.id}). Content: {message.clean_content}" @@ -321,23 +309,11 @@ async def on_bulk_message_delete( if not guild: return - embed = discord.Embed() - - embed = discord.Embed( + embed = EventEmbed( title="Bulk Message Delete", - colour=discord.Colour.orange(), - timestamp=discord.utils.utcnow(), - ) - - embed.add_field( - name="Channel", - value=( - f"**Channel:** {channel.mention}\n" - f"**Name:** #{channel.name}\n" - f"**ID:** {channel.id}" - ), - inline=True, + description="", ) + embed.addChannelField("Channel", channel) description_prefix = f"{len(messages)} messages were deleted:\n" @@ -401,58 +377,16 @@ async def on_reaction_add( if not guild: return - embed = discord.Embed( + embed = EventEmbed( title="Reaction Added", description=f"[Jump to Message]({message.jump_url})", - colour=discord.Colour.orange(), - timestamp=discord.utils.utcnow(), - ) - - embed.set_author( - name=str(user), - icon_url=user.display_avatar.url, - ) - - # Do a better job at handling custom emotes - if isinstance(reaction.emoji, (discord.Emoji, discord.PartialEmoji)): - emoji_value = ( - f"**Emoji:** {reaction.emoji}\n" - f"**Name:** {reaction.emoji.name}\n" - f"**ID:** {reaction.emoji.id}" - ) - else: - emoji_value = reaction.emoji - - embed.add_field(name="Emoji", value=emoji_value) - - embed.add_field( - name="User", - value=( - f"**User:** {user.mention}\n" - f"**Name:** {user.name}\n" - f"**ID:** {user.id}" - ), - inline=True, - ) - - embed.add_field( - name="Channel", - value=( - f"**Channel:** {channel.mention}\n" - f"**Name:** #{channel.name}\n" - f"**ID:** {channel.id}" - ), - inline=True, ) - embed.add_field( - name="Message Info", - value=( - f"**Message Content:** {message.clean_content[:50]}\n" - f"**Message Author:** {message.author.name} ({message.author.mention})\n" - f"**Message ID:** {message.id}" - ), - ) + embed.setEventAuthor(user) + embed.addEmojiField("Emoji", reaction.emoji) + embed.addMemberField("Message Author", user) + embed.addChannelField("Channel", message.channel) + embed.addMessageInfoField("Message Info", message) console_message = f"Reaction {reaction.emoji} added to message with ID: {message.id} by user {user.name} ({user.id})" @@ -491,58 +425,16 @@ async def on_reaction_remove( if not guild: return - embed = discord.Embed( + embed = EventEmbed( title="Reaction Removed", description=f"[Jump to Message]({message.jump_url})", - colour=discord.Colour.orange(), - timestamp=discord.utils.utcnow(), - ) - - embed.set_author( - name=str(user), - icon_url=user.display_avatar.url, ) - # Do a better job at handling custom emotes - if isinstance(reaction.emoji, (discord.Emoji, discord.PartialEmoji)): - emoji_value = ( - f"**Emoji:** {reaction.emoji}\n" - f"**Name:** {reaction.emoji.name}\n" - f"**ID:** {reaction.emoji.id}" - ) - else: - emoji_value = reaction.emoji - - embed.add_field(name="Emoji", value=emoji_value) - - embed.add_field( - name="User", - value=( - f"**User:** {user.mention}\n" - f"**Name:** {user.name}\n" - f"**ID:** {user.id}" - ), - inline=True, - ) - - embed.add_field( - name="Channel", - value=( - f"**Channel:** {channel.mention}\n" - f"**Name:** #{channel.name}\n" - f"**ID:** {channel.id}" - ), - inline=True, - ) - - embed.add_field( - name="Message Info", - value=( - f"**Message Content:** {message.clean_content[:50]}\n" - f"**Message Author:** {message.author.name} ({message.author.mention})\n" - f"**Message ID:** {message.id}" - ), - ) + embed.setEventAuthor(user) + embed.addEmojiField("Emoji", reaction.emoji) + embed.addMemberField("Message Author", user) + embed.addChannelField("Channel", message.channel) + embed.addMessageInfoField("Message Info", message) console_message = f"Reaction {reaction.emoji} removed from message with ID: {message.id} by user {user.name} ({user.id})" @@ -578,32 +470,15 @@ async def on_reaction_clear( emoji_str += f"`{reaction.emoji}`: {reaction.count}\n" total_emoji += reaction.count - embed = discord.Embed( + embed = EventEmbed( title="Reactions Cleared", description=f"[Jump to Message]({message.jump_url})", - colour=discord.Colour.orange(), - timestamp=discord.utils.utcnow(), ) + embed.add_field(name="Emojis", value=emoji_str) - embed.add_field( - name="Channel", - value=( - f"**Channel:** {channel.mention}\n" - f"**Name:** #{channel.name}\n" - f"**ID:** {channel.id}" - ), - inline=True, - ) - - embed.add_field( - name="Message Info", - value=( - f"**Message Content:** {message.clean_content[:50]}\n" - f"**Message Author:** {message.author.name} ({message.author.mention})\n" - f"**Message ID:** {message.id}" - ), - ) + embed.addChannelField("Channel", message.channel) + embed.addMessageInfoField("Message Info", message) console_message = f"{total_emoji} reactions cleared from message with ID: {message.id} in channel {channel.name} ({channel.id})" @@ -1187,14 +1062,38 @@ async def on_guild_join(self: Self, guild: discord.Guild) -> None: ) - # Should probably log: """ -Server deafen/mute (Member?) Polls creation (Message) + discord.on_poll_vote_add + discord.on_poll_vote_remove + +Server deafen/mute (Member?) + discord.on_voice_state_update +user_update (modifications to global name/username?) (member) (maybe?) + discord.on_user_update + Thread creation/delete (guild) - MAYBE + discord.on_thread_create + discord.on_thread_update + discord.on_thread_delete Automod stuff (guild) + discord.on_automod_rule_create + discord.on_automod_rule_update + discord.on_automod_rule_delete Soundboard & stickers (guild) + discord.on_soundboard_sound_create + discord.on_soundboard_sound_delete + discord.on_soundboard_sound_update + discord.on_guild_stickers_update Integrations (guild) -user_update (modifications to global name/username?) (member) + discord.on_integration_create + discord.on_integration_update +Invites (Guild) + discord.on_invite_create + discord.on_invite_delete +Scheduled Events (guild) + discord.on_scheduled_event_create + discord.on_scheduled_event_delete + discord.on_scheduled_event_update """ From 08380604731aed39070ef07b27a2b0fcba6521aa Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Wed, 10 Jun 2026 13:40:37 -0700 Subject: [PATCH 10/24] Poll vote add/remove --- modules/moderation/events.py | 96 ++++++++++++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 5 deletions(-) diff --git a/modules/moderation/events.py b/modules/moderation/events.py index 40c11753..35e5d1f7 100644 --- a/modules/moderation/events.py +++ b/modules/moderation/events.py @@ -59,7 +59,7 @@ def addMessageContentField( self: Self, title: str, message: discord.Message ) -> None: if not message.clean_content: - content = "*Non content*" + content = "*No content*" elif len(message.clean_content) > 1024: content = message.clean_content[:1021] + "..." else: @@ -76,7 +76,9 @@ def addMessageInfoField(self: Self, title: str, message: discord.Message) -> Non value=( f"**Message Content:** {message.clean_content[:50]}\n" f"**Message Author:** {message.author.name} ({message.author.mention})\n" - f"**Message ID:** {message.id}" + f"**Message ID:** {message.id}\n" + f"**Sent:** " + f"()" ), ) @@ -108,6 +110,24 @@ def addEmojiField( self.add_field(name=title, value=emoji_value) + def addPollField(self: Self, title: str, poll: discord.Poll) -> None: + self.add_field( + name=title, + value=( + f"**Question:** {poll.question}\n" + f"**Duration:** {poll.duration}\n" + f"**Answers:** {', '.join([answer.text for answer in poll.answers])}" + ), + inline=True, + ) + + def addPollAnswerField(self: Self, title: str, answer: discord.PollAnswer) -> None: + self.add_field( + name=title, + value=(f"**Answer:** {answer.text}\n**ID:** {answer.id}"), + inline=True, + ) + class EventLogger(cogs.BaseCog): """This is the cog that holds all of the discord event listeners @@ -272,10 +292,10 @@ async def on_message_delete(self: Self, message: discord.Message) -> None: embed.addMessageContentField("Content", message) embed.add_field( - name="Timestamps", + name="Message Sent", value=( - f"**Sent:** " - f"()\n" + f" " + f"()" ), inline=False, ) @@ -490,6 +510,72 @@ async def on_reaction_clear( channel_location=channel, ) + @commands.Cog.listener() + async def on_poll_vote_add( + self: Self, user: discord.Member, answer: discord.PollAnswer + ) -> None: + if not user.guild: + return + + guild = user.guild + message = answer.poll.message + channel = message.channel + + embed = EventEmbed( + title="Poll Answered", + description=f"[Jump to Message]({message.jump_url})", + ) + + embed.setEventAuthor(user) + embed.addPollField("Poll", answer.poll) + embed.addPollAnswerField("Answer", answer) + embed.addChannelField("Channel", channel) + embed.addMemberField("Member", user) + embed.addMessageInfoField("Message", message) + + console_message = f"User {user.name} ({user.id}) voted {answer.text} to poll message {message.id} in channel {channel.name} ({channel.id})" + + await self.send_event_log( + guild=guild, + log_location="message", + string_message=console_message, + embed_message=embed, + channel_location=channel, + ) + + @commands.Cog.listener() + async def on_poll_vote_remove( + self: Self, user: discord.Member, answer: discord.PollAnswer + ) -> None: + if not user.guild: + return + + guild = user.guild + message = answer.poll.message + channel = message.channel + + embed = EventEmbed( + title="Poll Answer Removed", + description=f"[Jump to Message]({message.jump_url})", + ) + + embed.setEventAuthor(user) + embed.addPollField("Poll", answer.poll) + embed.addPollAnswerField("Answer", answer) + embed.addChannelField("Channel", channel) + embed.addMemberField("Member", user) + embed.addMessageInfoField("Message", message) + + console_message = f"User {user.name} ({user.id}) removed vote {answer.text} from a poll message {message.id} in channel {channel.name} ({channel.id})" + + await self.send_event_log( + guild=guild, + log_location="message", + string_message=console_message, + embed_message=embed, + channel_location=channel, + ) + # Guild Events # Useful From 29f3876de766904fe995f5023092a09be91c2c7d Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Wed, 10 Jun 2026 15:56:22 -0700 Subject: [PATCH 11/24] Do all the member events --- modules/moderation/events.py | 414 +++++++++++++++++++++-------------- 1 file changed, 250 insertions(+), 164 deletions(-) diff --git a/modules/moderation/events.py b/modules/moderation/events.py index 35e5d1f7..0fc62864 100644 --- a/modules/moderation/events.py +++ b/modules/moderation/events.py @@ -13,6 +13,7 @@ import configuration from botlogging import LogContext, LogLevel from core import auxiliary, cogs +from modules.moderation import logger if TYPE_CHECKING: import bot @@ -177,7 +178,7 @@ async def send_event_log( embed_as_is=True, ) - # MESSAGE EVENTS + # Message events @commands.Cog.listener() async def on_message_edit( @@ -205,7 +206,7 @@ async def on_message_edit( # Message edits for content edit: if before.content != after.content: embed = EventEmbed( - title="Message Edited", + title="Message edited", description=f"[Jump to Message]({after.jump_url})", ) @@ -282,7 +283,7 @@ async def on_message_delete(self: Self, message: discord.Message) -> None: return embed = EventEmbed( - title="Message Deleted", + title="Message deleted", description=f"[Jump to Message]({message.jump_url})", ) @@ -330,7 +331,7 @@ async def on_bulk_message_delete( return embed = EventEmbed( - title="Bulk Message Delete", + title="Bulk message delete", description="", ) embed.addChannelField("Channel", channel) @@ -398,7 +399,7 @@ async def on_reaction_add( return embed = EventEmbed( - title="Reaction Added", + title="Reaction added", description=f"[Jump to Message]({message.jump_url})", ) @@ -446,7 +447,7 @@ async def on_reaction_remove( return embed = EventEmbed( - title="Reaction Removed", + title="Reaction removed", description=f"[Jump to Message]({message.jump_url})", ) @@ -491,7 +492,7 @@ async def on_reaction_clear( total_emoji += reaction.count embed = EventEmbed( - title="Reactions Cleared", + title="Reactions cleared", description=f"[Jump to Message]({message.jump_url})", ) @@ -522,7 +523,7 @@ async def on_poll_vote_add( channel = message.channel embed = EventEmbed( - title="Poll Answered", + title="Poll answered", description=f"[Jump to Message]({message.jump_url})", ) @@ -555,7 +556,7 @@ async def on_poll_vote_remove( channel = message.channel embed = EventEmbed( - title="Poll Answer Removed", + title="Poll answer removed", description=f"[Jump to Message]({message.jump_url})", ) @@ -576,7 +577,246 @@ async def on_poll_vote_remove( channel_location=channel, ) - # Guild Events + # Member events + + @commands.Cog.listener() + async def on_member_join(self: Self, member: discord.Member) -> None: + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_join + + Args: + member (discord.Member): The member who joined + """ + embed = EventEmbed( + title="Member joined", + description="", + ) + + embed.setEventAuthor(member) + embed.addMemberField("New Member", member) + + if member.flags.did_rejoin: + embed.set_footer(text="This user has joined this server before") + + console_message = f"Member joined: {member.name} ({member.id})" + + await self.send_event_log( + guild=member.guild, + log_location="member", + string_message=console_message, + embed_message=embed, + ) + + @commands.Cog.listener() + async def on_raw_member_remove( + self: Self, payload: discord.RawMemberRemoveEvent + ) -> None: + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_remove + + Args: + member (discord.Member): The member who left + """ + member = payload.user + embed = EventEmbed( + title="Member left", + description="", + ) + + embed.setEventAuthor(member) + embed.addMemberField("Member", member) + + if isinstance(member, discord.Member): + embed.add_field( + name="Joined at", + value=( + f" " + f"()" + ), + ) + embed.add_field( + name="Roles", + value=", ".join(logger.generate_role_list(member)), + ) + + # If member object, show roles and date joined? + + console_message = f"Member left: {member.name} ({member.id})" + + await self.send_event_log( + guild=member.guild, + log_location="member", + string_message=console_message, + embed_message=embed, + ) + + @commands.Cog.listener() + async def on_voice_state_update( + self: Self, + member: discord.Member, + before: discord.VoiceState, + after: discord.VoiceState, + ) -> None: + # We need to handle server deafen and server mute + if before.mute != after.mute: + embed = EventEmbed( + title=f"Member Server {'un' if before.mute else ''}muted", + description="", + ) + + embed.setEventAuthor(member) + embed.addMemberField("Member", member) + + if after.channel: + embed.addChannelField("Current Channel", after.channel) + + console_message = f"{embed.title}: {member.name} ({member.id})" + + await self.send_event_log( + guild=member.guild, + log_location="member", + string_message=console_message, + embed_message=embed, + ) + + if before.deaf != after.deaf: + embed = EventEmbed( + title=f"Member Server {'un' if before.deaf else ''}deafened", + description="", + ) + + embed.setEventAuthor(member) + embed.addMemberField("Member", member) + + if after.channel: + embed.addChannelField("Current Channel", after.channel) + + console_message = f"{embed.title}: {member.name} ({member.id})" + + await self.send_event_log( + guild=member.guild, + log_location="member", + string_message=console_message, + embed_message=embed, + ) + + @commands.Cog.listener() + async def on_user_update( + self: Self, before: discord.User, after: discord.User + ) -> None: + # We want to track name and global name changes + if before.name != after.name: + embed = EventEmbed( + title="Member username changed", + description="", + ) + + embed.setEventAuthor(after) + embed.addMemberField("Member", after) + embed.add_field( + name="name:", value=f"**Old:** {before.name}\n**New:** {after.name}" + ) + + console_message = f"Member changed their name: {after.name} ({after.id})" + + for guild in after.mutual_guilds: + await self.send_event_log( + guild=guild, + log_location="member", + string_message=console_message, + embed_message=embed, + ) + + if before.global_name != after.global_name: + embed = EventEmbed( + title="Member global name changed", + description="", + ) + + embed.setEventAuthor(after) + embed.addMemberField("Member", after) + embed.add_field( + name="global_name:", + value=f"**Old:** {before.global_name}\n**New:** {after.global_name}", + ) + + console_message = ( + f"Member changed their global_name: {after.name} ({after.id})" + ) + + for guild in after.mutual_guilds: + await self.send_event_log( + guild=guild, + log_location="member", + string_message=console_message, + embed_message=embed, + ) + + @commands.Cog.listener() + async def on_member_update( + self: Self, before: discord.Member, after: discord.Member + ) -> None: + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_update + + Args: + before (discord.Member): The updated member's old info + after (discord.Member): Teh updated member's new info + """ + # We want to track role and nickname changes + + if before.nick != after.nick: + embed = EventEmbed( + title="Member nickname changed", + description="", + ) + + embed.setEventAuthor(after) + embed.addMemberField("Member", after) + embed.add_field( + name="nick:", + value=f"**Old:** {before.nick}\n**New:** {after.nick}", + ) + + console_message = f"Member changed their nick: {after.name} ({after.id})" + + await self.send_event_log( + guild=after.guild, + log_location="member", + string_message=console_message, + embed_message=embed, + ) + + roles_lost = set(before.roles) - set(after.roles) + roles_gained = set(after.roles) - set(before.roles) + changed_role = set(before.roles) ^ set(after.roles) + if changed_role: + embed = EventEmbed( + title="Member roles updated", + description="", + ) + embed.setEventAuthor(after) + embed.addMemberField("Member", after) + + if roles_gained: + embed.add_field( + name="Roles added", + value=", ".join([role.mention for role in roles_gained]), + ) + + if roles_lost: + embed.add_field( + name="Roles removed", + value=", ".join([role.mention for role in roles_lost]), + ) + + console_message = f"Member roles updated: {after.name} ({after.id}). Roles changed {', '.join(role.name for role in changed_role)}" + + await self.send_event_log( + guild=after.guild, + log_location="member", + string_message=console_message, + embed_message=embed, + ) + + # Guild events # Useful @commands.Cog.listener() @@ -889,151 +1129,6 @@ async def on_guild_emojis_update( embed=embed, ) - # Member Events - - # Useful - # This will have a lot of potential things to log in it, such as roles and nickname - @commands.Cog.listener() - async def on_member_update( - self: Self, before: discord.Member, after: discord.Member - ) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_update - - Args: - before (discord.Member): The updated member's old info - after (discord.Member): Teh updated member's new info - """ - changed_role = set(before.roles) ^ set(after.roles) - if changed_role: - if len(before.roles) < len(after.roles): - embed = discord.Embed() - embed.add_field(name="Roles added", value=next(iter(changed_role))) - embed.add_field(name="Server", value=before.guild.name) - else: - embed = discord.Embed() - embed.add_field(name="Roles lost", value=next(iter(changed_role))) - embed.add_field(name="Server", value=before.guild.name) - - log_channel = configuration.get_config_entry( - before.guild.id, "core_member_events_channel" - ) - - await self.bot.logger.send_log( - message=( - f"Member with ID {before.id} has changed status in guild with ID" - f" {before.guild.id}" - ), - level=LogLevel.INFO, - context=LogContext(guild=before.guild), - channel=log_channel, - embed=embed, - ) - - # Useful. Should probably be turned into raw - @commands.Cog.listener() - async def on_member_remove(self: Self, member: discord.Member) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_remove - - Args: - member (discord.Member): The member who left - """ - embed = discord.Embed() - embed.add_field(name="Member", value=member) - embed.add_field(name="Server", value=member.guild.name) - log_channel = configuration.get_config_entry( - member.guild.id, "core_member_events_channel" - ) - - await self.bot.logger.send_log( - message=( - f"Member with ID {member.id} has left guild with ID {member.guild.id}" - ), - level=LogLevel.INFO, - context=LogContext(guild=member.guild), - channel=log_channel, - embed=embed, - ) - - # Maybe leave this to modlog and member remove? - @commands.Cog.listener() - async def on_member_ban( - self: Self, guild: discord.Guild, user: discord.User | discord.Member - ) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_ban - - Args: - guild (discord.Guild): The guild the user got banned from - user (discord.User | discord.Member): The user that got banned. Can be either User - or Member depending if the user was in the guild or not at the time of removal. - """ - embed = discord.Embed() - embed.add_field(name="User", value=user) - embed.add_field(name="Server", value=guild.name) - - log_channel = configuration.get_config_entry( - guild.id, "core_member_events_channel" - ) - - await self.bot.logger.send_log( - message=f"User with ID {user.id} banned from guild with ID {guild.id}", - level=LogLevel.INFO, - context=LogContext(guild=guild), - channel=log_channel, - embed=embed, - ) - - # Maybe leave this to modlog? - @commands.Cog.listener() - async def on_member_unban( - self: Self, guild: discord.Guild, user: discord.User - ) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_unban - - Args: - guild (discord.Guild): The guild the user got unbanned from - user (discord.User): The user that got unbanned - """ - embed = discord.Embed() - embed.add_field(name="User", value=user) - embed.add_field(name="Server", value=guild.name) - - log_channel = configuration.get_config_entry( - guild.id, "core_member_events_channel" - ) - - await self.bot.logger.send_log( - message=f"User with ID {user.id} unbanned from guild with ID {guild.id}", - level=LogLevel.INFO, - context=LogContext(guild=guild), - channel=log_channel, - embed=embed, - ) - - # Useful - @commands.Cog.listener() - async def on_member_join(self: Self, member: discord.Member) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_join - - Args: - member (discord.Member): The member who joined - """ - embed = discord.Embed() - embed.add_field(name="Member", value=member) - embed.add_field(name="Server", value=member.guild.name) - log_channel = configuration.get_config_entry( - member.guild.id, "core_member_events_channel" - ) - - await self.bot.logger.send_log( - message=( - f"Member with ID {member.id} has joined guild with ID {member.guild.id}" - ), - level=LogLevel.INFO, - context=LogContext(guild=member.guild), - channel=log_channel, - embed=embed, - ) - # Bot Events @commands.Cog.listener() @@ -1150,15 +1245,6 @@ async def on_guild_join(self: Self, guild: discord.Guild) -> None: # Should probably log: """ -Polls creation (Message) - discord.on_poll_vote_add - discord.on_poll_vote_remove - -Server deafen/mute (Member?) - discord.on_voice_state_update -user_update (modifications to global name/username?) (member) (maybe?) - discord.on_user_update - Thread creation/delete (guild) - MAYBE discord.on_thread_create discord.on_thread_update From 5e1136751d0903d5ba3b2cdad4b7e31e0e705592 Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Wed, 10 Jun 2026 18:55:29 -0700 Subject: [PATCH 12/24] AddMoreEvents --- modules/moderation/events.py | 374 ++++++++++++++++++++++++----------- 1 file changed, 254 insertions(+), 120 deletions(-) diff --git a/modules/moderation/events.py b/modules/moderation/events.py index 0fc62864..494e2e01 100644 --- a/modules/moderation/events.py +++ b/modules/moderation/events.py @@ -5,7 +5,7 @@ import datetime import sys from collections.abc import Sequence -from typing import TYPE_CHECKING, Self +from typing import TYPE_CHECKING, Any, Self import discord from discord.ext import commands @@ -91,6 +91,7 @@ def addChannelField( value=( f"**Channel:** {channel.mention}\n" f"**Name:** #{channel.name}\n" + f"**Type:** {channel.type.name}\n" f"**ID:** {channel.id}" ), inline=True, @@ -129,6 +130,51 @@ def addPollAnswerField(self: Self, title: str, answer: discord.PollAnswer) -> No inline=True, ) + def addPropertyChangeFields( + self: Self, properties: list[str], before: Any, after: Any + ) -> bool: + changes = [] + + for attr in properties: + old_value = getattr(before, attr, None) + new_value = getattr(after, attr, None) + + # If both are lists, sort them before comparing + if isinstance(old_value, list) and isinstance(new_value, list): + old_compare = sorted(old_value, key=str) + new_compare = sorted(new_value, key=str) + else: + old_compare = old_value + new_compare = new_value + + if old_compare != new_compare: + changes.append((attr, old_value, new_value)) + + if changes: + for attr, old_value, new_value in changes: + # Make the property name prettier + field_name = attr.replace("_", " ").title() + + # Special formatting for categories + if attr == "category": + old_value = old_value.mention if old_value else "None" + new_value = new_value.mention if new_value else "None" + + # Better formatting for booleans + elif isinstance(old_value, bool): + old_value = "Yes" if old_value else "No" + new_value = "Yes" if new_value else "No" + + self.add_field( + name=field_name, + value=f"**Old:** {old_value}\n**New:** {new_value}", + inline=True, + ) + + return True + + return False + class EventLogger(cogs.BaseCog): """This is the cog that holds all of the discord event listeners @@ -818,62 +864,59 @@ async def on_member_update( # Guild events - # Useful @commands.Cog.listener() - async def on_guild_channel_delete( + async def on_guild_channel_create( self: Self, channel: discord.abc.GuildChannel ) -> None: """ - See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_channel_delete + See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_channel_create Args: - channel (discord.abc.GuildChannel): The channel that got deleted + channel (discord.abc.GuildChannel): The channel that got created """ - embed = discord.Embed() - embed.add_field(name="Channel Name", value=channel.name) - embed.add_field(name="Server", value=channel.guild.name) - log_channel = configuration.get_config_entry( - channel.guild.id, "core_guild_events_channel" + embed = EventEmbed( + title="Channel created", + description=f"", ) - await self.bot.logger.send_log( - message=( - f"Channel with ID {channel.id} deleted in guild with ID" - f" {channel.guild.id}" - ), - level=LogLevel.INFO, - context=LogContext(guild=channel.guild, channel=channel), - channel=log_channel, - embed=embed, + embed.addChannelField("Channel", channel) + + console_message = f"Channel {channel.name} ({channel.id}) was created" + + await self.send_event_log( + guild=channel.guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + channel_location=channel, ) - # Useful @commands.Cog.listener() - async def on_guild_channel_create( + async def on_guild_channel_delete( self: Self, channel: discord.abc.GuildChannel ) -> None: """ - See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_channel_create + See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_channel_delete Args: - channel (discord.abc.GuildChannel): The channel that got created + channel (discord.abc.GuildChannel): The channel that got deleted """ - embed = discord.Embed() - embed.add_field(name="Channel Name", value=channel.name) - embed.add_field(name="Server", value=channel.guild.name) - log_channel = configuration.get_config_entry( - channel.guild.id, "core_guild_events_channel" + embed = EventEmbed( + title="Channel deleted", + description=f"", ) - await self.bot.logger.send_log( - message=( - f"Channel with ID {channel.id} created in guild with ID" - f" {channel.guild.id}" - ), - level=LogLevel.INFO, - context=LogContext(guild=channel.guild, channel=channel), - channel=log_channel, - embed=embed, + + embed.addChannelField("Channel", channel) + + console_message = f"Channel {channel.name} ({channel.id}) was deleted" + + await self.send_event_log( + guild=channel.guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + channel_location=channel, ) # Useful @@ -888,84 +931,143 @@ async def on_guild_channel_update( before (discord.abc.GuildChannel): The updated guild channel's old info after (discord.abc.GuildChannel): The updated guild channel's new info """ - attrs = [ - "category", - "changed_roles", - "name", - "overwrites", - "permissions_synced", - "position", - ] - diff = auxiliary.get_object_diff(before, after, attrs) - embed = discord.Embed() - embed = auxiliary.add_diff_fields(embed, diff) - embed.add_field(name="Channel Name", value=before.name) - embed.add_field(name="Server", value=before.guild.name) + # This is hell. Thanks claude + if before.overwrites != after.overwrites: + embed = EventEmbed( + title="Channel permissions updated", + description="", + ) - log_channel = configuration.get_config_entry( - before.guild.id, "core_guild_events_channel" - ) - await self.bot.logger.send_log( - message=( - f"Channel with ID {before.id} modified in guild with ID" - f" {before.guild.id}" - ), - level=LogLevel.INFO, - context=LogContext(guild=before.guild, channel=before), - channel=log_channel, - embed=embed, - ) + embed.addChannelField("Channel", after) - # Useless - @commands.Cog.listener() - async def on_guild_integrations_update(self: Self, guild: discord.Guild) -> None: - """ - See: - https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_integrations_update + console_changes: list[str] = [] - Args: - guild (discord.Guild): The guild that had its integrations updated. - """ - embed = discord.Embed() - embed.add_field(name="Server", value=guild) - log_channel = configuration.get_config_entry( - guild.id, "core_guild_events_channel" - ) - await self.bot.logger.send_log( - message=f"Integrations updated in guild with ID {guild.id}", - level=LogLevel.INFO, - context=LogContext(guild=guild), - channel=log_channel, - embed=embed, - ) + all_targets = set(before.overwrites) | set(after.overwrites) - # Useless - @commands.Cog.listener() - async def on_webhooks_update(self: Self, channel: discord.abc.GuildChannel) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_webhooks_update + for target in all_targets: + before_overwrite = before.overwrites.get( + target, discord.PermissionOverwrite() + ) + after_overwrite = after.overwrites.get( + target, discord.PermissionOverwrite() + ) - Args: - channel (discord.abc.GuildChannel): The channel that had its webhooks updated. - """ - embed = discord.Embed() - embed.add_field(name="Channel", value=channel.name) - embed.add_field(name="Server", value=channel.guild) + before_perms = dict(before_overwrite) + after_perms = dict(after_overwrite) - log_channel = configuration.get_config_entry( - channel.guild.id, "core_guild_events_channel" - ) + added: list[str] = [] + removed: list[str] = [] + changed: list[str] = [] - await self.bot.logger.send_log( - message=( - f"Webooks updated for channel with ID {channel.id} in guild with ID" - f" {channel.guild.id}" - ), - level=LogLevel.INFO, - context=LogContext(guild=channel.guild, channel=channel), - channel=log_channel, - embed=embed, - ) + all_permissions = set(before_perms) | set(after_perms) + + for permission in sorted(all_permissions): + old = before_perms.get(permission) + new = after_perms.get(permission) + + if old == new: + continue + + if old is None: + added.append( + f"✅ `{permission.replace('_', ' ').title()}` → {new}" + ) + elif new is None: + removed.append( + f"❌ `{permission.replace('_', ' ').title()}` (was {old})" + ) + else: + old_emoji = "✅" if old else "❌" + new_emoji = "✅" if new else "❌" + + changed.append( + f"➖ `{permission.replace('_', ' ').title()}` " + f"{old_emoji} → {new_emoji}" + ) + + if not (added or removed or changed): + continue + + value_parts = [] + + if isinstance(target, discord.Role): + target_name = f"Role:" + value_parts.append(f"{target.mention}") + elif isinstance(target, discord.Member): + target_name = f"Member:" + value_parts.append(f"{target.mention}") + else: + target_name = f"Unknown:" + value_parts.append(f"{target.id}") + + if added: + value_parts.append("**Added**\n" + "\n".join(added)) + + if removed: + value_parts.append("**Removed**\n" + "\n".join(removed)) + + if changed: + value_parts.append("**Changed**\n" + "\n".join(changed)) + + value = "\n\n".join(value_parts) + + # Discord field value limit + if len(value) > 1024: + value = value[:1021] + "..." + + embed.add_field( + name=target_name, + value=value, + inline=False, + ) + + console_changes.append(target_name) + + if not console_changes: + return + + console_message = ( + f"Permission overwrites updated for channel " + f"{after.name} ({after.id})" + ) + + await self.send_event_log( + guild=after.guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + channel_location=after, + ) + + properties_to_track = [ + "category", + "name", + "permissions_synced", + "position", + "topic", + "slowmode_delay", + "bitrate", + "user_limit", + "nsfw", + "rtc_region", + "type", + ] + embed = EventEmbed(title="Channel properties updated", description="") + embed.addChannelField("Channel", after) + + if embed.addPropertyChangeFields(properties_to_track, before, after): + console_message = ( + f"Channel properties updated for channel " f"{after.name} ({after.id})" + ) + + await self.send_event_log( + guild=after.guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + channel_location=after, + ) # Useful @commands.Cog.listener() @@ -1006,20 +1108,46 @@ async def on_guild_update( ], ) - embed = discord.Embed() - embed = auxiliary.add_diff_fields(embed, diff) - embed.add_field(name="Server", value=before.name) + properties_to_track = [ + "afk_channel", + "afk_timeout", + "banner", + "bitrate_limit", + "categories", + "description", + "default_notifications", + "dms_paused_until", + "discovery_splash", + "emoji_limit", + "explicit_content_filter", + "features", + "filesize_limit", + "icon", + "invites_paused_until", + "mfa_level", + "name", + "nsfw_level", + "owner", + "preferred_locale", + "premium_tier", + "public_updates_channel", + "rules_channel", + "safety_alerts_channel", + "splash", + "system_channel", + "verification_level", + ] + embed = EventEmbed(title="Guild properties updated", description="") - log_channel = configuration.get_config_entry( - before.guild.id, "core_guild_events_channel" - ) - await self.bot.logger.send_log( - message=f"Guild with ID {before.id} updated", - level=LogLevel.INFO, - context=LogContext(guild=before), - channel=log_channel, - embed=embed, - ) + if embed.addPropertyChangeFields(properties_to_track, before, after): + console_message = f"Guild properties updated." + + await self.send_event_log( + guild=after, + log_location="guild", + string_message=console_message, + embed_message=embed, + ) # Useful @commands.Cog.listener() @@ -1080,6 +1208,12 @@ async def on_guild_role_update( after (discord.Role): The updated role's updated info. """ attrs = ["color", "mentionable", "name", "permissions", "position", "tags"] + # Tags cannot change, so doesn't matter + # Probably want to do better with color changes, with 2nd/3rd color + # Probably want to do display_icon changes + # Probably want to do hoist changes + + # We probably want properties (everything but permissions) and permissions as 2 different logs diff = auxiliary.get_object_diff(before, after, attrs) embed = discord.Embed() From ca070b34172cb4e0378d7923a11a8422fc1f6acd Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Thu, 11 Jun 2026 20:54:23 -0700 Subject: [PATCH 13/24] 3 thread events, 2 invite events done --- modules/moderation/events.py | 234 +++++++++++++++-------------------- 1 file changed, 101 insertions(+), 133 deletions(-) diff --git a/modules/moderation/events.py b/modules/moderation/events.py index 494e2e01..af4b46ad 100644 --- a/modules/moderation/events.py +++ b/modules/moderation/events.py @@ -919,7 +919,6 @@ async def on_guild_channel_delete( channel_location=channel, ) - # Useful @commands.Cog.listener() async def on_guild_channel_update( self: Self, before: discord.abc.GuildChannel, after: discord.abc.GuildChannel @@ -1069,7 +1068,6 @@ async def on_guild_channel_update( channel_location=after, ) - # Useful @commands.Cog.listener() async def on_guild_update( self: Self, before: discord.Guild, after: discord.Guild @@ -1081,33 +1079,6 @@ async def on_guild_update( before (discord.Guild): The guild prior to being updated after (discord.Guild): The guild after being updated """ - diff = auxiliary.get_object_diff( - before, - after, - [ - "banner", - "banner_url", - "bitrate_limit", - "categories", - "default_role", - "description", - "discovery_splash", - "discovery_splash_url", - "emoji_limit", - "emojis", - "explicit_content_filter", - "features", - "icon", - "icon_url", - "name", - "owner", - "region", - "roles", - "rules_channel", - "verification_level", - ], - ) - properties_to_track = [ "afk_channel", "afk_timeout", @@ -1149,118 +1120,114 @@ async def on_guild_update( embed_message=embed, ) - # Useful @commands.Cog.listener() - async def on_guild_role_create(self: Self, role: discord.Role) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_role_create - - Args: - role (discord.Role): The role that was created - """ - embed = discord.Embed() - embed.add_field(name="Server", value=role.guild.name) - log_channel = configuration.get_config_entry( - role.guild.id, "core_guild_events_channel" + async def on_thread_create(self: Self, thread: discord.Thread) -> None: + embed = EventEmbed( + title="Thread created", + description=f"", ) - await self.bot.logger.send_log( - message=( - f"New role with name {role.name} added to guild with ID {role.guild.id}" - ), - level=LogLevel.INFO, - context=LogContext(guild=role.guild), - channel=log_channel, - embed=embed, + embed.addChannelField("Thread", thread) + embed.addChannelField("Parent channel", thread.parent) + + console_message = f"Thread {thread.name} ({thread.id}) was created" + + await self.send_event_log( + guild=thread.guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + channel_location=thread.parent, ) - # Useful @commands.Cog.listener() - async def on_guild_role_delete(self: Self, role: discord.Role) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_role_delete - - Args: - role (discord.Role): The role that was deleted - """ - embed = discord.Embed() - embed.add_field(name="Server", value=role.guild.name) - log_channel = configuration.get_config_entry( - role.guild.id, "core_guild_events_channel" + async def on_thread_delete(self: Self, thread: discord.Thread) -> None: + embed = EventEmbed( + title="Thread deleted", + description=f"", ) - await self.bot.logger.send_log( - message=( - f"Role with name {role.name} deleted from guild with ID {role.guild.id}" - ), - level=LogLevel.INFO, - context=LogContext(guild=role.guild), - channel=log_channel, - embed=embed, + + embed.addChannelField("Thread", thread) + embed.addChannelField("Parent channel", thread.parent) + + console_message = f"Thread {thread.name} ({thread.id}) was deleted" + + await self.send_event_log( + guild=thread.guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + channel_location=thread.parent, ) - # Useful @commands.Cog.listener() - async def on_guild_role_update( - self: Self, before: discord.Role, after: discord.Role + async def on_thread_update( + self: Self, before: discord.Thread, after: discord.Thread ) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_role_update - - Args: - before (discord.Role): The updated role's old info. - after (discord.Role): The updated role's updated info. - """ - attrs = ["color", "mentionable", "name", "permissions", "position", "tags"] - # Tags cannot change, so doesn't matter - # Probably want to do better with color changes, with 2nd/3rd color - # Probably want to do display_icon changes - # Probably want to do hoist changes + properties_to_track = [ + "applied_tags", + "archived", + "invitable", + "locked", + "name", + "slowmode_delay", + "type", + ] + embed = EventEmbed(title="Thread properties updated", description="") + embed.addChannelField("Thread", after) + embed.addChannelField("Parent channel", after.parent) - # We probably want properties (everything but permissions) and permissions as 2 different logs - diff = auxiliary.get_object_diff(before, after, attrs) + if embed.addPropertyChangeFields(properties_to_track, before, after): + console_message = ( + f"Thread properties updated for thread " f"{after.name} ({after.id})" + ) - embed = discord.Embed() - embed = auxiliary.add_diff_fields(embed, diff) - embed.add_field(name="Server", value=before.name) + await self.send_event_log( + guild=after.guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + channel_location=after, + ) - log_channel = configuration.get_config_entry( - before.guild.id, "core_guild_events_channel" + @commands.Cog.listener() + async def on_invite_create(self: Self, invite: discord.Invite) -> None: + embed = EventEmbed( + title="New invite created", description=f"https://discord.gg/{invite.code}" ) + if invite.channel: + embed.addChannelField("Channel", invite.channel) + if invite.inviter: + embed.addMemberField("Inviter", invite.inviter) - await self.bot.logger.send_log( - message=( - f"Role with name {before.name} updated in guild with ID" - f" {before.guild.id}" - ), - level=LogLevel.INFO, - context=LogContext(guild=before.guild), - channel=log_channel, - embed=embed, + console_message = f"New invite created: {invite.code}" + + await self.send_event_log( + guild=invite.guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + channel_location=invite.channel, ) - # Useful @commands.Cog.listener() - async def on_guild_emojis_update( - self: Self, - guild: discord.Guild, - _: Sequence[discord.Emoji], - __: Sequence[discord.Emoji], - ) -> None: - """ - See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_emojis_update + async def on_invite_delete(self: Self, invite: discord.Invite) -> None: + embed = EventEmbed( + title="Invite deleted", description=f"https://discord.gg/{invite.code}" + ) + if invite.channel: + embed.addChannelField("Channel", invite.channel) + if invite.inviter: + embed.addMemberField("Inviter", invite.inviter) - Args: - guild (discord.Guild): The guild who got their emojis updated. - """ - embed = discord.Embed() - embed.add_field(name="Server", value=guild.name) + console_message = f"Invite deleted: {invite.code}" - log_channel = configuration.get_config_entry( - guild.id, "core_guild_events_channel" - ) - await self.bot.logger.send_log( - message=f"Emojis updated in guild with ID {guild.id}", - level=LogLevel.INFO, - context=LogContext(guild=guild), - channel=log_channel, - embed=embed, + await self.send_event_log( + guild=invite.guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + channel_location=invite.channel, ) # Bot Events @@ -1379,27 +1346,28 @@ async def on_guild_join(self: Self, guild: discord.Guild) -> None: # Should probably log: """ -Thread creation/delete (guild) - MAYBE - discord.on_thread_create - discord.on_thread_update - discord.on_thread_delete -Automod stuff (guild) - discord.on_automod_rule_create - discord.on_automod_rule_update - discord.on_automod_rule_delete -Soundboard & stickers (guild) + @commands.Cog.listener() + async def on(self: Self) -> None: + +Soundboard, stickers & emoji (guild) discord.on_soundboard_sound_create discord.on_soundboard_sound_delete discord.on_soundboard_sound_update discord.on_guild_stickers_update + discord.on_guild_emojis_update Integrations (guild) discord.on_integration_create - discord.on_integration_update -Invites (Guild) - discord.on_invite_create - discord.on_invite_delete + discord.on_raw_integration_delete Scheduled Events (guild) discord.on_scheduled_event_create discord.on_scheduled_event_delete discord.on_scheduled_event_update +Roles (guild) + discord.on_guild_role_create + discord.on_guild_role_delete + discord.on_guild_role_update +Automod stuff (guild) + discord.on_automod_rule_create + discord.on_automod_rule_update + discord.on_automod_rule_delete """ From 34e77051abe0f842aa842cd0cf01175430f194bd Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Thu, 11 Jun 2026 21:01:46 -0700 Subject: [PATCH 14/24] Make some changes, move console only logs to bot.py --- bot.py | 56 +++++++++++++++++++++++ modules/moderation/events.py | 86 +----------------------------------- 2 files changed, 57 insertions(+), 85 deletions(-) diff --git a/bot.py b/bot.py index b3f817a3..70aee67a 100644 --- a/bot.py +++ b/bot.py @@ -8,6 +8,7 @@ import glob import io import os +import sys import threading from typing import Self @@ -181,6 +182,55 @@ async def setup_hook(self: Self) -> None: self.extension_name_list = [] await self.load_extensions() + async def on_guild_remove(self: Self, guild: discord.Guild) -> None: + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_remove + + Args: + guild (discord.Guild): The guild that got removed + """ + await self.logger.send_log( + message=f"Left guild with ID {guild.id}", + level=LogLevel.INFO, + console_only=True, + ) + + async def on_error(self: Self, event_method: str) -> None: + """Catches non-command errors and sends them to the error logger for processing. + + Args: + event_method (str): the event method name associated with the error (eg. on_message) + """ + _, exception, _ = sys.exc_info() + await self.bot.logger.send_log( + message=f"Bot error in {event_method}: {exception}", + level=LogLevel.ERROR, + exception=exception, + ) + + async def on_connect(self: Self) -> None: + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_connect""" + await self.logger.send_log( + message="Connected to Discord", + level=LogLevel.INFO, + console_only=True, + ) + + async def on_resumed(self: Self) -> None: + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_resumed""" + await self.logger.send_log( + message="Resume connection to discord", + level=LogLevel.INFO, + console_only=True, + ) + + async def on_disconnect(self: Self) -> None: + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_disconnect""" + await self.logger.send_log( + message="Disconnected from Discord", + level=LogLevel.INFO, + console_only=True, + ) + async def on_guild_join(self: Self, guild: discord.Guild) -> None: """Configures a new guild upon joining. This registers a new guild config, and starts any loop jobs that are configured @@ -188,6 +238,12 @@ async def on_guild_join(self: Self, guild: discord.Guild) -> None: Args: guild (discord.Guild): the guild that was joined """ + await self.logger.send_log( + message=f"Joined guild with ID {guild.id}", + level=LogLevel.INFO, + console_only=True, + ) + for cog in self.cogs.values(): if getattr(cog, "COG_TYPE", "").lower() == "loop": try: diff --git a/modules/moderation/events.py b/modules/moderation/events.py index af4b46ad..8e0be0d1 100644 --- a/modules/moderation/events.py +++ b/modules/moderation/events.py @@ -2,9 +2,6 @@ from __future__ import annotations -import datetime -import sys -from collections.abc import Sequence from typing import TYPE_CHECKING, Any, Self import discord @@ -12,7 +9,7 @@ import configuration from botlogging import LogContext, LogLevel -from core import auxiliary, cogs +from core import cogs from modules.moderation import logger if TYPE_CHECKING: @@ -1262,87 +1259,6 @@ async def on_command(self: Self, ctx: commands.Context) -> None: embed=embed, ) - # CONSOLE ONLY STUFF - - @commands.Cog.listener() - async def on_error(self: Self, event_method: str) -> None: - """Catches non-command errors and sends them to the error logger for processing. - - Args: - event_method (str): the event method name associated with the error (eg. on_message) - """ - _, exception, _ = sys.exc_info() - await self.bot.logger.send_log( - message=f"Bot error in {event_method}: {exception}", - level=LogLevel.ERROR, - exception=exception, - ) - - @commands.Cog.listener() - async def on_connect(self: Self) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_connect""" - await self.bot.logger.send_log( - message="Connected to Discord", - level=LogLevel.INFO, - console_only=True, - ) - - @commands.Cog.listener() - async def on_resumed(self: Self) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_resumed""" - await self.bot.logger.send_log( - message="Resume event", - level=LogLevel.INFO, - console_only=True, - ) - - @commands.Cog.listener() - async def on_disconnect(self: Self) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_disconnect""" - await self.bot.logger.send_log( - message="Disconnected from Discord", - level=LogLevel.INFO, - console_only=True, - ) - - @commands.Cog.listener() - async def on_guild_remove(self: Self, guild: discord.Guild) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_remove - - Args: - guild (discord.Guild): The guild that got removed - """ - embed = discord.Embed() - embed.add_field(name="Server", value=guild.name) - await self.bot.logger.send_log( - message=f"Left guild with ID {guild.id}", - level=LogLevel.INFO, - context=LogContext(guild=guild), - embed=embed, - ) - - @commands.Cog.listener() - async def on_guild_join(self: Self, guild: discord.Guild) -> None: - """Configures a new guild upon joining. - - Args: - guild (discord.Guild): the guild that was joined - """ - embed = discord.Embed() - embed.add_field(name="Server", value=guild.name) - - log_channel = configuration.get_config_entry( - guild.id, "core_guild_events_channel" - ) - - await self.bot.logger.send_log( - message=f"Joined guild with ID {guild.id}", - level=LogLevel.INFO, - context=LogContext(guild=guild), - channel=log_channel, - embed=embed, - ) - # Should probably log: """ From a4cf98a83f276f2af09808233e7c56468b9341c5 Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Thu, 11 Jun 2026 22:17:42 -0700 Subject: [PATCH 15/24] Soundboard events --- modules/moderation/events.py | 79 ++++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 4 deletions(-) diff --git a/modules/moderation/events.py b/modules/moderation/events.py index 8e0be0d1..225b8fbc 100644 --- a/modules/moderation/events.py +++ b/modules/moderation/events.py @@ -94,6 +94,20 @@ def addChannelField( inline=True, ) + def addSoundboardField( + self: Self, title: str, soundboard: discord.SoundboardSound + ) -> None: + self.add_field( + name=title, + value=( + f"**Name:** {soundboard.name}\n" + f"**Volume:** {soundboard.volume}\n" + f"**Emoji:** {soundboard.emoji}\n" + f"**ID:** {soundboard.id}" + ), + inline=True, + ) + def addEmojiField( self: Self, title: str, emoji: discord.Emoji | discord.PartialEmoji | str ) -> None: @@ -1227,6 +1241,66 @@ async def on_invite_delete(self: Self, invite: discord.Invite) -> None: channel_location=invite.channel, ) + @commands.Cog.listener() + async def on_soundboard_sound_create( + self: Self, sound: discord.SoundboardSound + ) -> None: + embed = EventEmbed(title="Soundboard sound created", description="") + embed.addSoundboardField("Sound", sound) + if sound.user: + embed.addMemberField("Uploader", sound.user) + + console_message = f"Soundboard sound created: {sound.name}" + + await self.send_event_log( + guild=sound.guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + ) + + @commands.Cog.listener() + async def on_soundboard_sound_delete( + self: Self, sound: discord.SoundboardSound + ) -> None: + embed = EventEmbed(title="Soundboard sound deleted", description="") + embed.addSoundboardField("Sound", sound) + if sound.user: + embed.addMemberField("Uploader", sound.user) + + console_message = f"Soundboard sound deleted: {sound.name}" + + await self.send_event_log( + guild=sound.guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + ) + + @commands.Cog.listener() + async def on_soundboard_sound_update( + self: Self, before: discord.SoundboardSound, after: discord.SoundboardSound + ) -> None: + embed = EventEmbed(title="Soundboard sound modified", description="") + embed.addSoundboardField("Sound", after) + if after.user: + embed.addMemberField("Uploader", after.user) + + properties_to_track = [ + "available", + "emoji", + "name", + "volume", + ] + if embed.addPropertyChangeFields(properties_to_track, before, after): + console_message = f"Soundboard sound modified: {after.name}" + await self.send_event_log( + guild=after.guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + ) + # Bot Events @commands.Cog.listener() @@ -1263,12 +1337,9 @@ async def on_command(self: Self, ctx: commands.Context) -> None: # Should probably log: """ @commands.Cog.listener() - async def on(self: Self) -> None: + async def on_soundboard_sound_create(self: Self) -> None: Soundboard, stickers & emoji (guild) - discord.on_soundboard_sound_create - discord.on_soundboard_sound_delete - discord.on_soundboard_sound_update discord.on_guild_stickers_update discord.on_guild_emojis_update Integrations (guild) From 72134c0a52470d10481206aaefe4567f0a27058d Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Fri, 12 Jun 2026 07:12:50 -0700 Subject: [PATCH 16/24] Stickers and Emojis --- modules/moderation/events.py | 174 ++++++++++++++++++++++++++++++++++- 1 file changed, 169 insertions(+), 5 deletions(-) diff --git a/modules/moderation/events.py b/modules/moderation/events.py index 225b8fbc..e31cbe0d 100644 --- a/modules/moderation/events.py +++ b/modules/moderation/events.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Self +from typing import TYPE_CHECKING, Any, Self, Sequence import discord from discord.ext import commands @@ -123,6 +123,21 @@ def addEmojiField( self.add_field(name=title, value=emoji_value) + def addStickerField( + self: Self, + title: str, + sticker: discord.GuildSticker, + ) -> None: + self.add_field( + name=title, + value=( + f"**Name:** {sticker.name}\n" + f"**ID:** {sticker.id}\n" + f"**Description:** {sticker.description or 'None'}\n" + f"**Emoji:** {sticker.emoji or 'None'}" + ), + ) + def addPollField(self: Self, title: str, poll: discord.Poll) -> None: self.add_field( name=title, @@ -1301,6 +1316,158 @@ async def on_soundboard_sound_update( embed_message=embed, ) + @commands.Cog.listener() + async def on_guild_emojis_update( + self: Self, + guild: discord.Guild, + before: Sequence[discord.Emoji], + after: Sequence[discord.Emoji], + ) -> None: + before_emojis = {emoji.id: emoji for emoji in before} + after_emojis = {emoji.id: emoji for emoji in after} + + # Created emojis + for emoji_id, emoji in after_emojis.items(): + if emoji_id not in before_emojis: + embed = EventEmbed(title="Emoji created", description="") + embed.addEmojiField("Emoji", emoji) + if emoji.user: + embed.addMemberField("Uploader", emoji.user) + + console_message = f"Emoji created: {emoji.name}" + + await self.send_event_log( + guild=guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + ) + + # Deleted emojis + for emoji_id, emoji in before_emojis.items(): + if emoji_id not in after_emojis: + embed = EventEmbed(title="Emoji deleted", description="") + embed.addEmojiField("Emoji", emoji) + if emoji.user: + embed.addMemberField("Uploader", emoji.user) + + console_message = f"Emoji deleted: {emoji.name}" + + await self.send_event_log( + guild=guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + ) + + # Modified emojis + # I honestly don't think this is possible to hit + properties_to_track = [ + "animated", + "name", + ] + + for emoji_id, after_emoji in after_emojis.items(): + before_emoji = before_emojis.get(emoji_id) + + if before_emoji is None: + continue + + embed = EventEmbed(title="Emoji modified", description="") + embed.addEmojiField("Emoji", after_emoji) + if after_emoji.user: + embed.addMemberField("Uploader", after_emoji.user) + + if embed.addPropertyChangeFields( + properties_to_track, + before_emoji, + after_emoji, + ): + console_message = f"Emoji modified: {after_emoji.name}" + + await self.send_event_log( + guild=guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + ) + + @commands.Cog.listener() + async def on_guild_stickers_update( + self: Self, + guild: discord.Guild, + before: Sequence[discord.GuildSticker], + after: Sequence[discord.GuildSticker], + ) -> None: + before_stickers = {sticker.id: sticker for sticker in before} + after_stickers = {sticker.id: sticker for sticker in after} + + # Created stickers + for sticker_id, sticker in after_stickers.items(): + if sticker_id not in before_stickers: + embed = EventEmbed(title="Sticker created", description="") + embed.addStickerField("Sticker", sticker) + if sticker.user: + embed.addMemberField("Uploader", sticker.user) + + console_message = f"Sticker created: {sticker.name}" + + await self.send_event_log( + guild=guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + ) + + # Deleted stickers + for sticker_id, sticker in before_stickers.items(): + if sticker_id not in after_stickers: + embed = EventEmbed(title="Sticker deleted", description="") + embed.addStickerField("Sticker", sticker) + if sticker.user: + embed.addMemberField("Uploader", sticker.user) + + console_message = f"Sticker deleted: {sticker.name}" + + await self.send_event_log( + guild=guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + ) + + # Modified stickers + properties_to_track = [ + "description", + "emoji", + "name", + ] + + for sticker_id, after_sticker in after_stickers.items(): + before_sticker = before_stickers.get(sticker_id) + + if before_sticker is None: + continue + + embed = EventEmbed(title="Sticker modified", description="") + embed.addStickerField("Sticker", after_sticker) + if after_sticker.user: + embed.addMemberField("Uploader", after_sticker.user) + + if embed.addPropertyChangeFields( + properties_to_track, + before_sticker, + after_sticker, + ): + console_message = f"Sticker modified: {after_sticker.name}" + + await self.send_event_log( + guild=guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + ) + # Bot Events @commands.Cog.listener() @@ -1337,11 +1504,8 @@ async def on_command(self: Self, ctx: commands.Context) -> None: # Should probably log: """ @commands.Cog.listener() - async def on_soundboard_sound_create(self: Self) -> None: + async def on_guild_stickers_update(self: Self) -> None: -Soundboard, stickers & emoji (guild) - discord.on_guild_stickers_update - discord.on_guild_emojis_update Integrations (guild) discord.on_integration_create discord.on_raw_integration_delete From cbecada1cc76be28b6415152f1514ef410d07058 Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Fri, 12 Jun 2026 08:15:39 -0700 Subject: [PATCH 17/24] Integration logging --- modules/moderation/events.py | 57 +++++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/modules/moderation/events.py b/modules/moderation/events.py index e31cbe0d..cd93858e 100644 --- a/modules/moderation/events.py +++ b/modules/moderation/events.py @@ -138,6 +138,21 @@ def addStickerField( ), ) + def addIntegrationField( + self: Self, + title: str, + integration: discord.Integration, + ) -> None: + self.add_field( + name=title, + value=( + f"**Name:** {integration.name}\n" + f"**Type:** {integration.type}\n" + f"**Scope:** {integration.scopes}\n" + f"**ID:** {integration.id}" + ), + ) + def addPollField(self: Self, title: str, poll: discord.Poll) -> None: self.add_field( name=title, @@ -1468,6 +1483,43 @@ async def on_guild_stickers_update( embed_message=embed, ) + @commands.Cog.listener() + async def on_integration_create( + self: Self, integration: discord.Integration + ) -> None: + embed = EventEmbed(title="Integration created", description="") + embed.addMemberField("Bot user", integration.account) + embed.addMemberField("Uploader", integration.user) + embed.addIntegrationField("Integration info", integration) + + console_message = f"Integration created: {integration.name} ({integration.id})" + + await self.send_event_log( + guild=integration.guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + ) + + @commands.Cog.listener() + async def on_raw_integration_delete( + self: Self, payload: discord.RawIntegrationDeleteEvent + ) -> None: + guild = await self.bot.fetch_guild(payload.guild_id) + embed = EventEmbed( + title="Integration created", + description=f"Integration ID: payload.integration_id", + ) + + console_message = f"Integration created: ({payload.integration_id})" + + await self.send_event_log( + guild=guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + ) + # Bot Events @commands.Cog.listener() @@ -1504,11 +1556,8 @@ async def on_command(self: Self, ctx: commands.Context) -> None: # Should probably log: """ @commands.Cog.listener() - async def on_guild_stickers_update(self: Self) -> None: + async def on_integration_create(self: Self) -> None: -Integrations (guild) - discord.on_integration_create - discord.on_raw_integration_delete Scheduled Events (guild) discord.on_scheduled_event_create discord.on_scheduled_event_delete From 3422cc756e8642d3ccc3ff99ea2cfb5c13240428 Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Fri, 12 Jun 2026 09:53:11 -0700 Subject: [PATCH 18/24] Add the rest of the events --- modules/moderation/events.py | 395 +++++++++++++++++++++++++++++++++-- 1 file changed, 372 insertions(+), 23 deletions(-) diff --git a/modules/moderation/events.py b/modules/moderation/events.py index cd93858e..d1290a9f 100644 --- a/modules/moderation/events.py +++ b/modules/moderation/events.py @@ -171,6 +171,158 @@ def addPollAnswerField(self: Self, title: str, answer: discord.PollAnswer) -> No inline=True, ) + def addRoleField(self: Self, title: str, role: discord.Role) -> None: + self.add_field( + name=title, + value=( + f"**Role:** {role.mention}\n" + f"**Name:** {role.name}\n" + f"**ID:** {role.id}\n" + f"**Position:** {role.position}\n" + f"**Members:** {len(role.members)}\n" + f"**Managed:** {'Yes' if role.managed else 'No'}\n" + f"**Hoisted:** {'Yes' if role.hoist else 'No'}\n" + f"**Mentionable:** {'Yes' if role.mentionable else 'No'}" + ), + inline=True, + ) + + def addRoleMetadataField(self: Self, title: str, role: discord.Role) -> None: + self.add_field( + name=title, + value=( + f"**Created:** " + f"()\n" + f"**Tags:** {role.tags or 'None'}\n" + f"**Flags:** {role.flags}\n" + f"**Icon:** {role.icon or 'None'}\n" + f"**Display Icon:** {role.display_icon or 'None'}" + ), + inline=True, + ) + + def addRoleColorField(self: Self, title: str, role: discord.Role) -> None: + self.add_field( + name=title, + value=( + f"**Primary:** {role.colour}\n" + f"**Secondary:** {role.secondary_colour}\n" + f"**Tertiary:** {role.tertiary_colour}\n" + f"**Unicode Emoji:** {role.unicode_emoji or 'None'}\n" + f"**Display Icon:** {role.display_icon or 'None'}" + ), + inline=True, + ) + + def addScheduledEventField( + self: Self, title: str, event: discord.ScheduledEvent + ) -> None: + start_time = ( + f" " + f"()" + if event.start_time + else "None" + ) + end_time = ( + f" " + f"()" + if event.end_time + else "None" + ) + location = event.channel.mention if event.channel else event.location or "None" + + self.add_field( + name=title, + value=( + f"**Name:** {event.name}\n" + f"**ID:** {event.id}\n" + f"**Status:** {event.status}\n" + f"**Entity Type:** {event.entity_type}\n" + f"**Location:** {location}\n" + f"**Start:** {start_time}\n" + f"**End:** {end_time}\n" + f"**Users:** {event.user_count or 0}" + ), + inline=True, + ) + + def addAutoModRuleField(self: Self, title: str, rule: discord.AutoModRule) -> None: + actions = ", ".join(str(action.type) for action in rule.actions) or "None" + if len(actions) > 200: + actions = actions[:197] + "..." + + self.add_field( + name=title, + value=( + f"**Name:** {rule.name}\n" + f"**ID:** {rule.id}\n" + f"**Enabled:** {'Yes' if rule.enabled else 'No'}\n" + f"**Event Type:** {rule.event_type}\n" + f"**Trigger:** {rule.trigger.type}\n" + f"**Actions:** {actions}\n" + f"**Exempt Roles:** {len(rule.exempt_role_ids)}\n" + f"**Exempt Channels:** {len(rule.exempt_channel_ids)}\n" + f"**Creator ID:** {rule.creator_id or 'Unknown'}" + ), + inline=True, + ) + + def addRolePermissionChangeFields( + self: Self, before: discord.Permissions, after: discord.Permissions + ) -> bool: + added: list[str] = [] + removed: list[str] = [] + changed: list[str] = [] + + before_permissions = dict(iter(before)) + after_permissions = dict(iter(after)) + all_permissions = set(before_permissions) | set(after_permissions) + + for permission in sorted(all_permissions): + old = before_permissions.get(permission) + new = after_permissions.get(permission) + + if old == new: + continue + + permission_name = permission.replace("_", " ").title() + + if old is None: + added.append(f"✅ `{permission_name}` → {new}") + elif new is None: + removed.append(f"❌ `{permission_name}` (was {old})") + else: + old_emoji = "✅" if old else "❌" + new_emoji = "✅" if new else "❌" + changed.append(f"➖ `{permission_name}` {old_emoji} → {new_emoji}") + + if not (added or removed or changed): + return False + + value_parts = [] + + if added: + value_parts.append("**Added**\n" + "\n".join(added)) + + if removed: + value_parts.append("**Removed**\n" + "\n".join(removed)) + + if changed: + value_parts.append("**Changed**\n" + "\n".join(changed)) + + value = "\n\n".join(value_parts) + + if len(value) > 1024: + value = value[:1021] + "..." + + self.add_field( + name="Permissions", + value=value, + inline=False, + ) + + return True + def addPropertyChangeFields( self: Self, properties: list[str], before: Any, after: Any ) -> bool: @@ -1507,11 +1659,11 @@ async def on_raw_integration_delete( ) -> None: guild = await self.bot.fetch_guild(payload.guild_id) embed = EventEmbed( - title="Integration created", - description=f"Integration ID: payload.integration_id", + title="Integration deleted", + description=f"Integration ID: {payload.integration_id}", ) - console_message = f"Integration created: ({payload.integration_id})" + console_message = f"Integration deleted: ({payload.integration_id})" await self.send_event_log( guild=guild, @@ -1520,6 +1672,223 @@ async def on_raw_integration_delete( embed_message=embed, ) + @commands.Cog.listener() + async def on_scheduled_event_create( + self: Self, event: discord.ScheduledEvent + ) -> None: + embed = EventEmbed(title="Scheduled event created", description=event.url) + embed.addScheduledEventField("Scheduled Event", event) + if event.creator: + embed.addMemberField("Creator", event.creator) + + console_message = f"Scheduled event created: {event.name} ({event.id})" + + await self.send_event_log( + guild=event.guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + channel_location=event.channel, + ) + + @commands.Cog.listener() + async def on_scheduled_event_delete( + self: Self, event: discord.ScheduledEvent + ) -> None: + embed = EventEmbed(title="Scheduled event deleted", description=event.url) + embed.addScheduledEventField("Scheduled Event", event) + if event.creator: + embed.addMemberField("Creator", event.creator) + + console_message = f"Scheduled event deleted: {event.name} ({event.id})" + + await self.send_event_log( + guild=event.guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + channel_location=event.channel, + ) + + @commands.Cog.listener() + async def on_scheduled_event_update( + self: Self, + before: discord.ScheduledEvent, + after: discord.ScheduledEvent, + ) -> None: + properties_to_track = [ + "channel_id", + "description", + "end_time", + "entity_type", + "location", + "name", + "privacy_level", + "start_time", + "status", + ] + embed = EventEmbed(title="Scheduled event updated", description=after.url) + embed.addScheduledEventField("Scheduled Event", after) + + if embed.addPropertyChangeFields(properties_to_track, before, after): + console_message = f"Scheduled event updated: {after.name} ({after.id})" + + await self.send_event_log( + guild=after.guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + channel_location=after.channel, + ) + + @commands.Cog.listener() + async def on_guild_role_create(self: Self, role: discord.Role) -> None: + embed = EventEmbed(title="Role created", description="") + embed.addRoleField("Role", role) + embed.addRoleMetadataField("Role Metadata", role) + embed.addRoleColorField("Role Colors", role) + + console_message = f"Role created: {role.name} ({role.id})" + + await self.send_event_log( + guild=role.guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + ) + + @commands.Cog.listener() + async def on_guild_role_delete(self: Self, role: discord.Role) -> None: + embed = EventEmbed(title="Role deleted", description="") + embed.addRoleField("Role", role) + embed.addRoleMetadataField("Role Metadata", role) + embed.addRoleColorField("Role Colors", role) + + console_message = f"Role deleted: {role.name} ({role.id})" + + await self.send_event_log( + guild=role.guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + ) + + @commands.Cog.listener() + async def on_guild_role_update( + self: Self, before: discord.Role, after: discord.Role + ) -> None: + general_properties_to_track = [ + "display_icon", + "flags", + "hoist", + "icon", + "managed", + "mentionable", + "name", + "position", + "tags", + "unicode_emoji", + ] + + general_embed = EventEmbed(title="Role properties updated", description="") + general_embed.addRoleField("Role", after) + + if general_embed.addPropertyChangeFields( + general_properties_to_track, before, after + ): + console_message = f"Role properties updated: {after.name} ({after.id})" + + await self.send_event_log( + guild=after.guild, + log_location="guild", + string_message=console_message, + embed_message=general_embed, + ) + + permission_embed = EventEmbed(title="Role permissions updated", description="") + permission_embed.addRoleField("Role", after) + + if permission_embed.addRolePermissionChangeFields( + before.permissions, after.permissions + ): + console_message = f"Role permissions updated: {after.name} ({after.id})" + + await self.send_event_log( + guild=after.guild, + log_location="guild", + string_message=console_message, + embed_message=permission_embed, + ) + + color_properties_to_track = [ + "colour", + "secondary_colour", + "tertiary_colour", + ] + color_embed = EventEmbed(title="Role colors updated", description="") + color_embed.addRoleField("Role", after) + color_embed.addRoleColorField("Role Colors", after) + + if color_embed.addPropertyChangeFields( + color_properties_to_track, before, after + ): + console_message = f"Role colors updated: {after.name} ({after.id})" + + await self.send_event_log( + guild=after.guild, + log_location="guild", + string_message=console_message, + embed_message=color_embed, + ) + + @commands.Cog.listener() + async def on_automod_rule_create(self: Self, rule: discord.AutoModRule) -> None: + embed = EventEmbed(title="AutoMod rule created", description="") + embed.addAutoModRuleField("AutoMod Rule", rule) + if rule.creator: + embed.addMemberField("Creator", rule.creator) + + console_message = f"AutoMod rule created: {rule.name} ({rule.id})" + + await self.send_event_log( + guild=rule.guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + ) + + @commands.Cog.listener() + async def on_automod_rule_update(self: Self, rule: discord.AutoModRule) -> None: + embed = EventEmbed(title="AutoMod rule updated", description="") + embed.addAutoModRuleField("AutoMod Rule", rule) + if rule.creator: + embed.addMemberField("Creator", rule.creator) + + console_message = f"AutoMod rule updated: {rule.name} ({rule.id})" + + await self.send_event_log( + guild=rule.guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + ) + + @commands.Cog.listener() + async def on_automod_rule_delete(self: Self, rule: discord.AutoModRule) -> None: + embed = EventEmbed(title="AutoMod rule deleted", description="") + embed.addAutoModRuleField("AutoMod Rule", rule) + if rule.creator: + embed.addMemberField("Creator", rule.creator) + + console_message = f"AutoMod rule deleted: {rule.name} ({rule.id})" + + await self.send_event_log( + guild=rule.guild, + log_location="guild", + string_message=console_message, + embed_message=embed, + ) + # Bot Events @commands.Cog.listener() @@ -1551,23 +1920,3 @@ async def on_command(self: Self, ctx: commands.Context) -> None: channel=log_channel, embed=embed, ) - - -# Should probably log: -""" - @commands.Cog.listener() - async def on_integration_create(self: Self) -> None: - -Scheduled Events (guild) - discord.on_scheduled_event_create - discord.on_scheduled_event_delete - discord.on_scheduled_event_update -Roles (guild) - discord.on_guild_role_create - discord.on_guild_role_delete - discord.on_guild_role_update -Automod stuff (guild) - discord.on_automod_rule_create - discord.on_automod_rule_update - discord.on_automod_rule_delete -""" From 2386aea483c70ab295f8146510eba2b4f35c2a90 Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Fri, 12 Jun 2026 12:04:33 -0700 Subject: [PATCH 19/24] Doc strings. So many doc strings --- modules/moderation/events.py | 385 ++++++++++++++++++++++++++++++----- 1 file changed, 338 insertions(+), 47 deletions(-) diff --git a/modules/moderation/events.py b/modules/moderation/events.py index d1290a9f..11e163c9 100644 --- a/modules/moderation/events.py +++ b/modules/moderation/events.py @@ -37,12 +37,24 @@ def __init__(self, *, title, description) -> None: ) def setEventAuthor(self, author: discord.Member) -> None: + """This sets an author object of an embed to be stylized as a passed member object + This shows the display_name and display_avatar + + Args: + author (discord.Member): The member to make the author of the embed + """ self.set_author( name=str(author.display_name), icon_url=author.display_avatar.url, ) def addMemberField(self: Self, title: str, member: discord.Member) -> None: + """This adds a member info field to the embed. A mention, username, and id are shown here. + + Args: + title (str): The title of the field + member (discord.Member): The member object to display information of + """ self.add_field( name=title, value=( @@ -56,6 +68,12 @@ def addMemberField(self: Self, title: str, member: discord.Member) -> None: def addMessageContentField( self: Self, title: str, message: discord.Message ) -> None: + """This adds a message content info field to the embed. Clean content, trimmed to 1024 in size, is what is used. + + Args: + title (str): The title of the field + message (discord.Message): The message object to get the content from + """ if not message.clean_content: content = "*No content*" elif len(message.clean_content) > 1024: @@ -69,6 +87,12 @@ def addMessageContentField( ) def addMessageInfoField(self: Self, title: str, message: discord.Message) -> None: + """This adds a message info field to the embed. Clean content, trimmed to 50, the author, the ID, and the sent at time are all displayed + + Args: + title (str): The title of the field + message (discord.Message): The message object to get the information from + """ self.add_field( name=title, value=( @@ -83,6 +107,12 @@ def addMessageInfoField(self: Self, title: str, message: discord.Message) -> Non def addChannelField( self: Self, title: str, channel: discord.abc.GuildChannel ) -> None: + """This adds a channel info field into the embed. Channel link, name, type and ID are all displayed. + + Args: + title (str): The title of the field + channel (discord.abc.GuildChannel): The channel object to get information from + """ self.add_field( name=title, value=( @@ -97,6 +127,12 @@ def addChannelField( def addSoundboardField( self: Self, title: str, soundboard: discord.SoundboardSound ) -> None: + """This adds a soundboard info field into the embed. Name, volume, emoji and ID are all displayed. + + Args: + title (str): The title of the field + soundboard (discord.SoundboardSound): The soundboard object to get information from + """ self.add_field( name=title, value=( @@ -111,6 +147,13 @@ def addSoundboardField( def addEmojiField( self: Self, title: str, emoji: discord.Emoji | discord.PartialEmoji | str ) -> None: + """This adds an emoji info field into the embed. For standard reactions, just the emoji is displayed. + For custom emojis, the emoji will attempt to be displayed, as well as the name and ID + + Args: + title (str): The title of the field + emoji (discord.Emoji | discord.PartialEmoji | str): The emoji object to get information from + """ # This is to better display custom emotes if isinstance(emoji, (discord.Emoji, discord.PartialEmoji)): emoji_value = ( @@ -128,6 +171,12 @@ def addStickerField( title: str, sticker: discord.GuildSticker, ) -> None: + """This adds a sticker info field into the embed. Name, ID, description and emoji are all displayed. + + Args: + title (str): The title of the field + sticker (discord.GuildSticker): The sticker object to get information from + """ self.add_field( name=title, value=( @@ -143,6 +192,12 @@ def addIntegrationField( title: str, integration: discord.Integration, ) -> None: + """This adds an integration info field into the embed. Name, type, scope and ID are all displayed + + Args: + title (str): The title of the field + integration (discord.Integration): The integration object to get information from + """ self.add_field( name=title, value=( @@ -154,6 +209,12 @@ def addIntegrationField( ) def addPollField(self: Self, title: str, poll: discord.Poll) -> None: + """This adds a poll info field into the embed. Question, duration, and answers are all displayed. + + Args: + title (str): The title of the field + poll (discord.Poll): The poll object to get information from + """ self.add_field( name=title, value=( @@ -165,6 +226,12 @@ def addPollField(self: Self, title: str, poll: discord.Poll) -> None: ) def addPollAnswerField(self: Self, title: str, answer: discord.PollAnswer) -> None: + """This adds a poll answer info field into the embed. The answer and ID are both displayed. + + Args: + title (str): The title of the field + answer (discord.PollAnswer): The answer object to get information from + """ self.add_field( name=title, value=(f"**Answer:** {answer.text}\n**ID:** {answer.id}"), @@ -172,6 +239,12 @@ def addPollAnswerField(self: Self, title: str, answer: discord.PollAnswer) -> No ) def addRoleField(self: Self, title: str, role: discord.Role) -> None: + """This adds a role info field into the embed. Mention, name, ID, position, hoisted and mentionable status are all displayed. + + Args: + title (str): The title of the field + role (discord.Role): The role object to get information from + """ self.add_field( name=title, value=( @@ -179,8 +252,6 @@ def addRoleField(self: Self, title: str, role: discord.Role) -> None: f"**Name:** {role.name}\n" f"**ID:** {role.id}\n" f"**Position:** {role.position}\n" - f"**Members:** {len(role.members)}\n" - f"**Managed:** {'Yes' if role.managed else 'No'}\n" f"**Hoisted:** {'Yes' if role.hoist else 'No'}\n" f"**Mentionable:** {'Yes' if role.mentionable else 'No'}" ), @@ -188,6 +259,12 @@ def addRoleField(self: Self, title: str, role: discord.Role) -> None: ) def addRoleMetadataField(self: Self, title: str, role: discord.Role) -> None: + """This adds a role metadata field into the embed. Timestamp, tags, flags, and icon are displayed + + Args: + title (str): The title of the field + role (discord.Role): The role object to get information from + """ self.add_field( name=title, value=( @@ -202,14 +279,18 @@ def addRoleMetadataField(self: Self, title: str, role: discord.Role) -> None: ) def addRoleColorField(self: Self, title: str, role: discord.Role) -> None: + """This adds a role color field into the embed. Primary, secondary, and tertiary colors are all displayed. + + Args: + title (str): The title of the field + role (discord.Role): The role object to get information from + """ self.add_field( name=title, value=( f"**Primary:** {role.colour}\n" f"**Secondary:** {role.secondary_colour}\n" - f"**Tertiary:** {role.tertiary_colour}\n" - f"**Unicode Emoji:** {role.unicode_emoji or 'None'}\n" - f"**Display Icon:** {role.display_icon or 'None'}" + f"**Tertiary:** {role.tertiary_colour}" ), inline=True, ) @@ -217,6 +298,12 @@ def addRoleColorField(self: Self, title: str, role: discord.Role) -> None: def addScheduledEventField( self: Self, title: str, event: discord.ScheduledEvent ) -> None: + """This adds a scheduled event field into the embed. Time, ID, status, location are all included. + + Args: + title (str): The title of the field + event (discord.ScheduledEvent): The event object to get information from + """ start_time = ( f" " f"()" @@ -240,13 +327,18 @@ def addScheduledEventField( f"**Entity Type:** {event.entity_type}\n" f"**Location:** {location}\n" f"**Start:** {start_time}\n" - f"**End:** {end_time}\n" - f"**Users:** {event.user_count or 0}" + f"**End:** {end_time}" ), inline=True, ) def addAutoModRuleField(self: Self, title: str, rule: discord.AutoModRule) -> None: + """This adds an automod rule info field into the embed. Name, ID, status, type, triggers, actions, and exempt info are all displayed + + Args: + title (str): The title of the field + rule (discord.AutoModRule): The automod object to get information from + """ actions = ", ".join(str(action.type) for action in rule.actions) or "None" if len(actions) > 200: actions = actions[:197] + "..." @@ -261,8 +353,7 @@ def addAutoModRuleField(self: Self, title: str, rule: discord.AutoModRule) -> No f"**Trigger:** {rule.trigger.type}\n" f"**Actions:** {actions}\n" f"**Exempt Roles:** {len(rule.exempt_role_ids)}\n" - f"**Exempt Channels:** {len(rule.exempt_channel_ids)}\n" - f"**Creator ID:** {rule.creator_id or 'Unknown'}" + f"**Exempt Channels:** {len(rule.exempt_channel_ids)}" ), inline=True, ) @@ -270,6 +361,15 @@ def addAutoModRuleField(self: Self, title: str, rule: discord.AutoModRule) -> No def addRolePermissionChangeFields( self: Self, before: discord.Permissions, after: discord.Permissions ) -> bool: + """This adds fields displaying permissions changes between two roles + + Args: + before (discord.Permissions): The old set of permissions + after (discord.Permissions): The new set of permissions + + Returns: + bool: Whether anything was added to the embed + """ added: list[str] = [] removed: list[str] = [] changed: list[str] = [] @@ -326,6 +426,16 @@ def addRolePermissionChangeFields( def addPropertyChangeFields( self: Self, properties: list[str], before: Any, after: Any ) -> bool: + """Adds fields to the embed for an arbitrary list of string properties to compare betweeen two objects + + Args: + properties (list[str]): A list of properties to get and compared + before (Any): The old object before any changes were made + after (Any): The new object after all changes were made + + Returns: + bool: Whether anything was added to the embed + """ changes = [] for attr in properties: @@ -423,10 +533,12 @@ async def send_event_log( async def on_message_edit( self: Self, before: discord.Message, after: discord.Message ) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_message_edit + """This logs message content edit and message (un)pin events + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_message_edit Args: - payload (discord.RawMessageUpdateEvent): The raw payload object for the message edit events + before (discord.Message): The original message, before the edit occured + after (discord.Message): The new message, after it is been edited """ # If for some reason there is no message object, log nothing if not after or not before: @@ -509,10 +621,11 @@ async def on_message_edit( @commands.Cog.listener() async def on_message_delete(self: Self, message: discord.Message) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_message_delete + """This logs message delete events, by both users and moderators + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_message_delete Args: - message (discord.Message): The deleted message + message (discord.Message): The message that was deleted """ guild = message.guild channel = message.channel @@ -556,11 +669,11 @@ async def on_message_delete(self: Self, message: discord.Message) -> None: async def on_bulk_message_delete( self: Self, messages: list[discord.Message] ) -> None: - """ - See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_bulk_message_delete + """This logs bulk message delete events, such as those by a purge command or ban + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_bulk_message_delete Args: - messages (list[discord.Message]): The messages that have been deleted + messages (list[discord.Message]): The list of message objects that have been deleted """ channel = messages[0].channel guild = channel.guild @@ -614,11 +727,12 @@ async def on_bulk_message_delete( async def on_reaction_add( self: Self, reaction: discord.Reaction, user: discord.Member | discord.User ) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_reaction_add + """This logs events where a reaction has been added to a message + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_reaction_add Args: - reaction (discord.Reaction): The current state of the reaction - user (discord.Member | discord.User): The user who added the reaction + reaction (discord.Reaction): The reaction object that was added + user (discord.Member | discord.User): The account that added the reaction """ message = reaction.message channel = message.channel @@ -662,11 +776,12 @@ async def on_reaction_add( async def on_reaction_remove( self: Self, reaction: discord.Reaction, user: discord.Member | discord.User ) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_reaction_remove + """This logs events where a reaction has been removed from a message + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_reaction_remove Args: - reaction (discord.Reaction): The current state of the reaction - user (discord.Member | discord.User): The user whose reaction was removed + reaction (discord.Reaction): The reaction object that was removed + user (discord.Member | discord.User): The account that originally had that reaction """ message = reaction.message channel = message.channel @@ -710,11 +825,11 @@ async def on_reaction_remove( async def on_reaction_clear( self: Self, message: discord.Message, reactions: list[discord.Reaction] ) -> None: - """ - See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_reaction_clear + """This logs events where all reactions, or all of a specific reaction, were cleared from a message + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_reaction_clear Args: - message (discord.Message): The message that had its reactions cleared + message (discord.Message): The messages reactions were cleared from reactions (list[discord.Reaction]): The reactions that were removed """ guild = getattr(message.channel, "guild", None) @@ -754,6 +869,13 @@ async def on_reaction_clear( async def on_poll_vote_add( self: Self, user: discord.Member, answer: discord.PollAnswer ) -> None: + """This logs events where a user has voted in a poll + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_poll_vote_add + + Args: + user (discord.Member): The user who voted in the poll + answer (discord.PollAnswer): The answer selected in the poll + """ if not user.guild: return @@ -787,6 +909,13 @@ async def on_poll_vote_add( async def on_poll_vote_remove( self: Self, user: discord.Member, answer: discord.PollAnswer ) -> None: + """This logs events where a user has removed their vote in a poll + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_poll_vote_remove + + Args: + user (discord.Member): The user who voted in the poll + answer (discord.PollAnswer): The answer removed in the poll + """ if not user.guild: return @@ -820,10 +949,11 @@ async def on_poll_vote_remove( @commands.Cog.listener() async def on_member_join(self: Self, member: discord.Member) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_join + """This logs events where new members have joined the guild + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_join Args: - member (discord.Member): The member who joined + member (discord.Member): The member who has joined """ embed = EventEmbed( title="Member joined", @@ -849,10 +979,12 @@ async def on_member_join(self: Self, member: discord.Member) -> None: async def on_raw_member_remove( self: Self, payload: discord.RawMemberRemoveEvent ) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_remove + """This logs events where members left or are kicked/banned from the guild. + This is a raw listener, so it does not rely on the cache to get this callout + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_raw_member_remove Args: - member (discord.Member): The member who left + payload (discord.RawMemberRemoveEvent): The member who left the service """ member = payload.user embed = EventEmbed( @@ -894,6 +1026,14 @@ async def on_voice_state_update( before: discord.VoiceState, after: discord.VoiceState, ) -> None: + """This logs events related to server (un)mute/deafing of members + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_voice_state_update + + Args: + member (discord.Member): The member who had their account changed + before (discord.VoiceState): The previous state of the members voice account + after (discord.VoiceState): The new state of the members voice account + """ # We need to handle server deafen and server mute if before.mute != after.mute: embed = EventEmbed( @@ -914,6 +1054,7 @@ async def on_voice_state_update( log_location="member", string_message=console_message, embed_message=embed, + channel_location=after.channel, ) if before.deaf != after.deaf: @@ -941,6 +1082,13 @@ async def on_voice_state_update( async def on_user_update( self: Self, before: discord.User, after: discord.User ) -> None: + """This logs events related to username and display name changes + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_user_update + + Args: + before (discord.User): The old user account object, before changes + after (discord.User): The new user account object, after changes + """ # We want to track name and global name changes if before.name != after.name: embed = EventEmbed( @@ -993,11 +1141,13 @@ async def on_user_update( async def on_member_update( self: Self, before: discord.Member, after: discord.Member ) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_update + """This logs events related to nickname changes and member role changes + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_update Args: - before (discord.Member): The updated member's old info - after (discord.Member): Teh updated member's new info + self (Self): _description_ + before (discord.Member): The old member object, pre changes + after (discord.Member): The new member object, post changes """ # We want to track role and nickname changes @@ -1061,13 +1211,12 @@ async def on_member_update( async def on_guild_channel_create( self: Self, channel: discord.abc.GuildChannel ) -> None: - """ - See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_channel_create + """This logs events to GuildChannel objects being created + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_channel_create Args: - channel (discord.abc.GuildChannel): The channel that got created + channel (discord.abc.GuildChannel): The channel object that was created. """ - embed = EventEmbed( title="Channel created", description=f"", @@ -1089,11 +1238,11 @@ async def on_guild_channel_create( async def on_guild_channel_delete( self: Self, channel: discord.abc.GuildChannel ) -> None: - """ - See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_channel_delete + """This logs events to GuildChannel objects being deleted + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_channel_delete Args: - channel (discord.abc.GuildChannel): The channel that got deleted + channel (discord.abc.GuildChannel): The channel object that was deleted. """ embed = EventEmbed( title="Channel deleted", @@ -1116,12 +1265,14 @@ async def on_guild_channel_delete( async def on_guild_channel_update( self: Self, before: discord.abc.GuildChannel, after: discord.abc.GuildChannel ) -> None: - """ - See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_channel_update + """This logs events related to property changes to guild channels. + A seperate event is logged for channel permission changes + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_channel_update Args: - before (discord.abc.GuildChannel): The updated guild channel's old info - after (discord.abc.GuildChannel): The updated guild channel's new info + self (Self): _description_ + before (discord.abc.GuildChannel): The previous channel, before any changes + after (discord.abc.GuildChannel): The new channel, after any changes """ # This is hell. Thanks claude @@ -1265,12 +1416,12 @@ async def on_guild_channel_update( async def on_guild_update( self: Self, before: discord.Guild, after: discord.Guild ) -> None: - """ - See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_update + """This logs a huge number of property changes for a given guild + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_update Args: - before (discord.Guild): The guild prior to being updated - after (discord.Guild): The guild after being updated + before (discord.Guild): The old guild state, before any property changes + after (discord.Guild): The new guild state, after any property changes """ properties_to_track = [ "afk_channel", @@ -1315,6 +1466,12 @@ async def on_guild_update( @commands.Cog.listener() async def on_thread_create(self: Self, thread: discord.Thread) -> None: + """This logs events related to creating a new thread + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_thread_create + + Args: + thread (discord.Thread): The thread object that was created + """ embed = EventEmbed( title="Thread created", description=f"", @@ -1335,6 +1492,12 @@ async def on_thread_create(self: Self, thread: discord.Thread) -> None: @commands.Cog.listener() async def on_thread_delete(self: Self, thread: discord.Thread) -> None: + """This logs events related to deleting a thread + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_thread_delete + + Args: + thread (discord.Thread): The thread object that was deleted + """ embed = EventEmbed( title="Thread deleted", description=f"", @@ -1357,6 +1520,14 @@ async def on_thread_delete(self: Self, thread: discord.Thread) -> None: async def on_thread_update( self: Self, before: discord.Thread, after: discord.Thread ) -> None: + """This logs changes related to a handful of properties of threads + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_thread_update + + Args: + self (Self): _description_ + before (discord.Thread): The previous thread, before any property changes + after (discord.Thread): The new thread, after any property changes + """ properties_to_track = [ "applied_tags", "archived", @@ -1385,6 +1556,12 @@ async def on_thread_update( @commands.Cog.listener() async def on_invite_create(self: Self, invite: discord.Invite) -> None: + """This logs events related to creating a new invite for the guild + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_thread_update + + Args: + invite (discord.Invite): The invite that was created. + """ embed = EventEmbed( title="New invite created", description=f"https://discord.gg/{invite.code}" ) @@ -1405,6 +1582,12 @@ async def on_invite_create(self: Self, invite: discord.Invite) -> None: @commands.Cog.listener() async def on_invite_delete(self: Self, invite: discord.Invite) -> None: + """This logs events related to deleting an invite for the guild + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_thread_update + + Args: + invite (discord.Invite): The invite that was deleted. + """ embed = EventEmbed( title="Invite deleted", description=f"https://discord.gg/{invite.code}" ) @@ -1427,6 +1610,12 @@ async def on_invite_delete(self: Self, invite: discord.Invite) -> None: async def on_soundboard_sound_create( self: Self, sound: discord.SoundboardSound ) -> None: + """This logs events related to a new soundboard sound being created + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_soundboard_sound_create + + Args: + sound (discord.SoundboardSound): The soundboard object that was created + """ embed = EventEmbed(title="Soundboard sound created", description="") embed.addSoundboardField("Sound", sound) if sound.user: @@ -1445,6 +1634,12 @@ async def on_soundboard_sound_create( async def on_soundboard_sound_delete( self: Self, sound: discord.SoundboardSound ) -> None: + """This logs events related to a new soundboard sound being deleted + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_soundboard_sound_delete + + Args: + sound (discord.SoundboardSound): The soundboard object that was deleted + """ embed = EventEmbed(title="Soundboard sound deleted", description="") embed.addSoundboardField("Sound", sound) if sound.user: @@ -1463,6 +1658,13 @@ async def on_soundboard_sound_delete( async def on_soundboard_sound_update( self: Self, before: discord.SoundboardSound, after: discord.SoundboardSound ) -> None: + """This logs events related to soundboard sounds being edited + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_soundboard_sound_update + + Args: + before (discord.SoundboardSound): The old sound, before any edits + after (discord.SoundboardSound): The new sound, after any edits + """ embed = EventEmbed(title="Soundboard sound modified", description="") embed.addSoundboardField("Sound", after) if after.user: @@ -1490,6 +1692,14 @@ async def on_guild_emojis_update( before: Sequence[discord.Emoji], after: Sequence[discord.Emoji], ) -> None: + """This logs events related to custom emojis in guilds being created, deleted or edited. + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_emojis_update + + Args: + guild (discord.Guild): The guild these changes occured in + before (Sequence[discord.Emoji]): The list of guild emojis before any changes + after (Sequence[discord.Emoji]): The list of guild emojis after any changes + """ before_emojis = {emoji.id: emoji for emoji in before} after_emojis = {emoji.id: emoji for emoji in after} @@ -1566,6 +1776,14 @@ async def on_guild_stickers_update( before: Sequence[discord.GuildSticker], after: Sequence[discord.GuildSticker], ) -> None: + """This logs events related to custom stickers in guilds being created, deleted or edited. + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_stickers_update + + Args: + guild (discord.Guild): The guild these changes occured in + before (Sequence[discord.GuildSticker]): The list of guild stickers before any changes + after (Sequence[discord.GuildSticker]): The list of guild stickers after any changes + """ before_stickers = {sticker.id: sticker for sticker in before} after_stickers = {sticker.id: sticker for sticker in after} @@ -1639,6 +1857,12 @@ async def on_guild_stickers_update( async def on_integration_create( self: Self, integration: discord.Integration ) -> None: + """This logs events related to new integrations being added in the guild + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_integration_create + + Args: + integration (discord.Integration): The integration that was created + """ embed = EventEmbed(title="Integration created", description="") embed.addMemberField("Bot user", integration.account) embed.addMemberField("Uploader", integration.user) @@ -1657,6 +1881,12 @@ async def on_integration_create( async def on_raw_integration_delete( self: Self, payload: discord.RawIntegrationDeleteEvent ) -> None: + """This logs events related to new integrations being removed from the guild + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_raw_integration_delete + + Args: + payload (discord.RawIntegrationDeleteEvent): The integration that was deleted + """ guild = await self.bot.fetch_guild(payload.guild_id) embed = EventEmbed( title="Integration deleted", @@ -1676,6 +1906,12 @@ async def on_raw_integration_delete( async def on_scheduled_event_create( self: Self, event: discord.ScheduledEvent ) -> None: + """Logs events related to creating new scheduled events in a guild + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_scheduled_event_create + + Args: + event (discord.ScheduledEvent): The event that has been created + """ embed = EventEmbed(title="Scheduled event created", description=event.url) embed.addScheduledEventField("Scheduled Event", event) if event.creator: @@ -1695,6 +1931,12 @@ async def on_scheduled_event_create( async def on_scheduled_event_delete( self: Self, event: discord.ScheduledEvent ) -> None: + """Logs events related to deleting scheduled events in a guild + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_scheduled_event_delete + + Args: + event (discord.ScheduledEvent): The event that has been deleted + """ embed = EventEmbed(title="Scheduled event deleted", description=event.url) embed.addScheduledEventField("Scheduled Event", event) if event.creator: @@ -1716,6 +1958,13 @@ async def on_scheduled_event_update( before: discord.ScheduledEvent, after: discord.ScheduledEvent, ) -> None: + """This logs events related to scheduled events being edited + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_scheduled_event_update + + Args: + before (discord.ScheduledEvent): The original event, before any edits + after (discord.ScheduledEvent): The new event, after any edits + """ properties_to_track = [ "channel_id", "description", @@ -1743,6 +1992,12 @@ async def on_scheduled_event_update( @commands.Cog.listener() async def on_guild_role_create(self: Self, role: discord.Role) -> None: + """This logs events related to creating new roles + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_role_create + + Args: + role (discord.Role): The role object that was created + """ embed = EventEmbed(title="Role created", description="") embed.addRoleField("Role", role) embed.addRoleMetadataField("Role Metadata", role) @@ -1759,6 +2014,12 @@ async def on_guild_role_create(self: Self, role: discord.Role) -> None: @commands.Cog.listener() async def on_guild_role_delete(self: Self, role: discord.Role) -> None: + """This logs events related to deleting roles + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_role_delete + + Args: + role (discord.Role): The role object that was deleted + """ embed = EventEmbed(title="Role deleted", description="") embed.addRoleField("Role", role) embed.addRoleMetadataField("Role Metadata", role) @@ -1777,6 +2038,17 @@ async def on_guild_role_delete(self: Self, role: discord.Role) -> None: async def on_guild_role_update( self: Self, before: discord.Role, after: discord.Role ) -> None: + """This logs events related to updating a role. 3 different logs are generated here: + Role color changes + Role permission changes + Other role property changes + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_role_update + + Args: + self (Self): _description_ + before (discord.Role): The old role, before any changes + after (discord.Role): The new role, after any changes + """ general_properties_to_track = [ "display_icon", "flags", @@ -1843,6 +2115,12 @@ async def on_guild_role_update( @commands.Cog.listener() async def on_automod_rule_create(self: Self, rule: discord.AutoModRule) -> None: + """This logs events related to creating a new automod rule + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_automod_rule_create + + Args: + rule (discord.AutoModRule): The automod rule that was created + """ embed = EventEmbed(title="AutoMod rule created", description="") embed.addAutoModRuleField("AutoMod Rule", rule) if rule.creator: @@ -1859,6 +2137,13 @@ async def on_automod_rule_create(self: Self, rule: discord.AutoModRule) -> None: @commands.Cog.listener() async def on_automod_rule_update(self: Self, rule: discord.AutoModRule) -> None: + """This logs events related to updating an automod rule + As discord does not give the old rule before edits, we cannot log what has changed. Only that something has changed. + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_automod_rule_update + + Args: + rule (discord.AutoModRule): The automod rule that was updated + """ embed = EventEmbed(title="AutoMod rule updated", description="") embed.addAutoModRuleField("AutoMod Rule", rule) if rule.creator: @@ -1875,6 +2160,12 @@ async def on_automod_rule_update(self: Self, rule: discord.AutoModRule) -> None: @commands.Cog.listener() async def on_automod_rule_delete(self: Self, rule: discord.AutoModRule) -> None: + """This logs events related to deleting an automod rule + https://discordpy.readthedocs.io/en/latest/api.html#discord.on_automod_rule_delete + + Args: + rule (discord.AutoModRule): The automod rule that was deleted + """ embed = EventEmbed(title="AutoMod rule deleted", description="") embed.addAutoModRuleField("AutoMod Rule", rule) if rule.creator: From c1df40490a8205ecbbbfd2b73fba1f3baaedbd99 Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Fri, 12 Jun 2026 12:05:19 -0700 Subject: [PATCH 20/24] Update changelog --- changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelog.md b/changelog.md index f8bf2269..59a47bb7 100644 --- a/changelog.md +++ b/changelog.md @@ -45,6 +45,9 @@ Changes since 2026.06.04 ## Moderation +### Events +- Complete overhaul of events logging system. A huge number of additional events are now tracked, and information is displayed in a more readable way + ### Logger - Now mentions roles instead of listing text names From e1be7d159ec7b8905408548d639cefc3d4aca55c Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Fri, 12 Jun 2026 12:42:19 -0700 Subject: [PATCH 21/24] Formatting changes --- modules/moderation/events.py | 123 ++++++++++++++++++++++++----------- 1 file changed, 86 insertions(+), 37 deletions(-) diff --git a/modules/moderation/events.py b/modules/moderation/events.py index 11e163c9..50720ac5 100644 --- a/modules/moderation/events.py +++ b/modules/moderation/events.py @@ -26,7 +26,9 @@ async def setup(bot: bot.TechSupportBot) -> None: class EventEmbed(discord.Embed): - """This subclass of embed contains several functions to create consistent fields for displaying various types of data in the event logs""" + """This subclass of embed contains several functions to create consistent fields + for displaying various types of data in the event logs + """ def __init__(self, *, title, description) -> None: super().__init__( @@ -49,7 +51,8 @@ def setEventAuthor(self, author: discord.Member) -> None: ) def addMemberField(self: Self, title: str, member: discord.Member) -> None: - """This adds a member info field to the embed. A mention, username, and id are shown here. + """This adds a member info field to the embed. + A mention, username, and id are shown here. Args: title (str): The title of the field @@ -68,7 +71,8 @@ def addMemberField(self: Self, title: str, member: discord.Member) -> None: def addMessageContentField( self: Self, title: str, message: discord.Message ) -> None: - """This adds a message content info field to the embed. Clean content, trimmed to 1024 in size, is what is used. + """This adds a message content info field to the embed. + Clean content, trimmed to 1024 in size, is what is used. Args: title (str): The title of the field @@ -87,7 +91,8 @@ def addMessageContentField( ) def addMessageInfoField(self: Self, title: str, message: discord.Message) -> None: - """This adds a message info field to the embed. Clean content, trimmed to 50, the author, the ID, and the sent at time are all displayed + """This adds a message info field to the embed. + Clean content, trimmed to 50, the author, the ID, and the sent at time are all displayed Args: title (str): The title of the field @@ -107,7 +112,8 @@ def addMessageInfoField(self: Self, title: str, message: discord.Message) -> Non def addChannelField( self: Self, title: str, channel: discord.abc.GuildChannel ) -> None: - """This adds a channel info field into the embed. Channel link, name, type and ID are all displayed. + """This adds a channel info field into the embed. + Channel link, name, type and ID are all displayed. Args: title (str): The title of the field @@ -127,7 +133,8 @@ def addChannelField( def addSoundboardField( self: Self, title: str, soundboard: discord.SoundboardSound ) -> None: - """This adds a soundboard info field into the embed. Name, volume, emoji and ID are all displayed. + """This adds a soundboard info field into the embed. + Name, volume, emoji and ID are all displayed. Args: title (str): The title of the field @@ -147,7 +154,8 @@ def addSoundboardField( def addEmojiField( self: Self, title: str, emoji: discord.Emoji | discord.PartialEmoji | str ) -> None: - """This adds an emoji info field into the embed. For standard reactions, just the emoji is displayed. + """This adds an emoji info field into the embed. + For standard reactions, just the emoji is displayed. For custom emojis, the emoji will attempt to be displayed, as well as the name and ID Args: @@ -171,7 +179,8 @@ def addStickerField( title: str, sticker: discord.GuildSticker, ) -> None: - """This adds a sticker info field into the embed. Name, ID, description and emoji are all displayed. + """This adds a sticker info field into the embed. + Name, ID, description and emoji are all displayed. Args: title (str): The title of the field @@ -192,7 +201,8 @@ def addIntegrationField( title: str, integration: discord.Integration, ) -> None: - """This adds an integration info field into the embed. Name, type, scope and ID are all displayed + """This adds an integration info field into the embed. + Name, type, scope and ID are all displayed Args: title (str): The title of the field @@ -209,7 +219,8 @@ def addIntegrationField( ) def addPollField(self: Self, title: str, poll: discord.Poll) -> None: - """This adds a poll info field into the embed. Question, duration, and answers are all displayed. + """This adds a poll info field into the embed. + Question, duration, and answers are all displayed. Args: title (str): The title of the field @@ -239,7 +250,8 @@ def addPollAnswerField(self: Self, title: str, answer: discord.PollAnswer) -> No ) def addRoleField(self: Self, title: str, role: discord.Role) -> None: - """This adds a role info field into the embed. Mention, name, ID, position, hoisted and mentionable status are all displayed. + """This adds a role info field into the embed. + Mention, name, ID, position, hoisted and mentionable status are all displayed. Args: title (str): The title of the field @@ -259,7 +271,8 @@ def addRoleField(self: Self, title: str, role: discord.Role) -> None: ) def addRoleMetadataField(self: Self, title: str, role: discord.Role) -> None: - """This adds a role metadata field into the embed. Timestamp, tags, flags, and icon are displayed + """This adds a role metadata field into the embed. + Timestamp, tags, flags, and icon are displayed Args: title (str): The title of the field @@ -279,7 +292,8 @@ def addRoleMetadataField(self: Self, title: str, role: discord.Role) -> None: ) def addRoleColorField(self: Self, title: str, role: discord.Role) -> None: - """This adds a role color field into the embed. Primary, secondary, and tertiary colors are all displayed. + """This adds a role color field into the embed. + Primary, secondary, and tertiary colors are all displayed. Args: title (str): The title of the field @@ -298,7 +312,8 @@ def addRoleColorField(self: Self, title: str, role: discord.Role) -> None: def addScheduledEventField( self: Self, title: str, event: discord.ScheduledEvent ) -> None: - """This adds a scheduled event field into the embed. Time, ID, status, location are all included. + """This adds a scheduled event field into the embed. + Time, ID, status, location are all included. Args: title (str): The title of the field @@ -333,7 +348,8 @@ def addScheduledEventField( ) def addAutoModRuleField(self: Self, title: str, rule: discord.AutoModRule) -> None: - """This adds an automod rule info field into the embed. Name, ID, status, type, triggers, actions, and exempt info are all displayed + """This adds an automod rule info field into the embed. + Name, ID, status, type, triggers, actions, and exempt info are all displayed Args: title (str): The title of the field @@ -426,7 +442,8 @@ def addRolePermissionChangeFields( def addPropertyChangeFields( self: Self, properties: list[str], before: Any, after: Any ) -> bool: - """Adds fields to the embed for an arbitrary list of string properties to compare betweeen two objects + """Adds fields to the embed for an arbitrary list of string properties + to compare betweeen two objects Args: properties (list[str]): A list of properties to get and compared @@ -583,7 +600,10 @@ async def on_message_edit( embed.set_footer(text=f"Message ID: {after.id}") - console_message = f"Message edit: ID: {after.id} in channel: {after.channel.name} ({after.channel.id}). Old: {old_content}, new {after.clean_content}" + console_message = ( + f"Message edit: ID: {after.id} in channel: {after.channel.name} " + f"({after.channel.id}). Old: {old_content}, new {after.clean_content}" + ) await self.send_event_log( guild=after.guild, @@ -609,7 +629,10 @@ async def on_message_edit( embed.set_footer(text=f"Message ID: {after.id}") - console_message = f"Message pins changed: ID: {after.id} in channel: {after.channel.name} ({after.channel.id}). Pinned status: {after.pinned}" + console_message = ( + f"Message pins changed: ID: {after.id} in channel: " + f"{after.channel.name} ({after.channel.id}). Pinned status: {after.pinned}" + ) await self.send_event_log( guild=after.guild, @@ -655,7 +678,10 @@ async def on_message_delete(self: Self, message: discord.Message) -> None: embed.set_footer(text=f"Message ID: {message.id}") - console_message = f"Message delete: ID: {message.id} in channel: {channel.name} ({channel.id}). Content: {message.clean_content}" + console_message = ( + f"Message delete: ID: {message.id} in channel: " + f"{channel.name} ({channel.id}). Content: {message.clean_content}" + ) await self.send_event_log( guild=guild, @@ -713,7 +739,10 @@ async def on_bulk_message_delete( embed.description = description - console_message = f"Bulk message delete: Channel: {channel.name} ({channel.id}). Amount: {len(messages)}" + console_message = ( + f"Bulk message delete: Channel: {channel.name} " + f"({channel.id}). Amount: {len(messages)}" + ) await self.send_event_log( guild=guild, @@ -762,7 +791,10 @@ async def on_reaction_add( embed.addChannelField("Channel", message.channel) embed.addMessageInfoField("Message Info", message) - console_message = f"Reaction {reaction.emoji} added to message with ID: {message.id} by user {user.name} ({user.id})" + console_message = ( + f"Reaction {reaction.emoji} added to message with " + f"ID: {message.id} by user {user.name} ({user.id})" + ) await self.send_event_log( guild=guild, @@ -811,7 +843,10 @@ async def on_reaction_remove( embed.addChannelField("Channel", message.channel) embed.addMessageInfoField("Message Info", message) - console_message = f"Reaction {reaction.emoji} removed from message with ID: {message.id} by user {user.name} ({user.id})" + console_message = ( + f"Reaction {reaction.emoji} removed from message " + f"with ID: {message.id} by user {user.name} ({user.id})" + ) await self.send_event_log( guild=guild, @@ -825,7 +860,8 @@ async def on_reaction_remove( async def on_reaction_clear( self: Self, message: discord.Message, reactions: list[discord.Reaction] ) -> None: - """This logs events where all reactions, or all of a specific reaction, were cleared from a message + """This logs events where all reactions or all of a specific reaction + were cleared from a message https://discordpy.readthedocs.io/en/latest/api.html#discord.on_reaction_clear Args: @@ -855,7 +891,10 @@ async def on_reaction_clear( embed.addChannelField("Channel", message.channel) embed.addMessageInfoField("Message Info", message) - console_message = f"{total_emoji} reactions cleared from message with ID: {message.id} in channel {channel.name} ({channel.id})" + console_message = ( + f"{total_emoji} reactions cleared from message with " + f"ID: {message.id} in channel {channel.name} ({channel.id})" + ) await self.send_event_log( guild=guild, @@ -895,7 +934,10 @@ async def on_poll_vote_add( embed.addMemberField("Member", user) embed.addMessageInfoField("Message", message) - console_message = f"User {user.name} ({user.id}) voted {answer.text} to poll message {message.id} in channel {channel.name} ({channel.id})" + console_message = ( + f"User {user.name} ({user.id}) voted {answer.text} " + f"to poll message {message.id} in channel {channel.name} ({channel.id})" + ) await self.send_event_log( guild=guild, @@ -935,7 +977,10 @@ async def on_poll_vote_remove( embed.addMemberField("Member", user) embed.addMessageInfoField("Message", message) - console_message = f"User {user.name} ({user.id}) removed vote {answer.text} from a poll message {message.id} in channel {channel.name} ({channel.id})" + console_message = ( + f"User {user.name} ({user.id}) removed vote {answer.text} " + f"from a poll message {message.id} in channel {channel.name} ({channel.id})" + ) await self.send_event_log( guild=guild, @@ -1196,7 +1241,10 @@ async def on_member_update( value=", ".join([role.mention for role in roles_lost]), ) - console_message = f"Member roles updated: {after.name} ({after.id}). Roles changed {', '.join(role.name for role in changed_role)}" + console_message = ( + f"Member roles updated: {after.name} ({after.id}). " + f"Roles changed {', '.join(role.name for role in changed_role)}" + ) await self.send_event_log( guild=after.guild, @@ -1219,7 +1267,7 @@ async def on_guild_channel_create( """ embed = EventEmbed( title="Channel created", - description=f"", + description="", ) embed.addChannelField("Channel", channel) @@ -1246,7 +1294,7 @@ async def on_guild_channel_delete( """ embed = EventEmbed( title="Channel deleted", - description=f"", + description="", ) embed.addChannelField("Channel", channel) @@ -1335,13 +1383,13 @@ async def on_guild_channel_update( value_parts = [] if isinstance(target, discord.Role): - target_name = f"Role:" + target_name = "Role:" value_parts.append(f"{target.mention}") elif isinstance(target, discord.Member): - target_name = f"Member:" + target_name = "Member:" value_parts.append(f"{target.mention}") else: - target_name = f"Unknown:" + target_name = "Unknown:" value_parts.append(f"{target.id}") if added: @@ -1371,7 +1419,7 @@ async def on_guild_channel_update( return console_message = ( - f"Permission overwrites updated for channel " + "Permission overwrites updated for channel " f"{after.name} ({after.id})" ) @@ -1455,7 +1503,7 @@ async def on_guild_update( embed = EventEmbed(title="Guild properties updated", description="") if embed.addPropertyChangeFields(properties_to_track, before, after): - console_message = f"Guild properties updated." + console_message = "Guild properties updated." await self.send_event_log( guild=after, @@ -1474,7 +1522,7 @@ async def on_thread_create(self: Self, thread: discord.Thread) -> None: """ embed = EventEmbed( title="Thread created", - description=f"", + description="", ) embed.addChannelField("Thread", thread) @@ -1500,7 +1548,7 @@ async def on_thread_delete(self: Self, thread: discord.Thread) -> None: """ embed = EventEmbed( title="Thread deleted", - description=f"", + description="", ) embed.addChannelField("Thread", thread) @@ -2138,7 +2186,8 @@ async def on_automod_rule_create(self: Self, rule: discord.AutoModRule) -> None: @commands.Cog.listener() async def on_automod_rule_update(self: Self, rule: discord.AutoModRule) -> None: """This logs events related to updating an automod rule - As discord does not give the old rule before edits, we cannot log what has changed. Only that something has changed. + As discord does not give the old rule before edits, we cannot log what has changed. + Only that something has changed. https://discordpy.readthedocs.io/en/latest/api.html#discord.on_automod_rule_update Args: From 7139fe40faa0b71a352c9e30ea5eb0997da9a3a2 Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Fri, 12 Jun 2026 12:43:45 -0700 Subject: [PATCH 22/24] Formatting changes --- modules/moderation/events.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/moderation/events.py b/modules/moderation/events.py index 50720ac5..be358c9b 100644 --- a/modules/moderation/events.py +++ b/modules/moderation/events.py @@ -160,7 +160,8 @@ def addEmojiField( Args: title (str): The title of the field - emoji (discord.Emoji | discord.PartialEmoji | str): The emoji object to get information from + emoji (discord.Emoji | discord.PartialEmoji | str): + The emoji object to get information from """ # This is to better display custom emotes if isinstance(emoji, (discord.Emoji, discord.PartialEmoji)): From d81e353428ec80d05731987a2ab31a810979e103 Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Fri, 12 Jun 2026 12:51:46 -0700 Subject: [PATCH 23/24] Formatting changes --- botlogging/logger.py | 2 ++ modules/moderation/events.py | 19 ++++++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/botlogging/logger.py b/botlogging/logger.py index 41eee000..29ff6e19 100644 --- a/botlogging/logger.py +++ b/botlogging/logger.py @@ -217,6 +217,8 @@ async def send_log( exception (Exception, optional): The exception item if you wish to log an exception with this log. Exceptions will be logged in plain text. Defaults to None. + embed_as_is (bool, optional): If the passed embed should be sent without any edits + Defaults to False """ log_level = self.convert_level(level) diff --git a/modules/moderation/events.py b/modules/moderation/events.py index be358c9b..c19bbe98 100644 --- a/modules/moderation/events.py +++ b/modules/moderation/events.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Self, Sequence +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any, Self import discord from discord.ext import commands @@ -28,9 +29,13 @@ async def setup(bot: bot.TechSupportBot) -> None: class EventEmbed(discord.Embed): """This subclass of embed contains several functions to create consistent fields for displaying various types of data in the event logs + + Args: + title (str): The title of the embed to be applied + description (str): The body of the embed to be applied """ - def __init__(self, *, title, description) -> None: + def __init__(self: Self, *, title: str, description: str) -> None: super().__init__( title=title, description=description, @@ -441,7 +446,7 @@ def addRolePermissionChangeFields( return True def addPropertyChangeFields( - self: Self, properties: list[str], before: Any, after: Any + self: Self, properties: list[str], before: Any, after: Any # noqa: ANN401 ) -> bool: """Adds fields to the embed for an arbitrary list of string properties to compare betweeen two objects @@ -500,6 +505,10 @@ def addPropertyChangeFields( class EventLogger(cogs.BaseCog): """This is the cog that holds all of the discord event listeners For the explicit purpose of logging, not taking further action + + Attrs: + CONFIG_MAP (dict[str, str]): A mpa of types of logs to the config names + of their respective logging channel """ CONFIG_MAP: dict[str, str] = { @@ -1191,7 +1200,6 @@ async def on_member_update( https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_update Args: - self (Self): _description_ before (discord.Member): The old member object, pre changes after (discord.Member): The new member object, post changes """ @@ -1319,7 +1327,6 @@ async def on_guild_channel_update( https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_channel_update Args: - self (Self): _description_ before (discord.abc.GuildChannel): The previous channel, before any changes after (discord.abc.GuildChannel): The new channel, after any changes """ @@ -1573,7 +1580,6 @@ async def on_thread_update( https://discordpy.readthedocs.io/en/latest/api.html#discord.on_thread_update Args: - self (Self): _description_ before (discord.Thread): The previous thread, before any property changes after (discord.Thread): The new thread, after any property changes """ @@ -2094,7 +2100,6 @@ async def on_guild_role_update( https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_role_update Args: - self (Self): _description_ before (discord.Role): The old role, before any changes after (discord.Role): The new role, after any changes """ From c21fab4151b19d0b3863569f9da0893c461c3920 Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Fri, 12 Jun 2026 12:54:05 -0700 Subject: [PATCH 24/24] minor formatting changes --- modules/moderation/events.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/moderation/events.py b/modules/moderation/events.py index c19bbe98..53f79b8c 100644 --- a/modules/moderation/events.py +++ b/modules/moderation/events.py @@ -43,7 +43,7 @@ def __init__(self: Self, *, title: str, description: str) -> None: timestamp=discord.utils.utcnow(), ) - def setEventAuthor(self, author: discord.Member) -> None: + def setEventAuthor(self: Self, author: discord.Member) -> None: """This sets an author object of an embed to be stylized as a passed member object This shows the display_name and display_avatar @@ -506,7 +506,7 @@ class EventLogger(cogs.BaseCog): """This is the cog that holds all of the discord event listeners For the explicit purpose of logging, not taking further action - Attrs: + Attributes: CONFIG_MAP (dict[str, str]): A mpa of types of logs to the config names of their respective logging channel """