WIP: Refactor Aurora (3.0.0) #29
6 changed files with 122 additions and 149 deletions
|
@ -19,7 +19,7 @@ from redbot.core import app_commands, commands, data_manager
|
|||
from redbot.core.app_commands import Choice
|
||||
from redbot.core.bot import Red
|
||||
from redbot.core.commands.converter import parse_relativedelta, parse_timedelta
|
||||
from redbot.core.utils.chat_formatting import box, error, humanize_list, humanize_timedelta, warning
|
||||
from redbot.core.utils.chat_formatting import bold, box, error, humanize_list, humanize_timedelta, warning
|
||||
|
||||
from .importers.aurora import ImportAuroraView
|
||||
from .importers.galacticbot import ImportGalacticBotView
|
||||
|
@ -30,11 +30,10 @@ from .menus.overrides import Overrides
|
|||
from .models.change import Change
|
||||
from .models.moderation import Moderation
|
||||
from .utilities.config import config, register_config
|
||||
from .utilities.database import connect, create_guild_table
|
||||
from .utilities.factory import addrole_embed, case_factory, changes_factory, evidenceformat_factory, guild_embed, immune_embed, message_factory, overrides_embed
|
||||
from .utilities.json import dump
|
||||
from .utilities.logger import logger
|
||||
from .utilities.utils import check_moddable, check_permissions, get_footer_image, log, send_evidenceformat, timedelta_from_relativedelta
|
||||
from .utilities.utils import check_moddable, check_permissions, create_guild_table, get_footer_image, log, send_evidenceformat, timedelta_from_relativedelta
|
||||
|
||||
|
||||
class Aurora(commands.Cog):
|
||||
|
@ -43,28 +42,22 @@ class Aurora(commands.Cog):
|
|||
This cog stores all of its data in an SQLite database."""
|
||||
|
||||
__author__ = ["SeaswimmerTheFsh"]
|
||||
__version__ = "2.2.0"
|
||||
__version__ = "2.3.0"
|
||||
__documentation__ = "https://seacogs.coastalcommits.com/aurora/"
|
||||
|
||||
async def red_delete_data_for_user(self, *, requester, user_id: int):
|
||||
if requester == "discord_deleted_user":
|
||||
await config.user_from_id(user_id).clear()
|
||||
|
||||
database = await connect()
|
||||
cursor = await database.cursor()
|
||||
|
||||
await cursor.execute("SHOW TABLES;")
|
||||
tables = [table[0] for table in cursor.fetchall()]
|
||||
results = await Moderation.execute(query="SHOW TABLES;", return_obj=False)
|
||||
tables = [table[0] for table in results]
|
||||
|
||||
condition = "target_id = %s OR moderator_id = %s;"
|
||||
|
||||
for table in tables:
|
||||
delete_query = f"DELETE FROM {table[0]} WHERE {condition}"
|
||||
await cursor.execute(delete_query, (user_id, user_id))
|
||||
await Moderation.execute(query=delete_query, parameters=(user_id, user_id), return_obj=False)
|
||||
|
||||
await database.commit()
|
||||
await cursor.close()
|
||||
await database.close()
|
||||
if requester == "owner":
|
||||
await config.user_from_id(user_id).clear()
|
||||
if requester == "user":
|
||||
|
@ -86,16 +79,16 @@ class Aurora(commands.Cog):
|
|||
# and the information that aiosqlite logs is not useful to the bot owner.
|
||||
# This is a bad solution though as it overrides it for any other cogs that are using aiosqlite too.
|
||||
# If there's a better solution that you're aware of, please let me know in Discord or in a CoastalCommits issue.
|
||||
py_logging.getLogger('aiosqlite').setLevel(py_logging.WARNING)
|
||||
py_logging.getLogger('aiosqlite').setLevel(py_logging.INFO)
|
||||
|
||||
def format_help_for_context(self, ctx: commands.Context) -> str:
|
||||
pre_processed = super().format_help_for_context(ctx) or ""
|
||||
n = "\n" if "\n\n" not in pre_processed else ""
|
||||
text = [
|
||||
f"{pre_processed}{n}",
|
||||
f"Cog Version: **{self.__version__}**",
|
||||
f"Author: {humanize_list(self.__author__)}",
|
||||
f"Documentation: {self.__documentation__}",
|
||||
f"{bold('Cog Version:')} {self.__version__}",
|
||||
f"{bold('Author:')} {humanize_list(self.__author__)}",
|
||||
f"{bold('Documentation:')} {self.__documentation__}",
|
||||
]
|
||||
return "\n".join(text)
|
||||
|
||||
|
@ -127,13 +120,12 @@ class Aurora(commands.Cog):
|
|||
async def addrole_on_member_join(self, member: discord.Member):
|
||||
"""This method automatically adds roles to users when they join the server."""
|
||||
if not await self.bot.cog_disabled_in_guild(self, member.guild):
|
||||
database = await connect()
|
||||
query = f"""SELECT moderation_id, role_id, reason FROM moderation_{member.guild.id} WHERE target_id = ? AND moderation_type = 'ADDROLE' AND expired = 0 AND resolved = 0;"""
|
||||
async with database.execute(query, (member.id,)) as cursor:
|
||||
async for row in cursor:
|
||||
role = member.guild.get_role(row[1])
|
||||
reason = row[2]
|
||||
await member.add_roles(role, reason=f"Role automatically added on member rejoin for: {reason} (Case #{row[0]:,})")
|
||||
results = Moderation.execute(query, (member.id,))
|
||||
for row in results:
|
||||
role = member.guild.get_role(row[1])
|
||||
reason = row[2]
|
||||
await member.add_roles(role, reason=f"Role automatically added on member rejoin for: {reason} (Case #{row[0]:,})")
|
||||
|
||||
@commands.Cog.listener("on_audit_log_entry_create")
|
||||
async def autologger(self, entry: discord.AuditLogEntry):
|
||||
|
@ -1093,7 +1085,7 @@ class Aurora(commands.Cog):
|
|||
)
|
||||
return
|
||||
|
||||
database = await connect()
|
||||
database = await Moderation.connect()
|
||||
|
||||
if export:
|
||||
try:
|
||||
|
@ -1268,7 +1260,7 @@ class Aurora(commands.Cog):
|
|||
ephemeral: bool | None = None,
|
||||
evidenceformat: bool = False,
|
||||
changes: bool = False,
|
||||
export: Choice[str] | None = None,
|
||||
raw: Choice[str] | None = None,
|
||||
):
|
||||
"""Check the details of a specific case.
|
||||
|
||||
|
@ -1278,9 +1270,11 @@ class Aurora(commands.Cog):
|
|||
What case are you looking up?
|
||||
ephemeral: bool
|
||||
Hide the command response
|
||||
evidenceformat: bool
|
||||
Display the evidence format of the case
|
||||
changes: bool
|
||||
List the changes made to the case
|
||||
export: bool
|
||||
raw: bool
|
||||
Export the case to a JSON file or codeblock"""
|
||||
permissions = check_permissions(
|
||||
interaction.client.user, ["embed_links"], interaction
|
||||
|
@ -1309,8 +1303,8 @@ class Aurora(commands.Cog):
|
|||
)
|
||||
return
|
||||
|
||||
if export:
|
||||
if export.value == "file" or len(mod.to_json(2)) > 1800:
|
||||
if raw:
|
||||
if raw.value == "file" or len(mod.to_json(2)) > 1800:
|
||||
filename = (
|
||||
str(data_manager.cog_data_path(cog_instance=self))
|
||||
+ str(os.sep)
|
||||
|
@ -1319,7 +1313,7 @@ class Aurora(commands.Cog):
|
|||
|
||||
with open(filename, "w", encoding="utf-8") as f:
|
||||
mod.to_json(2, f)
|
||||
if export.value == "codeblock":
|
||||
if raw.value == "codeblock":
|
||||
content = f"Case #{case:,} exported.\n" + warning(
|
||||
"Case was too large to export as codeblock, so it has been uploaded as a `.json` file."
|
||||
)
|
||||
|
|
|
@ -9,9 +9,8 @@ from redbot.core import commands, data_manager
|
|||
from redbot.core.utils.chat_formatting import warning
|
||||
|
||||
from ..models.moderation import Moderation
|
||||
from ..utilities.database import connect, create_guild_table
|
||||
from ..utilities.json import dump
|
||||
from ..utilities.utils import timedelta_from_string
|
||||
from ..utilities.utils import create_guild_table, timedelta_from_string
|
||||
|
||||
|
||||
class ImportAuroraView(ui.View):
|
||||
|
@ -29,10 +28,8 @@ class ImportAuroraView(ui.View):
|
|||
"Deleting original table...", ephemeral=True
|
||||
)
|
||||
|
||||
database = await connect()
|
||||
query = f"DROP TABLE IF EXISTS moderation_{self.ctx.guild.id};"
|
||||
await database.execute(query)
|
||||
await database.commit()
|
||||
await Moderation.execute(query=query, return_obj=False)
|
||||
|
||||
await interaction.edit_original_response(content="Creating new table...")
|
||||
|
||||
|
@ -111,15 +108,11 @@ class ImportAuroraView(ui.View):
|
|||
expired=case["expired"],
|
||||
changes=changes,
|
||||
metadata=metadata,
|
||||
database=database,
|
||||
return_obj=False
|
||||
)
|
||||
except Exception as e: # pylint: disable=broad-exception-caught
|
||||
failed_cases.append(str(case["moderation_id"]) + f": {e}")
|
||||
|
||||
await database.commit()
|
||||
await database.close()
|
||||
|
||||
await interaction.edit_original_response(content="Import complete.")
|
||||
if failed_cases:
|
||||
filename = (
|
||||
|
|
|
@ -8,13 +8,14 @@ from redbot.core import commands
|
|||
from redbot.core.utils.chat_formatting import box, warning
|
||||
|
||||
from ..models.moderation import Change, Moderation
|
||||
from ..utilities.database import connect, create_guild_table
|
||||
from ..utilities.database import create_guild_table
|
||||
|
||||
|
||||
class ImportGalacticBotView(ui.View):
|
||||
def __init__(self, timeout, ctx, message):
|
||||
super().__init__()
|
||||
self.ctx: commands.Context = ctx
|
||||
self.timeout = timeout
|
||||
self.message: Message = message
|
||||
|
||||
@ui.button(label="Yes", style=ButtonStyle.success)
|
||||
|
@ -26,7 +27,7 @@ class ImportGalacticBotView(ui.View):
|
|||
"Deleting original table...", ephemeral=True
|
||||
)
|
||||
|
||||
database = await connect()
|
||||
database = await Moderation.connect()
|
||||
cursor = await database.cursor()
|
||||
|
||||
query = f"DROP TABLE IF EXISTS moderation_{self.ctx.guild.id};"
|
||||
|
|
|
@ -5,8 +5,10 @@ from time import time
|
|||
from typing import Dict, Iterable, List, Optional, Tuple, Union
|
||||
|
||||
import discord
|
||||
from aiosqlite import Cursor
|
||||
from aiosqlite import Connection, Cursor, OperationalError, Row
|
||||
from aiosqlite import connect as aiosqlite_connect
|
||||
from discord import NotFound
|
||||
from redbot.core import data_manager
|
||||
from redbot.core.bot import Red
|
||||
|
||||
from ..utilities.logger import logger
|
||||
|
@ -211,34 +213,48 @@ class Moderation(AuroraGuildModel):
|
|||
}
|
||||
return cls.from_dict(bot=bot, data=case)
|
||||
|
||||
@staticmethod
|
||||
async def connect() -> Connection:
|
||||
"""Connects to the SQLite database, and returns a connection object."""
|
||||
try:
|
||||
connection = await aiosqlite_connect(
|
||||
database=data_manager.cog_data_path(raw_name="Aurora") / "aurora.db"
|
||||
)
|
||||
return connection
|
||||
|
||||
except OperationalError as e:
|
||||
logger.error("Unable to access the SQLite database!\nError:\n%s", e.msg)
|
||||
raise ConnectionRefusedError(
|
||||
f"Unable to access the SQLite Database!\n{e.msg}"
|
||||
) from e
|
||||
|
||||
@classmethod
|
||||
async def execute(cls, bot: Red, guild_id: int, query: str, parameters: tuple | None = None, cursor: Cursor | None = None, return_obj: bool = True) -> Tuple["Moderation"]:
|
||||
from ..utilities.database import connect
|
||||
logger.trace("Executing query: %s", query)
|
||||
logger.trace("With parameters: %s", parameters)
|
||||
async def execute(cls, query: str, parameters: tuple | None = None, bot: Red | None = None, guild_id: int | None = None, cursor: Cursor | None = None, return_obj: bool = True) -> Union[Tuple["Moderation"], Iterable[Row]]:
|
||||
logger.trace("Executing query: \"%s\" with parameters \"%s\"", query, parameters)
|
||||
if not parameters:
|
||||
parameters = ()
|
||||
if not cursor:
|
||||
no_cursor = True
|
||||
database = await connect()
|
||||
database = await cls.connect()
|
||||
cursor = await database.cursor()
|
||||
else:
|
||||
no_cursor = False
|
||||
|
||||
await cursor.execute(query, parameters)
|
||||
results = await cursor.fetchall()
|
||||
await database.commit()
|
||||
if no_cursor:
|
||||
await cursor.close()
|
||||
await database.close()
|
||||
|
||||
if results and return_obj:
|
||||
if results and return_obj and bot and guild_id:
|
||||
cases = []
|
||||
for result in results:
|
||||
case = cls.from_result(bot=bot, result=result, guild_id=guild_id)
|
||||
if case.moderation_id != 0:
|
||||
cases.append(case)
|
||||
return tuple(cases)
|
||||
return ()
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
async def get_latest(cls, bot: Red, guild_id: int, limit: int | None = None, offset: int = 0, types: Iterable | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]:
|
||||
|
@ -306,7 +322,6 @@ class Moderation(AuroraGuildModel):
|
|||
metadata: dict | None = None,
|
||||
return_obj: bool = True,
|
||||
) -> Union["Moderation", int]:
|
||||
from ..utilities.database import connect
|
||||
from ..utilities.json import dumps
|
||||
if not timestamp:
|
||||
timestamp = datetime.fromtimestamp(time())
|
||||
|
@ -341,7 +356,7 @@ class Moderation(AuroraGuildModel):
|
|||
role_id = None
|
||||
|
||||
if not database:
|
||||
database = await connect()
|
||||
database = await cls.connect()
|
||||
close_db = True
|
||||
else:
|
||||
close_db = False
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
# pylint: disable=cyclic-import
|
||||
import json
|
||||
|
||||
import aiosqlite
|
||||
from discord import Guild
|
||||
from redbot.core import data_manager
|
||||
|
||||
from .logger import logger
|
||||
|
||||
|
||||
async def connect() -> aiosqlite.Connection:
|
||||
"""Connects to the SQLite database, and returns a connection object."""
|
||||
try:
|
||||
connection = await aiosqlite.connect(
|
||||
database=data_manager.cog_data_path(raw_name="Aurora") / "aurora.db"
|
||||
)
|
||||
return connection
|
||||
|
||||
except aiosqlite.OperationalError as e:
|
||||
logger.error("Unable to access the SQLite database!\nError:\n%s", e.msg)
|
||||
raise ConnectionRefusedError(
|
||||
f"Unable to access the SQLite Database!\n{e.msg}"
|
||||
) from e
|
||||
|
||||
|
||||
async def create_guild_table(guild: Guild):
|
||||
database = await connect()
|
||||
|
||||
try:
|
||||
await database.execute(f"SELECT * FROM `moderation_{guild.id}`")
|
||||
logger.verbose("SQLite Table exists for server %s (%s)", guild.name, guild.id)
|
||||
|
||||
except aiosqlite.OperationalError:
|
||||
query = f"""
|
||||
CREATE TABLE `moderation_{guild.id}` (
|
||||
moderation_id INTEGER PRIMARY KEY,
|
||||
timestamp INTEGER NOT NULL,
|
||||
moderation_type TEXT NOT NULL,
|
||||
target_type TEXT NOT NULL,
|
||||
target_id INTEGER NOT NULL,
|
||||
moderator_id INTEGER NOT NULL,
|
||||
role_id INTEGER,
|
||||
duration TEXT,
|
||||
end_timestamp INTEGER,
|
||||
reason TEXT,
|
||||
resolved INTEGER NOT NULL,
|
||||
resolved_by TEXT,
|
||||
resolve_reason TEXT,
|
||||
expired INTEGER NOT NULL,
|
||||
changes JSON NOT NULL,
|
||||
metadata JSON NOT NULL
|
||||
)
|
||||
"""
|
||||
await database.execute(query)
|
||||
|
||||
index_query_1 = f"CREATE INDEX IF NOT EXISTS idx_target_id ON moderation_{guild.id}(target_id);"
|
||||
await database.execute(index_query_1)
|
||||
|
||||
index_query_2 = f"CREATE INDEX IF NOT EXISTS idx_moderator_id ON moderation_{guild.id}(moderator_id);"
|
||||
await database.execute(index_query_2)
|
||||
|
||||
index_query_3 = f"CREATE INDEX IF NOT EXISTS idx_moderation_id ON moderation_{guild.id}(moderation_id);"
|
||||
await database.execute(index_query_3)
|
||||
|
||||
insert_query = 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
"""
|
||||
insert_values = (
|
||||
0,
|
||||
0,
|
||||
"NULL",
|
||||
"NULL",
|
||||
0,
|
||||
0,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
json.dumps([]),
|
||||
json.dumps({}),
|
||||
)
|
||||
await database.execute(insert_query, insert_values)
|
||||
|
||||
await database.commit()
|
||||
|
||||
logger.debug(
|
||||
"SQLite Table (moderation_%s) created for %s (%s)",
|
||||
guild.id,
|
||||
guild.name,
|
||||
guild.id,
|
||||
)
|
||||
|
||||
await database.close()
|
|
@ -2,6 +2,7 @@
|
|||
from datetime import datetime, timedelta
|
||||
from typing import Optional, Tuple, Union
|
||||
|
||||
import aiosqlite
|
||||
from dateutil.relativedelta import relativedelta as rd
|
||||
from discord import File, Guild, Interaction, Member, SelectOption, TextChannel, User
|
||||
from discord.errors import Forbidden
|
||||
|
@ -9,6 +10,8 @@ from redbot.core import commands, data_manager
|
|||
from redbot.core.utils.chat_formatting import error
|
||||
|
||||
from ..utilities.config import config
|
||||
from ..utilities.json import dumps
|
||||
from ..utilities.logger import logger
|
||||
|
||||
|
||||
def check_permissions(
|
||||
|
@ -210,3 +213,69 @@ def get_footer_image(coginstance: commands.Cog) -> File:
|
|||
"""Returns the footer image for the embeds."""
|
||||
image_path = data_manager.bundled_data_path(coginstance) / "arrow.png"
|
||||
return File(image_path, filename="arrow.png", description="arrow")
|
||||
|
||||
async def create_guild_table(guild: Guild) -> None:
|
||||
from ..models.moderation import Moderation
|
||||
|
||||
try:
|
||||
await Moderation.execute(f"SELECT * FROM `moderation_{guild.id}`", return_obj=False)
|
||||
logger.trace("SQLite Table exists for server %s (%s)", guild.name, guild.id)
|
||||
|
||||
except aiosqlite.OperationalError:
|
||||
query = f"""
|
||||
CREATE TABLE `moderation_{guild.id}` (
|
||||
moderation_id INTEGER PRIMARY KEY,
|
||||
timestamp INTEGER NOT NULL,
|
||||
moderation_type TEXT NOT NULL,
|
||||
target_type TEXT NOT NULL,
|
||||
target_id INTEGER NOT NULL,
|
||||
moderator_id INTEGER NOT NULL,
|
||||
role_id INTEGER,
|
||||
duration TEXT,
|
||||
end_timestamp INTEGER,
|
||||
reason TEXT,
|
||||
resolved INTEGER NOT NULL,
|
||||
resolved_by TEXT,
|
||||
resolve_reason TEXT,
|
||||
expired INTEGER NOT NULL,
|
||||
changes JSON NOT NULL,
|
||||
metadata JSON NOT NULL
|
||||
)
|
||||
"""
|
||||
await Moderation.execute(query=query, return_obj=False)
|
||||
|
||||
index_query_1 = f"CREATE INDEX IF NOT EXISTS idx_target_id ON moderation_{guild.id}(target_id);"
|
||||
await Moderation.execute(query=index_query_1, return_obj=False)
|
||||
|
||||
index_query_2 = f"CREATE INDEX IF NOT EXISTS idx_moderator_id ON moderation_{guild.id}(moderator_id);"
|
||||
await Moderation.execute(query=index_query_2, return_obj=False)
|
||||
|
||||
index_query_3 = f"CREATE INDEX IF NOT EXISTS idx_moderation_id ON moderation_{guild.id}(moderation_id);"
|
||||
await Moderation.execute(query=index_query_3, return_obj=False)
|
||||
|
||||
insert_query = 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
"""
|
||||
insert_values = (
|
||||
0,
|
||||
0,
|
||||
"NULL",
|
||||
"NULL",
|
||||
0,
|
||||
0,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
dumps([]),
|
||||
dumps({}),
|
||||
)
|
||||
await Moderation.execute(query=insert_query, parameters=insert_values, return_obj=False)
|
||||
|
||||
logger.trace("SQLite Table created for server %s (%s)", guild.name, guild.id)
|
||||
|
|
Loading…
Reference in a new issue