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
14 changed files with 63 additions and 183 deletions
Showing only changes of commit ca50deedd5 - Show all commits

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,19 +8,19 @@
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
@ -28,7 +28,7 @@ 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_conf, 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,7 +569,7 @@ 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) cursor = database.cursor(dictionary=True)
@ -714,13 +691,8 @@ 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 = "SELECT * FROM moderation_%s WHERE moderation_id = %s;"
cursor.execute(query_1, (interaction.guild.id, case)) cursor.execute(query_1, (interaction.guild.id, case))
@ -782,9 +754,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 = %s, resolved_by = %s, resolve_reason = %s WHERE moderation_id = %s"
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 = %s, resolved_by = %s, resolve_reason = %s WHERE moderation_id = %s"
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()
@ -941,7 +913,7 @@ class Moderation(commands.Cog):
} }
) )
database = await connect() database = connect()
cursor = database.cursor() cursor = database.cursor()
db = await config.mysql_database() db = await config.mysql_database()
@ -970,7 +942,7 @@ class Moderation(commands.Cog):
if conf: if conf:
raise(LookupError) raise(LookupError)
database = await connect() database = connect()
cursor = database.cursor() cursor = database.cursor()
db = await config.mysql_database() db = await config.mysql_database()
@ -983,7 +955,7 @@ class Moderation(commands.Cog):
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.ProgrammingError:
continue continue
target_ids = [row[0] for row in result] target_ids = [row[0] for row in result]
@ -1010,8 +982,7 @@ class Moderation(commands.Cog):
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.ProgrammingError:
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 +1018,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 +1294,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};"
@ -67,8 +67,9 @@ class ImportModerationView(ui.View):
metadata = {} metadata = {}
else: else:
metadata: Dict[str, any] = case["metadata"] metadata: Dict[str, any] = 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":

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};"

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,51 +2,39 @@
import json import json
import time import time
import sqlite3
from os import sep
from datetime import datetime from datetime import datetime
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 check_conf, 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='Moderation') + sep + 'moderation.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.ProgrammingError 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.ProgrammingError:
query = f""" query = f"""
CREATE TABLE `moderation_{guild.id}` ( CREATE TABLE `moderation_{guild.id}` (
moderation_id INT UNIQUE PRIMARY KEY NOT NULL, moderation_id INT UNIQUE PRIMARY KEY NOT NULL,
@ -71,10 +59,12 @@ async def create_guild_table(guild: Guild):
index_query_1 = "CREATE INDEX idx_target_id ON moderation_%s(target_id(25));" index_query_1 = "CREATE INDEX idx_target_id ON moderation_%s(target_id(25));"
cursor.execute(index_query_1, (guild.id,)) cursor.execute(index_query_1, (guild.id,))
index_query_2 = ( index_query_2 = (
"CREATE INDEX idx_moderator_id ON moderation_%s(moderator_id(25));" "CREATE INDEX idx_moderator_id ON moderation_%s(moderator_id(25));"
) )
cursor.execute(index_query_2, (guild.id,)) cursor.execute(index_query_2, (guild.id,))
index_query_3 = ( index_query_3 = (
"CREATE INDEX idx_moderation_id ON moderation_%s(moderation_id);" "CREATE INDEX idx_moderation_id ON moderation_%s(moderation_id);"
) )
@ -108,7 +98,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,
@ -126,7 +116,7 @@ async def mysql_log(
role_id: int, role_id: int,
duration, duration,
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,7 +124,7 @@ async def mysql_log(
expired: bool = None, expired: bool = None,
changes: list = [], changes: list = [],
metadata: dict = {}, metadata: dict = {},
): # pylint: disable=dangerous-default-value ) -> int: # pylint: disable=dangerous-default-value
if not timestamp: if not timestamp:
timestamp = int(time.time()) timestamp = int(time.time())
@ -157,7 +147,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
@ -192,7 +182,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,9 +205,9 @@ 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 = "SELECT * FROM moderation_%s WHERE moderation_id = %s;"

View file

@ -116,7 +116,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"

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
}