convert Moderation to sqlite3 (and rename it to Aurora) #12

Merged
cswimr merged 16 commits from sqlite3 into main 2023-12-28 05:22:52 -05:00
16 changed files with 139 additions and 268 deletions

5
aurora/__init__.py Normal file
View file

@ -0,0 +1,5 @@
from .aurora import Aurora
async def setup(bot):
await bot.add_cog(Aurora(bot))

View file

@ -8,27 +8,27 @@
import json import json
import os import os
import time import time
import sqlite3
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from math import ceil from math import ceil
import discord import discord
import humanize import humanize
import mysql.connector
from discord.ext import tasks from discord.ext import tasks
from pytimeparse2 import disable_dateutil, parse from pytimeparse2 import disable_dateutil, parse
from redbot.core import app_commands, checks, commands, data_manager from redbot.core import app_commands, checks, commands, data_manager
from redbot.core.app_commands import Choice from redbot.core.app_commands import Choice
from .importers.galacticbot import ImportGalacticBotView from .importers.galacticbot import ImportGalacticBotView
from .importers.moderation import ImportModerationView from .importers.aurora import ImportAuroraView
from .utilities.config import config, register_config from .utilities.config import config, register_config
from .utilities.database import connect, create_guild_table, fetch_case, mysql_log from .utilities.database import connect, create_guild_table, fetch_case, mysql_log
from .utilities.embed_factory import embed_factory from .utilities.embed_factory import embed_factory
from .utilities.logger import logger from .utilities.logger import logger
from .utilities.utils import check_conf, check_moddable, check_permissions, fetch_channel_dict, fetch_user_dict, generate_dict, log, send_evidenceformat from .utilities.utils import check_moddable, check_permissions, fetch_channel_dict, fetch_user_dict, generate_dict, log, send_evidenceformat
class Moderation(commands.Cog): class Aurora(commands.Cog):
"""Custom moderation cog. """Custom moderation cog.
Developed by SeaswimmerTheFsh.""" Developed by SeaswimmerTheFsh."""
@ -36,7 +36,7 @@ class Moderation(commands.Cog):
if requester == "discord_deleted_user": if requester == "discord_deleted_user":
await config.user_from_id(user_id).clear() await config.user_from_id(user_id).clear()
database = await connect() database = connect()
cursor = database.cursor() cursor = database.cursor()
cursor.execute("SHOW TABLES;") cursor.execute("SHOW TABLES;")
@ -68,17 +68,6 @@ class Moderation(commands.Cog):
async def cog_load(self): async def cog_load(self):
"""This method prepares the database schema for all of the guilds the bot is currently in.""" """This method prepares the database schema for all of the guilds the bot is currently in."""
conf = await check_conf([
'mysql_address',
'mysql_database',
'mysql_username',
'mysql_password'
])
if conf:
logger.error("Failed to create tables, due to MySQL connection configuration being unset.")
return
guilds: list[discord.Guild] = self.bot.guilds guilds: list[discord.Guild] = self.bot.guilds
try: try:
@ -96,20 +85,8 @@ class Moderation(commands.Cog):
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."""
if not await self.bot.cog_disabled_in_guild(self, guild): if not await self.bot.cog_disabled_in_guild(self, guild):
conf = await check_conf([
'mysql_address',
'mysql_database',
'mysql_username',
'mysql_password'
])
if conf:
logger.error("Failed to create a table for %s, due to MySQL connection configuration being unset.", guild.id)
return
try: try:
await create_guild_table(guild) await create_guild_table(guild)
except ConnectionRefusedError: except ConnectionRefusedError:
return return
@ -592,15 +569,16 @@ class Moderation(commands.Cog):
await interaction.followup.send(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True) await interaction.followup.send(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True)
return return
database = await connect() database = connect()
if export: if export:
cursor = database.cursor(dictionary=True) database.row_factory = sqlite3.Row
cursor = database.cursor()
query = """SELECT * query = f"""SELECT *
FROM moderation_%s FROM moderation_{interaction.guild.id}
ORDER BY moderation_id DESC;""" ORDER BY moderation_id DESC;"""
cursor.execute(query, (interaction.guild.id,)) cursor.execute(query)
results = cursor.fetchall() results = cursor.fetchall()
@ -622,22 +600,22 @@ class Moderation(commands.Cog):
cursor = database.cursor() cursor = database.cursor()
if target: if target:
query = """SELECT * query = f"""SELECT *
FROM moderation_%s FROM moderation_{interaction.guild.id}
WHERE target_id = %s WHERE target_id = ?
ORDER BY moderation_id DESC;""" ORDER BY moderation_id DESC;"""
cursor.execute(query, (interaction.guild.id, target.id)) cursor.execute(query, (target.id,))
elif moderator: elif moderator:
query = """SELECT * query = f"""SELECT *
FROM moderation_%s FROM moderation_{interaction.guild.id}
WHERE moderator_id = %s WHERE moderator_id = ?
ORDER BY moderation_id DESC;""" ORDER BY moderation_id DESC;"""
cursor.execute(query, (interaction.guild.id, moderator.id)) cursor.execute(query, (moderator.id,))
else: else:
query = """SELECT * query = f"""SELECT *
FROM moderation_%s FROM moderation_{interaction.guild.id}
ORDER BY moderation_id DESC;""" ORDER BY moderation_id DESC;"""
cursor.execute(query, (interaction.guild.id,)) cursor.execute(query)
results = cursor.fetchall() results = cursor.fetchall()
result_dict_list = [] result_dict_list = []
@ -714,23 +692,18 @@ class Moderation(commands.Cog):
await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True) await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True)
return return
conf = await check_conf(['mysql_database']) database = connect()
if conf:
raise(LookupError)
database = await connect()
cursor = database.cursor() cursor = database.cursor()
db = await config.mysql_database()
query_1 = "SELECT * FROM moderation_%s WHERE moderation_id = %s;" query_1 = f"SELECT * FROM moderation_{interaction.guild.id} WHERE moderation_id = ?;"
cursor.execute(query_1, (interaction.guild.id, case)) cursor.execute(query_1, (case,))
result_1 = cursor.fetchone() result_1 = cursor.fetchone()
if result_1 is None or case == 0: if result_1 is None or case == 0:
await interaction.response.send_message(content=f"There is no moderation with a case number of {case}.", ephemeral=True) await interaction.response.send_message(content=f"There is no moderation with a case number of {case}.", ephemeral=True)
return return
query_2 = "SELECT * FROM moderation_%s WHERE moderation_id = %s AND resolved = 0;" query_2 = f"SELECT * FROM moderation_{interaction.guild.id} WHERE moderation_id = ? AND resolved = 0;"
cursor.execute(query_2, (interaction.guild.id, case)) cursor.execute(query_2, (case,))
result_2 = cursor.fetchone() result_2 = cursor.fetchone()
if result_2 is None: if result_2 is None:
await interaction.response.send_message(content=f"This moderation has already been resolved!\nUse `/case {case}` for more information.", ephemeral=True) await interaction.response.send_message(content=f"This moderation has already been resolved!\nUse `/case {case}` for more information.", ephemeral=True)
@ -782,9 +755,9 @@ class Moderation(commands.Cog):
except discord.NotFound: except discord.NotFound:
pass pass
resolve_query = f"UPDATE `{db}`.`moderation_{interaction.guild.id}` SET resolved = 1, changes = %s, resolved_by = %s, resolve_reason = %s WHERE moderation_id = %s" resolve_query = f"UPDATE `moderation_{interaction.guild.id}` SET resolved = 1, changes = ?, resolved_by = ?, resolve_reason = ? WHERE moderation_id = ?"
else: else:
resolve_query = f"UPDATE `{db}`.`moderation_{interaction.guild.id}` SET resolved = 1, changes = %s, resolved_by = %s, resolve_reason = %s WHERE moderation_id = %s" resolve_query = f"UPDATE `moderation_{interaction.guild.id}` SET resolved = 1, changes = ?, resolved_by = ?, resolve_reason = ? WHERE moderation_id = ?"
cursor.execute(resolve_query, (json.dumps(changes), interaction.user.id, reason, case_dict['moderation_id'])) cursor.execute(resolve_query, (json.dumps(changes), interaction.user.id, reason, case_dict['moderation_id']))
database.commit() database.commit()
@ -878,10 +851,6 @@ class Moderation(commands.Cog):
parsed_time = None parsed_time = None
case_dict = await fetch_case(case, interaction.guild.id) case_dict = await fetch_case(case, interaction.guild.id)
if case_dict: if case_dict:
conf = await check_conf(['mysql_database'])
if conf:
raise(LookupError)
if duration: if duration:
try: try:
parsed_time = parse(sval=duration, as_timedelta=True, raise_exception=True) parsed_time = parse(sval=duration, as_timedelta=True, raise_exception=True)
@ -941,15 +910,14 @@ class Moderation(commands.Cog):
} }
) )
database = await connect() database = connect()
cursor = database.cursor() cursor = database.cursor()
db = await config.mysql_database()
if parsed_time: if parsed_time:
update_query = f"UPDATE `{db}`.`moderation_{interaction.guild.id}` SET changes = %s, reason = %s, duration = %s, end_timestamp = %s WHERE moderation_id = %s" update_query = f"UPDATE `moderation_{interaction.guild.id}` SET changes = %s, reason = %s, duration = %s, end_timestamp = %s WHERE moderation_id = %s"
cursor.execute(update_query, (json.dumps(changes), reason, parsed_time, end_timestamp, case)) cursor.execute(update_query, (json.dumps(changes), reason, parsed_time, end_timestamp, case))
else: else:
update_query = f"UPDATE `{db}`.`moderation_{interaction.guild.id}` SET changes = %s, reason = %s WHERE moderation_id = %s" update_query = f"UPDATE `moderation_{interaction.guild.id}` SET changes = %s, reason = %s WHERE moderation_id = %s"
cursor.execute(update_query, (json.dumps(changes), reason, case)) cursor.execute(update_query, (json.dumps(changes), reason, case))
database.commit() database.commit()
@ -966,24 +934,19 @@ class Moderation(commands.Cog):
@tasks.loop(minutes=1) @tasks.loop(minutes=1)
async def handle_expiry(self): async def handle_expiry(self):
conf = await check_conf(['mysql_database']) database = connect()
if conf:
raise(LookupError)
database = await connect()
cursor = database.cursor() cursor = database.cursor()
db = await 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:
if not await self.bot.cog_disabled_in_guild(self, guild): if not await self.bot.cog_disabled_in_guild(self, guild):
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" tempban_query = f"SELECT target_id, moderation_id FROM moderation_{guild.id} WHERE end_timestamp != 0 AND end_timestamp <= ? AND moderation_type = 'TEMPBAN' AND expired = 0"
try: try:
cursor.execute(tempban_query, (time.time(),)) cursor.execute(tempban_query, (time.time(),))
result = cursor.fetchall() result = cursor.fetchall()
except mysql.connector.errors.ProgrammingError: except sqlite3.OperationalError:
continue continue
target_ids = [row[0] for row in result] target_ids = [row[0] for row in result]
@ -1003,15 +966,14 @@ class Moderation(commands.Cog):
except [discord.errors.NotFound, discord.errors.Forbidden, discord.errors.HTTPException] as e: except [discord.errors.NotFound, discord.errors.Forbidden, discord.errors.HTTPException] as e:
print(f"Failed to unban {user.name}#{user.discriminator} ({user.id}) from {guild.name} ({guild.id})\n{e}") print(f"Failed to unban {user.name}#{user.discriminator} ({user.id}) from {guild.name} ({guild.id})\n{e}")
expiry_query = f"UPDATE `{db}`.`moderation_{guild.id}` SET expired = 1 WHERE (end_timestamp != 0 AND end_timestamp <= %s AND expired = 0 AND moderation_type != 'BLACKLIST') OR (expired = 0 AND resolved = 1 AND moderation_type != 'BLACKLIST')" expiry_query = f"UPDATE `moderation_{guild.id}` SET expired = 1 WHERE (end_timestamp != 0 AND end_timestamp <= ? AND expired = 0 AND moderation_type != 'BLACKLIST') OR (expired = 0 AND resolved = 1 AND moderation_type != 'BLACKLIST')"
cursor.execute(expiry_query, (time.time(),)) cursor.execute(expiry_query, (time.time(),))
blacklist_query = f"SELECT target_id, moderation_id, role_id FROM moderation_{guild.id} WHERE end_timestamp != 0 AND end_timestamp <= %s AND moderation_type = 'BLACKLIST' AND expired = 0" blacklist_query = f"SELECT target_id, moderation_id, role_id FROM moderation_{guild.id} WHERE end_timestamp != 0 AND end_timestamp <= ? AND moderation_type = 'BLACKLIST' AND expired = 0"
try: try:
cursor.execute(blacklist_query, (time.time(),)) cursor.execute(blacklist_query, (time.time(),))
result = cursor.fetchall() result = cursor.fetchall()
except mysql.connector.errors.ProgrammingError: except sqlite3.OperationalError:
continue continue
target_ids = [row[0] for row in result] target_ids = [row[0] for row in result]
moderation_ids = [row[1] for row in result] moderation_ids = [row[1] for row in result]
@ -1047,7 +1009,7 @@ class Moderation(commands.Cog):
guild_settings_string = "" guild_settings_string = ""
for setting in guild_settings: for setting in guild_settings:
if 'mysql' in setting or 'roles' in setting: if 'roles' in setting:
continue continue
if setting == 'log_channel': if setting == 'log_channel':
channel = ctx.guild.get_channel(guild_settings[setting]) channel = ctx.guild.get_channel(guild_settings[setting])
@ -1323,96 +1285,20 @@ class Moderation(commands.Cog):
await config.guild(ctx.guild).log_channel.set(" ") await config.guild(ctx.guild).log_channel.set(" ")
await ctx.send("Logging channel disabled.") await ctx.send("Logging channel disabled.")
@moderationset.command(name="mysql")
@checks.is_owner()
async def moderationset_mysql(self, ctx: commands.Context):
"""Configure MySQL connection details."""
await ctx.message.add_reaction("")
await ctx.author.send(content="Click the button below to configure your MySQL connection details.", view=self.ConfigButtons(60))
class ConfigButtons(discord.ui.View):
def __init__(self, timeout):
super().__init__()
@discord.ui.button(label="Edit", style=discord.ButtonStyle.success)
async def config_button(self, interaction: discord.Interaction, button: discord.ui.Button): # pylint: disable=unused-argument
await interaction.response.send_modal(Moderation.MySQLConfigModal)
class MySQLConfigModal(discord.ui.Modal, title="MySQL Database Configuration"):
def __init__(self):
super().__init__()
address = discord.ui.TextInput(
label="Address",
placeholder="Input your MySQL address here.",
style=discord.TextStyle.short,
required=False,
max_length=300
)
database = discord.ui.TextInput(
label="Database",
placeholder="Input the name of your database here.",
style=discord.TextStyle.short,
required=False,
max_length=300
)
username = discord.ui.TextInput(
label="Username",
placeholder="Input your MySQL username here.",
style=discord.TextStyle.short,
required=False,
max_length=300
)
password = discord.ui.TextInput(
label="Password",
placeholder="Input your MySQL password here.",
style=discord.TextStyle.short,
required=False,
max_length=300
)
async def on_submit(self, interaction: discord.Interaction):
message = ""
if self.address.value != "":
await config.mysql_address.set(self.address.value)
message += f"- Address set to\n - `{self.address.value}`\n"
if self.database.value != "":
await config.mysql_database.set(self.database.value)
message += f"- Database set to\n - `{self.database.value}`\n"
if self.username.value != "":
await config.mysql_username.set(self.username.value)
message += f"- Username set to\n - `{self.username.value}`\n"
if self.password.value != "":
await config.mysql_password.set(self.password.value)
trimmed_password = self.password.value[:8]
message += f"- Password set to\n - `{trimmed_password}` - Trimmed for security\n"
if message == "":
trimmed_password = str(await config.mysql_password())[:8]
send = f"No changes were made.\nCurrent configuration:\n- Address:\n - `{await config.mysql_address()}`\n- Database:\n - `{await config.mysql_database()}`\n- Username:\n - `{await config.mysql_username()}`\n- Password:\n - `{trimmed_password}` - Trimmed for security"
else:
send = f"Configuration changed:\n{message}"
await interaction.response.send_message(send, ephemeral=True)
@moderationset.group(autohelp=True, name='import') @moderationset.group(autohelp=True, name='import')
@checks.admin() @checks.admin()
async def moderationset_import(self, ctx: commands.Context): async def moderationset_import(self, ctx: commands.Context):
"""Import moderations from other bots.""" """Import moderations from other bots."""
@moderationset_import.command(name="moderation") @moderationset_import.command(name="aurora")
@checks.admin() @checks.admin()
async def moderationset_import_moderation(self, ctx: commands.Context): async def moderationset_import_aurora(self, ctx: commands.Context):
"""Import moderations from another bot using this cog.""" """Import moderations from another bot using Aurora."""
if ctx.message.attachments and ctx.message.attachments[0].content_type == 'application/json; charset=utf-8': if ctx.message.attachments and ctx.message.attachments[0].content_type == 'application/json; charset=utf-8':
message = await ctx.send("Are you sure you want to import moderations from another bot?\n**This will overwrite any moderations that already exist in this guild's moderation table.**\n*The import process will block the rest of your bot until it is complete.*") message = await ctx.send("Are you sure you want to import moderations from another bot?\n**This will overwrite any moderations that already exist in this guild's moderation table.**\n*The import process will block the rest of your bot until it is complete.*")
await message.edit(view=ImportModerationView(60, ctx, message)) await message.edit(view=ImportAuroraView(60, ctx, message))
else: else:
await ctx.send("Please provide a valid moderation export file.") await ctx.send("Please provide a valid Aurora export file.")
@moderationset_import.command(name="galacticbot") @moderationset_import.command(name="galacticbot")
@checks.admin() @checks.admin()

View file

@ -8,7 +8,7 @@ from redbot.core import commands
from ..utilities.database import connect, create_guild_table, mysql_log from ..utilities.database import connect, create_guild_table, mysql_log
class ImportModerationView(ui.View): class ImportAuroraView(ui.View):
def __init__(self, timeout, ctx, message): def __init__(self, timeout, ctx, message):
super().__init__() super().__init__()
self.ctx: commands.Context = ctx self.ctx: commands.Context = ctx
@ -23,7 +23,7 @@ class ImportModerationView(ui.View):
"Deleting original table...", ephemeral=True "Deleting original table...", ephemeral=True
) )
database = await connect() database = connect()
cursor = database.cursor() cursor = database.cursor()
query = f"DROP TABLE IF EXISTS moderation_{self.ctx.guild.id};" query = f"DROP TABLE IF EXISTS moderation_{self.ctx.guild.id};"
@ -66,9 +66,10 @@ class ImportModerationView(ui.View):
if "metadata" not in case: if "metadata" not in case:
metadata = {} metadata = {}
else: else:
metadata: Dict[str, any] = case["metadata"] metadata: Dict[str, any] = json.loads(case["metadata"])
if not metadata['imported_from']:
metadata.update({ metadata.update({
'imported_from': 'SeaCogs/Moderation' 'imported_from': 'Aurora'
}) })
if case["duration"] != "NULL": if case["duration"] != "NULL":
@ -106,6 +107,6 @@ class ImportModerationView(ui.View):
async def import_button_n( async def import_button_n(
self, interaction: Interaction, button: ui.Button self, interaction: Interaction, button: ui.Button
): # pylint: disable=unused-argument ): # pylint: disable=unused-argument
await self.message.edit("Import cancelled.", view=None) await self.message.edit(content="Import cancelled.", view=None)
await self.message.delete(10) await self.message.delete(10)
await self.ctx.message.delete(10) await self.ctx.message.delete(10)

View file

@ -18,7 +18,7 @@ class ImportGalacticBotView(ui.View):
await self.message.delete() await self.message.delete()
await interaction.response.send_message("Deleting original table...", ephemeral=True) await interaction.response.send_message("Deleting original table...", ephemeral=True)
database = await connect() database = connect()
cursor = database.cursor() cursor = database.cursor()
query = f"DROP TABLE IF EXISTS moderation_{self.ctx.guild.id};" query = f"DROP TABLE IF EXISTS moderation_{self.ctx.guild.id};"
@ -140,6 +140,6 @@ class ImportGalacticBotView(ui.View):
@ui.button(label="No", style=ButtonStyle.danger) @ui.button(label="No", style=ButtonStyle.danger)
async def import_button_n(self, interaction: Interaction, button: ui.Button): # pylint: disable=unused-argument async def import_button_n(self, interaction: Interaction, button: ui.Button): # pylint: disable=unused-argument
await self.message.edit("Import cancelled.", view=None) await self.message.edit(content="Import cancelled.", view=None)
await self.message.delete(10) await self.message.delete(10)
await self.ctx.message.delete(10) await self.ctx.message.delete(10)

11
aurora/info.json Normal file
View file

@ -0,0 +1,11 @@
{
"author" : ["SeaswimmerTheFsh"],
"install_msg" : "Thank you for installing Aurora!\nYou can find the source code of this cog [here](https://coastalcommits.com/SeaswimmerTheFsh/SeaCogs).",
"name" : "Aurora",
"short" : "A full replacement for Red's core Mod cogs.",
"description" : "Aurora is designed to be a full replacement for Red's core Mod cogs. It is heavily inspired by GalacticBot, and is designed to be a more user-friendly alternative to Red's core Mod cogs. This cog stores all of its data in an SQLite database.",
"end_user_data_statement" : "This cog stores the following information:\n- User IDs of accounts who moderate users or are moderated\n- Guild IDs of guilds with the cog enabled\n- Timestamps of moderations\n- Other information relating to moderations",
"requirements": ["humanize", "pytimeparse2"],
"hidden": false,
"disabled": false
}

View file

@ -3,12 +3,6 @@ from redbot.core import Config
config: Config = Config.get_conf(None, identifier=481923957134912, cog_name="Moderation") config: Config = Config.get_conf(None, identifier=481923957134912, cog_name="Moderation")
def register_config(config_obj: Config): def register_config(config_obj: Config):
config_obj.register_global(
mysql_address= " ",
mysql_database = " ",
mysql_username = " ",
mysql_password = " "
)
config_obj.register_guild( config_obj.register_guild(
use_discord_permissions = True, use_discord_permissions = True,
ignore_other_bots = True, ignore_other_bots = True,

View file

@ -2,88 +2,73 @@
import json import json
import time import time
from datetime import datetime import sqlite3
from datetime import datetime, timedelta
import mysql.connector
from discord import Guild from discord import Guild
from redbot.core import data_manager
from .config import config
from .logger import logger from .logger import logger
from .utils import check_conf, generate_dict, get_next_case_number from .utils import generate_dict, get_next_case_number
async def connect(): def connect() -> sqlite3.Connection:
"""Connects to the MySQL database, and returns a connection object.""" """Connects to the SQLite database, and returns a connection object."""
conf = await check_conf(
["mysql_address", "mysql_database", "mysql_username", "mysql_password"]
)
if conf:
raise LookupError("MySQL connection details not set properly!")
try: try:
connection = mysql.connector.connect( connection = sqlite3.connect(database=data_manager.cog_data_path(raw_name='Aurora') / 'aurora.db')
host=await config.mysql_address(),
user=await config.mysql_username(),
password=await config.mysql_password(),
database=await config.mysql_database(),
)
return connection return connection
except mysql.connector.ProgrammingError as e: except sqlite3.OperationalError as e:
logger.error("Unable to access the MySQL database!\nError:\n%s", e.msg) logger.error("Unable to access the SQLite database!\nError:\n%s", e.msg)
raise ConnectionRefusedError( raise ConnectionRefusedError(
f"Unable to access the MySQL Database!\n{e.msg}" f"Unable to access the SQLite Database!\n{e.msg}"
) from e ) from e
async def create_guild_table(guild: Guild): async def create_guild_table(guild: Guild):
database = await connect() database = connect()
cursor = database.cursor() cursor = database.cursor()
try: try:
cursor.execute(f"SELECT * FROM `moderation_{guild.id}`") cursor.execute(f"SELECT * FROM `moderation_{guild.id}`")
logger.debug("MySQL Table exists for server %s (%s)", guild.name, guild.id) logger.debug("SQLite Table exists for server %s (%s)", guild.name, guild.id)
except mysql.connector.errors.ProgrammingError: except sqlite3.OperationalError:
query = f""" query = f"""
CREATE TABLE `moderation_{guild.id}` ( CREATE TABLE `moderation_{guild.id}` (
moderation_id INT UNIQUE PRIMARY KEY NOT NULL, moderation_id INTEGER PRIMARY KEY,
timestamp INT NOT NULL, timestamp INTEGER NOT NULL,
moderation_type LONGTEXT NOT NULL, moderation_type TEXT NOT NULL,
target_type LONGTEXT NOT NULL, target_type TEXT NOT NULL,
target_id LONGTEXT NOT NULL, target_id TEXT NOT NULL,
moderator_id LONGTEXT NOT NULL, moderator_id TEXT NOT NULL,
role_id LONGTEXT, role_id TEXT,
duration LONGTEXT, duration TEXT,
end_timestamp INT, end_timestamp INTEGER,
reason LONGTEXT, reason TEXT,
resolved BOOL NOT NULL, resolved INTEGER NOT NULL,
resolved_by LONGTEXT, resolved_by TEXT,
resolve_reason LONGTEXT, resolve_reason TEXT,
expired BOOL NOT NULL, expired INTEGER NOT NULL,
changes JSON NOT NULL, changes TEXT NOT NULL,
metadata JSON NOT NULL metadata TEXT NOT NULL
) )
""" """
cursor.execute(query) cursor.execute(query)
index_query_1 = "CREATE INDEX idx_target_id ON moderation_%s(target_id(25));" index_query_1 = "CREATE INDEX idx_target_id ON moderation_{}(target_id);"
cursor.execute(index_query_1, (guild.id,)) cursor.execute(index_query_1.format(guild.id))
index_query_2 = (
"CREATE INDEX idx_moderator_id ON moderation_%s(moderator_id(25));" index_query_2 = "CREATE INDEX idx_moderator_id ON moderation_{}(moderator_id);"
) cursor.execute(index_query_2.format(guild.id))
cursor.execute(index_query_2, (guild.id,))
index_query_3 = ( index_query_3 = "CREATE INDEX idx_moderation_id ON moderation_{}(moderation_id);"
"CREATE INDEX idx_moderation_id ON moderation_%s(moderation_id);" cursor.execute(index_query_3.format(guild.id))
)
cursor.execute(index_query_3, (guild.id,))
insert_query = f""" insert_query = f"""
INSERT INTO `moderation_{guild.id}` INSERT INTO `moderation_{guild.id}`
(moderation_id, timestamp, moderation_type, target_type, target_id, moderator_id, role_id, duration, end_timestamp, reason, resolved, resolved_by, resolve_reason, expired, changes, metadata) (moderation_id, timestamp, moderation_type, target_type, target_id, moderator_id, role_id, duration, end_timestamp, reason, resolved, resolved_by, resolve_reason, expired, changes, metadata)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""" """
insert_values = ( insert_values = (
0, 0,
@ -108,7 +93,7 @@ async def create_guild_table(guild: Guild):
database.commit() database.commit()
logger.debug( logger.debug(
"MySQL Table (moderation_%s) created for %s (%s)", "SQLite Table (moderation_%s) created for %s (%s)",
guild.id, guild.id,
guild.name, guild.name,
guild.id, guild.id,
@ -117,6 +102,7 @@ async def create_guild_table(guild: Guild):
database.close() database.close()
# pylint: disable=dangerous-default-value
async def mysql_log( async def mysql_log(
guild_id: str, guild_id: str,
author_id: str, author_id: str,
@ -124,9 +110,9 @@ async def mysql_log(
target_type: str, target_type: str,
target_id: int, target_id: int,
role_id: int, role_id: int,
duration, duration: timedelta,
reason: str, reason: str,
database: mysql.connector.MySQLConnection = None, database: sqlite3.Connection = None,
timestamp: int = None, timestamp: int = None,
resolved: bool = False, resolved: bool = False,
resolved_by: str = None, resolved_by: str = None,
@ -134,13 +120,19 @@ async def mysql_log(
expired: bool = None, expired: bool = None,
changes: list = [], changes: list = [],
metadata: dict = {}, metadata: dict = {},
): # pylint: disable=dangerous-default-value ) -> int:
if not timestamp: if not timestamp:
timestamp = int(time.time()) timestamp = int(time.time())
if duration != "NULL": if duration != "NULL":
end_timedelta = datetime.fromtimestamp(timestamp) + duration end_timedelta = datetime.fromtimestamp(timestamp) + duration
end_timestamp = int(end_timedelta.timestamp()) end_timestamp = int(end_timedelta.timestamp())
total_seconds = int(duration.total_seconds())
hours = total_seconds // 3600
minutes = (total_seconds % 3600) // 60
seconds = total_seconds % 60
duration = f"{hours}:{minutes}:{seconds}"
else: else:
end_timestamp = 0 end_timestamp = 0
@ -157,7 +149,7 @@ async def mysql_log(
resolved_reason = "NULL" resolved_reason = "NULL"
if not database: if not database:
database = await connect() database = connect()
close_db = True close_db = True
else: else:
close_db = False close_db = False
@ -165,7 +157,7 @@ async def mysql_log(
moderation_id = await get_next_case_number(guild_id=guild_id, cursor=cursor) moderation_id = await get_next_case_number(guild_id=guild_id, cursor=cursor)
sql = f"INSERT INTO `moderation_{guild_id}` (moderation_id, timestamp, moderation_type, target_type, target_id, moderator_id, role_id, duration, end_timestamp, reason, resolved, resolved_by, resolve_reason, expired, changes, metadata) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" sql = f"INSERT INTO `moderation_{guild_id}` (moderation_id, timestamp, moderation_type, target_type, target_id, moderator_id, role_id, duration, end_timestamp, reason, resolved, resolved_by, resolve_reason, expired, changes, metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
val = ( val = (
moderation_id, moderation_id,
timestamp, timestamp,
@ -192,7 +184,7 @@ async def mysql_log(
database.close() database.close()
logger.debug( logger.debug(
"MySQL row inserted into moderation_%s!\n%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s", "Row inserted into moderation_%s!\n%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s",
guild_id, guild_id,
moderation_id, moderation_id,
timestamp, timestamp,
@ -215,13 +207,13 @@ async def mysql_log(
return moderation_id return moderation_id
async def fetch_case(moderation_id: int, guild_id: str): async def fetch_case(moderation_id: int, guild_id: str) -> dict:
"""This method fetches a case from the database and returns the case's dictionary.""" """This method fetches a case from the database and returns the case's dictionary."""
database = await connect() database = connect()
cursor = database.cursor() cursor = database.cursor()
query = "SELECT * FROM moderation_%s WHERE moderation_id = %s;" query = f"SELECT * FROM moderation_{guild_id} WHERE moderation_id = ?;"
cursor.execute(query, (guild_id, moderation_id)) cursor.execute(query, (moderation_id,))
result = cursor.fetchone() result = cursor.fetchone()
cursor.close() cursor.close()

View file

@ -0,0 +1,3 @@
import logging
logger = logging.getLogger("red.sea.aurora")

View file

@ -10,17 +10,6 @@ from redbot.core import commands
from .config import config from .config import config
async def check_conf(config_list: list):
"""Checks if any required config options are not set."""
not_found_list = []
for item in config_list:
if await config.item() == " ":
not_found_list.append(item)
return not_found_list
def check_permissions( def check_permissions(
user: User, user: User,
permissions: list, permissions: list,
@ -116,7 +105,7 @@ async def get_next_case_number(guild_id: str, cursor=None):
from .database import connect from .database import connect
if not cursor: if not cursor:
database = await connect() database = connect()
cursor = database.cursor() cursor = database.cursor()
cursor.execute( cursor.execute(
f"SELECT moderation_id FROM `moderation_{guild_id}` ORDER BY moderation_id DESC LIMIT 1" f"SELECT moderation_id FROM `moderation_{guild_id}` ORDER BY moderation_id DESC LIMIT 1"

9
info.json Normal file
View file

@ -0,0 +1,9 @@
{
"author": [
"SeaswimmerTheFsh (seasw.)"
],
"install_msg": "Thanks for installing my repo!\n\nIf you have any issues with any of the cogs, please create an issue [here](https://coastalcommits.com/SeaswimmerTheFsh/SeaCogs/issues) or join my [Discord Server](https://discord.gg/eMUMe77Yb8 ).",
"name": "SeaCogs",
"short": "Various cogs for Red, by SeaswimmerTheFsh (seasw.)",
"description": "Various cogs for Red, by SeaswimmerTheFsh (seasw.)"
}

View file

@ -1,5 +0,0 @@
from .moderation import Moderation
async def setup(bot):
await bot.add_cog(Moderation(bot))

View file

@ -1,11 +0,0 @@
{
"author" : ["SeaswimmerTheFsh"],
"install_msg" : "Thank you for installing Moderation!\nYou can find the source code of this cog [here](https://coastalcommits.com/SeaswimmerTheFsh/SeaCogs).\nThis cog currently requires a MySQL database to function, instructions on how to set this up can be found [here]()",
"name" : "Moderation",
"short" : "Implements a variety of moderation commands",
"description" : "Implements a variety of moderation commands, including a warning system, a mute system, and a ban system.",
"end_user_data_statement" : "This cog stores the following information:\n- User IDs of accounts who moderate users or are moderated\n- Guild IDs of guilds with the cog enabled\n- Timestamps of moderations\n- Other information relating to moderations",
"requirements": ["mysql-connector-python", "humanize", "pytimeparse2"],
"hidden": false,
"disabled": false
}

View file

@ -1,3 +0,0 @@
import logging
logger = logging.getLogger("red.sea.moderation")