Compare commits

...

3 commits

View file

@ -1,6 +1,7 @@
import logging import logging
import time import time
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from typing import Union
import discord import discord
import humanize import humanize
import mysql.connector import mysql.connector
@ -11,7 +12,7 @@ from redbot.core.app_commands import Choice
class Moderation(commands.Cog): class Moderation(commands.Cog):
"""Custom cog moderation cog, meant to copy GalacticBot. """Custom moderation cog.
Developed by SeaswimmerTheFsh.""" Developed by SeaswimmerTheFsh."""
def __init__(self, bot): def __init__(self, bot):
@ -45,7 +46,8 @@ class Moderation(commands.Cog):
guilds: list[discord.Guild] = self.bot.guilds guilds: list[discord.Guild] = self.bot.guilds
try: try:
for guild in guilds: for guild in guilds:
await self.create_guild_table(guild) if not await self.bot.cog_disabled_in_guild(self, guild):
await self.create_guild_table(guild)
except ConnectionRefusedError: except ConnectionRefusedError:
return return
@ -55,52 +57,54 @@ class Moderation(commands.Cog):
@commands.Cog.listener('on_guild_join') @commands.Cog.listener('on_guild_join')
async def db_generate_guild_join(self, guild: discord.Guild): async def db_generate_guild_join(self, guild: discord.Guild):
"""This method prepares the database schema whenever the bot joins a guild.""" """This method prepares the database schema whenever the bot joins a guild."""
conf = await self.check_conf([ if not await self.bot.cog_disabled_in_guild(self, guild):
'mysql_address', conf = await self.check_conf([
'mysql_database', 'mysql_address',
'mysql_username', 'mysql_database',
'mysql_password' 'mysql_username',
]) 'mysql_password'
if conf: ])
self.logger.error("Failed to create a table for %s, due to MySQL connection configuration being unset.", guild.id) if conf:
return self.logger.error("Failed to create a table for %s, due to MySQL connection configuration being unset.", guild.id)
try: return
await self.create_guild_table(guild) try:
except ConnectionRefusedError: await self.create_guild_table(guild)
return except ConnectionRefusedError:
return
@commands.Cog.listener('on_audit_log_entry_create') @commands.Cog.listener('on_audit_log_entry_create')
async def autologger(self, entry: discord.AuditLogEntry): async def autologger(self, entry: discord.AuditLogEntry):
"""This method automatically logs moderations done by users manually ("right clicks").""" """This method automatically logs moderations done by users manually ("right clicks")."""
if await self.config.guild(entry.guild.id).ignore_other_bots() is True: if not await self.bot.cog_disabled_in_guild(self, entry.guild):
if entry.user.bot or entry.target.bot: if await self.config.guild(entry.guild.id).ignore_other_bots() is True:
return if entry.user.bot or entry.target.bot:
else: return
if entry.user.id == self.bot.user.id:
return
duration = "NULL"
if entry.reason:
reason = entry.reason + " (This action was performed without the bot.)"
else:
reason = "This action was performed without the bot."
if entry.action == discord.AuditLogAction.kick:
moderation_type = 'KICK'
elif entry.action == discord.AuditLogAction.ban:
moderation_type = 'BAN'
elif entry.action == discord.AuditLogAction.unban:
moderation_type = 'UNBAN'
elif entry.action == discord.AuditLogAction.member_update:
if entry.after.timed_out_until is not None:
timed_out_until_aware = entry.after.timed_out_until.replace(tzinfo=timezone.utc)
duration_datetime = timed_out_until_aware - datetime.now(tz=timezone.utc)
minutes = round(duration_datetime.total_seconds() / 60)
duration = timedelta(minutes=minutes)
moderation_type = 'MUTE'
else: else:
moderation_type = 'UNMUTE' if entry.user.id == self.bot.user.id:
else: return
return duration = "NULL"
await self.mysql_log(entry.guild.id, entry.user.id, moderation_type, entry.target.id, duration, reason) if entry.reason:
reason = entry.reason + " (This action was performed without the bot.)"
else:
reason = "This action was performed without the bot."
if entry.action == discord.AuditLogAction.kick:
moderation_type = 'KICK'
elif entry.action == discord.AuditLogAction.ban:
moderation_type = 'BAN'
elif entry.action == discord.AuditLogAction.unban:
moderation_type = 'UNBAN'
elif entry.action == discord.AuditLogAction.member_update:
if entry.after.timed_out_until is not None:
timed_out_until_aware = entry.after.timed_out_until.replace(tzinfo=timezone.utc)
duration_datetime = timed_out_until_aware - datetime.now(tz=timezone.utc)
minutes = round(duration_datetime.total_seconds() / 60)
duration = timedelta(minutes=minutes)
moderation_type = 'MUTE'
else:
moderation_type = 'UNMUTE'
else:
return
await self.mysql_log(entry.guild.id, entry.user.id, moderation_type, entry.target.id, duration, reason)
async def connect(self): async def connect(self):
"""Connects to the MySQL database, and returns a connection object.""" """Connects to the MySQL database, and returns a connection object."""
@ -173,6 +177,17 @@ class Moderation(commands.Cog):
not_found_list.append(item) not_found_list.append(item)
return not_found_list return not_found_list
def check_permissions(self, member: discord.Member, permissions: list, ctx: Union[commands.Context, discord.Interaction] = None):
"""Checks if the bot has a specific permission (or a list of permissions) in a channel."""
if ctx:
resolved_permissions = ctx.channel.permissions_for(member)
else:
resolved_permissions = member.guild_permissions
for permission in permissions:
if not getattr(resolved_permissions, permission, False) and not resolved_permissions.administrator is True:
return permission
return False
async def mysql_log(self, guild_id: str, author_id: str, moderation_type: str, target_id: int, duration, reason: str): async def mysql_log(self, guild_id: str, author_id: str, moderation_type: str, target_id: int, duration, reason: str):
timestamp = int(time.time()) timestamp = int(time.time())
if duration != "NULL": if duration != "NULL":
@ -301,6 +316,14 @@ class Moderation(commands.Cog):
Why are you noting this user? Why are you noting this user?
silent: bool silent: bool
Should the user be messaged?""" Should the user be messaged?"""
if interaction.guild.get_member(target.id):
target_member = interaction.guild.get_member(target.id)
if interaction.guild.get_member(interaction.client.user.id).top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a role higher than the bot!", ephemeral=True)
return
if interaction.user.top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a higher role than you!", ephemeral=True)
return
await interaction.response.send_message(content=f"{target.mention} has recieved a note!\n**Reason** - `{reason}`") await interaction.response.send_message(content=f"{target.mention} has recieved a note!\n**Reason** - `{reason}`")
if silent is None: if silent is None:
silent = not await self.config.guild(interaction.guild).dm_users() silent = not await self.config.guild(interaction.guild).dm_users()
@ -324,6 +347,14 @@ class Moderation(commands.Cog):
Why are you warning this user? Why are you warning this user?
silent: bool silent: bool
Should the user be messaged?""" Should the user be messaged?"""
if interaction.guild.get_member(target.id):
target_member = interaction.guild.get_member(target.id)
if interaction.guild.get_member(interaction.client.user.id).top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a role higher than the bot!", ephemeral=True)
return
if interaction.user.top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a higher role than you!", ephemeral=True)
return
await interaction.response.send_message(content=f"{target.mention} has been warned!\n**Reason** - `{reason}`") await interaction.response.send_message(content=f"{target.mention} has been warned!\n**Reason** - `{reason}`")
if silent is None: if silent is None:
silent = not await self.config.guild(interaction.guild).dm_users() silent = not await self.config.guild(interaction.guild).dm_users()
@ -349,6 +380,18 @@ class Moderation(commands.Cog):
Why are you unbanning this user? Why are you unbanning this user?
silent: bool silent: bool
Should the user be messaged?""" Should the user be messaged?"""
if interaction.guild.get_member(target.id):
target_member = interaction.guild.get_member(target.id)
if interaction.guild.get_member(interaction.client.user.id).top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a role higher than the bot!", ephemeral=True)
return
if interaction.user.top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a higher role than you!", ephemeral=True)
return
permissions = self.check_permissions(interaction.client.user, ['moderate_members'], interaction)
if permissions:
await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True)
return
if target.is_timed_out() is True: if target.is_timed_out() is True:
await interaction.response.send_message(f"{target.mention} is already muted!", allowed_mentions=discord.AllowedMentions(users=False), ephemeral=True) await interaction.response.send_message(f"{target.mention} is already muted!", allowed_mentions=discord.AllowedMentions(users=False), ephemeral=True)
return return
@ -384,6 +427,18 @@ class Moderation(commands.Cog):
Why are you unmuting this user? Why are you unmuting this user?
silent: bool silent: bool
Should the user be messaged?""" Should the user be messaged?"""
if interaction.guild.get_member(target.id):
target_member = interaction.guild.get_member(target.id)
if interaction.guild.get_member(interaction.client.user.id).top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a role higher than the bot!", ephemeral=True)
return
if interaction.user.top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a higher role than you!", ephemeral=True)
return
permissions = self.check_permissions(interaction.client.user, ['moderate_members'], interaction)
if permissions:
await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True)
return
if target.is_timed_out() is False: if target.is_timed_out() is False:
await interaction.response.send_message(f"{target.mention} is not muted!", allowed_mentions=discord.AllowedMentions(users=False), ephemeral=True) await interaction.response.send_message(f"{target.mention} is not muted!", allowed_mentions=discord.AllowedMentions(users=False), ephemeral=True)
return return
@ -415,6 +470,18 @@ class Moderation(commands.Cog):
Why are you kicking this user? Why are you kicking this user?
silent: bool silent: bool
Should the user be messaged?""" Should the user be messaged?"""
if interaction.guild.get_member(target.id):
target_member = interaction.guild.get_member(target.id)
if interaction.guild.get_member(interaction.client.user.id).top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a role higher than the bot!", ephemeral=True)
return
if interaction.user.top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a higher role than you!", ephemeral=True)
return
permissions = self.check_permissions(interaction.client.user, ['kick_members'], interaction)
if permissions:
await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True)
return
await interaction.response.send_message(content=f"{target.mention} has been kicked!\n**Reason** - `{reason}`") await interaction.response.send_message(content=f"{target.mention} has been kicked!\n**Reason** - `{reason}`")
if silent is None: if silent is None:
silent = not await self.config.guild(interaction.guild).dm_users() silent = not await self.config.guild(interaction.guild).dm_users()
@ -451,6 +518,18 @@ class Moderation(commands.Cog):
How many days of messages to delete? How many days of messages to delete?
silent: bool silent: bool
Should the user be messaged?""" Should the user be messaged?"""
if interaction.guild.get_member(target.id):
target_member = interaction.guild.get_member(target.id)
if interaction.guild.get_member(interaction.client.user.id).top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a role higher than the bot!", ephemeral=True)
return
if interaction.user.top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a higher role than you!", ephemeral=True)
return
permissions = self.check_permissions(interaction.client.user, ['ban_members'], interaction)
if permissions:
await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True)
return
try: try:
await interaction.guild.fetch_ban(target) await interaction.guild.fetch_ban(target)
await interaction.response.send_message(content=f"{target.mention} is already banned!", ephemeral=True) await interaction.response.send_message(content=f"{target.mention} is already banned!", ephemeral=True)
@ -496,6 +575,18 @@ class Moderation(commands.Cog):
Why are you unbanning this user? Why are you unbanning this user?
silent: bool silent: bool
Should the user be messaged?""" Should the user be messaged?"""
if interaction.guild.get_member(target.id):
target_member = interaction.guild.get_member(target.id)
if interaction.guild.get_member(interaction.client.user.id).top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a role higher than the bot!", ephemeral=True)
return
if interaction.user.top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a higher role than you!", ephemeral=True)
return
permissions = self.check_permissions(interaction.client.user, ['moderate_members'], interaction)
if permissions:
await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True)
return
try: try:
await interaction.guild.fetch_ban(target) await interaction.guild.fetch_ban(target)
except discord.errors.NotFound: except discord.errors.NotFound:
@ -533,6 +624,10 @@ class Moderation(commands.Cog):
Page to select Page to select
epheremal: bool epheremal: bool
Hide the command response""" Hide the command response"""
permissions = self.check_permissions(interaction.client.user, ['embed_links'], interaction)
if permissions:
await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True)
return
database = await self.connect() database = await self.connect()
cursor = database.cursor() cursor = database.cursor()
if target: if target:
@ -595,6 +690,10 @@ class Moderation(commands.Cog):
Case number of the case you're trying to resolve Case number of the case you're trying to resolve
reason: str reason: str
Reason for resolving case""" Reason for resolving case"""
permissions = self.check_permissions(interaction.client.user, ['embed_links'], interaction)
if permissions:
await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True)
return
conf = await self.check_conf(['mysql_database']) conf = await self.check_conf(['mysql_database'])
if conf: if conf:
raise(LookupError) raise(LookupError)
@ -655,6 +754,10 @@ class Moderation(commands.Cog):
What case are you looking up? What case are you looking up?
ephemeral: bool ephemeral: bool
Hide the command response""" Hide the command response"""
permissions = self.check_permissions(interaction.client.user, ['embed_links'], interaction)
if permissions:
await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True)
return
database = await self.connect() database = await self.connect()
cursor = database.cursor() cursor = database.cursor()
query = "SELECT * FROM moderation_%s WHERE moderation_id = %s;" query = "SELECT * FROM moderation_%s WHERE moderation_id = %s;"
@ -679,24 +782,25 @@ class Moderation(commands.Cog):
db = await self.config.mysql_database() db = await self.config.mysql_database()
guilds: list[discord.Guild] = self.bot.guilds guilds: list[discord.Guild] = self.bot.guilds
for guild in guilds: for guild in guilds:
tempban_query = f"SELECT target_id, moderation_id FROM moderation_{guild.id} WHERE end_timestamp != 0 AND end_timestamp <= %s AND moderation_type = 'TEMPBAN' AND expired = 0" if not await self.bot.cog_disabled_in_guild(self, guild):
try: tempban_query = f"SELECT target_id, moderation_id FROM moderation_{guild.id} WHERE end_timestamp != 0 AND end_timestamp <= %s AND moderation_type = 'TEMPBAN' AND expired = 0"
cursor.execute(tempban_query, (time.time(),))
result = cursor.fetchall()
except mysql.connector.errors.ProgrammingError:
continue
target_ids = [row[0] for row in result]
moderation_ids = [row[1] for row in result]
for target_id, moderation_id in zip(target_ids, moderation_ids):
user: discord.User = await self.bot.fetch_user(target_id)
await guild.unban(user, reason=f"Automatic unban from case #{moderation_id}")
embed = await self.embed_factory('message', guild, f'Automatic unban from case #{moderation_id}', 'unbanned')
try: try:
await user.send(embed=embed) cursor.execute(tempban_query, (time.time(),))
except discord.errors.HTTPException: result = cursor.fetchall()
pass except mysql.connector.errors.ProgrammingError:
expiry_query = f"UPDATE `{db}`.`moderation_{guild.id}` SET expired = 1 WHERE (end_timestamp != 0 AND end_timestamp <= %s AND expired = 0) OR (expired = 0 AND resolved = 1)" continue
cursor.execute(expiry_query, (time.time(),)) target_ids = [row[0] for row in result]
moderation_ids = [row[1] for row in result]
for target_id, moderation_id in zip(target_ids, moderation_ids):
user: discord.User = await self.bot.fetch_user(target_id)
await guild.unban(user, reason=f"Automatic unban from case #{moderation_id}")
embed = await self.embed_factory('message', guild, f'Automatic unban from case #{moderation_id}', 'unbanned')
try:
await user.send(embed=embed)
except discord.errors.HTTPException:
pass
expiry_query = f"UPDATE `{db}`.`moderation_{guild.id}` SET expired = 1 WHERE (end_timestamp != 0 AND end_timestamp <= %s AND expired = 0) OR (expired = 0 AND resolved = 1)"
cursor.execute(expiry_query, (time.time(),))
database.commit() database.commit()
cursor.close() cursor.close()
database.close() database.close()