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 json
|
||||||
|
import logging as py_logging
|
||||||
import os
|
import os
|
||||||
import sqlite3
|
|
||||||
import time
|
import time
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from math import ceil
|
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.app_commands import Choice
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
from redbot.core.commands.converter import parse_relativedelta, parse_timedelta
|
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.aurora import ImportAuroraView
|
||||||
from .importers.galacticbot import ImportGalacticBotView
|
from .importers.galacticbot import ImportGalacticBotView
|
||||||
|
@ -30,11 +30,10 @@ from .menus.overrides import Overrides
|
||||||
from .models.change import Change
|
from .models.change import Change
|
||||||
from .models.moderation import Moderation
|
from .models.moderation import Moderation
|
||||||
from .utilities.config import config, register_config
|
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.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.json import dump
|
||||||
from .utilities.logger import logger
|
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):
|
class Aurora(commands.Cog):
|
||||||
|
@ -43,28 +42,22 @@ class Aurora(commands.Cog):
|
||||||
This cog stores all of its data in an SQLite database."""
|
This cog stores all of its data in an SQLite database."""
|
||||||
|
|
||||||
__author__ = ["SeaswimmerTheFsh"]
|
__author__ = ["SeaswimmerTheFsh"]
|
||||||
__version__ = "2.2.0"
|
__version__ = "2.3.0"
|
||||||
__documentation__ = "https://seacogs.coastalcommits.com/aurora/"
|
__documentation__ = "https://seacogs.coastalcommits.com/aurora/"
|
||||||
|
|
||||||
async def red_delete_data_for_user(self, *, requester, user_id: int):
|
async def red_delete_data_for_user(self, *, requester, user_id: int):
|
||||||
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 = connect()
|
results = await Moderation.execute(query="SHOW TABLES;", return_obj=False)
|
||||||
cursor = database.cursor()
|
tables = [table[0] for table in results]
|
||||||
|
|
||||||
cursor.execute("SHOW TABLES;")
|
|
||||||
tables = [table[0] for table in cursor.fetchall()]
|
|
||||||
|
|
||||||
condition = "target_id = %s OR moderator_id = %s;"
|
condition = "target_id = %s OR moderator_id = %s;"
|
||||||
|
|
||||||
for table in tables:
|
for table in tables:
|
||||||
delete_query = f"DELETE FROM {table[0]} WHERE {condition}"
|
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":
|
if requester == "owner":
|
||||||
await config.user_from_id(user_id).clear()
|
await config.user_from_id(user_id).clear()
|
||||||
if requester == "user":
|
if requester == "user":
|
||||||
|
@ -76,20 +69,26 @@ class Aurora(commands.Cog):
|
||||||
"Invalid requester passed to red_delete_data_for_user: %s", requester
|
"Invalid requester passed to red_delete_data_for_user: %s", requester
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, bot: Red):
|
def __init__(self, bot: Red) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
register_config(config)
|
register_config(config)
|
||||||
self.handle_expiry.start()
|
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:
|
def format_help_for_context(self, ctx: commands.Context) -> str:
|
||||||
pre_processed = super().format_help_for_context(ctx) or ""
|
pre_processed = super().format_help_for_context(ctx) or ""
|
||||||
n = "\n" if "\n\n" not in pre_processed else ""
|
n = "\n" if "\n\n" not in pre_processed else ""
|
||||||
text = [
|
text = [
|
||||||
f"{pre_processed}{n}",
|
f"{pre_processed}{n}",
|
||||||
f"Cog Version: **{self.__version__}**",
|
f"{bold('Cog Version:')} {self.__version__}",
|
||||||
f"Author: {humanize_list(self.__author__)}",
|
f"{bold('Author:')} {humanize_list(self.__author__)}",
|
||||||
f"Documentation: {self.__documentation__}",
|
f"{bold('Documentation:')} {self.__documentation__}",
|
||||||
]
|
]
|
||||||
return "\n".join(text)
|
return "\n".join(text)
|
||||||
|
|
||||||
|
@ -122,14 +121,11 @@ class Aurora(commands.Cog):
|
||||||
"""This method automatically adds roles to users when they join the server."""
|
"""This method automatically adds roles to users when they join the server."""
|
||||||
if not await self.bot.cog_disabled_in_guild(self, member.guild):
|
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;"""
|
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()
|
results = Moderation.execute(query, (member.id,))
|
||||||
cursor = database.cursor()
|
for row in results:
|
||||||
cursor.execute(query, (member.id,))
|
role = member.guild.get_role(row[1])
|
||||||
results = cursor.fetchall()
|
reason = row[2]
|
||||||
for result in results:
|
await member.add_roles(role, reason=f"Role automatically added on member rejoin for: {reason} (Case #{row[0]:,})")
|
||||||
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]:,})")
|
|
||||||
|
|
||||||
@commands.Cog.listener("on_audit_log_entry_create")
|
@commands.Cog.listener("on_audit_log_entry_create")
|
||||||
async def autologger(self, entry: discord.AuditLogEntry):
|
async def autologger(self, entry: discord.AuditLogEntry):
|
||||||
|
@ -175,7 +171,7 @@ class Aurora(commands.Cog):
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
Moderation.log(
|
await Moderation.log(
|
||||||
self.bot,
|
self.bot,
|
||||||
entry.guild.id,
|
entry.guild.id,
|
||||||
entry.user.id,
|
entry.user.id,
|
||||||
|
@ -233,7 +229,7 @@ class Aurora(commands.Cog):
|
||||||
except discord.errors.HTTPException:
|
except discord.errors.HTTPException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
moderation = Moderation.log(
|
moderation = await Moderation.log(
|
||||||
interaction.client,
|
interaction.client,
|
||||||
interaction.guild.id,
|
interaction.guild.id,
|
||||||
interaction.user.id,
|
interaction.user.id,
|
||||||
|
@ -292,7 +288,7 @@ class Aurora(commands.Cog):
|
||||||
except discord.errors.HTTPException:
|
except discord.errors.HTTPException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
moderation = Moderation.log(
|
moderation = await Moderation.log(
|
||||||
interaction.client,
|
interaction.client,
|
||||||
interaction.guild.id,
|
interaction.guild.id,
|
||||||
interaction.user.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}`"
|
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.client,
|
||||||
interaction.guild.id,
|
interaction.guild.id,
|
||||||
interaction.user.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}`"
|
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.client,
|
||||||
interaction.guild.id,
|
interaction.guild.id,
|
||||||
interaction.user.id,
|
interaction.user.id,
|
||||||
|
@ -592,7 +588,7 @@ class Aurora(commands.Cog):
|
||||||
except discord.errors.HTTPException:
|
except discord.errors.HTTPException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
moderation = Moderation.log(
|
moderation = await Moderation.log(
|
||||||
interaction.client,
|
interaction.client,
|
||||||
interaction.guild.id,
|
interaction.guild.id,
|
||||||
interaction.user.id,
|
interaction.user.id,
|
||||||
|
@ -728,7 +724,7 @@ class Aurora(commands.Cog):
|
||||||
|
|
||||||
await target.kick(reason=f"Kicked by {interaction.user.id} for: {reason}")
|
await target.kick(reason=f"Kicked by {interaction.user.id} for: {reason}")
|
||||||
|
|
||||||
moderation = Moderation.log(
|
moderation = await Moderation.log(
|
||||||
interaction.client,
|
interaction.client,
|
||||||
interaction.guild.id,
|
interaction.guild.id,
|
||||||
interaction.user.id,
|
interaction.user.id,
|
||||||
|
@ -836,7 +832,7 @@ class Aurora(commands.Cog):
|
||||||
delete_message_seconds=delete_messages_seconds,
|
delete_message_seconds=delete_messages_seconds,
|
||||||
)
|
)
|
||||||
|
|
||||||
moderation = Moderation.log(
|
moderation = await Moderation.log(
|
||||||
interaction.client,
|
interaction.client,
|
||||||
interaction.guild.id,
|
interaction.guild.id,
|
||||||
interaction.user.id,
|
interaction.user.id,
|
||||||
|
@ -880,7 +876,7 @@ class Aurora(commands.Cog):
|
||||||
delete_message_seconds=delete_messages_seconds,
|
delete_message_seconds=delete_messages_seconds,
|
||||||
)
|
)
|
||||||
|
|
||||||
moderation = Moderation.log(
|
moderation = await Moderation.log(
|
||||||
interaction.client,
|
interaction.client,
|
||||||
interaction.guild.id,
|
interaction.guild.id,
|
||||||
interaction.user.id,
|
interaction.user.id,
|
||||||
|
@ -957,7 +953,7 @@ class Aurora(commands.Cog):
|
||||||
except discord.errors.HTTPException:
|
except discord.errors.HTTPException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
moderation = Moderation.log(
|
moderation = await Moderation.log(
|
||||||
interaction.client,
|
interaction.client,
|
||||||
interaction.guild.id,
|
interaction.guild.id,
|
||||||
interaction.user.id,
|
interaction.user.id,
|
||||||
|
@ -1001,7 +997,7 @@ class Aurora(commands.Cog):
|
||||||
await channel.edit(slowmode_delay=interval)
|
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}`")
|
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.client,
|
||||||
interaction.guild.id,
|
interaction.guild.id,
|
||||||
interaction.user.id,
|
interaction.user.id,
|
||||||
|
@ -1046,7 +1042,7 @@ class Aurora(commands.Cog):
|
||||||
inline: bool
|
inline: bool
|
||||||
Display infractions in a grid arrangement (does not look very good)
|
Display infractions in a grid arrangement (does not look very good)
|
||||||
export: bool
|
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:
|
if ephemeral is None:
|
||||||
ephemeral = (
|
ephemeral = (
|
||||||
await config.user(interaction.user).history_ephemeral()
|
await config.user(interaction.user).history_ephemeral()
|
||||||
|
@ -1089,47 +1085,45 @@ class Aurora(commands.Cog):
|
||||||
)
|
)
|
||||||
return
|
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:
|
if export:
|
||||||
try:
|
try:
|
||||||
filename = (
|
filepath = (
|
||||||
str(data_manager.cog_data_path(cog_instance=self))
|
str(data_manager.cog_data_path(cog_instance=self))
|
||||||
+ str(os.sep)
|
+ str(os.sep)
|
||||||
+ f"moderation_{interaction.guild.id}.json"
|
+ filename
|
||||||
)
|
)
|
||||||
|
|
||||||
cases = Moderation.get_latest(bot=interaction.client, guild_id=interaction.guild.id)
|
with open(filepath, "w", encoding="utf-8") as f:
|
||||||
|
dump(obj=moderations, fp=f, indent=2)
|
||||||
with open(filename, "w", encoding="utf-8") as f:
|
|
||||||
dump(obj=cases, fp=f, indent=2)
|
|
||||||
|
|
||||||
await interaction.followup.send(
|
await interaction.followup.send(
|
||||||
file=discord.File(
|
file=discord.File(
|
||||||
filename, f"moderation_{interaction.guild.id}.json"
|
fp=filepath, filename=filename
|
||||||
),
|
),
|
||||||
ephemeral=ephemeral,
|
ephemeral=ephemeral,
|
||||||
)
|
)
|
||||||
|
|
||||||
os.remove(filename)
|
os.remove(filepath)
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
await interaction.followup.send(
|
await interaction.followup.send(
|
||||||
content=error(
|
content=error(
|
||||||
"An error occured while exporting the moderation history.\nError:\n"
|
"An error occured while exporting the moderation history.\nError:\n"
|
||||||
)
|
)
|
||||||
+ box(e, "py"),
|
+ box(text=e, lang="py"),
|
||||||
ephemeral=ephemeral,
|
ephemeral=ephemeral,
|
||||||
)
|
)
|
||||||
database.close()
|
|
||||||
return
|
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)
|
case_quantity = len(moderations)
|
||||||
page_quantity = ceil(case_quantity / pagesize)
|
page_quantity = ceil(case_quantity / pagesize)
|
||||||
start_index = (page - 1) * pagesize
|
start_index = (page - 1) * pagesize
|
||||||
|
@ -1214,7 +1208,7 @@ class Aurora(commands.Cog):
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
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:
|
except ValueError:
|
||||||
await interaction.response.send_message(
|
await interaction.response.send_message(
|
||||||
content=error(f"Case #{case:,} does not exist!"), ephemeral=True
|
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.command(name="case")
|
||||||
@app_commands.choices(
|
@app_commands.choices(
|
||||||
export=[
|
raw=[
|
||||||
Choice(name="Export as File", value="file"),
|
|
||||||
Choice(name="Export as Codeblock", value="codeblock"),
|
Choice(name="Export as Codeblock", value="codeblock"),
|
||||||
|
Choice(name="Export as File", value="file"),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
async def case(
|
async def case(
|
||||||
|
@ -1264,7 +1258,7 @@ class Aurora(commands.Cog):
|
||||||
ephemeral: bool | None = None,
|
ephemeral: bool | None = None,
|
||||||
evidenceformat: bool = False,
|
evidenceformat: bool = False,
|
||||||
changes: bool = False,
|
changes: bool = False,
|
||||||
export: Choice[str] | None = None,
|
raw: Choice[str] | None = None,
|
||||||
):
|
):
|
||||||
"""Check the details of a specific case.
|
"""Check the details of a specific case.
|
||||||
|
|
||||||
|
@ -1274,9 +1268,11 @@ class Aurora(commands.Cog):
|
||||||
What case are you looking up?
|
What case are you looking up?
|
||||||
ephemeral: bool
|
ephemeral: bool
|
||||||
Hide the command response
|
Hide the command response
|
||||||
|
evidenceformat: bool
|
||||||
|
Display the evidence format of the case
|
||||||
changes: bool
|
changes: bool
|
||||||
List the changes made to the case
|
List the changes made to the case
|
||||||
export: bool
|
raw: bool
|
||||||
Export the case to a JSON file or codeblock"""
|
Export the case to a JSON file or codeblock"""
|
||||||
permissions = check_permissions(
|
permissions = check_permissions(
|
||||||
interaction.client.user, ["embed_links"], interaction
|
interaction.client.user, ["embed_links"], interaction
|
||||||
|
@ -1298,15 +1294,15 @@ class Aurora(commands.Cog):
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
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:
|
except ValueError:
|
||||||
await interaction.response.send_message(
|
await interaction.response.send_message(
|
||||||
content=error(f"Case #{case:,} does not exist!"), ephemeral=True
|
content=error(f"Case #{case:,} does not exist!"), ephemeral=True
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if export:
|
if raw:
|
||||||
if export.value == "file" or len(mod.to_json(2)) > 1800:
|
if raw.value == "file" or len(mod.to_json(2)) > 1800:
|
||||||
filename = (
|
filename = (
|
||||||
str(data_manager.cog_data_path(cog_instance=self))
|
str(data_manager.cog_data_path(cog_instance=self))
|
||||||
+ str(os.sep)
|
+ str(os.sep)
|
||||||
|
@ -1315,7 +1311,7 @@ class Aurora(commands.Cog):
|
||||||
|
|
||||||
with open(filename, "w", encoding="utf-8") as f:
|
with open(filename, "w", encoding="utf-8") as f:
|
||||||
mod.to_json(2, f)
|
mod.to_json(2, f)
|
||||||
if export.value == "codeblock":
|
if raw.value == "codeblock":
|
||||||
content = f"Case #{case:,} exported.\n" + warning(
|
content = f"Case #{case:,} exported.\n" + warning(
|
||||||
"Case was too large to export as codeblock, so it has been uploaded as a `.json` file."
|
"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
|
return
|
||||||
|
|
||||||
try:
|
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
|
old_moderation = moderation
|
||||||
except ValueError:
|
except ValueError:
|
||||||
await interaction.response.send_message(
|
await interaction.response.send_message(
|
||||||
|
@ -1469,7 +1465,7 @@ class Aurora(commands.Cog):
|
||||||
"end_timestamp": moderation.end_timestamp,
|
"end_timestamp": moderation.end_timestamp,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
moderation.update()
|
await moderation.update()
|
||||||
embed = await case_factory(interaction=interaction, moderation=moderation)
|
embed = await case_factory(interaction=interaction, moderation=moderation)
|
||||||
|
|
||||||
await interaction.response.send_message(
|
await interaction.response.send_message(
|
||||||
|
@ -1485,8 +1481,6 @@ class Aurora(commands.Cog):
|
||||||
async def handle_expiry(self):
|
async def handle_expiry(self):
|
||||||
await self.bot.wait_until_red_ready()
|
await self.bot.wait_until_red_ready()
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
database = connect()
|
|
||||||
cursor = database.cursor()
|
|
||||||
global_unban_num = 0
|
global_unban_num = 0
|
||||||
global_addrole_num = 0
|
global_addrole_num = 0
|
||||||
global_removerole_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):
|
if not await self.bot.cog_disabled_in_guild(self, guild):
|
||||||
time_per_guild = time.time()
|
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"
|
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(),))
|
||||||
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]
|
|
||||||
|
|
||||||
unban_num = 0
|
unban_num = 0
|
||||||
for target_id, moderation_id in zip(target_ids, moderation_ids):
|
for moderation in tempbans:
|
||||||
user: discord.User = await self.bot.fetch_user(target_id)
|
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 = (
|
name = (
|
||||||
f"{user.name}#{user.discriminator}"
|
f"{user.name}#{user.discriminator}"
|
||||||
if user.discriminator != "0"
|
if user.discriminator != "0"
|
||||||
|
@ -1517,14 +1508,14 @@ class Aurora(commands.Cog):
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await guild.unban(
|
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(
|
embed = await message_factory(
|
||||||
bot=self.bot,
|
bot=self.bot,
|
||||||
color=await self.bot.get_embed_color(guild.channels[0]),
|
color=await self.bot.get_embed_color(guild.channels[0]),
|
||||||
guild=guild,
|
guild=guild,
|
||||||
reason=f"Automatic unban from case #{moderation_id}",
|
reason=f"Automatic unban from case #{moderation.id}",
|
||||||
moderation_type="unbanned",
|
moderation_type="unbanned",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1533,14 +1524,14 @@ class Aurora(commands.Cog):
|
||||||
except discord.errors.HTTPException:
|
except discord.errors.HTTPException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
logger.debug(
|
logger.trace(
|
||||||
"Unbanned %s (%s) from %s (%s)",
|
"Unbanned %s (%s) from %s (%s)",
|
||||||
name,
|
name,
|
||||||
user.id,
|
user.id,
|
||||||
guild.name,
|
guild.name,
|
||||||
guild.id,
|
guild.id,
|
||||||
)
|
)
|
||||||
unban_num = unban_num + 1
|
unban_num += 1
|
||||||
except (
|
except (
|
||||||
discord.errors.NotFound,
|
discord.errors.NotFound,
|
||||||
discord.errors.Forbidden,
|
discord.errors.Forbidden,
|
||||||
|
@ -1556,26 +1547,23 @@ class Aurora(commands.Cog):
|
||||||
)
|
)
|
||||||
|
|
||||||
removerole_num = 0
|
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"
|
addrole_query = f"SELECT * FROM moderation_{guild.id} WHERE end_timestamp IS NOT NULL AND end_timestamp <= ? AND moderation_type = 'ADDROLE' AND expired = 0"
|
||||||
try:
|
addroles = await Moderation.execute(bot=self.bot, guild_id=guild.id, query=addrole_query, parameters=(time.time(),))
|
||||||
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]
|
|
||||||
|
|
||||||
for target_id, moderation_id, role_id in zip(
|
for moderation in addroles:
|
||||||
target_ids, moderation_ids, role_ids
|
|
||||||
):
|
|
||||||
try:
|
try:
|
||||||
member = await guild.fetch_member(target_id)
|
member = await guild.fetch_member(moderation.target_id)
|
||||||
|
|
||||||
await member.remove_roles(
|
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
|
removerole_num = removerole_num + 1
|
||||||
except (
|
except (
|
||||||
discord.errors.NotFound,
|
discord.errors.NotFound,
|
||||||
|
@ -1584,44 +1572,36 @@ class Aurora(commands.Cog):
|
||||||
) as e:
|
) as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
"Removing the role %s from user %s failed due to: \n%s",
|
"Removing the role %s from user %s failed due to: \n%s",
|
||||||
role_id,
|
moderation.role_id,
|
||||||
target_id,
|
moderation.target_id,
|
||||||
e,
|
e,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
addrole_num = 0
|
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"
|
removerole_query = f"SELECT * FROM moderation_{guild.id} WHERE end_timestamp IS NOT NULL AND end_timestamp <= ? AND moderation_type = 'REMOVEROLE' AND expired = 0"
|
||||||
try:
|
removeroles = await Moderation.execute(bot=self.bot, guild_id=guild.id, query=removerole_query, parameters=(time.time(),))
|
||||||
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]
|
|
||||||
|
|
||||||
for target_id, moderation_id, role_id in zip(
|
for moderation in removeroles:
|
||||||
target_ids, moderation_ids, role_ids
|
|
||||||
):
|
|
||||||
try:
|
try:
|
||||||
member = await guild.fetch_member(target_id)
|
member = await guild.fetch_member(moderation.target_id)
|
||||||
|
|
||||||
await member.add_roles(
|
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
|
addrole_num = addrole_num + 1
|
||||||
except (
|
except (
|
||||||
discord.errors.NotFound,
|
discord.errors.NotFound,
|
||||||
discord.errors.Forbidden,
|
discord.errors.Forbidden,
|
||||||
discord.errors.HTTPException,
|
discord.errors.HTTPException,
|
||||||
) as e:
|
) 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
|
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);"
|
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
|
per_guild_completion_time = (time.time() - time_per_guild) * 1000
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
@ -1637,9 +1617,6 @@ class Aurora(commands.Cog):
|
||||||
global_addrole_num = global_addrole_num + addrole_num
|
global_addrole_num = global_addrole_num + addrole_num
|
||||||
global_removerole_num = global_removerole_num + removerole_num
|
global_removerole_num = global_removerole_num + removerole_num
|
||||||
|
|
||||||
database.commit()
|
|
||||||
cursor.close()
|
|
||||||
database.close()
|
|
||||||
|
|
||||||
completion_time = (time.time() - current_time) * 1000
|
completion_time = (time.time() - current_time) * 1000
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
# pylint: disable=duplicate-code
|
# pylint: disable=duplicate-code
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
from time import time
|
from time import time
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from discord import ButtonStyle, Interaction, Message, ui
|
from discord import ButtonStyle, File, Interaction, Message, ui
|
||||||
from redbot.core import commands
|
from redbot.core import commands, data_manager
|
||||||
from redbot.core.utils.chat_formatting import box, warning
|
from redbot.core.utils.chat_formatting import warning
|
||||||
|
|
||||||
from ..models.moderation import Moderation
|
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):
|
class ImportAuroraView(ui.View):
|
||||||
|
@ -27,14 +28,8 @@ class ImportAuroraView(ui.View):
|
||||||
"Deleting original table...", ephemeral=True
|
"Deleting original table...", ephemeral=True
|
||||||
)
|
)
|
||||||
|
|
||||||
database = connect()
|
|
||||||
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};"
|
||||||
cursor.execute(query)
|
await Moderation.execute(query=query, return_obj=False)
|
||||||
|
|
||||||
cursor.close()
|
|
||||||
database.commit()
|
|
||||||
|
|
||||||
await interaction.edit_original_response(content="Creating new table...")
|
await interaction.edit_original_response(content="Creating new table...")
|
||||||
|
|
||||||
|
@ -96,7 +91,7 @@ class ImportAuroraView(ui.View):
|
||||||
duration = None
|
duration = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
Moderation.log(
|
await Moderation.log(
|
||||||
bot=interaction.client,
|
bot=interaction.client,
|
||||||
guild_id=self.ctx.guild.id,
|
guild_id=self.ctx.guild.id,
|
||||||
moderator_id=case["moderator_id"],
|
moderator_id=case["moderator_id"],
|
||||||
|
@ -113,7 +108,6 @@ class ImportAuroraView(ui.View):
|
||||||
expired=case["expired"],
|
expired=case["expired"],
|
||||||
changes=changes,
|
changes=changes,
|
||||||
metadata=metadata,
|
metadata=metadata,
|
||||||
database=database,
|
|
||||||
return_obj=False
|
return_obj=False
|
||||||
)
|
)
|
||||||
except Exception as e: # pylint: disable=broad-exception-caught
|
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.")
|
await interaction.edit_original_response(content="Import complete.")
|
||||||
if failed_cases:
|
if failed_cases:
|
||||||
await interaction.edit_original_response(
|
filename = (
|
||||||
content="Import complete.\n"
|
str(data_manager.cog_data_path(cog_instance=self))
|
||||||
+ warning("Failed to import the following cases:\n")
|
+ str(os.sep)
|
||||||
+ box(failed_cases)
|
+ 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)
|
@ui.button(label="No", style=ButtonStyle.danger)
|
||||||
async def import_button_n(
|
async def import_button_n(
|
||||||
self, interaction: Interaction, button: ui.Button
|
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 redbot.core.utils.chat_formatting import box, warning
|
||||||
|
|
||||||
from ..models.moderation import Change, Moderation
|
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):
|
class ImportGalacticBotView(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
|
||||||
|
self.timeout = timeout
|
||||||
self.message: Message = message
|
self.message: Message = message
|
||||||
|
|
||||||
@ui.button(label="Yes", style=ButtonStyle.success)
|
@ui.button(label="Yes", style=ButtonStyle.success)
|
||||||
|
@ -26,14 +27,14 @@ class ImportGalacticBotView(ui.View):
|
||||||
"Deleting original table...", ephemeral=True
|
"Deleting original table...", ephemeral=True
|
||||||
)
|
)
|
||||||
|
|
||||||
database = connect()
|
database = await Moderation.connect()
|
||||||
cursor = database.cursor()
|
cursor = await database.cursor()
|
||||||
|
|
||||||
query = f"DROP TABLE IF EXISTS moderation_{self.ctx.guild.id};"
|
query = f"DROP TABLE IF EXISTS moderation_{self.ctx.guild.id};"
|
||||||
cursor.execute(query)
|
await cursor.execute(query)
|
||||||
|
|
||||||
cursor.close()
|
await cursor.close()
|
||||||
database.commit()
|
await database.commit()
|
||||||
|
|
||||||
await interaction.edit_original_response(content="Creating new table...")
|
await interaction.edit_original_response(content="Creating new table...")
|
||||||
|
|
||||||
|
@ -124,7 +125,7 @@ class ImportGalacticBotView(ui.View):
|
||||||
else:
|
else:
|
||||||
reason = None
|
reason = None
|
||||||
|
|
||||||
Moderation.log(
|
await Moderation.log(
|
||||||
self.ctx.guild.id,
|
self.ctx.guild.id,
|
||||||
case["executor"],
|
case["executor"],
|
||||||
case["type"],
|
case["type"],
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"disabled": false,
|
"disabled": false,
|
||||||
"min_bot_version": "3.5.0",
|
"min_bot_version": "3.5.0",
|
||||||
"min_python_version": [3, 10, 0],
|
"min_python_version": [3, 10, 0],
|
||||||
"requirements": ["pydantic"],
|
"requirements": ["pydantic", "aiosqlite"],
|
||||||
"tags": [
|
"tags": [
|
||||||
"mod",
|
"mod",
|
||||||
"moderate",
|
"moderate",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from typing import Any
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
from discord import Guild
|
||||||
from pydantic import BaseModel, ConfigDict
|
from pydantic import BaseModel, ConfigDict
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
|
|
||||||
|
@ -12,17 +13,19 @@ class AuroraBaseModel(BaseModel):
|
||||||
def dump(self) -> dict:
|
def dump(self) -> dict:
|
||||||
return self.model_dump(exclude={"bot"})
|
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
|
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):
|
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_id: int
|
||||||
|
guild: Optional[Guild] = None
|
||||||
|
|
||||||
def dump(self) -> dict:
|
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
|
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 json
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from sqlite3 import Cursor
|
|
||||||
from time import time
|
from time import time
|
||||||
from typing import Dict, Iterable, List, Optional, Tuple, Union
|
from typing import Dict, Iterable, List, Optional, Tuple, Union
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
from aiosqlite import Connection, Cursor, OperationalError, Row
|
||||||
|
from aiosqlite import connect as aiosqlite_connect
|
||||||
from discord import NotFound
|
from discord import NotFound
|
||||||
|
from redbot.core import data_manager
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
|
|
||||||
from ..utilities.logger import logger
|
from ..utilities.logger import logger
|
||||||
|
@ -52,7 +54,7 @@ class Moderation(AuroraGuildModel):
|
||||||
async def get_target(self) -> Union["PartialUser", "PartialChannel"]:
|
async def get_target(self) -> Union["PartialUser", "PartialChannel"]:
|
||||||
if self.target_type == "USER":
|
if self.target_type == "USER":
|
||||||
return await PartialUser.from_id(self.bot, self.target_id)
|
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"]:
|
async def get_resolved_by(self) -> Optional["PartialUser"]:
|
||||||
if self.resolved_by:
|
if self.resolved_by:
|
||||||
|
@ -61,7 +63,7 @@ class Moderation(AuroraGuildModel):
|
||||||
|
|
||||||
async def get_role(self) -> Optional["PartialRole"]:
|
async def get_role(self) -> Optional["PartialRole"]:
|
||||||
if self.role_id:
|
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
|
return None
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
|
@ -115,33 +117,31 @@ class Moderation(AuroraGuildModel):
|
||||||
"user_id": resolved_by,
|
"user_id": resolved_by,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
self.update()
|
await self.update()
|
||||||
|
|
||||||
def update(self) -> None:
|
async def update(self) -> None:
|
||||||
from ..utilities.database import connect
|
|
||||||
from ..utilities.json import dumps
|
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 = ?;"
|
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:
|
await self.execute(query, (
|
||||||
database.execute(query, (
|
self.timestamp.timestamp(),
|
||||||
self.timestamp.timestamp(),
|
self.moderation_type,
|
||||||
self.moderation_type,
|
self.target_type,
|
||||||
self.target_type,
|
self.moderator_id,
|
||||||
self.moderator_id,
|
self.role_id,
|
||||||
self.role_id,
|
str(self.duration) if self.duration else None,
|
||||||
str(self.duration) if self.duration else None,
|
self.end_timestamp.timestamp() if self.end_timestamp else None,
|
||||||
self.end_timestamp.timestamp() if self.end_timestamp else None,
|
self.reason,
|
||||||
self.reason,
|
self.resolved,
|
||||||
self.resolved,
|
self.resolved_by,
|
||||||
self.resolved_by,
|
self.resolve_reason,
|
||||||
self.resolve_reason,
|
self.expired,
|
||||||
self.expired,
|
dumps(self.changes),
|
||||||
dumps(self.changes),
|
dumps(self.metadata),
|
||||||
dumps(self.metadata),
|
self.moderation_id,
|
||||||
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.moderation_id,
|
||||||
self.guild_id,
|
self.guild_id,
|
||||||
self.timestamp.timestamp(),
|
self.timestamp.timestamp(),
|
||||||
|
@ -162,6 +162,14 @@ class Moderation(AuroraGuildModel):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, bot: Red, data: dict) -> "Moderation":
|
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)
|
return cls(bot=bot, **data)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -205,37 +213,51 @@ class Moderation(AuroraGuildModel):
|
||||||
}
|
}
|
||||||
return cls.from_dict(bot=bot, data=case)
|
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
|
@classmethod
|
||||||
def execute(cls, bot: Red, guild_id: int, query: str, parameters: tuple | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]:
|
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]]:
|
||||||
from ..utilities.database import connect
|
logger.trace("Executing query: \"%s\" with parameters \"%s\"", query, parameters)
|
||||||
logger.trace("Executing query: %s", query)
|
|
||||||
logger.trace("With parameters: %s", parameters)
|
|
||||||
if not parameters:
|
if not parameters:
|
||||||
parameters = ()
|
parameters = ()
|
||||||
if not cursor:
|
if not cursor:
|
||||||
no_cursor = True
|
no_cursor = True
|
||||||
database = connect()
|
database = await cls.connect()
|
||||||
cursor = database.cursor()
|
cursor = await database.cursor()
|
||||||
else:
|
else:
|
||||||
no_cursor = False
|
no_cursor = False
|
||||||
|
|
||||||
cursor.execute(query, parameters)
|
await cursor.execute(query, parameters)
|
||||||
results = cursor.fetchall()
|
results = await cursor.fetchall()
|
||||||
|
await database.commit()
|
||||||
if no_cursor:
|
if no_cursor:
|
||||||
cursor.close()
|
await cursor.close()
|
||||||
database.close()
|
await database.close()
|
||||||
|
|
||||||
if results:
|
if results and return_obj and bot and guild_id:
|
||||||
cases = []
|
cases = []
|
||||||
for result in results:
|
for result in results:
|
||||||
case = cls.from_result(bot=bot, result=result, guild_id=guild_id)
|
case = cls.from_result(bot=bot, result=result, guild_id=guild_id)
|
||||||
if case.moderation_id != 0:
|
if case.moderation_id != 0:
|
||||||
cases.append(case)
|
cases.append(case)
|
||||||
return tuple(cases)
|
return tuple(cases)
|
||||||
return ()
|
return results
|
||||||
|
|
||||||
@classmethod
|
@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 = []
|
params = []
|
||||||
query = f"SELECT * FROM moderation_{guild_id} ORDER BY moderation_id DESC"
|
query = f"SELECT * FROM moderation_{guild_id} ORDER BY moderation_id DESC"
|
||||||
if types:
|
if types:
|
||||||
|
@ -245,41 +267,41 @@ class Moderation(AuroraGuildModel):
|
||||||
query += " LIMIT ? OFFSET ?"
|
query += " LIMIT ? OFFSET ?"
|
||||||
params.extend((limit, offset))
|
params.extend((limit, offset))
|
||||||
query += ";"
|
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
|
@classmethod
|
||||||
def get_next_case_number(cls, bot: Red, guild_id: int, cursor: Cursor | None = None) -> int:
|
async 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)
|
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
|
return (result[0].moderation_id + 1) if result else 1
|
||||||
|
|
||||||
@classmethod
|
@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 = ?;"
|
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:
|
if case:
|
||||||
return case[0]
|
return case[0]
|
||||||
raise ValueError(f"Case {moderation_id} not found in moderation_{guild_id}!")
|
raise ValueError(f"Case {moderation_id} not found in moderation_{guild_id}!")
|
||||||
|
|
||||||
@classmethod
|
@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 = ?"
|
query = f"SELECT * FROM moderation_{guild_id} WHERE target_id = ?"
|
||||||
if types:
|
if types:
|
||||||
query += f" AND moderation_type IN ({', '.join(['?' for _ in types])})"
|
query += f" AND moderation_type IN ({', '.join(['?' for _ in types])})"
|
||||||
query += " ORDER BY moderation_id DESC;"
|
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
|
@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 = ?"
|
query = f"SELECT * FROM moderation_{guild_id} WHERE moderator_id = ?"
|
||||||
if types:
|
if types:
|
||||||
query += f" AND moderation_type IN ({', '.join(['?' for _ in types])})"
|
query += f" AND moderation_type IN ({', '.join(['?' for _ in types])})"
|
||||||
query += " ORDER BY moderation_id DESC;"
|
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
|
@classmethod
|
||||||
def log(
|
async def log(
|
||||||
cls,
|
cls,
|
||||||
bot: Red,
|
bot: Red,
|
||||||
guild_id: int,
|
guild_id: int,
|
||||||
|
@ -300,7 +322,6 @@ class Moderation(AuroraGuildModel):
|
||||||
metadata: dict | None = None,
|
metadata: dict | None = None,
|
||||||
return_obj: bool = True,
|
return_obj: bool = True,
|
||||||
) -> Union["Moderation", int]:
|
) -> Union["Moderation", int]:
|
||||||
from ..utilities.database import connect
|
|
||||||
from ..utilities.json import dumps
|
from ..utilities.json import dumps
|
||||||
if not timestamp:
|
if not timestamp:
|
||||||
timestamp = datetime.fromtimestamp(time())
|
timestamp = datetime.fromtimestamp(time())
|
||||||
|
@ -335,13 +356,12 @@ class Moderation(AuroraGuildModel):
|
||||||
role_id = None
|
role_id = None
|
||||||
|
|
||||||
if not database:
|
if not database:
|
||||||
database = connect()
|
database = await cls.connect()
|
||||||
close_db = True
|
close_db = True
|
||||||
else:
|
else:
|
||||||
close_db = False
|
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 = {
|
case = {
|
||||||
"moderation_id": moderation_id,
|
"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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
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()
|
await database.commit()
|
||||||
database.commit()
|
|
||||||
if close_db:
|
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",
|
"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,
|
||||||
case["moderation_id"],
|
case["moderation_id"],
|
||||||
|
@ -392,5 +411,5 @@ class Moderation(AuroraGuildModel):
|
||||||
)
|
)
|
||||||
|
|
||||||
if return_obj:
|
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
|
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 redbot.core.bot import Red
|
||||||
|
|
||||||
from .base import AuroraBaseModel, AuroraGuildModel
|
from .base import AuroraBaseModel, AuroraGuildModel
|
||||||
|
@ -31,6 +31,7 @@ class PartialUser(AuroraBaseModel):
|
||||||
class PartialChannel(AuroraGuildModel):
|
class PartialChannel(AuroraGuildModel):
|
||||||
id: int
|
id: int
|
||||||
name: str
|
name: str
|
||||||
|
type: ChannelType
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mention(self):
|
def mention(self):
|
||||||
|
@ -42,17 +43,17 @@ class PartialChannel(AuroraGuildModel):
|
||||||
return self.mention
|
return self.mention
|
||||||
|
|
||||||
@classmethod
|
@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)
|
channel = bot.get_channel(channel_id)
|
||||||
if not channel:
|
if not channel:
|
||||||
try:
|
try:
|
||||||
channel = await bot.fetch_channel(channel_id)
|
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:
|
except (NotFound, InvalidData, HTTPException, Forbidden) as e:
|
||||||
if e == Forbidden:
|
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="Forbidden Channel")
|
||||||
return cls(bot=bot, guild_id=0, id=channel_id, name="Deleted Channel")
|
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, 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)
|
||||||
|
|
||||||
class PartialRole(AuroraGuildModel):
|
class PartialRole(AuroraGuildModel):
|
||||||
id: int
|
id: int
|
||||||
|
@ -68,12 +69,8 @@ class PartialRole(AuroraGuildModel):
|
||||||
return self.mention
|
return self.mention
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def from_id(cls, bot: Red, guild_id: int, role_id: int) -> "PartialRole":
|
async def from_id(cls, bot: Red, guild: Guild, 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")
|
|
||||||
role = guild.get_role(role_id)
|
role = guild.get_role(role_id)
|
||||||
if not role:
|
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="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=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_author(name=guild.name)
|
||||||
|
|
||||||
embed.set_footer(
|
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",
|
icon_url="attachment://arrow.png",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Optional, Tuple, Union
|
from typing import Optional, Tuple, Union
|
||||||
|
|
||||||
|
import aiosqlite
|
||||||
from dateutil.relativedelta import relativedelta as rd
|
from dateutil.relativedelta import relativedelta as rd
|
||||||
from discord import File, Guild, Interaction, Member, SelectOption, TextChannel, User
|
from discord import File, Guild, Interaction, Member, SelectOption, TextChannel, User
|
||||||
from discord.errors import Forbidden
|
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 redbot.core.utils.chat_formatting import error
|
||||||
|
|
||||||
from ..utilities.config import config
|
from ..utilities.config import config
|
||||||
|
from ..utilities.json import dumps
|
||||||
|
from ..utilities.logger import logger
|
||||||
|
|
||||||
|
|
||||||
def check_permissions(
|
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)
|
logging_channel = interaction.guild.get_channel(logging_channel_id)
|
||||||
|
|
||||||
try:
|
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(
|
embed = await log_factory(
|
||||||
interaction=interaction, moderation=moderation, resolved=resolved
|
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:
|
if send_evidence_bool is False:
|
||||||
return
|
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)
|
content = await evidenceformat_factory(moderation=moderation)
|
||||||
await interaction.followup.send(content=content, ephemeral=True)
|
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."""
|
"""Returns the footer image for the embeds."""
|
||||||
image_path = data_manager.bundled_data_path(coginstance) / "arrow.png"
|
image_path = data_manager.bundled_data_path(coginstance) / "arrow.png"
|
||||||
return File(image_path, filename="arrow.png", description="arrow")
|
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]
|
[package.dependencies]
|
||||||
frozenlist = ">=1.1.0"
|
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]]
|
[[package]]
|
||||||
name = "annotated-types"
|
name = "annotated-types"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
@ -2655,4 +2673,4 @@ multidict = ">=4.0"
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = ">=3.11,<3.12"
|
python-versions = ">=3.11,<3.12"
|
||||||
content-hash = "3f732c0b0b0eb2a31fb9484c7cf699cd3c26474d6779528102af4c509a48351e"
|
content-hash = "22b824824f73dc3dc1a9a0a01060371ee1f6414e5bef39cb7455d21121988b47"
|
||||||
|
|
|
@ -18,6 +18,7 @@ pydantic = "^2.7.1"
|
||||||
colorthief = "^0.2.1"
|
colorthief = "^0.2.1"
|
||||||
beautifulsoup4 = "^4.12.3"
|
beautifulsoup4 = "^4.12.3"
|
||||||
markdownify = "^0.12.1"
|
markdownify = "^0.12.1"
|
||||||
|
aiosqlite = "^0.20.0"
|
||||||
|
|
||||||
[tool.poetry.group.dev]
|
[tool.poetry.group.dev]
|
||||||
optional = true
|
optional = true
|
||||||
|
|
Loading…
Reference in a new issue