aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/cogs/clean.py8
-rw-r--r--bot/cogs/moderation.py192
-rw-r--r--bot/cogs/modlog.py40
-rw-r--r--bot/cogs/verification.py4
-rw-r--r--bot/constants.py31
-rw-r--r--config-default.yml4
6 files changed, 259 insertions, 20 deletions
diff --git a/bot/cogs/clean.py b/bot/cogs/clean.py
index da0a5a9f2..ffa247b4a 100644
--- a/bot/cogs/clean.py
+++ b/bot/cogs/clean.py
@@ -9,8 +9,8 @@ from discord.ext.commands import Bot, Context, group
from bot.cogs.modlog import ModLog
from bot.constants import (
- Channels, CleanMessages, Colours, Icons,
- Keys, NEGATIVE_REPLIES, Roles, URLs
+ Channels, CleanMessages, Colours, Event,
+ Icons, Keys, NEGATIVE_REPLIES, Roles, URLs
)
from bot.decorators import with_role
@@ -169,7 +169,7 @@ class Clean:
# Always start by deleting the invocation
if not invocation_deleted:
- self.mod_log.ignore_message_deletion(message.id)
+ self.mod_log.ignore(Event.message_delete, message.id)
await message.delete()
invocation_deleted = True
continue
@@ -202,7 +202,7 @@ class Clean:
self.cleaning = False
# We should ignore the ID's we stored, so we don't get mod-log spam.
- self.mod_log.ignore_message_deletion(*message_ids)
+ self.mod_log.ignore(Event.message_delete, *message_ids)
# Use bulk delete to actually do the cleaning. It's far faster.
await ctx.channel.purge(
diff --git a/bot/cogs/moderation.py b/bot/cogs/moderation.py
index 0a0bbae53..b04887b3f 100644
--- a/bot/cogs/moderation.py
+++ b/bot/cogs/moderation.py
@@ -1,6 +1,7 @@
import asyncio
import datetime
import logging
+import textwrap
from typing import Dict
from aiohttp import ClientError
@@ -8,7 +9,8 @@ from discord import Colour, Embed, Guild, Member, Object, User
from discord.ext.commands import Bot, Context, command, group
from bot import constants
-from bot.constants import Keys, Roles, URLs
+from bot.cogs.modlog import ModLog
+from bot.constants import Colours, Event, Icons, Keys, Roles, URLs
from bot.converters import InfractionSearchQuery
from bot.decorators import with_role
from bot.pagination import LinePaginator
@@ -29,6 +31,10 @@ class Moderation:
self.expiration_tasks: Dict[str, asyncio.Task] = {}
self._muted_role = Object(constants.Roles.muted)
+ @property
+ def mod_log(self) -> ModLog:
+ return self.bot.get_cog("ModLog")
+
async def on_ready(self):
# Schedule expiration for previous infractions
response = await self.bot.http_session.get(
@@ -111,6 +117,7 @@ class Moderation:
await ctx.send(f":x: There was an error adding the infraction: {response_object['error_message']}")
return
+ self.mod_log.ignore(Event.member_remove, user.id)
await user.kick(reason=reason)
if reason is None:
@@ -120,6 +127,19 @@ class Moderation:
await ctx.send(result_message)
+ # Send a log message to the mod log
+ await self.mod_log.send_log_message(
+ icon_url=Icons.sign_out,
+ colour=Colour(Colours.soft_red),
+ title="Member kicked",
+ thumbnail=user.avatar_url_as(static_format="png"),
+ text=textwrap.dedent(f"""
+ Member: {user.mention} (`{user.id}`)
+ Actor: {ctx.message.author}
+ Reason: {reason}
+ """)
+ )
+
@with_role(*MODERATION_ROLES)
@command(name="ban")
async def ban(self, ctx: Context, user: User, *, reason: str = None):
@@ -150,6 +170,8 @@ class Moderation:
await ctx.send(f":x: There was an error adding the infraction: {response_object['error_message']}")
return
+ self.mod_log.ignore(Event.member_ban, user.id)
+ self.mod_log.ignore(Event.member_remove, user.id)
await ctx.guild.ban(user, reason=reason)
if reason is None:
@@ -159,6 +181,19 @@ class Moderation:
await ctx.send(result_message)
+ # Send a log message to the mod log
+ await self.mod_log.send_log_message(
+ icon_url=Icons.user_ban,
+ colour=Colour(Colours.soft_red),
+ title="Member permanently banned",
+ thumbnail=user.avatar_url_as(static_format="png"),
+ text=textwrap.dedent(f"""
+ Member: {user.mention} (`{user.id}`)
+ Actor: {ctx.message.author}
+ Reason: {reason}
+ """)
+ )
+
@with_role(*MODERATION_ROLES)
@command(name="mute")
async def mute(self, ctx: Context, user: Member, *, reason: str = None):
@@ -190,6 +225,7 @@ class Moderation:
return
# add the mute role
+ self.mod_log.ignore(Event.member_update, user.id)
await user.add_roles(self._muted_role, reason=reason)
if reason is None:
@@ -199,6 +235,19 @@ class Moderation:
await ctx.send(result_message)
+ # Send a log message to the mod log
+ await self.mod_log.send_log_message(
+ icon_url=Icons.user_mute,
+ colour=Colour(Colours.soft_red),
+ title="Member permanently muted",
+ thumbnail=user.avatar_url_as(static_format="png"),
+ text=textwrap.dedent(f"""
+ Member: {user.mention} (`{user.id}`)
+ Actor: {ctx.message.author}
+ Reason: {reason}
+ """)
+ )
+
# endregion
# region: Temporary infractions
@@ -234,6 +283,7 @@ class Moderation:
await ctx.send(f":x: There was an error adding the infraction: {response_object['error_message']}")
return
+ self.mod_log.ignore(Event.member_update, user.id)
await user.add_roles(self._muted_role, reason=reason)
infraction_object = response_object["infraction"]
@@ -249,6 +299,21 @@ class Moderation:
await ctx.send(result_message)
+ # Send a log message to the mod log
+ await self.mod_log.send_log_message(
+ icon_url=Icons.user_mute,
+ colour=Colour(Colours.soft_red),
+ title="Member temporarily muted",
+ thumbnail=user.avatar_url_as(static_format="png"),
+ text=textwrap.dedent(f"""
+ Member: {user.mention} (`{user.id}`)
+ Actor: {ctx.message.author}
+ Reason: {reason}
+ Duration: {duration}
+ Expires: {infraction_expiration}
+ """)
+ )
+
@with_role(*MODERATION_ROLES)
@command(name="tempban")
async def tempban(self, ctx, user: User, duration: str, *, reason: str = None):
@@ -281,6 +346,8 @@ class Moderation:
await ctx.send(f":x: There was an error adding the infraction: {response_object['error_message']}")
return
+ self.mod_log.ignore(Event.member_ban, user.id)
+ self.mod_log.ignore(Event.member_remove, user.id)
guild: Guild = ctx.guild
await guild.ban(user, reason=reason)
@@ -297,6 +364,21 @@ class Moderation:
await ctx.send(result_message)
+ # Send a log message to the mod log
+ await self.mod_log.send_log_message(
+ icon_url=Icons.user_ban,
+ colour=Colour(Colours.soft_red),
+ thumbnail=user.avatar_url_as(static_format="png"),
+ title="Member temporarily banned",
+ text=textwrap.dedent(f"""
+ Member: {user.mention} (`{user.id}`)
+ Actor: {ctx.message.author}
+ Reason: {reason}
+ Duration: {duration}
+ Expires: {infraction_expiration}
+ """)
+ )
+
# endregion
# region: Remove infractions (un- commands)
@@ -333,6 +415,19 @@ class Moderation:
self.cancel_expiration(infraction_object["id"])
await ctx.send(f":ok_hand: Un-muted {user.mention}.")
+
+ # Send a log message to the mod log
+ await self.mod_log.send_log_message(
+ icon_url=Icons.user_unmute,
+ colour=Colour(Colours.soft_green),
+ title="Member unmuted",
+ thumbnail=user.avatar_url_as(static_format="png"),
+ text=textwrap.dedent(f"""
+ Member: {user.mention} (`{user.id}`)
+ Actor: {ctx.message.author}
+ Intended expiry: {infraction_object['expires_at']}
+ """)
+ )
except Exception:
log.exception("There was an error removing an infraction.")
await ctx.send(":x: There was an error removing the infraction.")
@@ -371,6 +466,19 @@ class Moderation:
self.cancel_expiration(infraction_object["id"])
await ctx.send(f":ok_hand: Un-banned {user.mention}.")
+
+ # Send a log message to the mod log
+ await self.mod_log.send_log_message(
+ icon_url=Icons.user_unban,
+ colour=Colour(Colours.soft_green),
+ title="Member unbanned",
+ thumbnail=user.avatar_url_as(static_format="png"),
+ text=textwrap.dedent(f"""
+ Member: {user.mention} (`{user.id}`)
+ Actor: {ctx.message.author}
+ Intended expiry: {infraction_object['expires_at']}
+ """)
+ )
except Exception:
log.exception("There was an error removing an infraction.")
await ctx.send(":x: There was an error removing the infraction.")
@@ -400,6 +508,15 @@ class Moderation:
"""
try:
+ previous = await self.bot.http_session.get(
+ URLs.site_infractions_by_id.format(
+ infraction_id=infraction_id
+ ),
+ headers=self.headers
+ )
+
+ previous_object = await previous.json()
+
if duration == "permanent":
duration = None
# check the current active infraction
@@ -432,6 +549,37 @@ class Moderation:
await ctx.send(":x: There was an error updating the infraction.")
return
+ prev_infraction = previous_object["infraction"]
+
+ # Get information about the infraction's user
+ user_id = int(infraction_object["user"]["user_id"])
+ user = ctx.guild.get_member(user_id)
+
+ if user:
+ member_text = f"{user.mention} (`{user.id}`)"
+ thumbnail = user.avatar_url_as(static_format="png")
+ else:
+ member_text = f"`{user_id}`"
+ thumbnail = None
+
+ # The infraction's actor
+ actor_id = int(infraction_object["actor"]["user_id"])
+ actor = ctx.guild.get_member(actor_id) or f"`{actor_id}`"
+
+ await self.mod_log.send_log_message(
+ icon_url=Icons.pencil,
+ colour=Colour.blurple(),
+ title="Infraction edited",
+ thumbnail=thumbnail,
+ text=textwrap.dedent(f"""
+ Member: {member_text}
+ Actor: {actor}
+ Edited by: {ctx.message.author}
+ Previous expiry: {prev_infraction['expires_at']}
+ New expiry: {infraction_object['expires_at']}
+ """)
+ )
+
@with_role(*MODERATION_ROLES)
@infraction_edit_group.command(name="reason")
async def edit_reason(self, ctx, infraction_id: str, *, reason: str):
@@ -442,6 +590,15 @@ class Moderation:
"""
try:
+ previous = await self.bot.http_session.get(
+ URLs.site_infractions_by_id.format(
+ infraction_id=infraction_id
+ ),
+ headers=self.headers
+ )
+
+ previous_object = await previous.json()
+
response = await self.bot.http_session.patch(
URLs.site_infractions,
json={
@@ -461,6 +618,38 @@ class Moderation:
await ctx.send(":x: There was an error updating the infraction.")
return
+ new_infraction = response_object["infraction"]
+ prev_infraction = previous_object["infraction"]
+
+ # Get information about the infraction's user
+ user_id = int(new_infraction["user"]["user_id"])
+ user = ctx.guild.get_member(user_id)
+
+ if user:
+ user_text = f"{user.mention} (`{user.id}`)"
+ thumbnail = user.avatar_url_as(static_format="png")
+ else:
+ user_text = f"`{user_id}`"
+ thumbnail = None
+
+ # The infraction's actor
+ actor_id = int(new_infraction["actor"]["user_id"])
+ actor = ctx.guild.get_member(actor_id) or f"`{actor_id}`"
+
+ await self.mod_log.send_log_message(
+ icon_url=Icons.pencil,
+ colour=Colour.blurple(),
+ title="Infraction edited",
+ thumbnail=thumbnail,
+ text=textwrap.dedent(f"""
+ Member: {user_text}
+ Actor: {actor}
+ Edited by: {ctx.message.author}
+ Previous reason: {prev_infraction['reason']}
+ New reason: {new_infraction['reason']}
+ """)
+ )
+
# endregion
# region: Search infractions
@@ -609,6 +798,7 @@ class Moderation:
member: Member = guild.get_member(user_id)
if member:
# remove the mute role
+ self.mod_log.ignore(Event.member_update, member.id)
await member.remove_roles(self._muted_role)
else:
log.warning(f"Failed to un-mute user: {user_id} (not found)")
diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py
index b5a73d6e0..9dd3dce5d 100644
--- a/bot/cogs/modlog.py
+++ b/bot/cogs/modlog.py
@@ -13,7 +13,7 @@ from discord import (
from discord.abc import GuildChannel
from discord.ext.commands import Bot
-from bot.constants import Channels, Colours, Emojis, Icons
+from bot.constants import Channels, Colours, Emojis, Event, Icons
from bot.constants import Guild as GuildConstant
from bot.utils.time import humanize
@@ -35,15 +35,15 @@ class ModLog:
def __init__(self, bot: Bot):
self.bot = bot
- self._ignored_deletions = []
+ self._ignored = {event: [] for event in Event}
self._cached_deletes = []
self._cached_edits = []
- def ignore_message_deletion(self, *message_ids: int):
- for message_id in message_ids:
- if message_id not in self._ignored_deletions:
- self._ignored_deletions.append(message_id)
+ def ignore(self, event: Event, *items: int):
+ for item in items:
+ if item not in self._ignored[event]:
+ self._ignored[event].append(item)
async def send_log_message(
self, icon_url: Optional[str], colour: Colour, title: Optional[str], text: str, thumbnail: str = None,
@@ -288,6 +288,10 @@ class ModLog:
if guild.id != GuildConstant.id:
return
+ if member.id in self._ignored[Event.member_ban]:
+ self._ignored[Event.member_ban].remove(member.id)
+ return
+
await self.send_log_message(
Icons.user_ban, Colour(Colours.soft_red),
"User banned", f"{member.name}#{member.discriminator} (`{member.id}`)",
@@ -318,6 +322,10 @@ class ModLog:
if member.guild.id != GuildConstant.id:
return
+ if member.id in self._ignored[Event.member_remove]:
+ self._ignored[Event.member_remove].remove(member.id)
+ return
+
await self.send_log_message(
Icons.sign_out, Colour(Colours.soft_red),
"User left", f"{member.name}#{member.discriminator} (`{member.id}`)",
@@ -328,6 +336,10 @@ class ModLog:
if guild.id != GuildConstant.id:
return
+ if member.id in self._ignored[Event.member_unban]:
+ self._ignored[Event.member_unban].remove(member.id)
+ return
+
await self.send_log_message(
Icons.user_unban, Colour.blurple(),
"User unbanned", f"{member.name}#{member.discriminator} (`{member.id}`)",
@@ -338,6 +350,10 @@ class ModLog:
if before.guild.id != GuildConstant.id:
return
+ if before.id in self._ignored[Event.member_update]:
+ self._ignored[Event.member_update].remove(before.id)
+ return
+
diff = DeepDiff(before, after)
changes = []
done = []
@@ -427,8 +443,8 @@ class ModLog:
ignored_messages = 0
for message_id in event.message_ids:
- if message_id in self._ignored_deletions:
- self._ignored_deletions.remove(message_id)
+ if message_id in self._ignored[Event.message_delete]:
+ self._ignored[Event.message_delete].remove(message_id)
ignored_messages += 1
if ignored_messages >= len(event.message_ids):
@@ -457,8 +473,8 @@ class ModLog:
self._cached_deletes.append(message.id)
- if message.id in self._ignored_deletions:
- self._ignored_deletions.remove(message.id)
+ if message.id in self._ignored[Event.message_delete]:
+ self._ignored[Event.message_delete].remove(message.id)
return
if author.bot:
@@ -503,8 +519,8 @@ class ModLog:
self._cached_deletes.remove(event.message_id)
return
- if event.message_id in self._ignored_deletions:
- self._ignored_deletions.remove(event.message_id)
+ if event.message_id in self._ignored[Event.message_delete]:
+ self._ignored[Event.message_delete].remove(event.message_id)
return
channel = self.bot.get_channel(event.channel_id)
diff --git a/bot/cogs/verification.py b/bot/cogs/verification.py
index 84912e947..8d29a4bee 100644
--- a/bot/cogs/verification.py
+++ b/bot/cogs/verification.py
@@ -4,7 +4,7 @@ from discord import Message, NotFound, Object
from discord.ext.commands import Bot, Context, command
from bot.cogs.modlog import ModLog
-from bot.constants import Channels, Roles
+from bot.constants import Channels, Event, Roles
from bot.decorators import in_channel, without_role
log = logging.getLogger(__name__)
@@ -90,7 +90,7 @@ class Verification:
log.trace(f"Deleting the message posted by {ctx.author}.")
try:
- self.mod_log.ignore_message_deletion(ctx.message.id)
+ self.mod_log.ignore(Event.message_delete, ctx.message.id)
await ctx.message.delete()
except NotFound:
log.trace("No message found, it must have been deleted by another bot.")
diff --git a/bot/constants.py b/bot/constants.py
index ab426a7ab..756c03027 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -13,6 +13,7 @@ their default values from `config-default.yml`.
import logging
import os
from collections.abc import Mapping
+from enum import Enum
from pathlib import Path
from typing import Dict, List
@@ -281,6 +282,11 @@ class Icons(metaclass=YAMLGetter):
user_unban: str
user_update: str
+ user_mute: str
+ user_unmute: str
+
+ pencil: str
+
class CleanMessages(metaclass=YAMLGetter):
section = "bot"
@@ -394,6 +400,7 @@ class URLs(metaclass=YAMLGetter):
site_infractions: str
site_infractions_user: str
site_infractions_type: str
+ site_infractions_by_id: str
site_infractions_user_type_current: str
site_infractions_user_type: str
status: str
@@ -464,3 +471,27 @@ ERROR_REPLIES = [
"Are you trying to kill me?",
"Noooooo!!"
]
+
+
+class Event(Enum):
+ """
+ Event names. This does not include every event (for example, raw
+ events aren't here), but only events used in ModLog for now.
+ """
+
+ guild_channel_create = "guild_channel_create"
+ guild_channel_delete = "guild_channel_delete"
+ guild_channel_update = "guild_channel_update"
+ guild_role_create = "guild_role_create"
+ guild_role_delete = "guild_role_delete"
+ guild_role_update = "guild_role_update"
+ guild_update = "guild_update"
+
+ member_join = "member_join"
+ member_remove = "member_remove"
+ member_ban = "member_ban"
+ member_unban = "member_unban"
+ member_update = "member_update"
+
+ message_delete = "message_delete"
+ message_edit = "message_edit"
diff --git a/config-default.yml b/config-default.yml
index 0519244b0..415c1fcdf 100644
--- a/config-default.yml
+++ b/config-default.yml
@@ -65,7 +65,9 @@ style:
user_update: "https://cdn.discordapp.com/emojis/469952898684551168.png"
user_mute: "https://cdn.discordapp.com/emojis/472472640100106250.png"
- user_unmute: "https://cdn.discordapp.com/emojis/472472640100106250.png"
+ user_unmute: "https://cdn.discordapp.com/emojis/472472639206719508.png"
+
+ pencil: "https://cdn.discordapp.com/emojis/470326272401211415.png"
guild: