Compare commits
27 commits
027144f35d
...
2aadf5134d
Author | SHA1 | Date | |
---|---|---|---|
2aadf5134d | |||
3b102debac | |||
c28b089edb | |||
987fc0dbf8 | |||
87942213a5 | |||
60d4afc6f3 | |||
0bcbcd6c0c | |||
f6a42b97d9 | |||
5cb61ecd65 | |||
9622e037c9 | |||
e9a64e5a39 | |||
afac274978 | |||
e988917319 | |||
42f7f9f69b | |||
d07e5ed804 | |||
df465e5ba6 | |||
76572e2281 | |||
d629f1a5a2 | |||
ca4510d3a5 | |||
3247e6fb82 | |||
67e3abf5ce | |||
d1b5346396 | |||
fe5823b637 | |||
3383e84221 | |||
56a2f96a2d | |||
eebddd6e89 | |||
5cbf4e7e47 |
12 changed files with 319 additions and 327 deletions
217
aurora/aurora.py
217
aurora/aurora.py
|
@ -6,8 +6,8 @@
|
|||
# |_____/ \___|\__,_|___/ \_/\_/ |_|_| |_| |_|_| |_| |_|\___|_|
|
||||
|
||||
import json
|
||||
import logging as py_logging
|
||||
import os
|
||||
import sqlite3
|
||||
import time
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from math import ceil
|
||||
|
@ -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 = connect()
|
||||
cursor = database.cursor()
|
||||
|
||||
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}"
|
||||
cursor.execute(delete_query, (user_id, user_id))
|
||||
await Moderation.execute(query=delete_query, parameters=(user_id, user_id), return_obj=False)
|
||||
|
||||
database.commit()
|
||||
cursor.close()
|
||||
database.close()
|
||||
if requester == "owner":
|
||||
await config.user_from_id(user_id).clear()
|
||||
if requester == "user":
|
||||
|
@ -76,20 +69,26 @@ class Aurora(commands.Cog):
|
|||
"Invalid requester passed to red_delete_data_for_user: %s", requester
|
||||
)
|
||||
|
||||
def __init__(self, bot: Red):
|
||||
def __init__(self, bot: Red) -> None:
|
||||
super().__init__()
|
||||
self.bot = bot
|
||||
register_config(config)
|
||||
self.handle_expiry.start()
|
||||
# If we don't override aiosqlite's logging level, it will spam the console with dozens of debug messages per query.
|
||||
# This is unnecessary because Aurora already logs all of its SQL queries (or at least, most of them),
|
||||
# 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.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)
|
||||
|
||||
|
@ -122,14 +121,11 @@ class Aurora(commands.Cog):
|
|||
"""This method automatically adds roles to users when they join the server."""
|
||||
if not await self.bot.cog_disabled_in_guild(self, member.guild):
|
||||
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;"""
|
||||
database = connect()
|
||||
cursor = database.cursor()
|
||||
cursor.execute(query, (member.id,))
|
||||
results = cursor.fetchall()
|
||||
for result in results:
|
||||
role = member.guild.get_role(result[1])
|
||||
reason = result[2]
|
||||
await member.add_roles(role, reason=f"Role automatically added on member rejoin for: {reason} (Case #{result[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):
|
||||
|
@ -175,7 +171,7 @@ class Aurora(commands.Cog):
|
|||
else:
|
||||
return
|
||||
|
||||
Moderation.log(
|
||||
await Moderation.log(
|
||||
self.bot,
|
||||
entry.guild.id,
|
||||
entry.user.id,
|
||||
|
@ -233,7 +229,7 @@ class Aurora(commands.Cog):
|
|||
except discord.errors.HTTPException:
|
||||
pass
|
||||
|
||||
moderation = Moderation.log(
|
||||
moderation = await Moderation.log(
|
||||
interaction.client,
|
||||
interaction.guild.id,
|
||||
interaction.user.id,
|
||||
|
@ -292,7 +288,7 @@ class Aurora(commands.Cog):
|
|||
except discord.errors.HTTPException:
|
||||
pass
|
||||
|
||||
moderation = Moderation.log(
|
||||
moderation = await Moderation.log(
|
||||
interaction.client,
|
||||
interaction.guild.id,
|
||||
interaction.user.id,
|
||||
|
@ -398,7 +394,7 @@ class Aurora(commands.Cog):
|
|||
content=f"{target.mention} has been given the {role.mention} role{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''}!\n**Reason** - `{reason}`"
|
||||
)
|
||||
|
||||
moderation = Moderation.log(
|
||||
moderation = await Moderation.log(
|
||||
interaction.client,
|
||||
interaction.guild.id,
|
||||
interaction.user.id,
|
||||
|
@ -504,7 +500,7 @@ class Aurora(commands.Cog):
|
|||
content=f"{target.mention} has had the {role.mention} role removed{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''}!\n**Reason** - `{reason}`"
|
||||
)
|
||||
|
||||
moderation = Moderation.log(
|
||||
moderation = await Moderation.log(
|
||||
interaction.client,
|
||||
interaction.guild.id,
|
||||
interaction.user.id,
|
||||
|
@ -592,7 +588,7 @@ class Aurora(commands.Cog):
|
|||
except discord.errors.HTTPException:
|
||||
pass
|
||||
|
||||
moderation = Moderation.log(
|
||||
moderation = await Moderation.log(
|
||||
interaction.client,
|
||||
interaction.guild.id,
|
||||
interaction.user.id,
|
||||
|
@ -728,7 +724,7 @@ class Aurora(commands.Cog):
|
|||
|
||||
await target.kick(reason=f"Kicked by {interaction.user.id} for: {reason}")
|
||||
|
||||
moderation = Moderation.log(
|
||||
moderation = await Moderation.log(
|
||||
interaction.client,
|
||||
interaction.guild.id,
|
||||
interaction.user.id,
|
||||
|
@ -836,7 +832,7 @@ class Aurora(commands.Cog):
|
|||
delete_message_seconds=delete_messages_seconds,
|
||||
)
|
||||
|
||||
moderation = Moderation.log(
|
||||
moderation = await Moderation.log(
|
||||
interaction.client,
|
||||
interaction.guild.id,
|
||||
interaction.user.id,
|
||||
|
@ -880,7 +876,7 @@ class Aurora(commands.Cog):
|
|||
delete_message_seconds=delete_messages_seconds,
|
||||
)
|
||||
|
||||
moderation = Moderation.log(
|
||||
moderation = await Moderation.log(
|
||||
interaction.client,
|
||||
interaction.guild.id,
|
||||
interaction.user.id,
|
||||
|
@ -957,7 +953,7 @@ class Aurora(commands.Cog):
|
|||
except discord.errors.HTTPException:
|
||||
pass
|
||||
|
||||
moderation = Moderation.log(
|
||||
moderation = await Moderation.log(
|
||||
interaction.client,
|
||||
interaction.guild.id,
|
||||
interaction.user.id,
|
||||
|
@ -1001,7 +997,7 @@ class Aurora(commands.Cog):
|
|||
await channel.edit(slowmode_delay=interval)
|
||||
await interaction.response.send_message(f"Slowmode in {channel.mention} has been set to {interval} seconds!\n**Reason** - `{reason}`")
|
||||
|
||||
moderation = Moderation.log(
|
||||
moderation = await Moderation.log(
|
||||
interaction.client,
|
||||
interaction.guild.id,
|
||||
interaction.user.id,
|
||||
|
@ -1046,7 +1042,7 @@ class Aurora(commands.Cog):
|
|||
inline: bool
|
||||
Display infractions in a grid arrangement (does not look very good)
|
||||
export: bool
|
||||
Exports the server's entire moderation history to a JSON file"""
|
||||
Exports the server's moderation history to a JSON file"""
|
||||
if ephemeral is None:
|
||||
ephemeral = (
|
||||
await config.user(interaction.user).history_ephemeral()
|
||||
|
@ -1089,47 +1085,45 @@ class Aurora(commands.Cog):
|
|||
)
|
||||
return
|
||||
|
||||
database = connect()
|
||||
if target:
|
||||
filename = f"moderation_target_{str(target.id)}_{str(interaction.guild.id)}.json"
|
||||
moderations = await Moderation.find_by_target(interaction.client, interaction.guild.id, target.id)
|
||||
elif moderator:
|
||||
filename = f"moderation_moderator_{str(moderator.id)}_{str(interaction.guild.id)}.json"
|
||||
moderations = await Moderation.find_by_moderator(interaction.client, interaction.guild.id, moderator.id)
|
||||
else:
|
||||
filename = f"moderation_{str(interaction.guild.id)}.json"
|
||||
moderations = await Moderation.get_latest(interaction.client, interaction.guild.id)
|
||||
|
||||
if export:
|
||||
try:
|
||||
filename = (
|
||||
filepath = (
|
||||
str(data_manager.cog_data_path(cog_instance=self))
|
||||
+ str(os.sep)
|
||||
+ f"moderation_{interaction.guild.id}.json"
|
||||
+ filename
|
||||
)
|
||||
|
||||
cases = Moderation.get_latest(bot=interaction.client, guild_id=interaction.guild.id)
|
||||
|
||||
with open(filename, "w", encoding="utf-8") as f:
|
||||
dump(obj=cases, fp=f, indent=2)
|
||||
with open(filepath, "w", encoding="utf-8") as f:
|
||||
dump(obj=moderations, fp=f, indent=2)
|
||||
|
||||
await interaction.followup.send(
|
||||
file=discord.File(
|
||||
filename, f"moderation_{interaction.guild.id}.json"
|
||||
fp=filepath, filename=filename
|
||||
),
|
||||
ephemeral=ephemeral,
|
||||
)
|
||||
|
||||
os.remove(filename)
|
||||
os.remove(filepath)
|
||||
except json.JSONDecodeError as e:
|
||||
await interaction.followup.send(
|
||||
content=error(
|
||||
"An error occured while exporting the moderation history.\nError:\n"
|
||||
)
|
||||
+ box(e, "py"),
|
||||
+ box(text=e, lang="py"),
|
||||
ephemeral=ephemeral,
|
||||
)
|
||||
database.close()
|
||||
return
|
||||
|
||||
if target:
|
||||
moderations = Moderation.find_by_target(interaction.client, interaction.guild.id, target.id)
|
||||
elif moderator:
|
||||
moderations = Moderation.find_by_moderator(interaction.client, interaction.guild.id, moderator.id)
|
||||
else:
|
||||
moderations = Moderation.get_latest(interaction.client, interaction.guild.id)
|
||||
|
||||
case_quantity = len(moderations)
|
||||
page_quantity = ceil(case_quantity / pagesize)
|
||||
start_index = (page - 1) * pagesize
|
||||
|
@ -1214,7 +1208,7 @@ class Aurora(commands.Cog):
|
|||
return
|
||||
|
||||
try:
|
||||
moderation = Moderation.find_by_id(interaction.client, case, interaction.guild.id)
|
||||
moderation = await Moderation.find_by_id(interaction.client, case, interaction.guild.id)
|
||||
except ValueError:
|
||||
await interaction.response.send_message(
|
||||
content=error(f"Case #{case:,} does not exist!"), ephemeral=True
|
||||
|
@ -1252,9 +1246,9 @@ class Aurora(commands.Cog):
|
|||
|
||||
@app_commands.command(name="case")
|
||||
@app_commands.choices(
|
||||
export=[
|
||||
Choice(name="Export as File", value="file"),
|
||||
raw=[
|
||||
Choice(name="Export as Codeblock", value="codeblock"),
|
||||
Choice(name="Export as File", value="file"),
|
||||
]
|
||||
)
|
||||
async def case(
|
||||
|
@ -1264,7 +1258,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.
|
||||
|
||||
|
@ -1274,9 +1268,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
|
||||
|
@ -1298,15 +1294,15 @@ class Aurora(commands.Cog):
|
|||
)
|
||||
|
||||
try:
|
||||
mod = Moderation.find_by_id(interaction.client, case, interaction.guild.id)
|
||||
mod = await Moderation.find_by_id(interaction.client, case, interaction.guild.id)
|
||||
except ValueError:
|
||||
await interaction.response.send_message(
|
||||
content=error(f"Case #{case:,} does not exist!"), ephemeral=True
|
||||
)
|
||||
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)
|
||||
|
@ -1315,7 +1311,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."
|
||||
)
|
||||
|
@ -1391,7 +1387,7 @@ class Aurora(commands.Cog):
|
|||
return
|
||||
|
||||
try:
|
||||
moderation = Moderation.find_by_id(interaction.client, case, interaction.guild.id)
|
||||
moderation = await Moderation.find_by_id(interaction.client, case, interaction.guild.id)
|
||||
old_moderation = moderation
|
||||
except ValueError:
|
||||
await interaction.response.send_message(
|
||||
|
@ -1469,7 +1465,7 @@ class Aurora(commands.Cog):
|
|||
"end_timestamp": moderation.end_timestamp,
|
||||
}))
|
||||
|
||||
moderation.update()
|
||||
await moderation.update()
|
||||
embed = await case_factory(interaction=interaction, moderation=moderation)
|
||||
|
||||
await interaction.response.send_message(
|
||||
|
@ -1485,8 +1481,6 @@ class Aurora(commands.Cog):
|
|||
async def handle_expiry(self):
|
||||
await self.bot.wait_until_red_ready()
|
||||
current_time = time.time()
|
||||
database = connect()
|
||||
cursor = database.cursor()
|
||||
global_unban_num = 0
|
||||
global_addrole_num = 0
|
||||
global_removerole_num = 0
|
||||
|
@ -1496,20 +1490,17 @@ class Aurora(commands.Cog):
|
|||
if not await self.bot.cog_disabled_in_guild(self, guild):
|
||||
time_per_guild = time.time()
|
||||
|
||||
tempban_query = f"SELECT target_id, moderation_id FROM moderation_{guild.id} WHERE end_timestamp IS NOT NULL 0 AND end_timestamp <= ? AND moderation_type = 'TEMPBAN' AND expired = 0"
|
||||
|
||||
try:
|
||||
cursor.execute(tempban_query, (time.time(),))
|
||||
result = cursor.fetchall()
|
||||
except sqlite3.OperationalError:
|
||||
continue
|
||||
|
||||
target_ids = [row[0] for row in result]
|
||||
moderation_ids = [row[1] for row in result]
|
||||
tempban_query = f"SELECT * FROM moderation_{guild.id} WHERE end_timestamp IS NOT NULL AND end_timestamp <= ? AND moderation_type = 'TEMPBAN' AND expired = 0"
|
||||
tempbans = await Moderation.execute(bot=self.bot, guild_id=guild.id, query=tempban_query, parameters=(time.time(),))
|
||||
|
||||
unban_num = 0
|
||||
for target_id, moderation_id in zip(target_ids, moderation_ids):
|
||||
user: discord.User = await self.bot.fetch_user(target_id)
|
||||
for moderation in tempbans:
|
||||
user = self.bot.get_user(moderation.target_id)
|
||||
if user is None:
|
||||
try:
|
||||
user = await self.bot.fetch_user(moderation.target_id)
|
||||
except discord.errors.NotFound:
|
||||
continue
|
||||
name = (
|
||||
f"{user.name}#{user.discriminator}"
|
||||
if user.discriminator != "0"
|
||||
|
@ -1517,14 +1508,14 @@ class Aurora(commands.Cog):
|
|||
)
|
||||
try:
|
||||
await guild.unban(
|
||||
user, reason=f"Automatic unban from case #{moderation_id}"
|
||||
user, reason=f"Automatic unban from case #{moderation.id}"
|
||||
)
|
||||
|
||||
embed = await message_factory(
|
||||
bot=self.bot,
|
||||
color=await self.bot.get_embed_color(guild.channels[0]),
|
||||
guild=guild,
|
||||
reason=f"Automatic unban from case #{moderation_id}",
|
||||
reason=f"Automatic unban from case #{moderation.id}",
|
||||
moderation_type="unbanned",
|
||||
)
|
||||
|
||||
|
@ -1533,14 +1524,14 @@ class Aurora(commands.Cog):
|
|||
except discord.errors.HTTPException:
|
||||
pass
|
||||
|
||||
logger.debug(
|
||||
logger.trace(
|
||||
"Unbanned %s (%s) from %s (%s)",
|
||||
name,
|
||||
user.id,
|
||||
guild.name,
|
||||
guild.id,
|
||||
)
|
||||
unban_num = unban_num + 1
|
||||
unban_num += 1
|
||||
except (
|
||||
discord.errors.NotFound,
|
||||
discord.errors.Forbidden,
|
||||
|
@ -1556,26 +1547,23 @@ class Aurora(commands.Cog):
|
|||
)
|
||||
|
||||
removerole_num = 0
|
||||
addrole_query = f"SELECT target_id, moderation_id, role_id FROM moderation_{guild.id} WHERE end_timestamp IS NOT NULL AND end_timestamp <= ? AND moderation_type = 'ADDROLE' AND expired = 0"
|
||||
try:
|
||||
cursor.execute(addrole_query, (time.time(),))
|
||||
result = cursor.fetchall()
|
||||
except sqlite3.OperationalError:
|
||||
continue
|
||||
target_ids = [row[0] for row in result]
|
||||
moderation_ids = [row[1] for row in result]
|
||||
role_ids = [row[2] for row in result]
|
||||
addrole_query = f"SELECT * FROM moderation_{guild.id} WHERE end_timestamp IS NOT NULL AND end_timestamp <= ? AND moderation_type = 'ADDROLE' AND expired = 0"
|
||||
addroles = await Moderation.execute(bot=self.bot, guild_id=guild.id, query=addrole_query, parameters=(time.time(),))
|
||||
|
||||
for target_id, moderation_id, role_id in zip(
|
||||
target_ids, moderation_ids, role_ids
|
||||
):
|
||||
for moderation in addroles:
|
||||
try:
|
||||
member = await guild.fetch_member(target_id)
|
||||
member = await guild.fetch_member(moderation.target_id)
|
||||
|
||||
await member.remove_roles(
|
||||
Object(role_id), reason=f"Automatic role removal from case #{moderation_id}"
|
||||
Object(moderation.role_id), reason=f"Automatic role removal from case #{moderation.id}"
|
||||
)
|
||||
|
||||
logger.trace(
|
||||
"Removed role %s from %s (%s)",
|
||||
moderation.role_id,
|
||||
member.name,
|
||||
member.id,
|
||||
)
|
||||
removerole_num = removerole_num + 1
|
||||
except (
|
||||
discord.errors.NotFound,
|
||||
|
@ -1584,44 +1572,36 @@ class Aurora(commands.Cog):
|
|||
) as e:
|
||||
logger.error(
|
||||
"Removing the role %s from user %s failed due to: \n%s",
|
||||
role_id,
|
||||
target_id,
|
||||
moderation.role_id,
|
||||
moderation.target_id,
|
||||
e,
|
||||
)
|
||||
continue
|
||||
|
||||
addrole_num = 0
|
||||
removerole_query = f"SELECT target_id, moderation_id, role_id FROM moderation_{guild.id} WHERE end_timestamp IS NOT NULL 0 AND end_timestamp <= ? AND moderation_type = 'REMOVEROLE' AND expired = 0"
|
||||
try:
|
||||
cursor.execute(removerole_query, (time.time(),))
|
||||
result = cursor.fetchall()
|
||||
except sqlite3.OperationalError:
|
||||
continue
|
||||
target_ids = [row[0] for row in result]
|
||||
moderation_ids = [row[1] for row in result]
|
||||
role_ids = [row[2] for row in result]
|
||||
removerole_query = f"SELECT * FROM moderation_{guild.id} WHERE end_timestamp IS NOT NULL AND end_timestamp <= ? AND moderation_type = 'REMOVEROLE' AND expired = 0"
|
||||
removeroles = await Moderation.execute(bot=self.bot, guild_id=guild.id, query=removerole_query, parameters=(time.time(),))
|
||||
|
||||
for target_id, moderation_id, role_id in zip(
|
||||
target_ids, moderation_ids, role_ids
|
||||
):
|
||||
for moderation in removeroles:
|
||||
try:
|
||||
member = await guild.fetch_member(target_id)
|
||||
member = await guild.fetch_member(moderation.target_id)
|
||||
|
||||
await member.add_roles(
|
||||
Object(role_id), reason=f"Automatic role addition from case #{moderation_id}"
|
||||
Object(moderation.role_id), reason=f"Automatic role addition from case #{moderation.id}"
|
||||
)
|
||||
|
||||
logger.trace("Added role %s to %s (%s)", moderation.role_id, member.name, member.id)
|
||||
addrole_num = addrole_num + 1
|
||||
except (
|
||||
discord.errors.NotFound,
|
||||
discord.errors.Forbidden,
|
||||
discord.errors.HTTPException,
|
||||
) as e:
|
||||
logger.error("Adding the role %s to user %s failed due to: \n%s", role_id, target_id, e)
|
||||
logger.error("Adding the role %s to user %s failed due to: \n%s", moderation.role_id, moderation.target_id, e)
|
||||
continue
|
||||
|
||||
expiry_query = f"UPDATE `moderation_{guild.id}` SET expired = 1 WHERE (end_timestamp IS NOT NULL AND end_timestamp <= ? AND expired = 0) OR (expired = 0 AND resolved = 1);"
|
||||
cursor.execute(expiry_query, (time.time(),))
|
||||
await Moderation.execute(bot=self.bot, guild_id=guild.id, query=expiry_query, parameters=(time.time(),), return_obj=False)
|
||||
|
||||
per_guild_completion_time = (time.time() - time_per_guild) * 1000
|
||||
logger.debug(
|
||||
|
@ -1637,9 +1617,6 @@ class Aurora(commands.Cog):
|
|||
global_addrole_num = global_addrole_num + addrole_num
|
||||
global_removerole_num = global_removerole_num + removerole_num
|
||||
|
||||
database.commit()
|
||||
cursor.close()
|
||||
database.close()
|
||||
|
||||
completion_time = (time.time() - current_time) * 1000
|
||||
logger.debug(
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
# pylint: disable=duplicate-code
|
||||
import json
|
||||
import os
|
||||
from time import time
|
||||
from typing import Dict
|
||||
|
||||
from discord import ButtonStyle, Interaction, Message, ui
|
||||
from redbot.core import commands
|
||||
from redbot.core.utils.chat_formatting import box, warning
|
||||
from discord import ButtonStyle, File, Interaction, Message, ui
|
||||
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.utils import timedelta_from_string
|
||||
from ..utilities.json import dump
|
||||
from ..utilities.utils import create_guild_table, timedelta_from_string
|
||||
|
||||
|
||||
class ImportAuroraView(ui.View):
|
||||
|
@ -27,14 +28,8 @@ class ImportAuroraView(ui.View):
|
|||
"Deleting original table...", ephemeral=True
|
||||
)
|
||||
|
||||
database = connect()
|
||||
cursor = database.cursor()
|
||||
|
||||
query = f"DROP TABLE IF EXISTS moderation_{self.ctx.guild.id};"
|
||||
cursor.execute(query)
|
||||
|
||||
cursor.close()
|
||||
database.commit()
|
||||
await Moderation.execute(query=query, return_obj=False)
|
||||
|
||||
await interaction.edit_original_response(content="Creating new table...")
|
||||
|
||||
|
@ -96,7 +91,7 @@ class ImportAuroraView(ui.View):
|
|||
duration = None
|
||||
|
||||
try:
|
||||
Moderation.log(
|
||||
await Moderation.log(
|
||||
bot=interaction.client,
|
||||
guild_id=self.ctx.guild.id,
|
||||
moderator_id=case["moderator_id"],
|
||||
|
@ -113,7 +108,6 @@ 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
|
||||
|
@ -121,12 +115,25 @@ class ImportAuroraView(ui.View):
|
|||
|
||||
await interaction.edit_original_response(content="Import complete.")
|
||||
if failed_cases:
|
||||
await interaction.edit_original_response(
|
||||
content="Import complete.\n"
|
||||
+ warning("Failed to import the following cases:\n")
|
||||
+ box(failed_cases)
|
||||
filename = (
|
||||
str(data_manager.cog_data_path(cog_instance=self))
|
||||
+ str(os.sep)
|
||||
+ f"failed_cases_{interaction.guild.id}.json"
|
||||
)
|
||||
|
||||
with open(filename, "w", encoding="utf-8") as f:
|
||||
dump(obj=failed_cases, fp=f, indent=2)
|
||||
|
||||
await interaction.channel.send(
|
||||
content="Import complete.\n"
|
||||
+ warning("Failed to import the following cases:\n"),
|
||||
file=File(
|
||||
filename, f"failed_cases_{interaction.guild.id}.json"
|
||||
)
|
||||
)
|
||||
|
||||
os.remove(filename)
|
||||
|
||||
@ui.button(label="No", style=ButtonStyle.danger)
|
||||
async def import_button_n(
|
||||
self, interaction: Interaction, button: ui.Button
|
||||
|
|
|
@ -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,14 +27,14 @@ class ImportGalacticBotView(ui.View):
|
|||
"Deleting original table...", ephemeral=True
|
||||
)
|
||||
|
||||
database = connect()
|
||||
cursor = database.cursor()
|
||||
database = await Moderation.connect()
|
||||
cursor = await database.cursor()
|
||||
|
||||
query = f"DROP TABLE IF EXISTS moderation_{self.ctx.guild.id};"
|
||||
cursor.execute(query)
|
||||
await cursor.execute(query)
|
||||
|
||||
cursor.close()
|
||||
database.commit()
|
||||
await cursor.close()
|
||||
await database.commit()
|
||||
|
||||
await interaction.edit_original_response(content="Creating new table...")
|
||||
|
||||
|
@ -124,7 +125,7 @@ class ImportGalacticBotView(ui.View):
|
|||
else:
|
||||
reason = None
|
||||
|
||||
Moderation.log(
|
||||
await Moderation.log(
|
||||
self.ctx.guild.id,
|
||||
case["executor"],
|
||||
case["type"],
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"disabled": false,
|
||||
"min_bot_version": "3.5.0",
|
||||
"min_python_version": [3, 10, 0],
|
||||
"requirements": ["pydantic"],
|
||||
"requirements": ["pydantic", "aiosqlite"],
|
||||
"tags": [
|
||||
"mod",
|
||||
"moderate",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from typing import Any
|
||||
from typing import Any, Optional
|
||||
|
||||
from discord import Guild
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from redbot.core.bot import Red
|
||||
|
||||
|
@ -12,17 +13,19 @@ class AuroraBaseModel(BaseModel):
|
|||
def dump(self) -> dict:
|
||||
return self.model_dump(exclude={"bot"})
|
||||
|
||||
def to_json(self, indent: int | None = None, file: Any | None = None, **kwargs):
|
||||
def to_json(self, indent: int | None = None, file: Any | None = None, **kwargs) -> str:
|
||||
from ..utilities.json import dump, dumps # pylint: disable=cyclic-import
|
||||
return dump(self.dump(), file, indent=indent, **kwargs) if file else dumps(self.model_dump(exclude={"bot"}), indent=indent, **kwargs)
|
||||
return dump(self.dump(), file, indent=indent, **kwargs) if file else dumps(self.dump(), indent=indent, **kwargs)
|
||||
|
||||
class AuroraGuildModel(AuroraBaseModel):
|
||||
"""Subclass of AuroraBaseModel that includes a guild_id attribute, and a modified to_json() method to match."""
|
||||
"""Subclass of AuroraBaseModel that includes a guild_id attribute and a guild attribute, and a modified to_json() method to match."""
|
||||
model_config = ConfigDict(ignored_types=(Red, Guild), arbitrary_types_allowed=True)
|
||||
guild_id: int
|
||||
guild: Optional[Guild] = None
|
||||
|
||||
def dump(self) -> dict:
|
||||
return self.model_dump(exclude={"bot", "guild_id"})
|
||||
return self.model_dump(exclude={"bot", "guild_id", "guild"})
|
||||
|
||||
def to_json(self, indent: int | None = None, file: Any | None = None, **kwargs):
|
||||
def to_json(self, indent: int | None = None, file: Any | None = None, **kwargs) -> str:
|
||||
from ..utilities.json import dump, dumps # pylint: disable=cyclic-import
|
||||
return dump(self.dump(), file, indent=indent, **kwargs) if file else dumps(self.model_dump(exclude={"bot", "guild_id"}), indent=indent, **kwargs)
|
||||
return dump(self.dump(), file, indent=indent, **kwargs) if file else dumps(self.dump(), indent=indent, **kwargs)
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import json
|
||||
import sqlite3
|
||||
from datetime import datetime, timedelta
|
||||
from sqlite3 import Cursor
|
||||
from time import time
|
||||
from typing import Dict, Iterable, List, Optional, Tuple, Union
|
||||
|
||||
import discord
|
||||
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
|
||||
|
@ -52,7 +54,7 @@ class Moderation(AuroraGuildModel):
|
|||
async def get_target(self) -> Union["PartialUser", "PartialChannel"]:
|
||||
if self.target_type == "USER":
|
||||
return await PartialUser.from_id(self.bot, self.target_id)
|
||||
return await PartialChannel.from_id(self.bot, self.target_id)
|
||||
return await PartialChannel.from_id(self.bot, self.target_id, self.guild)
|
||||
|
||||
async def get_resolved_by(self) -> Optional["PartialUser"]:
|
||||
if self.resolved_by:
|
||||
|
@ -61,7 +63,7 @@ class Moderation(AuroraGuildModel):
|
|||
|
||||
async def get_role(self) -> Optional["PartialRole"]:
|
||||
if self.role_id:
|
||||
return await PartialRole.from_id(self.bot, self.guild_id, self.role_id)
|
||||
return await PartialRole.from_id(self.bot, self.guild, self.role_id)
|
||||
return None
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
@ -115,33 +117,31 @@ class Moderation(AuroraGuildModel):
|
|||
"user_id": resolved_by,
|
||||
}))
|
||||
|
||||
self.update()
|
||||
await self.update()
|
||||
|
||||
def update(self) -> None:
|
||||
from ..utilities.database import connect
|
||||
async def update(self) -> None:
|
||||
from ..utilities.json import dumps
|
||||
query = f"UPDATE moderation_{self.guild_id} SET timestamp = ?, moderation_type = ?, target_type = ?, moderator_id = ?, role_id = ?, duration = ?, end_timestamp = ?, reason = ?, resolved = ?, resolved_by = ?, resolve_reason = ?, expired = ?, changes = ?, metadata = ? WHERE moderation_id = ?;"
|
||||
|
||||
with connect() as database:
|
||||
database.execute(query, (
|
||||
self.timestamp.timestamp(),
|
||||
self.moderation_type,
|
||||
self.target_type,
|
||||
self.moderator_id,
|
||||
self.role_id,
|
||||
str(self.duration) if self.duration else None,
|
||||
self.end_timestamp.timestamp() if self.end_timestamp else None,
|
||||
self.reason,
|
||||
self.resolved,
|
||||
self.resolved_by,
|
||||
self.resolve_reason,
|
||||
self.expired,
|
||||
dumps(self.changes),
|
||||
dumps(self.metadata),
|
||||
self.moderation_id,
|
||||
))
|
||||
await self.execute(query, (
|
||||
self.timestamp.timestamp(),
|
||||
self.moderation_type,
|
||||
self.target_type,
|
||||
self.moderator_id,
|
||||
self.role_id,
|
||||
str(self.duration) if self.duration else None,
|
||||
self.end_timestamp.timestamp() if self.end_timestamp else None,
|
||||
self.reason,
|
||||
self.resolved,
|
||||
self.resolved_by,
|
||||
self.resolve_reason,
|
||||
self.expired,
|
||||
dumps(self.changes),
|
||||
dumps(self.metadata),
|
||||
self.moderation_id,
|
||||
))
|
||||
|
||||
logger.debug("Row updated in moderation_%s!\n%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s",
|
||||
logger.verbose("Row updated in moderation_%s!\n%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s",
|
||||
self.moderation_id,
|
||||
self.guild_id,
|
||||
self.timestamp.timestamp(),
|
||||
|
@ -162,6 +162,14 @@ class Moderation(AuroraGuildModel):
|
|||
|
||||
@classmethod
|
||||
def from_dict(cls, bot: Red, data: dict) -> "Moderation":
|
||||
if data.get("guild_id"):
|
||||
try:
|
||||
guild: discord.Guild = bot.get_guild(data["guild_id"])
|
||||
if not guild:
|
||||
guild = bot.fetch_guild(data["guild_id"])
|
||||
except (discord.Forbidden, discord.HTTPException):
|
||||
guild = None
|
||||
data.update({"guild": guild})
|
||||
return cls(bot=bot, **data)
|
||||
|
||||
@classmethod
|
||||
|
@ -205,37 +213,51 @@ 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
|
||||
def execute(cls, bot: Red, guild_id: int, query: str, parameters: tuple | None = None, cursor: Cursor | None = None) -> 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 = connect()
|
||||
cursor = database.cursor()
|
||||
database = await cls.connect()
|
||||
cursor = await database.cursor()
|
||||
else:
|
||||
no_cursor = False
|
||||
|
||||
cursor.execute(query, parameters)
|
||||
results = cursor.fetchall()
|
||||
await cursor.execute(query, parameters)
|
||||
results = await cursor.fetchall()
|
||||
await database.commit()
|
||||
if no_cursor:
|
||||
cursor.close()
|
||||
database.close()
|
||||
await cursor.close()
|
||||
await database.close()
|
||||
|
||||
if results:
|
||||
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
|
||||
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"]:
|
||||
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"]:
|
||||
params = []
|
||||
query = f"SELECT * FROM moderation_{guild_id} ORDER BY moderation_id DESC"
|
||||
if types:
|
||||
|
@ -245,41 +267,41 @@ class Moderation(AuroraGuildModel):
|
|||
query += " LIMIT ? OFFSET ?"
|
||||
params.extend((limit, offset))
|
||||
query += ";"
|
||||
return cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=tuple(params) if params else (), cursor=cursor)
|
||||
return await cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=tuple(params) if params else (), cursor=cursor)
|
||||
|
||||
@classmethod
|
||||
def get_next_case_number(cls, bot: Red, guild_id: int, cursor: Cursor | None = None) -> int:
|
||||
result = cls.get_latest(bot=bot, guild_id=guild_id, cursor=cursor, limit=1)
|
||||
async def get_next_case_number(cls, bot: Red, guild_id: int, cursor: Cursor | None = None) -> int:
|
||||
result = await cls.get_latest(bot=bot, guild_id=guild_id, cursor=cursor, limit=1)
|
||||
return (result[0].moderation_id + 1) if result else 1
|
||||
|
||||
@classmethod
|
||||
def find_by_id(cls, bot: Red, moderation_id: int, guild_id: int, cursor: Cursor | None = None) -> "Moderation":
|
||||
async def find_by_id(cls, bot: Red, moderation_id: int, guild_id: int, cursor: Cursor | None = None) -> "Moderation":
|
||||
query = f"SELECT * FROM moderation_{guild_id} WHERE moderation_id = ?;"
|
||||
case = cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=(moderation_id,), cursor=cursor)
|
||||
case = await cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=(moderation_id,), cursor=cursor)
|
||||
if case:
|
||||
return case[0]
|
||||
raise ValueError(f"Case {moderation_id} not found in moderation_{guild_id}!")
|
||||
|
||||
@classmethod
|
||||
def find_by_target(cls, bot: Red, guild_id: int, target: int, types: Iterable | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]:
|
||||
async def find_by_target(cls, bot: Red, guild_id: int, target: int, types: Iterable | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]:
|
||||
query = f"SELECT * FROM moderation_{guild_id} WHERE target_id = ?"
|
||||
if types:
|
||||
query += f" AND moderation_type IN ({', '.join(['?' for _ in types])})"
|
||||
query += " ORDER BY moderation_id DESC;"
|
||||
|
||||
return cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=(target, *types) if types else (target,), cursor=cursor)
|
||||
return await cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=(target, *types) if types else (target,), cursor=cursor)
|
||||
|
||||
@classmethod
|
||||
def find_by_moderator(cls, bot: Red, guild_id: int, moderator: int, types: Iterable | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]:
|
||||
async def find_by_moderator(cls, bot: Red, guild_id: int, moderator: int, types: Iterable | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]:
|
||||
query = f"SELECT * FROM moderation_{guild_id} WHERE moderator_id = ?"
|
||||
if types:
|
||||
query += f" AND moderation_type IN ({', '.join(['?' for _ in types])})"
|
||||
query += " ORDER BY moderation_id DESC;"
|
||||
|
||||
return cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=(moderator, *types) if types else (moderator,), cursor=cursor)
|
||||
return await cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=(moderator, *types) if types else (moderator,), cursor=cursor)
|
||||
|
||||
@classmethod
|
||||
def log(
|
||||
async def log(
|
||||
cls,
|
||||
bot: Red,
|
||||
guild_id: int,
|
||||
|
@ -300,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())
|
||||
|
@ -335,13 +356,12 @@ class Moderation(AuroraGuildModel):
|
|||
role_id = None
|
||||
|
||||
if not database:
|
||||
database = connect()
|
||||
database = await cls.connect()
|
||||
close_db = True
|
||||
else:
|
||||
close_db = False
|
||||
cursor = database.cursor()
|
||||
|
||||
moderation_id = cls.get_next_case_number(bot=bot, guild_id=guild_id, cursor=cursor)
|
||||
moderation_id = await cls.get_next_case_number(bot=bot, guild_id=guild_id)
|
||||
|
||||
case = {
|
||||
"moderation_id": moderation_id,
|
||||
|
@ -363,14 +383,13 @@ class Moderation(AuroraGuildModel):
|
|||
}
|
||||
|
||||
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
cursor.execute(sql, tuple(case.values()))
|
||||
await database.execute(sql, tuple(case.values()))
|
||||
|
||||
cursor.close()
|
||||
database.commit()
|
||||
await database.commit()
|
||||
if close_db:
|
||||
database.close()
|
||||
await database.close()
|
||||
|
||||
logger.debug(
|
||||
logger.verbose(
|
||||
"Row inserted into moderation_%s!\n%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s",
|
||||
guild_id,
|
||||
case["moderation_id"],
|
||||
|
@ -392,5 +411,5 @@ class Moderation(AuroraGuildModel):
|
|||
)
|
||||
|
||||
if return_obj:
|
||||
return cls.find_by_id(bot=bot, moderation_id=moderation_id, guild_id=guild_id)
|
||||
return await cls.find_by_id(bot=bot, moderation_id=moderation_id, guild_id=guild_id)
|
||||
return moderation_id
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from discord import Forbidden, HTTPException, InvalidData, NotFound
|
||||
from discord import ChannelType, Forbidden, Guild, HTTPException, InvalidData, NotFound
|
||||
from redbot.core.bot import Red
|
||||
|
||||
from .base import AuroraBaseModel, AuroraGuildModel
|
||||
|
@ -31,6 +31,7 @@ class PartialUser(AuroraBaseModel):
|
|||
class PartialChannel(AuroraGuildModel):
|
||||
id: int
|
||||
name: str
|
||||
type: ChannelType
|
||||
|
||||
@property
|
||||
def mention(self):
|
||||
|
@ -42,17 +43,17 @@ class PartialChannel(AuroraGuildModel):
|
|||
return self.mention
|
||||
|
||||
@classmethod
|
||||
async def from_id(cls, bot: Red, channel_id: int) -> "PartialChannel":
|
||||
async def from_id(cls, bot: Red, channel_id: int, guild: Guild) -> "PartialChannel":
|
||||
channel = bot.get_channel(channel_id)
|
||||
if not channel:
|
||||
try:
|
||||
channel = await bot.fetch_channel(channel_id)
|
||||
return cls(bot=bot, guild_id=channel.guild.id, id=channel.id, name=channel.name)
|
||||
return cls(bot=bot, guild_id=channel.guild.id, guild=guild, id=channel.id, name=channel.name, type=channel.type)
|
||||
except (NotFound, InvalidData, HTTPException, Forbidden) as e:
|
||||
if e == Forbidden:
|
||||
return cls(bot=bot, guild_id=0, id=channel_id, name="Forbidden Channel")
|
||||
return cls(bot=bot, guild_id=0, id=channel_id, name="Deleted Channel")
|
||||
return cls(bot=bot, guild_id=channel.guild.id, id=channel.id, name=channel.name)
|
||||
return cls(bot=bot, guild_id=0, id=channel_id, name="Deleted Channel", type=ChannelType.text)
|
||||
return cls(bot=bot, guild_id=channel.guild.id, guild=guild, id=channel.id, name=channel.name, type=channel.type)
|
||||
|
||||
class PartialRole(AuroraGuildModel):
|
||||
id: int
|
||||
|
@ -68,12 +69,8 @@ class PartialRole(AuroraGuildModel):
|
|||
return self.mention
|
||||
|
||||
@classmethod
|
||||
async def from_id(cls, bot: Red, guild_id: int, role_id: int) -> "PartialRole":
|
||||
try:
|
||||
guild = await bot.fetch_guild(guild_id, with_counts=False)
|
||||
except (Forbidden, HTTPException):
|
||||
return cls(bot=bot, guild_id=guild_id, id=role_id, name="Forbidden Role")
|
||||
async def from_id(cls, bot: Red, guild: Guild, role_id: int) -> "PartialRole":
|
||||
role = guild.get_role(role_id)
|
||||
if not role:
|
||||
return cls(bot=bot, guild_id=guild_id, id=role_id, name="Deleted Role")
|
||||
return cls(bot=bot, guild_id=guild_id, id=role.id, name=role.name)
|
||||
return cls(bot=bot, guild_id=guild.id, id=role_id, name="Deleted Role")
|
||||
return cls(bot=bot, guild_id=guild.id, id=role.id, name=role.name)
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
# pylint: disable=cyclic-import
|
||||
import json
|
||||
import sqlite3
|
||||
|
||||
from discord import Guild
|
||||
from redbot.core import data_manager
|
||||
|
||||
from .logger import logger
|
||||
|
||||
|
||||
def connect() -> sqlite3.Connection:
|
||||
"""Connects to the SQLite database, and returns a connection object."""
|
||||
try:
|
||||
connection = sqlite3.connect(
|
||||
database=data_manager.cog_data_path(raw_name="Aurora") / "aurora.db"
|
||||
)
|
||||
return connection
|
||||
|
||||
except sqlite3.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 = connect()
|
||||
cursor = database.cursor()
|
||||
|
||||
try:
|
||||
cursor.execute(f"SELECT * FROM `moderation_{guild.id}`")
|
||||
logger.debug("SQLite Table exists for server %s (%s)", guild.name, guild.id)
|
||||
|
||||
except sqlite3.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
|
||||
)
|
||||
"""
|
||||
cursor.execute(query)
|
||||
|
||||
index_query_1 = f"CREATE INDEX IF NOT EXISTS idx_target_id ON moderation_{guild.id}(target_id);"
|
||||
cursor.execute(index_query_1)
|
||||
|
||||
index_query_2 = f"CREATE INDEX IF NOT EXISTS idx_moderator_id ON moderation_{guild.id}(moderator_id);"
|
||||
cursor.execute(index_query_2)
|
||||
|
||||
index_query_3 = f"CREATE INDEX IF NOT EXISTS idx_moderation_id ON moderation_{guild.id}(moderation_id);"
|
||||
cursor.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({}),
|
||||
)
|
||||
cursor.execute(insert_query, insert_values)
|
||||
|
||||
database.commit()
|
||||
|
||||
logger.debug(
|
||||
"SQLite Table (moderation_%s) created for %s (%s)",
|
||||
guild.id,
|
||||
guild.name,
|
||||
guild.id,
|
||||
)
|
||||
|
||||
database.close()
|
|
@ -91,7 +91,7 @@ async def message_factory(
|
|||
embed.set_author(name=guild.name)
|
||||
|
||||
embed.set_footer(
|
||||
text=f"Case #{Moderation.get_next_case_number(bot=bot, guild_id=guild.id):,}",
|
||||
text=f"Case #{await Moderation.get_next_case_number(bot=bot, guild_id=guild.id):,}",
|
||||
icon_url="attachment://arrow.png",
|
||||
)
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
@ -120,7 +123,7 @@ async def log(interaction: Interaction, moderation_id: int, resolved: bool = Fal
|
|||
logging_channel = interaction.guild.get_channel(logging_channel_id)
|
||||
|
||||
try:
|
||||
moderation = Moderation.find_by_id(interaction.client, moderation_id, interaction.guild_id)
|
||||
moderation = await Moderation.find_by_id(interaction.client, moderation_id, interaction.guild_id)
|
||||
embed = await log_factory(
|
||||
interaction=interaction, moderation=moderation, resolved=resolved
|
||||
)
|
||||
|
@ -145,7 +148,7 @@ async def send_evidenceformat(interaction: Interaction, moderation_id: int) -> N
|
|||
if send_evidence_bool is False:
|
||||
return
|
||||
|
||||
moderation = Moderation.find_by_id(interaction.client, moderation_id, interaction.guild.id)
|
||||
moderation = await Moderation.find_by_id(interaction.client, moderation_id, interaction.guild.id)
|
||||
content = await evidenceformat_factory(moderation=moderation)
|
||||
await interaction.followup.send(content=content, ephemeral=True)
|
||||
|
||||
|
@ -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)
|
||||
|
|
20
poetry.lock
generated
20
poetry.lock
generated
|
@ -123,6 +123,24 @@ files = [
|
|||
[package.dependencies]
|
||||
frozenlist = ">=1.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "aiosqlite"
|
||||
version = "0.20.0"
|
||||
description = "asyncio bridge to the standard sqlite3 module"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "aiosqlite-0.20.0-py3-none-any.whl", hash = "sha256:36a1deaca0cac40ebe32aac9977a6e2bbc7f5189f23f4a54d5908986729e5bd6"},
|
||||
{file = "aiosqlite-0.20.0.tar.gz", hash = "sha256:6d35c8c256637f4672f843c31021464090805bf925385ac39473fb16eaaca3d7"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
typing_extensions = ">=4.0"
|
||||
|
||||
[package.extras]
|
||||
dev = ["attribution (==1.7.0)", "black (==24.2.0)", "coverage[toml] (==7.4.1)", "flake8 (==7.0.0)", "flake8-bugbear (==24.2.6)", "flit (==3.9.0)", "mypy (==1.8.0)", "ufmt (==2.3.0)", "usort (==1.0.8.post1)"]
|
||||
docs = ["sphinx (==7.2.6)", "sphinx-mdinclude (==0.5.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.7.0"
|
||||
|
@ -2655,4 +2673,4 @@ multidict = ">=4.0"
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.11,<3.12"
|
||||
content-hash = "3f732c0b0b0eb2a31fb9484c7cf699cd3c26474d6779528102af4c509a48351e"
|
||||
content-hash = "22b824824f73dc3dc1a9a0a01060371ee1f6414e5bef39cb7455d21121988b47"
|
||||
|
|
|
@ -18,6 +18,7 @@ pydantic = "^2.7.1"
|
|||
colorthief = "^0.2.1"
|
||||
beautifulsoup4 = "^4.12.3"
|
||||
markdownify = "^0.12.1"
|
||||
aiosqlite = "^0.20.0"
|
||||
|
||||
[tool.poetry.group.dev]
|
||||
optional = true
|
||||
|
|
Loading…
Reference in a new issue