Refactor logging cog to use layout views
This commit is contained in:
parent
fd3fb3fa90
commit
6549537d7d
@ -2,6 +2,7 @@ import discord
|
|||||||
from discord.ext import commands, tasks
|
from discord.ext import commands, tasks
|
||||||
from discord import AllowedMentions, ui
|
from discord import AllowedMentions, ui
|
||||||
import datetime
|
import datetime
|
||||||
|
import difflib
|
||||||
import asyncio
|
import asyncio
|
||||||
import aiohttp # Added for webhook sending
|
import aiohttp # Added for webhook sending
|
||||||
import logging # Use logging instead of print
|
import logging # Use logging instead of print
|
||||||
@ -16,6 +17,12 @@ except ImportError:
|
|||||||
|
|
||||||
log = logging.getLogger(__name__) # Setup logger for this cog
|
log = logging.getLogger(__name__) # Setup logger for this cog
|
||||||
|
|
||||||
|
# Mapping for consistent event styling
|
||||||
|
EVENT_STYLES = {
|
||||||
|
"message_edit": ("✏️", discord.Color.light_grey()),
|
||||||
|
"message_delete": ("🗑️", discord.Color.dark_grey()),
|
||||||
|
}
|
||||||
|
|
||||||
# Define all possible event keys for toggling
|
# Define all possible event keys for toggling
|
||||||
# Keep this list updated if new loggable events are added
|
# Keep this list updated if new loggable events are added
|
||||||
ALL_EVENT_KEYS = sorted(
|
ALL_EVENT_KEYS = sorted(
|
||||||
@ -82,9 +89,7 @@ class LoggingCog(commands.Cog):
|
|||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: commands.Bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.session: Optional[aiohttp.ClientSession] = None # Session for webhooks
|
self.session: Optional[aiohttp.ClientSession] = None # Session for webhooks
|
||||||
self.last_audit_log_ids: dict[int, Optional[int]] = (
|
self.last_audit_log_ids: dict[int, Optional[int]] = {} # Store last ID per guild
|
||||||
{}
|
|
||||||
) # Store last ID per guild
|
|
||||||
# Start the audit log poller task if the bot is ready, otherwise wait
|
# Start the audit log poller task if the bot is ready, otherwise wait
|
||||||
if bot.is_ready():
|
if bot.is_ready():
|
||||||
asyncio.create_task(self.initialize_cog()) # Use async init helper
|
asyncio.create_task(self.initialize_cog()) # Use async init helper
|
||||||
@ -110,9 +115,7 @@ class LoggingCog(commands.Cog):
|
|||||||
self.add_item(self.container)
|
self.add_item(self.container)
|
||||||
|
|
||||||
if author is not None:
|
if author is not None:
|
||||||
header = ui.Section(
|
header = ui.Section(accessory=ui.Thumbnail(media=author.display_avatar.url))
|
||||||
accessory=ui.Thumbnail(media=author.display_avatar.url)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
header = ui.Section()
|
header = ui.Section()
|
||||||
|
|
||||||
@ -120,13 +123,13 @@ class LoggingCog(commands.Cog):
|
|||||||
if description:
|
if description:
|
||||||
header.add_item(ui.TextDisplay(description))
|
header.add_item(ui.TextDisplay(description))
|
||||||
self.container.add_item(header)
|
self.container.add_item(header)
|
||||||
self.container.add_item(
|
self.container.add_item(ui.Separator(spacing=discord.SeparatorSpacing.small))
|
||||||
ui.Separator(spacing=discord.SeparatorSpacing.small)
|
|
||||||
)
|
|
||||||
|
|
||||||
footer_text = footer or f"Bot ID: {bot.user.id}" + (
|
timestamp = discord.utils.format_dt(datetime.datetime.utcnow(), style="f")
|
||||||
f" | User ID: {author.id}" if author else ""
|
parts = [timestamp, footer or f"Bot ID: {bot.user.id}"]
|
||||||
)
|
if author:
|
||||||
|
parts.append(f"User ID: {author.id}")
|
||||||
|
footer_text = " | ".join(parts)
|
||||||
self.footer_display = ui.TextDisplay(footer_text)
|
self.footer_display = ui.TextDisplay(footer_text)
|
||||||
self.container.add_item(self.footer_display)
|
self.container.add_item(self.footer_display)
|
||||||
|
|
||||||
@ -153,9 +156,7 @@ class LoggingCog(commands.Cog):
|
|||||||
"""Fetch the latest audit log ID for each guild the bot is in."""
|
"""Fetch the latest audit log ID for each guild the bot is in."""
|
||||||
log.info("Initializing last audit log IDs for guilds...")
|
log.info("Initializing last audit log IDs for guilds...")
|
||||||
for guild in self.bot.guilds:
|
for guild in self.bot.guilds:
|
||||||
if (
|
if guild.id not in self.last_audit_log_ids: # Only initialize if not already set
|
||||||
guild.id not in self.last_audit_log_ids
|
|
||||||
): # Only initialize if not already set
|
|
||||||
try:
|
try:
|
||||||
if guild.me.guild_permissions.view_audit_log:
|
if guild.me.guild_permissions.view_audit_log:
|
||||||
async for entry in guild.audit_logs(limit=1):
|
async for entry in guild.audit_logs(limit=1):
|
||||||
@ -168,26 +169,20 @@ class LoggingCog(commands.Cog):
|
|||||||
log.warning(
|
log.warning(
|
||||||
f"Missing 'View Audit Log' permission in guild {guild.id}. Cannot initialize audit log ID."
|
f"Missing 'View Audit Log' permission in guild {guild.id}. Cannot initialize audit log ID."
|
||||||
)
|
)
|
||||||
self.last_audit_log_ids[guild.id] = (
|
self.last_audit_log_ids[guild.id] = None # Mark as unable to fetch
|
||||||
None # Mark as unable to fetch
|
|
||||||
)
|
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
log.warning(
|
log.warning(
|
||||||
f"Forbidden error fetching initial audit log ID for guild {guild.id}."
|
f"Forbidden error fetching initial audit log ID for guild {guild.id}."
|
||||||
)
|
)
|
||||||
self.last_audit_log_ids[guild.id] = None
|
self.last_audit_log_ids[guild.id] = None
|
||||||
except discord.HTTPException as e:
|
except discord.HTTPException as e:
|
||||||
log.error(
|
log.error(f"HTTP error fetching initial audit log ID for guild {guild.id}: {e}")
|
||||||
f"HTTP error fetching initial audit log ID for guild {guild.id}: {e}"
|
|
||||||
)
|
|
||||||
self.last_audit_log_ids[guild.id] = None
|
self.last_audit_log_ids[guild.id] = None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception(
|
log.exception(
|
||||||
f"Unexpected error fetching initial audit log ID for guild {guild.id}: {e}"
|
f"Unexpected error fetching initial audit log ID for guild {guild.id}: {e}"
|
||||||
)
|
)
|
||||||
self.last_audit_log_ids[guild.id] = (
|
self.last_audit_log_ids[guild.id] = None # Mark as unable on other errors
|
||||||
None # Mark as unable on other errors
|
|
||||||
)
|
|
||||||
log.info("Finished initializing audit log IDs.")
|
log.info("Finished initializing audit log IDs.")
|
||||||
|
|
||||||
async def start_audit_log_poller_when_ready(self):
|
async def start_audit_log_poller_when_ready(self):
|
||||||
@ -231,9 +226,7 @@ class LoggingCog(commands.Cog):
|
|||||||
)
|
)
|
||||||
# log.debug(f"Sent log embed via webhook for guild {guild.id}") # Can be noisy
|
# log.debug(f"Sent log embed via webhook for guild {guild.id}") # Can be noisy
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
log.exception(
|
log.exception(f"ValueError sending log via webhook for guild {guild.id}. Error: {e}")
|
||||||
f"ValueError sending log via webhook for guild {guild.id}. Error: {e}"
|
|
||||||
)
|
|
||||||
# Consider notifying an admin or disabling logging for this guild temporarily
|
# Consider notifying an admin or disabling logging for this guild temporarily
|
||||||
# await settings_manager.set_logging_webhook(guild.id, None) # Example: Auto-disable on invalid URL
|
# await settings_manager.set_logging_webhook(guild.id, None) # Example: Auto-disable on invalid URL
|
||||||
except (discord.Forbidden, discord.NotFound):
|
except (discord.Forbidden, discord.NotFound):
|
||||||
@ -245,13 +238,9 @@ class LoggingCog(commands.Cog):
|
|||||||
except discord.HTTPException as e:
|
except discord.HTTPException as e:
|
||||||
log.error(f"HTTP error sending log via webhook for guild {guild.id}: {e}")
|
log.error(f"HTTP error sending log via webhook for guild {guild.id}: {e}")
|
||||||
except aiohttp.ClientError as e:
|
except aiohttp.ClientError as e:
|
||||||
log.error(
|
log.error(f"aiohttp client error sending log via webhook for guild {guild.id}: {e}")
|
||||||
f"aiohttp client error sending log via webhook for guild {guild.id}: {e}"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception(
|
log.exception(f"Unexpected error sending log via webhook for guild {guild.id}: {e}")
|
||||||
f"Unexpected error sending log via webhook for guild {guild.id}: {e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def _create_log_embed(
|
def _create_log_embed(
|
||||||
self,
|
self,
|
||||||
@ -284,8 +273,16 @@ class LoggingCog(commands.Cog):
|
|||||||
if target_id:
|
if target_id:
|
||||||
existing_footer = getattr(embed, "footer_display", None)
|
existing_footer = getattr(embed, "footer_display", None)
|
||||||
if existing_footer:
|
if existing_footer:
|
||||||
|
parts = [f"{id_name}: {target_id}"]
|
||||||
|
link = None
|
||||||
|
if hasattr(obj, "jump_url"):
|
||||||
|
link = f"[Jump]({obj.jump_url})"
|
||||||
|
elif isinstance(obj, discord.abc.GuildChannel):
|
||||||
|
link = obj.mention
|
||||||
|
if link:
|
||||||
|
parts.append(link)
|
||||||
sep = " | " if existing_footer.content else ""
|
sep = " | " if existing_footer.content else ""
|
||||||
existing_footer.content += f"{sep}{id_name}: {target_id}"
|
existing_footer.content += sep + " | ".join(parts)
|
||||||
|
|
||||||
async def _check_log_enabled(self, guild_id: int, event_key: str) -> bool:
|
async def _check_log_enabled(self, guild_id: int, event_key: str) -> bool:
|
||||||
"""Checks if logging is enabled for a specific event key in a guild."""
|
"""Checks if logging is enabled for a specific event key in a guild."""
|
||||||
@ -363,9 +360,7 @@ class LoggingCog(commands.Cog):
|
|||||||
allowed_mentions=AllowedMentions.none(),
|
allowed_mentions=AllowedMentions.none(),
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception(
|
log.exception(f"Error fetching existing webhook during setup for guild {guild.id}")
|
||||||
f"Error fetching existing webhook during setup for guild {guild.id}"
|
|
||||||
)
|
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
f"⚠️ An error occurred while checking the existing webhook. Proceeding to create a new one for {channel.mention}.",
|
f"⚠️ An error occurred while checking the existing webhook. Proceeding to create a new one for {channel.mention}.",
|
||||||
allowed_mentions=AllowedMentions.none(),
|
allowed_mentions=AllowedMentions.none(),
|
||||||
@ -379,9 +374,7 @@ class LoggingCog(commands.Cog):
|
|||||||
try:
|
try:
|
||||||
avatar_bytes = await self.bot.user.display_avatar.read()
|
avatar_bytes = await self.bot.user.display_avatar.read()
|
||||||
except Exception:
|
except Exception:
|
||||||
log.warning(
|
log.warning(f"Could not read bot avatar for webhook creation in guild {guild.id}.")
|
||||||
f"Could not read bot avatar for webhook creation in guild {guild.id}."
|
|
||||||
)
|
|
||||||
|
|
||||||
new_webhook = await channel.create_webhook(
|
new_webhook = await channel.create_webhook(
|
||||||
name=webhook_name,
|
name=webhook_name,
|
||||||
@ -392,9 +385,7 @@ class LoggingCog(commands.Cog):
|
|||||||
f"Created logging webhook '{webhook_name}' in channel {channel.id} for guild {guild.id}"
|
f"Created logging webhook '{webhook_name}' in channel {channel.id} for guild {guild.id}"
|
||||||
)
|
)
|
||||||
except discord.HTTPException as e:
|
except discord.HTTPException as e:
|
||||||
log.error(
|
log.error(f"Failed to create webhook in {channel.mention} for guild {guild.id}: {e}")
|
||||||
f"Failed to create webhook in {channel.mention} for guild {guild.id}: {e}"
|
|
||||||
)
|
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
f"❌ Failed to create webhook. Error: {e}. This could be due to hitting the channel webhook limit (15).",
|
f"❌ Failed to create webhook. Error: {e}. This could be due to hitting the channel webhook limit (15).",
|
||||||
allowed_mentions=AllowedMentions.none(),
|
allowed_mentions=AllowedMentions.none(),
|
||||||
@ -431,9 +422,7 @@ class LoggingCog(commands.Cog):
|
|||||||
allowed_mentions=AllowedMentions.none(),
|
allowed_mentions=AllowedMentions.none(),
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(
|
log.error(f"Failed to send test message via new webhook for guild {guild.id}: {e}")
|
||||||
f"Failed to send test message via new webhook for guild {guild.id}: {e}"
|
|
||||||
)
|
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"⚠️ Could not send a test message via the new webhook, but the URL has been saved.",
|
"⚠️ Could not send a test message via the new webhook, but the URL has been saved.",
|
||||||
allowed_mentions=AllowedMentions.none(),
|
allowed_mentions=AllowedMentions.none(),
|
||||||
@ -449,9 +438,7 @@ class LoggingCog(commands.Cog):
|
|||||||
# Attempt to delete the created webhook to avoid orphans
|
# Attempt to delete the created webhook to avoid orphans
|
||||||
try:
|
try:
|
||||||
await new_webhook.delete(reason="Failed to save URL to settings")
|
await new_webhook.delete(reason="Failed to save URL to settings")
|
||||||
log.info(
|
log.info(f"Deleted orphaned webhook '{new_webhook.name}' for guild {guild.id}")
|
||||||
f"Deleted orphaned webhook '{new_webhook.name}' for guild {guild.id}"
|
|
||||||
)
|
|
||||||
except Exception as del_e:
|
except Exception as del_e:
|
||||||
log.error(
|
log.error(
|
||||||
f"Failed to delete orphaned webhook '{new_webhook.name}' for guild {guild.id}: {del_e}"
|
f"Failed to delete orphaned webhook '{new_webhook.name}' for guild {guild.id}: {del_e}"
|
||||||
@ -493,9 +480,7 @@ class LoggingCog(commands.Cog):
|
|||||||
new_status = enabled_status
|
new_status = enabled_status
|
||||||
|
|
||||||
# Save the new status
|
# Save the new status
|
||||||
success = await settings_manager.set_log_event_enabled(
|
success = await settings_manager.set_log_event_enabled(guild_id, event_key, new_status)
|
||||||
guild_id, event_key, new_status
|
|
||||||
)
|
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
status_str = "ENABLED" if new_status else "DISABLED"
|
status_str = "ENABLED" if new_status else "DISABLED"
|
||||||
@ -529,15 +514,11 @@ class LoggingCog(commands.Cog):
|
|||||||
# Paginate if too long for one embed description
|
# Paginate if too long for one embed description
|
||||||
description = ""
|
description = ""
|
||||||
for line in lines:
|
for line in lines:
|
||||||
if (
|
if len(description) + len(line) + 1 > 4000: # Embed description limit (approx)
|
||||||
len(description) + len(line) + 1 > 4000
|
|
||||||
): # Embed description limit (approx)
|
|
||||||
embed.description = description
|
embed.description = description
|
||||||
await ctx.send(embed=embed, allowed_mentions=AllowedMentions.none())
|
await ctx.send(embed=embed, allowed_mentions=AllowedMentions.none())
|
||||||
description = line + "\n" # Start new description
|
description = line + "\n" # Start new description
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(color=discord.Color.blue()) # New embed for continuation
|
||||||
color=discord.Color.blue()
|
|
||||||
) # New embed for continuation
|
|
||||||
else:
|
else:
|
||||||
description += line + "\n"
|
description += line + "\n"
|
||||||
|
|
||||||
@ -548,9 +529,7 @@ class LoggingCog(commands.Cog):
|
|||||||
@log_group.command(name="list_keys")
|
@log_group.command(name="list_keys")
|
||||||
async def log_list_keys(self, ctx: commands.Context):
|
async def log_list_keys(self, ctx: commands.Context):
|
||||||
"""Lists all valid event keys for use with the 'log toggle' command."""
|
"""Lists all valid event keys for use with the 'log toggle' command."""
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(title="Available Logging Event Keys", color=discord.Color.purple())
|
||||||
title="Available Logging Event Keys", color=discord.Color.purple()
|
|
||||||
)
|
|
||||||
keys_text = "\n".join(f"`{key}`" for key in ALL_EVENT_KEYS)
|
keys_text = "\n".join(f"`{key}`" for key in ALL_EVENT_KEYS)
|
||||||
|
|
||||||
# Paginate if needed
|
# Paginate if needed
|
||||||
@ -594,9 +573,7 @@ class LoggingCog(commands.Cog):
|
|||||||
footer=f"Thread ID: {thread.id} | Parent ID: {thread.parent_id}",
|
footer=f"Thread ID: {thread.id} | Parent ID: {thread.parent_id}",
|
||||||
)
|
)
|
||||||
if thread.owner: # Sometimes owner isn't cached immediately
|
if thread.owner: # Sometimes owner isn't cached immediately
|
||||||
embed.set_author(
|
embed.set_author(name=str(thread.owner), icon_url=thread.owner.display_avatar.url)
|
||||||
name=str(thread.owner), icon_url=thread.owner.display_avatar.url
|
|
||||||
)
|
|
||||||
await self._send_log_embed(guild, embed)
|
await self._send_log_embed(guild, embed)
|
||||||
|
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
@ -630,9 +607,7 @@ class LoggingCog(commands.Cog):
|
|||||||
if before.locked != after.locked:
|
if before.locked != after.locked:
|
||||||
changes.append(f"**Locked:** `{before.locked}` → `{after.locked}`")
|
changes.append(f"**Locked:** `{before.locked}` → `{after.locked}`")
|
||||||
if before.slowmode_delay != after.slowmode_delay:
|
if before.slowmode_delay != after.slowmode_delay:
|
||||||
changes.append(
|
changes.append(f"**Slowmode:** `{before.slowmode_delay}s` → `{after.slowmode_delay}s`")
|
||||||
f"**Slowmode:** `{before.slowmode_delay}s` → `{after.slowmode_delay}s`"
|
|
||||||
)
|
|
||||||
if before.auto_archive_duration != after.auto_archive_duration:
|
if before.auto_archive_duration != after.auto_archive_duration:
|
||||||
changes.append(
|
changes.append(
|
||||||
f"**Auto-Archive:** `{before.auto_archive_duration} mins` → `{after.auto_archive_duration} mins`"
|
f"**Auto-Archive:** `{before.auto_archive_duration} mins` → `{after.auto_archive_duration} mins`"
|
||||||
@ -711,9 +686,7 @@ class LoggingCog(commands.Cog):
|
|||||||
# Initialization is now handled by initialize_cog called from __init__ or start_audit_log_poller_when_ready
|
# Initialization is now handled by initialize_cog called from __init__ or start_audit_log_poller_when_ready
|
||||||
# Ensure the poller is running if it wasn't started earlier
|
# Ensure the poller is running if it wasn't started earlier
|
||||||
if self.bot.is_ready() and not self.poll_audit_log.is_running():
|
if self.bot.is_ready() and not self.poll_audit_log.is_running():
|
||||||
log.warning(
|
log.warning("Poll audit log task was not running after on_ready, attempting to start.")
|
||||||
"Poll audit log task was not running after on_ready, attempting to start."
|
|
||||||
)
|
|
||||||
await self.initialize_cog() # Re-initialize just in case
|
await self.initialize_cog() # Re-initialize just in case
|
||||||
|
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
@ -730,14 +703,10 @@ class LoggingCog(commands.Cog):
|
|||||||
)
|
)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
log.warning(
|
log.warning(f"Missing 'View Audit Log' permission in new guild {guild.id}.")
|
||||||
f"Missing 'View Audit Log' permission in new guild {guild.id}."
|
|
||||||
)
|
|
||||||
self.last_audit_log_ids[guild.id] = None
|
self.last_audit_log_ids[guild.id] = None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception(
|
log.exception(f"Error fetching initial audit log ID for new guild {guild.id}: {e}")
|
||||||
f"Error fetching initial audit log ID for new guild {guild.id}: {e}"
|
|
||||||
)
|
|
||||||
self.last_audit_log_ids[guild.id] = None
|
self.last_audit_log_ids[guild.id] = None
|
||||||
|
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
@ -790,9 +759,7 @@ class LoggingCog(commands.Cog):
|
|||||||
await self._send_log_embed(member.guild, embed)
|
await self._send_log_embed(member.guild, embed)
|
||||||
|
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
async def on_member_ban(
|
async def on_member_ban(self, guild: discord.Guild, user: Union[discord.User, discord.Member]):
|
||||||
self, guild: discord.Guild, user: Union[discord.User, discord.Member]
|
|
||||||
):
|
|
||||||
event_key = "member_ban_event"
|
event_key = "member_ban_event"
|
||||||
if not await self._check_log_enabled(guild.id, event_key):
|
if not await self._check_log_enabled(guild.id, event_key):
|
||||||
return
|
return
|
||||||
@ -832,9 +799,7 @@ class LoggingCog(commands.Cog):
|
|||||||
changes = []
|
changes = []
|
||||||
# Nickname change
|
# Nickname change
|
||||||
if before.nick != after.nick:
|
if before.nick != after.nick:
|
||||||
changes.append(
|
changes.append(f"**Nickname:** `{before.nick or 'None'}` → `{after.nick or 'None'}`")
|
||||||
f"**Nickname:** `{before.nick or 'None'}` → `{after.nick or 'None'}`"
|
|
||||||
)
|
|
||||||
# Role changes (handled more reliably by audit log for who did it)
|
# Role changes (handled more reliably by audit log for who did it)
|
||||||
if before.roles != after.roles:
|
if before.roles != after.roles:
|
||||||
added_roles = [r.mention for r in after.roles if r not in before.roles]
|
added_roles = [r.mention for r in after.roles if r not in before.roles]
|
||||||
@ -846,9 +811,7 @@ class LoggingCog(commands.Cog):
|
|||||||
# Timeout change
|
# Timeout change
|
||||||
if before.timed_out_until != after.timed_out_until:
|
if before.timed_out_until != after.timed_out_until:
|
||||||
if after.timed_out_until:
|
if after.timed_out_until:
|
||||||
timeout_duration = discord.utils.format_dt(
|
timeout_duration = discord.utils.format_dt(after.timed_out_until, style="R")
|
||||||
after.timed_out_until, style="R"
|
|
||||||
)
|
|
||||||
changes.append(f"**Timed Out Until:** {timeout_duration}")
|
changes.append(f"**Timed Out Until:** {timeout_duration}")
|
||||||
else:
|
else:
|
||||||
changes.append("**Timeout Removed**")
|
changes.append("**Timeout Removed**")
|
||||||
@ -857,9 +820,7 @@ class LoggingCog(commands.Cog):
|
|||||||
|
|
||||||
# Add avatar change detection
|
# Add avatar change detection
|
||||||
if before.display_avatar != after.display_avatar:
|
if before.display_avatar != after.display_avatar:
|
||||||
changes.append(
|
changes.append(f"**Avatar Changed**") # URL is enough, no need to show old/new
|
||||||
f"**Avatar Changed**"
|
|
||||||
) # URL is enough, no need to show old/new
|
|
||||||
|
|
||||||
if changes:
|
if changes:
|
||||||
embed = self._create_log_embed(
|
embed = self._create_log_embed(
|
||||||
@ -917,9 +878,7 @@ class LoggingCog(commands.Cog):
|
|||||||
if before.hoist != after.hoist:
|
if before.hoist != after.hoist:
|
||||||
changes.append(f"**Hoisted:** `{before.hoist}` → `{after.hoist}`")
|
changes.append(f"**Hoisted:** `{before.hoist}` → `{after.hoist}`")
|
||||||
if before.mentionable != after.mentionable:
|
if before.mentionable != after.mentionable:
|
||||||
changes.append(
|
changes.append(f"**Mentionable:** `{before.mentionable}` → `{after.mentionable}`")
|
||||||
f"**Mentionable:** `{before.mentionable}` → `{after.mentionable}`"
|
|
||||||
)
|
|
||||||
if before.permissions != after.permissions:
|
if before.permissions != after.permissions:
|
||||||
# Comparing permissions can be complex, just note that they changed.
|
# Comparing permissions can be complex, just note that they changed.
|
||||||
# Audit log provides specifics on permission changes.
|
# Audit log provides specifics on permission changes.
|
||||||
@ -988,28 +947,20 @@ class LoggingCog(commands.Cog):
|
|||||||
|
|
||||||
if before.name != after.name:
|
if before.name != after.name:
|
||||||
changes.append(f"**Name:** `{before.name}` → `{after.name}`")
|
changes.append(f"**Name:** `{before.name}` → `{after.name}`")
|
||||||
if isinstance(before, discord.TextChannel) and isinstance(
|
if isinstance(before, discord.TextChannel) and isinstance(after, discord.TextChannel):
|
||||||
after, discord.TextChannel
|
|
||||||
):
|
|
||||||
if before.topic != after.topic:
|
if before.topic != after.topic:
|
||||||
changes.append(
|
changes.append(f"**Topic:** `{before.topic or 'None'}` → `{after.topic or 'None'}`")
|
||||||
f"**Topic:** `{before.topic or 'None'}` → `{after.topic or 'None'}`"
|
|
||||||
)
|
|
||||||
if before.slowmode_delay != after.slowmode_delay:
|
if before.slowmode_delay != after.slowmode_delay:
|
||||||
changes.append(
|
changes.append(
|
||||||
f"**Slowmode:** `{before.slowmode_delay}s` → `{after.slowmode_delay}s`"
|
f"**Slowmode:** `{before.slowmode_delay}s` → `{after.slowmode_delay}s`"
|
||||||
)
|
)
|
||||||
if before.nsfw != after.nsfw:
|
if before.nsfw != after.nsfw:
|
||||||
changes.append(f"**NSFW:** `{before.nsfw}` → `{after.nsfw}`")
|
changes.append(f"**NSFW:** `{before.nsfw}` → `{after.nsfw}`")
|
||||||
if isinstance(before, discord.VoiceChannel) and isinstance(
|
if isinstance(before, discord.VoiceChannel) and isinstance(after, discord.VoiceChannel):
|
||||||
after, discord.VoiceChannel
|
|
||||||
):
|
|
||||||
if before.bitrate != after.bitrate:
|
if before.bitrate != after.bitrate:
|
||||||
changes.append(f"**Bitrate:** `{before.bitrate}` → `{after.bitrate}`")
|
changes.append(f"**Bitrate:** `{before.bitrate}` → `{after.bitrate}`")
|
||||||
if before.user_limit != after.user_limit:
|
if before.user_limit != after.user_limit:
|
||||||
changes.append(
|
changes.append(f"**User Limit:** `{before.user_limit}` → `{after.user_limit}`")
|
||||||
f"**User Limit:** `{before.user_limit}` → `{after.user_limit}`"
|
|
||||||
)
|
|
||||||
# Permission overwrites change
|
# Permission overwrites change
|
||||||
if before.overwrites != after.overwrites:
|
if before.overwrites != after.overwrites:
|
||||||
# Identify changes without detailing every permission bit
|
# Identify changes without detailing every permission bit
|
||||||
@ -1031,17 +982,13 @@ class LoggingCog(commands.Cog):
|
|||||||
f"Removed overwrites for: {', '.join([f'<@{t.id}>' if isinstance(t, discord.Member) else f'<@&{t.id}>' for t in removed_targets])}"
|
f"Removed overwrites for: {', '.join([f'<@{t.id}>' if isinstance(t, discord.Member) else f'<@&{t.id}>' for t in removed_targets])}"
|
||||||
)
|
)
|
||||||
# Check if any *values* changed for targets present both before and after
|
# Check if any *values* changed for targets present both before and after
|
||||||
if any(
|
if any(before.overwrites[t] != after.overwrites[t] for t in updated_targets):
|
||||||
before.overwrites[t] != after.overwrites[t] for t in updated_targets
|
|
||||||
):
|
|
||||||
overwrite_changes.append(
|
overwrite_changes.append(
|
||||||
f"Modified overwrites for: {', '.join([f'<@{t.id}>' if isinstance(t, discord.Member) else f'<@&{t.id}>' for t in updated_targets if before.overwrites[t] != after.overwrites[t]])}"
|
f"Modified overwrites for: {', '.join([f'<@{t.id}>' if isinstance(t, discord.Member) else f'<@&{t.id}>' for t in updated_targets if before.overwrites[t] != after.overwrites[t]])}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if overwrite_changes:
|
if overwrite_changes:
|
||||||
changes.append(
|
changes.append(f"**Permission Overwrites:**\n - " + "\n - ".join(overwrite_changes))
|
||||||
f"**Permission Overwrites:**\n - " + "\n - ".join(overwrite_changes)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
changes.append(
|
changes.append(
|
||||||
"**Permission Overwrites Updated** (No specific target changes detected by event)"
|
"**Permission Overwrites Updated** (No specific target changes detected by event)"
|
||||||
@ -1081,23 +1028,22 @@ class LoggingCog(commands.Cog):
|
|||||||
if not await self._check_log_enabled(guild.id, event_key):
|
if not await self._check_log_enabled(guild.id, event_key):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
emoji, color = EVENT_STYLES.get("message_edit", ("", discord.Color.light_grey()))
|
||||||
embed = self._create_log_embed(
|
embed = self._create_log_embed(
|
||||||
title="✏️ Message Edited",
|
title=f"{emoji} Message Edited",
|
||||||
description=f"Message edited in {after.channel.mention} [Jump to Message]({after.jump_url})",
|
description=f"Message edited in {after.channel.mention}",
|
||||||
color=discord.Color.light_grey(),
|
color=color,
|
||||||
author=after.author,
|
author=after.author,
|
||||||
)
|
)
|
||||||
# Add fields for before and after, handling potential length limits
|
diff = "\n".join(difflib.ndiff(before.content.splitlines(), after.content.splitlines()))
|
||||||
|
if len(diff) > 1000:
|
||||||
|
diff = diff[:997] + "..."
|
||||||
|
embed.add_field(name="Jump", value=f"[Link]({after.jump_url})")
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name="Before",
|
name="Changes",
|
||||||
value=before.content[:1020] + ("..." if len(before.content) > 1020 else "")
|
value=(
|
||||||
or "`Empty Message`",
|
f"```diff\n{diff}\n```" if diff.strip() else "`(only embeds/attachments changed)`"
|
||||||
inline=False,
|
),
|
||||||
)
|
|
||||||
embed.add_field(
|
|
||||||
name="After",
|
|
||||||
value=after.content[:1020] + ("..." if len(after.content) > 1020 else "")
|
|
||||||
or "`Empty Message`",
|
|
||||||
inline=False,
|
inline=False,
|
||||||
)
|
)
|
||||||
self._add_id_footer(embed, after, id_name="Message ID") # Add message ID
|
self._add_id_footer(embed, after, id_name="Message ID") # Add message ID
|
||||||
@ -1122,21 +1068,18 @@ class LoggingCog(commands.Cog):
|
|||||||
if not await self._check_log_enabled(guild.id, event_key):
|
if not await self._check_log_enabled(guild.id, event_key):
|
||||||
return
|
return
|
||||||
|
|
||||||
desc = f"Message deleted in {message.channel.mention}"
|
emoji, color = EVENT_STYLES.get("message_delete", ("", discord.Color.dark_grey()))
|
||||||
# Audit log needed for *who* deleted it, if not the author themselves
|
|
||||||
# We can add a placeholder here and update it if the audit log confirms a moderator deletion later
|
|
||||||
|
|
||||||
embed = self._create_log_embed(
|
embed = self._create_log_embed(
|
||||||
title="🗑️ Message Deleted",
|
title=f"{emoji} Message Deleted",
|
||||||
description=f"{desc}\n*Audit log may contain deleter if not the author.*",
|
description=f"Message deleted in {message.channel.mention}",
|
||||||
color=discord.Color.dark_grey(),
|
color=color,
|
||||||
author=message.author,
|
author=message.author,
|
||||||
)
|
)
|
||||||
|
embed.add_field(name="Jump", value=f"[Link]({message.jump_url})")
|
||||||
if message.content:
|
if message.content:
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name="Content",
|
name="Content",
|
||||||
value=message.content[:1020]
|
value=message.content[:1020] + ("..." if len(message.content) > 1020 else "")
|
||||||
+ ("..." if len(message.content) > 1020 else "")
|
|
||||||
or "`Empty Message`",
|
or "`Empty Message`",
|
||||||
inline=False,
|
inline=False,
|
||||||
)
|
)
|
||||||
@ -1196,9 +1139,7 @@ class LoggingCog(commands.Cog):
|
|||||||
await self._send_log_embed(guild, embed)
|
await self._send_log_embed(guild, embed)
|
||||||
|
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
async def on_reaction_clear(
|
async def on_reaction_clear(self, message: discord.Message, _: list[discord.Reaction]):
|
||||||
self, message: discord.Message, _: list[discord.Reaction]
|
|
||||||
):
|
|
||||||
guild = message.guild
|
guild = message.guild
|
||||||
if not guild:
|
if not guild:
|
||||||
return # Should not happen in guilds but safety check
|
return # Should not happen in guilds but safety check
|
||||||
@ -1418,9 +1359,7 @@ class LoggingCog(commands.Cog):
|
|||||||
if invite.max_age:
|
if invite.max_age:
|
||||||
# Use invite.created_at if available, otherwise fall back to current time
|
# Use invite.created_at if available, otherwise fall back to current time
|
||||||
created_time = (
|
created_time = (
|
||||||
invite.created_at
|
invite.created_at if invite.created_at is not None else discord.utils.utcnow()
|
||||||
if invite.created_at is not None
|
|
||||||
else discord.utils.utcnow()
|
|
||||||
)
|
)
|
||||||
expires_at = created_time + datetime.timedelta(seconds=invite.max_age)
|
expires_at = created_time + datetime.timedelta(seconds=invite.max_age)
|
||||||
desc += f"\nExpires: {discord.utils.format_dt(expires_at, style='R')}"
|
desc += f"\nExpires: {discord.utils.format_dt(expires_at, style='R')}"
|
||||||
@ -1478,9 +1417,7 @@ class LoggingCog(commands.Cog):
|
|||||||
# await self._send_log_embed(ctx.guild, embed)
|
# await self._send_log_embed(ctx.guild, embed)
|
||||||
|
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
async def on_command_error(
|
async def on_command_error(self, ctx: commands.Context, error: commands.CommandError):
|
||||||
self, ctx: commands.Context, error: commands.CommandError
|
|
||||||
):
|
|
||||||
# Log only significant errors, ignore things like CommandNotFound or CheckFailure if desired
|
# Log only significant errors, ignore things like CommandNotFound or CheckFailure if desired
|
||||||
ignored = (
|
ignored = (
|
||||||
commands.CommandNotFound,
|
commands.CommandNotFound,
|
||||||
@ -1508,14 +1445,10 @@ class LoggingCog(commands.Cog):
|
|||||||
# Get traceback if available (might need error handling specific to your bot's setup)
|
# Get traceback if available (might need error handling specific to your bot's setup)
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
tb = "".join(
|
tb = "".join(traceback.format_exception(type(error), error, error.__traceback__))
|
||||||
traceback.format_exception(type(error), error, error.__traceback__)
|
|
||||||
)
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name="Error Details",
|
name="Error Details",
|
||||||
value=(
|
value=(f"```py\n{tb[:1000]}\n...```" if len(tb) > 1000 else f"```py\n{tb}```"),
|
||||||
f"```py\n{tb[:1000]}\n...```" if len(tb) > 1000 else f"```py\n{tb}```"
|
|
||||||
),
|
|
||||||
inline=False,
|
inline=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1579,9 +1512,7 @@ class LoggingCog(commands.Cog):
|
|||||||
)
|
)
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception(
|
log.exception(f"Error re-initializing audit log ID for guild {guild.id}: {e}")
|
||||||
f"Error re-initializing audit log ID for guild {guild.id}: {e}"
|
|
||||||
)
|
|
||||||
continue # Skip this cycle if re-init fails
|
continue # Skip this cycle if re-init fails
|
||||||
|
|
||||||
last_id = self.last_audit_log_ids.get(guild_id)
|
last_id = self.last_audit_log_ids.get(guild_id)
|
||||||
@ -1648,14 +1579,10 @@ class LoggingCog(commands.Cog):
|
|||||||
)
|
)
|
||||||
# Consider adding backoff logic here if errors persist
|
# Consider adding backoff logic here if errors persist
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception(
|
log.exception(f"Unexpected error in poll_audit_log for guild {guild.id}: {e}")
|
||||||
f"Unexpected error in poll_audit_log for guild {guild.id}: {e}"
|
|
||||||
)
|
|
||||||
# Don't update last_audit_log_id on unexpected error, retry next time
|
# Don't update last_audit_log_id on unexpected error, retry next time
|
||||||
|
|
||||||
async def _process_audit_log_entry(
|
async def _process_audit_log_entry(self, guild: discord.Guild, entry: discord.AuditLogEntry):
|
||||||
self, guild: discord.Guild, entry: discord.AuditLogEntry
|
|
||||||
):
|
|
||||||
"""Processes a single relevant audit log entry and sends an embed."""
|
"""Processes a single relevant audit log entry and sends an embed."""
|
||||||
user = entry.user # Moderator/Actor
|
user = entry.user # Moderator/Actor
|
||||||
target = entry.target # User/Channel/Role/Message affected
|
target = entry.target # User/Channel/Role/Message affected
|
||||||
@ -1673,9 +1600,7 @@ class LoggingCog(commands.Cog):
|
|||||||
if not await self._check_log_enabled(guild.id, audit_event_key):
|
if not await self._check_log_enabled(guild.id, audit_event_key):
|
||||||
return
|
return
|
||||||
title = "🛡️ Audit Log: Member Banned"
|
title = "🛡️ Audit Log: Member Banned"
|
||||||
action_desc = (
|
action_desc = f"{self._user_display(user)} banned {self._user_display(target)}"
|
||||||
f"{self._user_display(user)} banned {self._user_display(target)}"
|
|
||||||
)
|
|
||||||
color = discord.Color.red()
|
color = discord.Color.red()
|
||||||
# self._add_id_footer(embed, target, id_name="Target ID") # Footer set later
|
# self._add_id_footer(embed, target, id_name="Target ID") # Footer set later
|
||||||
elif entry.action == discord.AuditLogAction.unban:
|
elif entry.action == discord.AuditLogAction.unban:
|
||||||
@ -1683,9 +1608,7 @@ class LoggingCog(commands.Cog):
|
|||||||
if not await self._check_log_enabled(guild.id, audit_event_key):
|
if not await self._check_log_enabled(guild.id, audit_event_key):
|
||||||
return
|
return
|
||||||
title = "🛡️ Audit Log: Member Unbanned"
|
title = "🛡️ Audit Log: Member Unbanned"
|
||||||
action_desc = (
|
action_desc = f"{self._user_display(user)} unbanned {self._user_display(target)}"
|
||||||
f"{self._user_display(user)} unbanned {self._user_display(target)}"
|
|
||||||
)
|
|
||||||
color = discord.Color.blurple()
|
color = discord.Color.blurple()
|
||||||
# self._add_id_footer(embed, target, id_name="Target ID") # Footer set later
|
# self._add_id_footer(embed, target, id_name="Target ID") # Footer set later
|
||||||
elif entry.action == discord.AuditLogAction.kick:
|
elif entry.action == discord.AuditLogAction.kick:
|
||||||
@ -1693,9 +1616,7 @@ class LoggingCog(commands.Cog):
|
|||||||
if not await self._check_log_enabled(guild.id, audit_event_key):
|
if not await self._check_log_enabled(guild.id, audit_event_key):
|
||||||
return
|
return
|
||||||
title = "🛡️ Audit Log: Member Kicked"
|
title = "🛡️ Audit Log: Member Kicked"
|
||||||
action_desc = (
|
action_desc = f"{self._user_display(user)} kicked {self._user_display(target)}"
|
||||||
f"{self._user_display(user)} kicked {self._user_display(target)}"
|
|
||||||
)
|
|
||||||
color = discord.Color.brand_red()
|
color = discord.Color.brand_red()
|
||||||
# self._add_id_footer(embed, target, id_name="Target ID") # Footer set later
|
# self._add_id_footer(embed, target, id_name="Target ID") # Footer set later
|
||||||
elif entry.action == discord.AuditLogAction.member_prune:
|
elif entry.action == discord.AuditLogAction.member_prune:
|
||||||
@ -1705,7 +1626,9 @@ class LoggingCog(commands.Cog):
|
|||||||
title = "🛡️ Audit Log: Member Prune"
|
title = "🛡️ Audit Log: Member Prune"
|
||||||
days = entry.extra.get("delete_member_days")
|
days = entry.extra.get("delete_member_days")
|
||||||
count = entry.extra.get("members_removed")
|
count = entry.extra.get("members_removed")
|
||||||
action_desc = f"{self._user_display(user)} pruned {count} members inactive for {days} days."
|
action_desc = (
|
||||||
|
f"{self._user_display(user)} pruned {count} members inactive for {days} days."
|
||||||
|
)
|
||||||
color = discord.Color.dark_red()
|
color = discord.Color.dark_red()
|
||||||
# No specific target ID here
|
# No specific target ID here
|
||||||
|
|
||||||
@ -1739,9 +1662,7 @@ class LoggingCog(commands.Cog):
|
|||||||
return
|
return
|
||||||
title = "🛡️ Audit Log: Member Timeout Update"
|
title = "🛡️ Audit Log: Member Timeout Update"
|
||||||
if after_timed_out:
|
if after_timed_out:
|
||||||
timeout_duration = discord.utils.format_dt(
|
timeout_duration = discord.utils.format_dt(after_timed_out, style="R")
|
||||||
after_timed_out, style="R"
|
|
||||||
)
|
|
||||||
action_desc = f"{self._user_display(user)} timed out {self._user_display(target)} ({target.id}) until {timeout_duration}"
|
action_desc = f"{self._user_display(user)} timed out {self._user_display(target)} ({target.id}) until {timeout_duration}"
|
||||||
color = discord.Color.orange()
|
color = discord.Color.orange()
|
||||||
else:
|
else:
|
||||||
@ -1799,9 +1720,7 @@ class LoggingCog(commands.Cog):
|
|||||||
and hasattr(entry.after, "hoist")
|
and hasattr(entry.after, "hoist")
|
||||||
and entry.before.hoist != entry.after.hoist
|
and entry.before.hoist != entry.after.hoist
|
||||||
):
|
):
|
||||||
changes.append(
|
changes.append(f"Hoisted: `{entry.before.hoist}` → `{entry.after.hoist}`")
|
||||||
f"Hoisted: `{entry.before.hoist}` → `{entry.after.hoist}`"
|
|
||||||
)
|
|
||||||
if (
|
if (
|
||||||
hasattr(entry.before, "mentionable")
|
hasattr(entry.before, "mentionable")
|
||||||
and hasattr(entry.after, "mentionable")
|
and hasattr(entry.after, "mentionable")
|
||||||
@ -1837,7 +1756,9 @@ class LoggingCog(commands.Cog):
|
|||||||
title = "🛡️ Audit Log: Channel Created"
|
title = "🛡️ Audit Log: Channel Created"
|
||||||
channel = target
|
channel = target
|
||||||
ch_type = str(channel.type).capitalize()
|
ch_type = str(channel.type).capitalize()
|
||||||
action_desc = f"{user.mention} created {ch_type} channel {channel.mention} (`{channel.name}`)"
|
action_desc = (
|
||||||
|
f"{user.mention} created {ch_type} channel {channel.mention} (`{channel.name}`)"
|
||||||
|
)
|
||||||
color = discord.Color.green()
|
color = discord.Color.green()
|
||||||
# self._add_id_footer(embed, channel, id_name="Channel ID") # Footer set later
|
# self._add_id_footer(embed, channel, id_name="Channel ID") # Footer set later
|
||||||
elif entry.action == discord.AuditLogAction.channel_delete:
|
elif entry.action == discord.AuditLogAction.channel_delete:
|
||||||
@ -1849,7 +1770,9 @@ class LoggingCog(commands.Cog):
|
|||||||
channel_name = entry.before.name
|
channel_name = entry.before.name
|
||||||
channel_id = entry.target.id
|
channel_id = entry.target.id
|
||||||
ch_type = str(entry.before.type).capitalize()
|
ch_type = str(entry.before.type).capitalize()
|
||||||
action_desc = f"{user.mention} deleted {ch_type} channel `{channel_name}` ({channel_id})"
|
action_desc = (
|
||||||
|
f"{user.mention} deleted {ch_type} channel `{channel_name}` ({channel_id})"
|
||||||
|
)
|
||||||
color = discord.Color.red()
|
color = discord.Color.red()
|
||||||
# self._add_id_footer(embed, obj_id=channel_id, id_name="Channel ID") # Footer set later
|
# self._add_id_footer(embed, obj_id=channel_id, id_name="Channel ID") # Footer set later
|
||||||
elif entry.action == discord.AuditLogAction.channel_update:
|
elif entry.action == discord.AuditLogAction.channel_update:
|
||||||
@ -1892,9 +1815,7 @@ class LoggingCog(commands.Cog):
|
|||||||
and hasattr(entry.after, "bitrate")
|
and hasattr(entry.after, "bitrate")
|
||||||
and entry.before.bitrate != entry.after.bitrate
|
and entry.before.bitrate != entry.after.bitrate
|
||||||
):
|
):
|
||||||
changes.append(
|
changes.append(f"Bitrate: `{entry.before.bitrate}` → `{entry.after.bitrate}`")
|
||||||
f"Bitrate: `{entry.before.bitrate}` → `{entry.after.bitrate}`"
|
|
||||||
)
|
|
||||||
# Process detailed changes from entry.changes
|
# Process detailed changes from entry.changes
|
||||||
detailed_changes = []
|
detailed_changes = []
|
||||||
|
|
||||||
@ -1907,33 +1828,21 @@ class LoggingCog(commands.Cog):
|
|||||||
before_val = change.before
|
before_val = change.before
|
||||||
after_val = change.after
|
after_val = change.after
|
||||||
if attr == "name":
|
if attr == "name":
|
||||||
detailed_changes.append(
|
detailed_changes.append(f"Name: `{before_val}` → `{after_val}`")
|
||||||
f"Name: `{before_val}` → `{after_val}`"
|
|
||||||
)
|
|
||||||
elif attr == "topic":
|
elif attr == "topic":
|
||||||
detailed_changes.append(
|
detailed_changes.append(
|
||||||
f"Topic: `{before_val or 'None'}` → `{after_val or 'None'}`"
|
f"Topic: `{before_val or 'None'}` → `{after_val or 'None'}`"
|
||||||
)
|
)
|
||||||
elif attr == "nsfw":
|
elif attr == "nsfw":
|
||||||
detailed_changes.append(
|
detailed_changes.append(f"NSFW: `{before_val}` → `{after_val}`")
|
||||||
f"NSFW: `{before_val}` → `{after_val}`"
|
|
||||||
)
|
|
||||||
elif attr == "slowmode_delay":
|
elif attr == "slowmode_delay":
|
||||||
detailed_changes.append(
|
detailed_changes.append(f"Slowmode: `{before_val}s` → `{after_val}s`")
|
||||||
f"Slowmode: `{before_val}s` → `{after_val}s`"
|
|
||||||
)
|
|
||||||
elif attr == "bitrate":
|
elif attr == "bitrate":
|
||||||
detailed_changes.append(
|
detailed_changes.append(f"Bitrate: `{before_val}` → `{after_val}`")
|
||||||
f"Bitrate: `{before_val}` → `{after_val}`"
|
|
||||||
)
|
|
||||||
elif attr == "user_limit":
|
elif attr == "user_limit":
|
||||||
detailed_changes.append(
|
detailed_changes.append(f"User Limit: `{before_val}` → `{after_val}`")
|
||||||
f"User Limit: `{before_val}` → `{after_val}`"
|
|
||||||
)
|
|
||||||
elif attr == "position":
|
elif attr == "position":
|
||||||
detailed_changes.append(
|
detailed_changes.append(f"Position: `{before_val}` → `{after_val}`")
|
||||||
f"Position: `{before_val}` → `{after_val}`"
|
|
||||||
)
|
|
||||||
elif attr == "category":
|
elif attr == "category":
|
||||||
detailed_changes.append(
|
detailed_changes.append(
|
||||||
f"Category: {getattr(before_val, 'mention', 'None')} → {getattr(after_val, 'mention', 'None')}"
|
f"Category: {getattr(before_val, 'mention', 'None')} → {getattr(after_val, 'mention', 'None')}"
|
||||||
@ -1954,9 +1863,7 @@ class LoggingCog(commands.Cog):
|
|||||||
)
|
)
|
||||||
# Determine if added, removed, or updated (before/after values are PermissionOverwrite objects)
|
# Determine if added, removed, or updated (before/after values are PermissionOverwrite objects)
|
||||||
if before_val is None and after_val is not None:
|
if before_val is None and after_val is not None:
|
||||||
detailed_changes.append(
|
detailed_changes.append(f"Added overwrite for {target_mention}")
|
||||||
f"Added overwrite for {target_mention}"
|
|
||||||
)
|
|
||||||
elif before_val is not None and after_val is None:
|
elif before_val is not None and after_val is None:
|
||||||
detailed_changes.append(
|
detailed_changes.append(
|
||||||
f"Removed overwrite for {target_mention}"
|
f"Removed overwrite for {target_mention}"
|
||||||
@ -1977,9 +1884,7 @@ class LoggingCog(commands.Cog):
|
|||||||
else:
|
else:
|
||||||
# Handle AuditLogChanges as a non-iterable object
|
# Handle AuditLogChanges as a non-iterable object
|
||||||
# We can access the before and after attributes directly
|
# We can access the before and after attributes directly
|
||||||
if hasattr(entry.changes, "before") and hasattr(
|
if hasattr(entry.changes, "before") and hasattr(entry.changes, "after"):
|
||||||
entry.changes, "after"
|
|
||||||
):
|
|
||||||
before = entry.changes.before
|
before = entry.changes.before
|
||||||
after = entry.changes.after
|
after = entry.changes.after
|
||||||
|
|
||||||
@ -1989,9 +1894,7 @@ class LoggingCog(commands.Cog):
|
|||||||
and hasattr(after, "name")
|
and hasattr(after, "name")
|
||||||
and before.name != after.name
|
and before.name != after.name
|
||||||
):
|
):
|
||||||
detailed_changes.append(
|
detailed_changes.append(f"Name: `{before.name}` → `{after.name}`")
|
||||||
f"Name: `{before.name}` → `{after.name}`"
|
|
||||||
)
|
|
||||||
if (
|
if (
|
||||||
hasattr(before, "topic")
|
hasattr(before, "topic")
|
||||||
and hasattr(after, "topic")
|
and hasattr(after, "topic")
|
||||||
@ -2005,9 +1908,7 @@ class LoggingCog(commands.Cog):
|
|||||||
and hasattr(after, "nsfw")
|
and hasattr(after, "nsfw")
|
||||||
and before.nsfw != after.nsfw
|
and before.nsfw != after.nsfw
|
||||||
):
|
):
|
||||||
detailed_changes.append(
|
detailed_changes.append(f"NSFW: `{before.nsfw}` → `{after.nsfw}`")
|
||||||
f"NSFW: `{before.nsfw}` → `{after.nsfw}`"
|
|
||||||
)
|
|
||||||
if (
|
if (
|
||||||
hasattr(before, "slowmode_delay")
|
hasattr(before, "slowmode_delay")
|
||||||
and hasattr(after, "slowmode_delay")
|
and hasattr(after, "slowmode_delay")
|
||||||
@ -2078,21 +1979,15 @@ class LoggingCog(commands.Cog):
|
|||||||
channel_display = ""
|
channel_display = ""
|
||||||
if hasattr(channel_target, "mention"):
|
if hasattr(channel_target, "mention"):
|
||||||
channel_display = channel_target.mention
|
channel_display = channel_target.mention
|
||||||
elif isinstance(channel_target, discord.Object) and hasattr(
|
elif isinstance(channel_target, discord.Object) and hasattr(channel_target, "id"):
|
||||||
channel_target, "id"
|
|
||||||
):
|
|
||||||
# If it's an Object, it might be a deleted channel or not fully loaded.
|
# If it's an Object, it might be a deleted channel or not fully loaded.
|
||||||
# Using <#id> is a safe way to reference it.
|
# Using <#id> is a safe way to reference it.
|
||||||
channel_display = f"<#{channel_target.id}>"
|
channel_display = f"<#{channel_target.id}>"
|
||||||
else:
|
else:
|
||||||
# Fallback if it's not an object with 'mention' or an 'Object' with 'id'
|
# Fallback if it's not an object with 'mention' or an 'Object' with 'id'
|
||||||
channel_display = (
|
channel_display = f"an unknown channel (ID: {getattr(channel_target, 'id', 'N/A')})"
|
||||||
f"an unknown channel (ID: {getattr(channel_target, 'id', 'N/A')})"
|
|
||||||
)
|
|
||||||
|
|
||||||
action_desc = (
|
action_desc = f"{user.mention} bulk deleted {count} messages in {channel_display}"
|
||||||
f"{user.mention} bulk deleted {count} messages in {channel_display}"
|
|
||||||
)
|
|
||||||
color = discord.Color.dark_grey()
|
color = discord.Color.dark_grey()
|
||||||
# self._add_id_footer(embed, channel_target, id_name="Channel ID") # Footer set later
|
# self._add_id_footer(embed, channel_target, id_name="Channel ID") # Footer set later
|
||||||
|
|
||||||
@ -2146,9 +2041,7 @@ class LoggingCog(commands.Cog):
|
|||||||
if invite.max_age:
|
if invite.max_age:
|
||||||
# Use invite.created_at if available, otherwise fall back to current time
|
# Use invite.created_at if available, otherwise fall back to current time
|
||||||
created_time = (
|
created_time = (
|
||||||
invite.created_at
|
invite.created_at if invite.created_at is not None else discord.utils.utcnow()
|
||||||
if invite.created_at is not None
|
|
||||||
else discord.utils.utcnow()
|
|
||||||
)
|
)
|
||||||
expires_at = created_time + datetime.timedelta(seconds=invite.max_age)
|
expires_at = created_time + datetime.timedelta(seconds=invite.max_age)
|
||||||
desc += f"\nExpires: {discord.utils.format_dt(expires_at, style='R')}"
|
desc += f"\nExpires: {discord.utils.format_dt(expires_at, style='R')}"
|
||||||
@ -2166,7 +2059,9 @@ class LoggingCog(commands.Cog):
|
|||||||
invite_code = entry.before.code
|
invite_code = entry.before.code
|
||||||
channel_id = entry.before.channel_id
|
channel_id = entry.before.channel_id
|
||||||
channel_mention = f"<#{channel_id}>" if channel_id else "Unknown Channel"
|
channel_mention = f"<#{channel_id}>" if channel_id else "Unknown Channel"
|
||||||
action_desc = f"{user.mention} deleted invite `{invite_code}` for channel {channel_mention}"
|
action_desc = (
|
||||||
|
f"{user.mention} deleted invite `{invite_code}` for channel {channel_mention}"
|
||||||
|
)
|
||||||
color = discord.Color.dark_red()
|
color = discord.Color.dark_red()
|
||||||
# Cannot get invite ID after deletion easily, use code in footer later
|
# Cannot get invite ID after deletion easily, use code in footer later
|
||||||
|
|
||||||
@ -2221,8 +2116,7 @@ class LoggingCog(commands.Cog):
|
|||||||
if (
|
if (
|
||||||
hasattr(entry.before, "explicit_content_filter")
|
hasattr(entry.before, "explicit_content_filter")
|
||||||
and hasattr(entry.after, "explicit_content_filter")
|
and hasattr(entry.after, "explicit_content_filter")
|
||||||
and entry.before.explicit_content_filter
|
and entry.before.explicit_content_filter != entry.after.explicit_content_filter
|
||||||
!= entry.after.explicit_content_filter
|
|
||||||
):
|
):
|
||||||
changes.append(
|
changes.append(
|
||||||
f"Explicit Content Filter: `{entry.before.explicit_content_filter}` → `{entry.after.explicit_content_filter}`"
|
f"Explicit Content Filter: `{entry.before.explicit_content_filter}` → `{entry.after.explicit_content_filter}`"
|
||||||
@ -2253,7 +2147,9 @@ class LoggingCog(commands.Cog):
|
|||||||
# Generic fallback log
|
# Generic fallback log
|
||||||
title = f"🛡️ Audit Log: {str(entry.action).replace('_', ' ').title()}"
|
title = f"🛡️ Audit Log: {str(entry.action).replace('_', ' ').title()}"
|
||||||
# Determine the generic audit key based on the action category if possible
|
# Determine the generic audit key based on the action category if possible
|
||||||
generic_audit_key = f"audit_{str(entry.action).split('.')[0]}" # e.g., audit_member, audit_channel
|
generic_audit_key = (
|
||||||
|
f"audit_{str(entry.action).split('.')[0]}" # e.g., audit_member, audit_channel
|
||||||
|
)
|
||||||
if generic_audit_key in ALL_EVENT_KEYS:
|
if generic_audit_key in ALL_EVENT_KEYS:
|
||||||
if not await self._check_log_enabled(guild.id, generic_audit_key):
|
if not await self._check_log_enabled(guild.id, generic_audit_key):
|
||||||
return
|
return
|
||||||
@ -2271,9 +2167,7 @@ class LoggingCog(commands.Cog):
|
|||||||
# self._add_id_footer(embed, target, id_name="Target ID") # Footer set later
|
# self._add_id_footer(embed, target, id_name="Target ID") # Footer set later
|
||||||
color = discord.Color.light_grey()
|
color = discord.Color.light_grey()
|
||||||
|
|
||||||
if (
|
if not action_desc: # If no description was generated (e.g., skipped update), skip logging
|
||||||
not action_desc
|
|
||||||
): # If no description was generated (e.g., skipped update), skip logging
|
|
||||||
# log.debug(f"Skipping audit log entry {entry.id} (action: {entry.action}) as no action description was generated.")
|
# log.debug(f"Skipping audit log entry {entry.id} (action: {entry.action}) as no action description was generated.")
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -2285,24 +2179,22 @@ class LoggingCog(commands.Cog):
|
|||||||
author=user, # The moderator/actor is the author of the log entry
|
author=user, # The moderator/actor is the author of the log entry
|
||||||
)
|
)
|
||||||
if reason:
|
if reason:
|
||||||
embed.add_field(
|
embed.add_field(name="Reason", value=reason[:1024], inline=False) # Limit reason length
|
||||||
name="Reason", value=reason[:1024], inline=False
|
|
||||||
) # Limit reason length
|
|
||||||
|
|
||||||
# Add relevant IDs to footer (target ID if available, otherwise just mod/entry ID)
|
# Add relevant IDs to footer (target ID if available, otherwise just mod/entry ID)
|
||||||
target_id_str = ""
|
target_id_str = ""
|
||||||
if target:
|
if target:
|
||||||
target_id_str = f" | Target ID: {target.id}"
|
target_id_str = f" | Target ID: {target.id}"
|
||||||
elif entry.action == discord.AuditLogAction.role_delete:
|
elif entry.action == discord.AuditLogAction.role_delete:
|
||||||
target_id_str = f" | Role ID: {entry.target.id}" # Get ID from target even if object deleted
|
target_id_str = (
|
||||||
|
f" | Role ID: {entry.target.id}" # Get ID from target even if object deleted
|
||||||
|
)
|
||||||
elif entry.action == discord.AuditLogAction.channel_delete:
|
elif entry.action == discord.AuditLogAction.channel_delete:
|
||||||
target_id_str = f" | Channel ID: {entry.target.id}"
|
target_id_str = f" | Channel ID: {entry.target.id}"
|
||||||
elif entry.action == discord.AuditLogAction.emoji_delete:
|
elif entry.action == discord.AuditLogAction.emoji_delete:
|
||||||
target_id_str = f" | Emoji ID: {entry.target.id}"
|
target_id_str = f" | Emoji ID: {entry.target.id}"
|
||||||
elif entry.action == discord.AuditLogAction.invite_delete:
|
elif entry.action == discord.AuditLogAction.invite_delete:
|
||||||
target_id_str = (
|
target_id_str = f" | Invite Code: {entry.before.code}" # Use code for deleted invites
|
||||||
f" | Invite Code: {entry.before.code}" # Use code for deleted invites
|
|
||||||
)
|
|
||||||
|
|
||||||
embed.set_footer(
|
embed.set_footer(
|
||||||
text=f"Audit Log Entry ID: {entry.id} | Moderator ID: {user.id}{target_id_str}"
|
text=f"Audit Log Entry ID: {entry.id} | Moderator ID: {user.id}{target_id_str}"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user