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 os
import time
import sqlite3
from datetime import datetime, timedelta, timezone
from math import ceil
import discord
import humanize
import mysql.connector
from discord.ext import tasks
from pytimeparse2 import disable_dateutil, parse
from redbot.core import app_commands, checks, commands, data_manager
from redbot.core.app_commands import Choice
from .importers.galacticbot import ImportGalacticBotView
from .importers.moderation import ImportModerationView
from .importers.aurora import ImportAuroraView
from .utilities.config import config, register_config
from .utilities.database import connect, create_guild_table, fetch_case, mysql_log
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
class Moderation(commands.Cog):
class Aurora(commands.Cog):
"""Custom moderation cog.
Developed by SeaswimmerTheFsh."""
@ -36,7 +36,7 @@ class Moderation(commands.Cog):
if requester == "discord_deleted_user":
await config.user_from_id(user_id).clear()
database = await connect()
database = connect()
cursor = database.cursor()
cursor.execute("SHOW TABLES;")
@ -68,17 +68,6 @@ class Moderation(commands.Cog):
async def cog_load(self):
"""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
try:
@ -96,20 +85,8 @@ class Moderation(commands.Cog):
async def db_generate_guild_join(self, guild: discord.Guild):
"""This method prepares the database schema whenever the bot joins a 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:
await create_guild_table(guild)
except ConnectionRefusedError:
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)
return
database = await connect()
database = connect()
if export:
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)
return
conf = await check_conf(['mysql_database'])
if conf:
raise(LookupError)
database = await connect()
database = connect()
cursor = database.cursor()
db = await config.mysql_database()
query_1 = "SELECT * FROM moderation_%s WHERE moderation_id = %s;"
cursor.execute(query_1, (interaction.guild.id, case))
@ -782,9 +754,9 @@ class Moderation(commands.Cog):
except discord.NotFound:
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:
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']))
database.commit()
@ -941,7 +913,7 @@ class Moderation(commands.Cog):
}
)
database = await connect()
database = connect()
cursor = database.cursor()
db = await config.mysql_database()
@ -970,7 +942,7 @@ class Moderation(commands.Cog):
if conf:
raise(LookupError)
database = await connect()
database = connect()
cursor = database.cursor()
db = await config.mysql_database()
@ -983,7 +955,7 @@ class Moderation(commands.Cog):
try:
cursor.execute(tempban_query, (time.time(),))
result = cursor.fetchall()
except mysql.connector.errors.ProgrammingError:
except sqlite3.ProgrammingError:
continue
target_ids = [row[0] for row in result]
@ -1010,8 +982,7 @@ class Moderation(commands.Cog):
try:
cursor.execute(blacklist_query, (time.time(),))
result = cursor.fetchall()
except mysql.connector.errors.ProgrammingError:
except sqlite3.ProgrammingError:
continue
target_ids = [row[0] for row in result]
moderation_ids = [row[1] for row in result]
@ -1047,7 +1018,7 @@ class Moderation(commands.Cog):
guild_settings_string = ""
for setting in guild_settings:
if 'mysql' in setting or 'roles' in setting:
if 'roles' in setting:
continue
if setting == 'log_channel':
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 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')
@checks.admin()
async def moderationset_import(self, ctx: commands.Context):
"""Import moderations from other bots."""
@moderationset_import.command(name="moderation")
@moderationset_import.command(name="aurora")
@checks.admin()
async def moderationset_import_moderation(self, ctx: commands.Context):
"""Import moderations from another bot using this cog."""
async def moderationset_import_aurora(self, ctx: commands.Context):
"""Import moderations from another bot using Aurora."""
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.*")
await message.edit(view=ImportModerationView(60, ctx, message))
await message.edit(view=ImportAuroraView(60, ctx, message))
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")
@checks.admin()

View file

@ -8,7 +8,7 @@ from redbot.core import commands
from ..utilities.database import connect, create_guild_table, mysql_log
class ImportModerationView(ui.View):
class ImportAuroraView(ui.View):
def __init__(self, timeout, ctx, message):
super().__init__()
self.ctx: commands.Context = ctx
@ -23,7 +23,7 @@ class ImportModerationView(ui.View):
"Deleting original table...", ephemeral=True
)
database = await connect()
database = connect()
cursor = database.cursor()
query = f"DROP TABLE IF EXISTS moderation_{self.ctx.guild.id};"
@ -67,8 +67,9 @@ class ImportModerationView(ui.View):
metadata = {}
else:
metadata: Dict[str, any] = case["metadata"]
if not metadata['imported_from']:
metadata.update({
'imported_from': 'SeaCogs/Moderation'
'imported_from': 'Aurora'
})
if case["duration"] != "NULL":

View file

@ -18,7 +18,7 @@ class ImportGalacticBotView(ui.View):
await self.message.delete()
await interaction.response.send_message("Deleting original table...", ephemeral=True)
database = await connect()
database = connect()
cursor = database.cursor()
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")
def register_config(config_obj: Config):
config_obj.register_global(
mysql_address= " ",
mysql_database = " ",
mysql_username = " ",
mysql_password = " "
)
config_obj.register_guild(
use_discord_permissions = True,
ignore_other_bots = True,

View file

@ -2,51 +2,39 @@
import json
import time
import sqlite3
from os import sep
from datetime import datetime
import mysql.connector
from discord import Guild
from redbot.core import data_manager
from .config import config
from .logger import logger
from .utils import check_conf, generate_dict, get_next_case_number
async def connect():
"""Connects to the MySQL 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!")
def connect() -> sqlite3.Connection:
"""Connects to the SQLite database, and returns a connection object."""
try:
connection = mysql.connector.connect(
host=await config.mysql_address(),
user=await config.mysql_username(),
password=await config.mysql_password(),
database=await config.mysql_database(),
)
connection = sqlite3.connect(database=data_manager.cog_data_path(raw_name='Moderation') + sep + 'moderation.db')
return connection
except mysql.connector.ProgrammingError as e:
logger.error("Unable to access the MySQL database!\nError:\n%s", e.msg)
except sqlite3.ProgrammingError as e:
logger.error("Unable to access the SQLite database!\nError:\n%s", e.msg)
raise ConnectionRefusedError(
f"Unable to access the MySQL Database!\n{e.msg}"
f"Unable to access the SQLite Database!\n{e.msg}"
) from e
async def create_guild_table(guild: Guild):
database = await connect()
database = connect()
cursor = database.cursor()
try:
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"""
CREATE TABLE `moderation_{guild.id}` (
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));"
cursor.execute(index_query_1, (guild.id,))
index_query_2 = (
"CREATE INDEX idx_moderator_id ON moderation_%s(moderator_id(25));"
)
cursor.execute(index_query_2, (guild.id,))
index_query_3 = (
"CREATE INDEX idx_moderation_id ON moderation_%s(moderation_id);"
)
@ -108,7 +98,7 @@ async def create_guild_table(guild: Guild):
database.commit()
logger.debug(
"MySQL Table (moderation_%s) created for %s (%s)",
"SQLite Table (moderation_%s) created for %s (%s)",
guild.id,
guild.name,
guild.id,
@ -126,7 +116,7 @@ async def mysql_log(
role_id: int,
duration,
reason: str,
database: mysql.connector.MySQLConnection = None,
database: sqlite3.Connection = None,
timestamp: int = None,
resolved: bool = False,
resolved_by: str = None,
@ -134,7 +124,7 @@ async def mysql_log(
expired: bool = None,
changes: list = [],
metadata: dict = {},
): # pylint: disable=dangerous-default-value
) -> int: # pylint: disable=dangerous-default-value
if not timestamp:
timestamp = int(time.time())
@ -157,7 +147,7 @@ async def mysql_log(
resolved_reason = "NULL"
if not database:
database = await connect()
database = connect()
close_db = True
else:
close_db = False
@ -192,7 +182,7 @@ async def mysql_log(
database.close()
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,
moderation_id,
timestamp,
@ -215,9 +205,9 @@ async def mysql_log(
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."""
database = await connect()
database = connect()
cursor = database.cursor()
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
if not cursor:
database = await connect()
database = connect()
cursor = database.cursor()
cursor.execute(
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
}