2023-12-14 18:55:35 -05:00
# _____ _
# / ____| (_)
# | (___ ___ __ _ _____ ___ _ __ ___ _ __ ___ ___ _ __
# \___ \ / _ \/ _` / __\ \ /\ / / | '_ ` _ \| '_ ` _ \ / _ \ '__|
# ____) | __/ (_| \__ \\ V V /| | | | | | | | | | | | __/ |
# |_____/ \___|\__,_|___/ \_/\_/ |_|_| |_| |_|_| |_| |_|\___|_|
2023-12-14 18:35:25 -05:00
import json
2024-06-05 00:25:19 -04:00
import logging as py_logging
2023-12-14 18:35:25 -05:00
import os
2024-02-02 11:21:56 -05:00
import time
2023-12-14 18:35:25 -05:00
from datetime import datetime , timedelta , timezone
2023-12-21 17:47:13 -05:00
from math import ceil
2024-08-13 15:09:49 -04:00
from typing import List , Union
2023-12-18 15:57:51 -05:00
2023-12-14 18:35:25 -05:00
import discord
2024-08-12 17:48:01 -04:00
from class_registry . registry import RegistryKeyError
2024-08-10 13:27:49 -04:00
from dateutil . parser import ParserError , parse
2023-12-14 18:35:25 -05:00
from discord . ext import tasks
2024-01-15 06:49:20 -05:00
from redbot . core import app_commands , commands , data_manager
2023-12-14 18:35:25 -05:00
from redbot . core . app_commands import Choice
2024-01-15 09:03:02 -05:00
from redbot . core . bot import Red
2024-03-08 14:19:48 -05:00
from redbot . core . commands . converter import parse_relativedelta , parse_timedelta
2024-06-08 20:12:22 -04:00
from redbot . core . utils . chat_formatting import bold , box , error , humanize_list , humanize_timedelta , warning
2023-12-18 15:57:51 -05:00
2024-05-06 21:39:43 -04:00
from . importers . aurora import ImportAuroraView
from . importers . galacticbot import ImportGalacticBotView
from . menus . addrole import Addrole
from . menus . guild import Guild
from . menus . immune import Immune
from . menus . overrides import Overrides
2024-08-12 17:40:17 -04:00
from . menus . types import Types
2024-05-06 21:39:43 -04:00
from . models . change import Change
from . models . moderation import Moderation
2024-08-13 15:09:49 -04:00
from . models . type import Type , type_registry
2024-05-06 21:39:43 -04:00
from . utilities . config import config , register_config
2024-08-12 17:39:13 -04:00
from . utilities . factory import addrole_embed , case_factory , changes_factory , evidenceformat_factory , guild_embed , immune_embed , overrides_embed , type_embed
2024-05-06 21:39:43 -04:00
from . utilities . json import dump
from . utilities . logger import logger
2024-08-19 14:46:08 -04:00
from . utilities . utils import check_moddable , check_permissions , create_guild_table , log , timedelta_from_relativedelta , timedelta_to_string
2024-05-06 20:45:22 -04:00
2024-01-16 09:23:45 -05:00
class Aurora ( commands . Cog ) :
2023-12-28 05:52:30 -05:00
""" Aurora is a fully-featured moderation system.
It is heavily inspired by GalacticBot , and is designed to be a more user - friendly alternative to Red ' s core Mod cogs.
This cog stores all of its data in an SQLite database . """
2024-08-22 18:47:17 -04:00
__author__ = [ " [cswimr](https://www.coastalcommits.com/cswimr) " ]
2024-08-24 19:21:49 -04:00
__version__ = " 3.0.0-indev26 "
2024-08-22 18:47:17 -04:00
__git__ = " https://www.coastalcommits.com/cswimr/SeaCogs "
2024-03-29 07:17:39 -04:00
__documentation__ = " https://seacogs.coastalcommits.com/aurora/ "
2023-12-14 18:35:25 -05:00
2023-12-14 19:04:27 -05:00
async def red_delete_data_for_user ( self , * , requester , user_id : int ) :
if requester == " discord_deleted_user " :
2023-12-18 15:57:51 -05:00
await config . user_from_id ( user_id ) . clear ( )
2023-12-14 19:04:27 -05:00
2024-06-08 20:12:22 -04:00
results = await Moderation . execute ( query = " SHOW TABLES; " , return_obj = False )
tables = [ table [ 0 ] for table in results ]
2023-12-14 19:04:27 -05:00
condition = " target_id = %s OR moderator_id = %s ; "
for table in tables :
delete_query = f " DELETE FROM { table [ 0 ] } WHERE { condition } "
2024-06-08 20:12:22 -04:00
await Moderation . execute ( query = delete_query , parameters = ( user_id , user_id ) , return_obj = False )
2023-12-14 19:04:27 -05:00
if requester == " owner " :
2023-12-18 15:57:51 -05:00
await config . user_from_id ( user_id ) . clear ( )
2023-12-14 19:04:27 -05:00
if requester == " user " :
2023-12-18 15:57:51 -05:00
await config . user_from_id ( user_id ) . clear ( )
2023-12-14 19:04:27 -05:00
if requester == " user_strict " :
2023-12-18 15:57:51 -05:00
await config . user_from_id ( user_id ) . clear ( )
2023-12-14 19:04:27 -05:00
else :
2024-02-02 11:21:56 -05:00
logger . warning (
" Invalid requester passed to red_delete_data_for_user: %s " , requester
)
2023-12-14 19:04:27 -05:00
2024-06-05 00:36:12 -04:00
def __init__ ( self , bot : Red ) - > None :
2024-01-15 09:06:58 -05:00
super ( ) . __init__ ( )
2023-12-14 18:35:25 -05:00
self . bot = bot
2024-07-06 11:30:37 -04:00
self . type_registry = type_registry
2023-12-18 15:57:51 -05:00
register_config ( config )
2024-02-14 11:04:26 -05:00
self . handle_expiry . start ( )
2024-06-05 00:25:19 -04:00
# 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.
2024-06-08 20:12:22 -04:00
py_logging . getLogger ( ' aiosqlite ' ) . setLevel ( py_logging . INFO )
2023-12-14 18:35:25 -05:00
2024-03-07 03:38:34 -05:00
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 } " ,
2024-08-22 18:47:17 -04:00
f " { bold ( ' Cog Version: ' ) } [ { self . __version__ } ]( { self . __git__ } ) " ,
2024-06-08 20:12:22 -04:00
f " { bold ( ' Author: ' ) } { humanize_list ( self . __author__ ) } " ,
f " { bold ( ' Documentation: ' ) } { self . __documentation__ } " ,
2024-03-07 03:38:34 -05:00
]
return " \n " . join ( text )
2023-12-14 18:35:25 -05:00
async def cog_load ( self ) :
2024-01-01 12:47:13 -05:00
""" This method prepares the database schema for all of the guilds the bot is currently in. """
2023-12-14 18:35:25 -05:00
guilds : list [ discord . Guild ] = self . bot . guilds
try :
for guild in guilds :
if not await self . bot . cog_disabled_in_guild ( self , guild ) :
2023-12-17 02:36:18 -05:00
await create_guild_table ( guild )
2023-12-14 18:35:25 -05:00
except ConnectionRefusedError :
return
async def cog_unload ( self ) :
2024-02-14 11:04:26 -05:00
self . handle_expiry . cancel ( )
2023-12-14 18:35:25 -05:00
2024-08-13 15:09:49 -04:00
@staticmethod
2024-08-23 14:54:06 -04:00
async def moderate ( ctx : Union [ commands . Context , discord . Interaction ] , target : discord . Member | discord . User | discord . abc . Messageable , permissions : List [ str ] , moderation_type : Type | str , silent : bool | None = None , * * kwargs ) - > None | Type :
2024-08-13 15:09:49 -04:00
""" This function is used to moderate users.
It checks if the target can be moderated , then calls the handler method of the moderation type specified .
Args :
ctx ( Union [ commands . Context , discord . Interaction ] ) : The context of the command . If this is a ` discord . Interaction ` object , it will be converted to a ` commands . Context ` object . Additionally , if the interaction orignated from a context menu , the ` ctx . author ` attribute will be overriden to ` interaction . user ` .
target ( discord . Member , discord . User , discord . abc . Messageable ) : The target user or channel to moderate .
permissions ( List [ str ] ) : The permissions required to moderate the target .
moderation_type ( Type ) : The moderation type ( handler ) to use . See ` aurora . models . moderation_types ` for some examples .
2024-08-23 14:54:06 -04:00
silent ( bool , optional ) : Whether or not to message the target . Defaults to None .
2024-08-13 15:09:49 -04:00
* * kwargs : The keyword arguments to pass to the handler method .
"""
if isinstance ( moderation_type , str ) :
moderation_type = type_registry [ str . lower ( moderation_type ) ]
if isinstance ( ctx , discord . Interaction ) :
interaction = ctx
ctx = await commands . Context . from_interaction ( interaction )
if isinstance ( interaction . command , app_commands . ContextMenu ) :
ctx . author = interaction . user
if not await check_moddable ( target = target , ctx = ctx , permissions = permissions , moderation_type = moderation_type ) :
return
if silent is None :
dm_users = await config . custom ( " types " , ctx . guild . id , moderation_type . key ) . dm_users ( )
if dm_users is None :
dm_users = await config . guild ( ctx . guild ) . dm_users ( )
silent = not dm_users
return await moderation_type . handler (
ctx = ctx ,
target = target ,
silent = silent ,
* * kwargs
)
2024-02-02 11:21:56 -05:00
@commands.Cog.listener ( " on_guild_join " )
2024-08-12 20:33:37 -04:00
async def db_generate_on_guild_join ( self , guild : discord . Guild ) :
2023-12-14 18:35:25 -05:00
""" This method prepares the database schema whenever the bot joins a guild. """
if not await self . bot . cog_disabled_in_guild ( self , guild ) :
try :
2023-12-17 02:16:44 -05:00
await create_guild_table ( guild )
2023-12-14 18:35:25 -05:00
except ConnectionRefusedError :
return
2024-05-03 20:10:26 -04:00
@commands.Cog.listener ( " on_member_join " )
async def addrole_on_member_join ( self , member : discord . Member ) :
""" This method automatically adds roles to users when they join the server. """
if not await self . bot . cog_disabled_in_guild ( self , member . guild ) :
2024-06-05 00:39:56 -04:00
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; """
2024-08-12 20:32:14 -04:00
results = await Moderation . execute ( query , ( member . id , ) )
2024-06-08 20:12:22 -04:00
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 ] : , } ) " )
2024-05-03 20:10:26 -04:00
2024-02-02 11:21:56 -05:00
@commands.Cog.listener ( " on_audit_log_entry_create " )
2023-12-14 18:35:25 -05:00
async def autologger ( self , entry : discord . AuditLogEntry ) :
""" This method automatically logs moderations done by users manually ( " right clicks " ). """
2024-08-12 20:33:37 -04:00
try :
if not await self . bot . cog_disabled_in_guild ( self , entry . guild ) :
if await config . guild ( entry . guild ) . ignore_other_bots ( ) is True :
if entry . user . bot or entry . target . bot :
return
else :
if entry . user . id == self . bot . user . id :
return
2023-12-14 18:35:25 -05:00
2024-08-12 20:33:37 -04:00
duration = None
2023-12-14 18:35:25 -05:00
2024-08-12 20:33:37 -04:00
if entry . reason :
reason = entry . reason + " (This action was performed without the bot.) "
2023-12-14 18:35:25 -05:00
else :
2024-08-12 20:33:37 -04:00
reason = " This action was performed without the bot. "
if entry . action == discord . AuditLogAction . kick :
2024-08-23 14:43:43 -04:00
moderation_type = type_registry [ ' kick ' ]
2024-08-12 20:33:37 -04:00
elif entry . action == discord . AuditLogAction . ban :
2024-08-23 14:43:43 -04:00
moderation_type = type_registry [ ' ban ' ]
2024-08-12 20:33:37 -04:00
elif entry . action == discord . AuditLogAction . unban :
2024-08-23 14:43:43 -04:00
moderation_type = type_registry [ ' unban ' ]
2024-08-12 20:33:37 -04:00
elif entry . action == discord . AuditLogAction . member_update :
if entry . after . timed_out_until is not None :
timed_out_until_aware = entry . after . timed_out_until . replace (
tzinfo = timezone . utc
)
duration_datetime = timed_out_until_aware - datetime . now (
tz = timezone . utc
)
minutes = round ( duration_datetime . total_seconds ( ) / 60 )
duration = timedelta ( minutes = minutes )
2024-08-23 14:43:43 -04:00
moderation_type = type_registry [ ' mute ' ]
2024-08-12 20:33:37 -04:00
else :
2024-08-23 14:43:43 -04:00
moderation_type = type_registry [ ' unmute ' ]
2024-08-12 20:33:37 -04:00
else :
return
2023-12-14 18:35:25 -05:00
2024-08-12 20:33:37 -04:00
await Moderation . log (
2024-08-23 14:43:43 -04:00
bot = self . bot ,
guild_id = entry . guild . id ,
moderator_id = entry . user . id ,
moderation_type = moderation_type ,
target_type = " USER " ,
target_id = entry . target . id ,
role_id = None ,
duration = duration ,
reason = reason ,
2024-08-12 20:33:37 -04:00
)
except AttributeError :
return
2023-12-14 18:35:25 -05:00
#######################################################################################################################
### COMMANDS
#######################################################################################################################
@app_commands.command ( name = " note " )
2024-02-02 11:21:56 -05:00
async def note (
self ,
interaction : discord . Interaction ,
target : discord . User ,
reason : str ,
2024-05-06 21:04:08 -04:00
silent : bool | None = None ,
2024-02-02 11:21:56 -05:00
) :
2023-12-14 18:35:25 -05:00
""" Add a note to a user.
Parameters
- - - - - - - - - - -
target : discord . User
Who are you noting ?
reason : str
Why are you noting this user ?
silent : bool
Should the user be messaged ? """
2024-08-13 15:09:49 -04:00
await self . moderate (
2024-07-12 15:22:24 -04:00
ctx = interaction ,
target = target ,
silent = silent ,
permissions = [ " moderate_members " ] ,
moderation_type = type_registry [ ' note ' ] ,
reason = reason ,
2024-02-02 11:21:56 -05:00
)
2023-12-18 18:33:37 -05:00
2023-12-14 18:35:25 -05:00
@app_commands.command ( name = " warn " )
2024-02-02 11:21:56 -05:00
async def warn (
self ,
interaction : discord . Interaction ,
target : discord . Member ,
reason : str ,
2024-05-06 21:04:08 -04:00
silent : bool | None = None ,
2024-02-02 11:21:56 -05:00
) :
2023-12-14 18:35:25 -05:00
""" Warn a user.
Parameters
- - - - - - - - - - -
target : discord . Member
Who are you warning ?
reason : str
Why are you warning this user ?
silent : bool
Should the user be messaged ? """
2024-08-13 15:09:49 -04:00
await self . moderate (
2024-07-12 15:22:24 -04:00
ctx = interaction ,
target = target ,
silent = silent ,
permissions = [ " moderate_members " ] ,
moderation_type = type_registry [ ' warn ' ] ,
reason = reason ,
2024-02-02 11:21:56 -05:00
)
2023-12-18 18:33:37 -05:00
2024-02-02 11:21:56 -05:00
@app_commands.command ( name = " addrole " )
async def addrole (
self ,
interaction : discord . Interaction ,
target : discord . Member ,
2024-02-02 11:33:17 -05:00
role : discord . Role ,
reason : str ,
2024-05-06 21:04:08 -04:00
duration : str | None = None ,
silent : bool | None = None ,
2024-02-02 11:21:56 -05:00
) :
""" Add a role to a user.
2023-12-16 21:02:54 -05:00
Parameters
- - - - - - - - - - -
target : discord . Member
2024-02-02 11:21:56 -05:00
Who are you adding a role to ?
2024-02-02 11:32:20 -05:00
role : discord . Role
2024-02-02 11:21:56 -05:00
What role are you adding to the target ?
2023-12-16 21:02:54 -05:00
reason : str
2024-02-02 11:21:56 -05:00
Why are you adding a role to this user ?
2024-01-06 13:31:59 -05:00
duration : str
2024-02-02 11:21:56 -05:00
How long are you adding this role for ?
2023-12-16 21:02:54 -05:00
silent : bool
Should the user be messaged ? """
2024-08-13 15:09:49 -04:00
await self . moderate (
2024-07-12 15:22:24 -04:00
ctx = interaction ,
target = target ,
silent = silent ,
permissions = [ " moderate_members " , " manage_roles " ] ,
moderation_type = type_registry [ ' addrole ' ] ,
reason = reason ,
role = role ,
duration = duration
2024-02-02 11:21:56 -05:00
)
2023-12-18 18:33:37 -05:00
2024-05-03 20:08:57 -04:00
@app_commands.command ( name = " removerole " )
async def removerole (
self ,
interaction : discord . Interaction ,
target : discord . Member ,
role : discord . Role ,
reason : str ,
2024-05-06 21:04:08 -04:00
duration : str | None = None ,
silent : bool | None = None ,
2024-05-03 20:08:57 -04:00
) :
2024-05-03 20:56:07 -04:00
""" Remove a role from a user.
2024-05-03 20:08:57 -04:00
Parameters
- - - - - - - - - - -
target : discord . Member
Who are you removing a role from ?
role : discord . Role
What role are you removing from the target ?
reason : str
Why are you removing a role from this user ?
duration : str
How long are you removing this role for ?
silent : bool
Should the user be messaged ? """
2024-08-13 15:09:49 -04:00
await self . moderate (
2024-07-12 15:22:24 -04:00
ctx = interaction ,
target = target ,
silent = silent ,
permissions = [ " moderate_members " , " manage_roles " ] ,
moderation_type = type_registry [ ' removerole ' ] ,
reason = reason ,
role = role ,
duration = duration
2024-05-03 20:08:57 -04:00
)
2023-12-14 18:35:25 -05:00
@app_commands.command ( name = " mute " )
2024-02-02 11:21:56 -05:00
async def mute (
self ,
interaction : discord . Interaction ,
target : discord . Member ,
duration : str ,
reason : str ,
2024-05-06 21:04:08 -04:00
silent : bool | None = None ,
2024-02-02 11:21:56 -05:00
) :
2023-12-14 18:35:25 -05:00
""" Mute a user.
Parameters
- - - - - - - - - - -
target : discord . Member
Who are you unbanning ?
duration : str
How long are you muting this user for ?
reason : str
Why are you unbanning this user ?
silent : bool
Should the user be messaged ? """
2024-08-13 15:09:49 -04:00
await self . moderate (
2024-07-12 15:22:24 -04:00
ctx = interaction ,
target = target ,
silent = silent ,
permissions = [ " moderate_members " ] ,
moderation_type = type_registry [ ' mute ' ] ,
duration = duration ,
reason = reason ,
2024-02-02 11:21:56 -05:00
)
2023-12-14 18:35:25 -05:00
@app_commands.command ( name = " unmute " )
2024-02-02 11:21:56 -05:00
async def unmute (
self ,
interaction : discord . Interaction ,
target : discord . Member ,
2024-05-06 21:04:08 -04:00
reason : str | None = None ,
silent : bool | None = None ,
2024-02-02 11:21:56 -05:00
) :
2023-12-14 18:35:25 -05:00
""" Unmute a user.
Parameters
- - - - - - - - - - -
target : discord . user
Who are you unmuting ?
reason : str
Why are you unmuting this user ?
silent : bool
Should the user be messaged ? """
2024-08-13 15:09:49 -04:00
await self . moderate (
2024-07-12 15:22:24 -04:00
ctx = interaction ,
target = target ,
silent = silent ,
permissions = [ " moderate_members " ] ,
moderation_type = type_registry [ ' unmute ' ] ,
reason = reason ,
2024-02-02 11:21:56 -05:00
)
2023-12-14 18:35:25 -05:00
@app_commands.command ( name = " kick " )
2024-02-02 11:21:56 -05:00
async def kick (
self ,
interaction : discord . Interaction ,
target : discord . Member ,
reason : str ,
2024-05-06 21:04:08 -04:00
silent : bool | None = None ,
2024-02-02 11:21:56 -05:00
) :
2023-12-14 18:35:25 -05:00
""" Kick a user.
Parameters
- - - - - - - - - - -
target : discord . user
Who are you kicking ?
reason : str
Why are you kicking this user ?
silent : bool
Should the user be messaged ? """
2024-08-13 15:09:49 -04:00
await self . moderate (
2024-07-12 15:22:24 -04:00
ctx = interaction ,
target = target ,
silent = silent ,
permissions = [ " kick_members " ] ,
moderation_type = type_registry [ ' kick ' ] ,
reason = reason ,
2024-02-02 11:21:56 -05:00
)
2023-12-18 18:33:37 -05:00
2023-12-14 18:35:25 -05:00
@app_commands.command ( name = " ban " )
2024-02-02 11:21:56 -05:00
@app_commands.choices (
delete_messages = [
Choice ( name = " None " , value = 0 ) ,
Choice ( name = " 1 Hour " , value = 3600 ) ,
Choice ( name = " 12 Hours " , value = 43200 ) ,
Choice ( name = " 1 Day " , value = 86400 ) ,
Choice ( name = " 3 Days " , value = 259200 ) ,
Choice ( name = " 7 Days " , value = 604800 ) ,
]
)
async def ban (
self ,
interaction : discord . Interaction ,
target : discord . User ,
reason : str ,
2024-05-06 21:04:08 -04:00
duration : str | None = None ,
delete_messages : Choice [ int ] | None = None ,
silent : bool | None = None ,
2024-02-02 11:21:56 -05:00
) :
2023-12-14 18:35:25 -05:00
""" Ban a user.
Parameters
- - - - - - - - - - -
target : discord . user
Who are you banning ?
duration : str
How long are you banning this user for ?
reason : str
Why are you banning this user ?
delete_messages : Choices [ int ]
How many days of messages to delete ?
silent : bool
Should the user be messaged ? """
if duration :
2024-08-13 15:09:49 -04:00
await self . moderate (
2024-07-12 15:22:24 -04:00
ctx = interaction ,
target = target ,
silent = silent ,
permissions = [ " ban_members " ] ,
moderation_type = type_registry [ ' tempban ' ] ,
2024-07-06 11:30:37 -04:00
reason = reason ,
duration = duration ,
delete_messages = delete_messages ,
2024-02-02 11:21:56 -05:00
)
2023-12-14 18:35:25 -05:00
else :
2024-08-13 15:09:49 -04:00
await self . moderate (
2024-07-12 15:22:24 -04:00
ctx = interaction ,
target = target ,
silent = silent ,
permissions = [ " ban_members " ] ,
moderation_type = type_registry [ ' ban ' ] ,
2024-07-06 11:30:37 -04:00
reason = reason ,
delete_messages = delete_messages ,
2024-02-02 11:21:56 -05:00
)
2023-12-18 18:33:37 -05:00
2023-12-14 18:35:25 -05:00
@app_commands.command ( name = " unban " )
2024-02-02 11:21:56 -05:00
async def unban (
self ,
interaction : discord . Interaction ,
target : discord . User ,
2024-05-06 21:04:08 -04:00
reason : str | None = None ,
silent : bool | None = None ,
2024-02-02 11:21:56 -05:00
) :
2023-12-14 18:35:25 -05:00
""" Unban a user.
Parameters
- - - - - - - - - - -
target : discord . user
Who are you unbanning ?
reason : str
Why are you unbanning this user ?
silent : bool
Should the user be messaged ? """
2024-08-13 15:09:49 -04:00
await self . moderate (
2024-07-12 15:22:24 -04:00
ctx = interaction ,
target = target ,
silent = silent ,
permissions = [ " ban_members " ] ,
moderation_type = type_registry [ ' unban ' ] ,
reason = reason ,
2024-02-02 11:21:56 -05:00
)
2023-12-18 18:33:37 -05:00
2024-05-24 03:46:20 -04:00
@app_commands.command ( name = " slowmode " )
async def slowmode (
self ,
interaction : discord . Interaction ,
interval : int ,
channel : discord . TextChannel | None = None ,
reason : str | None = None ,
) :
""" Set the slowmode of a channel.
Parameters
- - - - - - - - - - -
interval : int
The slowmode interval in seconds
channel : discord . TextChannel
The channel to set the slowmode in
reason : str
Why are you setting the slowmode ? """
if channel is None :
channel = interaction . channel
2024-08-13 15:09:49 -04:00
await self . moderate (
2024-07-12 15:22:24 -04:00
ctx = interaction ,
target = channel ,
silent = True ,
permissions = [ " manage_channel " ] ,
moderation_type = type_registry [ ' slowmode ' ] ,
interval = interval ,
reason = reason ,
2024-05-24 03:46:20 -04:00
)
2023-12-14 18:35:25 -05:00
@app_commands.command ( name = " history " )
2024-02-02 11:21:56 -05:00
async def history (
self ,
interaction : discord . Interaction ,
2024-05-06 21:04:08 -04:00
target : discord . User | None = None ,
moderator : discord . User | None = None ,
pagesize : app_commands . Range [ int , 1 , 20 ] | None = None ,
2024-02-02 11:21:56 -05:00
page : int = 1 ,
2024-08-10 13:27:49 -04:00
on : str | None = None ,
2024-08-10 13:17:16 -04:00
before : str | None = None ,
after : str | None = None ,
2024-08-13 14:25:03 -04:00
expired : bool | None = None ,
2024-08-12 18:13:04 -04:00
types : str | None = None ,
2024-05-06 21:04:08 -04:00
ephemeral : bool | None = None ,
inline : bool | None = None ,
2024-02-02 11:21:56 -05:00
export : bool = False ,
) :
2023-12-14 18:35:25 -05:00
""" List previous infractions.
Parameters
- - - - - - - - - - -
target : discord . User
User whose infractions to query , overrides moderator if both are given
moderator : discord . User
Query by moderator
pagesize : app_commands . Range [ int , 1 , 25 ]
Amount of infractions to list per page
page : int
Page to select
2024-08-10 13:27:49 -04:00
on : str
List infractions on a certain date
2024-08-10 13:17:16 -04:00
before : str
List infractions before a certain date
after : str
List infractions after a certain date
2024-08-13 14:25:03 -04:00
expired : bool
List expired or unexpired infractions
types : str
2024-08-12 18:13:04 -04:00
List infractions of specific types , comma separated
2023-12-14 18:35:25 -05:00
ephemeral : bool
Hide the command response
inline : bool
Display infractions in a grid arrangement ( does not look very good )
export : bool
2024-06-30 05:02:32 -04:00
Exports the server ' s moderation history to a JSON file " " "
2023-12-15 10:10:22 -05:00
if ephemeral is None :
2024-02-02 11:21:56 -05:00
ephemeral = (
await config . user ( interaction . user ) . history_ephemeral ( )
2023-12-18 15:57:51 -05:00
or await config . guild ( interaction . guild ) . history_ephemeral ( )
2024-02-02 11:21:56 -05:00
or False
)
2023-12-14 18:35:25 -05:00
2023-12-15 10:13:40 -05:00
if inline is None :
2024-02-02 11:21:56 -05:00
inline = (
await config . user ( interaction . user ) . history_inline ( )
2023-12-18 15:57:51 -05:00
or await config . guild ( interaction . guild ) . history_inline ( )
2024-02-02 11:21:56 -05:00
or False
)
2023-12-15 10:13:40 -05:00
2023-12-14 18:35:25 -05:00
if pagesize is None :
if inline is True :
2024-02-02 11:21:56 -05:00
pagesize = (
await config . user ( interaction . user ) . history_inline_pagesize ( )
2023-12-18 15:57:51 -05:00
or await config . guild ( interaction . guild ) . history_inline_pagesize ( )
2024-02-02 11:21:56 -05:00
or 6
)
2023-12-14 18:35:25 -05:00
else :
2024-02-02 11:21:56 -05:00
pagesize = (
await config . user ( interaction . user ) . history_pagesize ( )
2023-12-18 15:57:51 -05:00
or await config . guild ( interaction . guild ) . history_pagesize ( )
2024-02-02 11:21:56 -05:00
or 5
)
2023-12-14 18:35:25 -05:00
2024-08-10 13:27:49 -04:00
if before and not on :
try :
before = parse ( before )
except ( ParserError , OverflowError ) as e :
if e == ParserError :
await interaction . response . send_message (
content = error ( " Invalid date format for `before` parameter! " ) , ephemeral = True
)
return
if e == OverflowError :
await interaction . response . send_message (
content = error ( " Date is too far in the future! " ) , ephemeral = True
)
return
2024-08-10 13:17:16 -04:00
2024-08-10 13:27:49 -04:00
if after and not on :
try :
after = parse ( after )
except ( ParserError , OverflowError ) as e :
if e == ParserError :
await interaction . response . send_message (
content = error ( " Invalid date format for `after` parameter! " ) , ephemeral = True
)
return
if e == OverflowError :
await interaction . response . send_message (
content = error ( " Date is too far in the future! " ) , ephemeral = True
)
return
if on :
try :
on = parse ( on )
except ( ParserError , OverflowError ) as e :
if e == ParserError :
await interaction . response . send_message (
content = error ( " Invalid date format for `on` parameter! " ) , ephemeral = True
)
return
if e == OverflowError :
await interaction . response . send_message (
content = error ( " Date is too far in the future! " ) , ephemeral = True
)
return
2024-08-10 13:50:47 -04:00
before = datetime . combine ( on , datetime . max . time ( ) )
2024-08-10 13:51:17 -04:00
after = datetime . combine ( on , datetime . min . time ( ) )
2024-08-10 13:17:16 -04:00
2023-12-15 10:10:22 -05:00
await interaction . response . defer ( ephemeral = ephemeral )
2024-02-02 11:21:56 -05:00
permissions = check_permissions (
interaction . client . user , [ " embed_links " ] , interaction
)
2023-12-15 10:10:22 -05:00
if permissions :
2024-02-02 11:21:56 -05:00
await interaction . followup . send (
error (
f " I do not have the ` { permissions } ` permission, required for this action. "
) ,
ephemeral = True ,
)
2023-12-15 10:10:22 -05:00
return
2024-08-12 19:45:14 -04:00
if export and not types :
types = ' all '
2024-08-12 18:13:04 -04:00
type_list = [ ]
registry_values = type_registry . values ( )
if types :
for t in types . split ( " , " ) :
stripped = t . strip ( ) . lower ( )
if stripped == " all " :
type_list . clear ( )
2024-08-13 16:39:36 -04:00
type_list . extend ( ( t for t in registry_values ) )
2024-08-12 18:13:04 -04:00
break
try :
type_list . append ( type_registry [ stripped ] )
except RegistryKeyError :
continue
else :
for t in registry_values :
2024-08-12 18:18:34 -04:00
if await config . custom ( " types " , interaction . guild . id , t . key ) . show_in_history ( ) is True :
2024-08-12 18:13:04 -04:00
type_list . append ( t )
2024-06-30 04:56:42 -04:00
if target :
2024-07-05 19:03:44 -04:00
filename = f " moderation_target_ { str ( target . id ) } _ { str ( interaction . guild . id ) } .json "
2024-08-13 14:25:03 -04:00
moderations = await Moderation . find_by_target ( bot = interaction . client , guild_id = interaction . guild . id , target = target . id , before = before , after = after , types = type_list , expired = expired )
2024-06-30 04:56:42 -04:00
elif moderator :
2024-07-05 19:03:44 -04:00
filename = f " moderation_moderator_ { str ( moderator . id ) } _ { str ( interaction . guild . id ) } .json "
2024-08-13 14:25:03 -04:00
moderations = await Moderation . find_by_moderator ( bot = interaction . client , guild_id = interaction . guild . id , moderator = moderator . id , before = before , after = after , types = type_list , expired = expired )
2024-06-30 04:56:42 -04:00
else :
2024-07-05 19:03:44 -04:00
filename = f " moderation_ { str ( interaction . guild . id ) } .json "
2024-08-13 14:25:03 -04:00
moderations = await Moderation . get_latest ( bot = interaction . client , guild_id = interaction . guild . id , before = before , after = after , types = type_list , expired = expired )
2023-12-18 16:27:22 -05:00
if export :
try :
2024-06-30 04:56:42 -04:00
filepath = (
2024-02-02 11:21:56 -05:00
str ( data_manager . cog_data_path ( cog_instance = self ) )
+ str ( os . sep )
2024-06-30 04:56:42 -04:00
+ filename
2024-02-02 11:21:56 -05:00
)
2023-12-18 16:27:22 -05:00
2024-06-30 05:04:21 -04:00
with open ( filepath , " w " , encoding = " utf-8 " ) as f :
2024-08-22 15:59:23 -04:00
dump ( obj = moderations , fp = f )
2023-12-18 16:27:22 -05:00
2024-02-02 11:21:56 -05:00
await interaction . followup . send (
file = discord . File (
2024-06-30 04:56:42 -04:00
fp = filepath , filename = filename
2024-02-02 11:21:56 -05:00
) ,
ephemeral = ephemeral ,
)
2023-12-18 16:27:22 -05:00
2024-06-30 04:56:42 -04:00
os . remove ( filepath )
2023-12-18 16:27:22 -05:00
except json . JSONDecodeError as e :
2024-02-02 11:21:56 -05:00
await interaction . followup . send (
content = error (
" An error occured while exporting the moderation history. \n Error: \n "
)
2024-06-30 04:56:42 -04:00
+ box ( text = e , lang = " py " ) ,
2024-02-02 11:21:56 -05:00
ephemeral = ephemeral ,
)
2023-12-18 16:35:38 -05:00
return
2023-12-18 16:27:22 -05:00
2024-06-03 01:07:00 -04:00
case_quantity = len ( moderations )
2023-12-21 17:47:13 -05:00
page_quantity = ceil ( case_quantity / pagesize )
2023-12-14 18:35:25 -05:00
start_index = ( page - 1 ) * pagesize
end_index = page * pagesize
2024-01-08 01:14:48 -05:00
embed = discord . Embed ( color = await self . bot . get_embed_color ( interaction . channel ) )
2024-02-02 11:21:56 -05:00
embed . set_author ( icon_url = interaction . guild . icon . url , name = " Infraction History " )
embed . set_footer (
text = f " Page { page : , } / { page_quantity : , } | { case_quantity : , } Results "
)
2023-12-14 18:35:25 -05:00
memory_dict = { }
2024-06-03 01:07:00 -04:00
for mod in moderations [ start_index : end_index ] :
2024-05-06 15:59:43 -04:00
if mod . target_id not in memory_dict :
memory_dict . update ( {
str ( mod . target_id ) : await mod . get_target ( )
} )
target = memory_dict [ str ( mod . target_id ) ]
2024-02-02 11:21:56 -05:00
2024-05-06 15:59:43 -04:00
if mod . moderator_id not in memory_dict :
memory_dict . update ( {
str ( mod . moderator_id ) : await mod . get_moderator ( )
} )
moderator = memory_dict [ str ( mod . moderator_id ) ]
2023-12-14 18:35:25 -05:00
2024-07-06 13:16:53 -04:00
field_name = f " Case # { mod . id : , } ( { mod . type . string . title ( ) } ) "
2024-05-06 15:59:43 -04:00
field_value = f " **Target:** ` { target . name } ` ( { target . id } ) \n **Moderator:** ` { moderator . name } ` ( { moderator . id } ) "
2023-12-14 18:35:25 -05:00
2024-05-24 04:26:58 -04:00
if len ( str ( mod . reason ) ) > 125 :
2024-05-06 15:59:43 -04:00
field_value + = f " \n **Reason:** ` { str ( mod . reason ) [ : 125 ] } ...` "
2023-12-14 18:35:25 -05:00
else :
2024-05-06 15:59:43 -04:00
field_value + = f " \n **Reason:** ` { str ( mod . reason ) } ` "
if mod . duration :
2024-02-02 11:21:56 -05:00
duration_embed = (
2024-05-06 15:59:43 -04:00
f " { humanize_timedelta ( timedelta = mod . duration ) } | <t: { int ( mod . end_timestamp . timestamp ( ) ) } :R> "
if mod . expired is False
else f " { humanize_timedelta ( timedelta = mod . duration ) } | Expired "
2024-02-02 11:21:56 -05:00
)
2023-12-14 18:35:25 -05:00
field_value + = f " \n **Duration:** { duration_embed } "
2024-02-02 11:21:56 -05:00
field_value + = (
2024-05-06 15:59:43 -04:00
f " \n **Timestamp:** <t: { int ( mod . timestamp . timestamp ( ) ) } > | <t: { int ( mod . timestamp . timestamp ( ) ) } :R> "
2024-02-02 11:21:56 -05:00
)
2023-12-14 18:35:25 -05:00
2024-05-06 15:59:43 -04:00
if mod . role_id :
role = await mod . get_role ( )
field_value + = f " \n **Role:** { role . mention } ( { role . id } ) "
2024-02-02 12:05:05 -05:00
2024-05-06 15:59:43 -04:00
if mod . resolved :
2023-12-14 18:35:25 -05:00
field_value + = " \n **Resolved:** True "
embed . add_field ( name = field_name , value = field_value , inline = inline )
await interaction . followup . send ( embed = embed , ephemeral = ephemeral )
2024-08-13 01:22:18 -04:00
@history.autocomplete ( ' types ' )
2024-08-14 00:06:48 -04:00
async def _history_types ( self , interaction : discord . Interaction , current : str ) - > List [ app_commands . Choice [ str ] ] : # pylint: disable=unused-argument
2024-08-13 01:22:18 -04:00
types : List [ str ] = sorted ( self . type_registry . keys ( ) )
2024-08-14 00:06:48 -04:00
choices = [ ]
2024-08-13 01:22:18 -04:00
if current . endswith ( " , " ) :
for c in current . split ( " , " ) :
if c in types :
types . remove ( c )
for t in types :
choices . append ( app_commands . Choice ( name = current + t , value = current + t ) )
else :
2024-08-13 16:23:35 -04:00
choices . append ( app_commands . Choice ( name = " all " , value = " all " ) )
2024-08-13 01:22:18 -04:00
for t in types :
2024-08-13 16:08:16 -04:00
if t . startswith ( current ) :
choices . append ( app_commands . Choice ( name = t , value = t ) )
2024-08-13 01:22:18 -04:00
return choices [ : 25 ]
2023-12-14 18:35:25 -05:00
@app_commands.command ( name = " resolve " )
2024-02-02 11:21:56 -05:00
async def resolve (
2024-08-13 15:11:39 -04:00
self , interaction : discord . Interaction , case : int , reason : str = " No reason provided. "
2024-02-02 11:21:56 -05:00
) :
2023-12-14 18:35:25 -05:00
""" Resolve a specific case.
Parameters
- - - - - - - - - - -
2023-12-14 20:16:25 -05:00
case : int
2023-12-14 18:35:25 -05:00
Case number of the case you ' re trying to resolve
reason : str
Reason for resolving case """
2024-02-02 11:21:56 -05:00
permissions = check_permissions (
interaction . client . user ,
2024-06-03 23:46:22 -04:00
( " embed_links " , " moderate_members " , " ban_members " ) ,
2024-02-02 11:21:56 -05:00
interaction ,
)
2023-12-14 18:35:25 -05:00
if permissions :
2024-02-02 11:21:56 -05:00
await interaction . response . send_message (
error (
f " I do not have the ` { permissions } ` permission, required for this action. "
) ,
ephemeral = True ,
)
2023-12-14 18:35:25 -05:00
return
2024-05-06 15:59:43 -04:00
try :
2024-06-05 00:14:43 -04:00
moderation = await Moderation . find_by_id ( interaction . client , case , interaction . guild . id )
2024-05-06 15:59:43 -04:00
except ValueError :
2024-02-02 11:21:56 -05:00
await interaction . response . send_message (
2024-05-06 15:59:43 -04:00
content = error ( f " Case # { case : , } does not exist! " ) , ephemeral = True
2024-02-02 11:21:56 -05:00
)
2023-12-14 18:35:25 -05:00
return
2024-05-06 15:59:43 -04:00
if len ( moderation . changes ) > 25 :
2024-02-02 11:21:56 -05:00
await interaction . response . send_message (
content = error (
" Due to limitations with Discord ' s embed system, you cannot edit a case more than 25 times. "
) ,
ephemeral = True ,
)
2023-12-14 19:57:43 -05:00
return
2023-12-14 18:35:25 -05:00
2024-05-06 15:59:43 -04:00
try :
2024-07-12 15:22:24 -04:00
success , msg = await moderation . resolve ( interaction . user . id , reason )
2024-05-06 15:59:43 -04:00
except ( ValueError , TypeError ) as e :
if e == ValueError :
await interaction . response . send_message (
content = error ( " This case has already been resolved! " ) , ephemeral = True
)
elif e == TypeError :
await interaction . response . send_message (
content = error ( " This case type cannot be resolved! " ) , ephemeral = True
)
2023-12-14 18:35:25 -05:00
2024-02-02 11:21:56 -05:00
embed = await case_factory (
interaction = interaction ,
2024-05-06 16:34:08 -04:00
moderation = moderation ,
2024-02-02 11:21:56 -05:00
)
await interaction . response . send_message (
2024-07-12 15:22:24 -04:00
content = f " ✅ Moderation # { case : , } resolved! \n " + error ( f " Resolve handler returned an error message: ` { msg } ` " ) if success is False else " " , embed = embed
2024-02-02 11:21:56 -05:00
)
2024-07-12 15:22:24 -04:00
ctx = await self . bot . get_context ( interaction , cls = commands . Context )
await log ( ctx = ctx , moderation_id = case , resolved = True )
2023-12-14 18:35:25 -05:00
@app_commands.command ( name = " case " )
2024-02-02 11:21:56 -05:00
@app_commands.choices (
2024-06-30 05:02:32 -04:00
raw = [
2024-02-02 11:21:56 -05:00
Choice ( name = " Export as Codeblock " , value = " codeblock " ) ,
2024-06-30 05:02:32 -04:00
Choice ( name = " Export as File " , value = " file " ) ,
2024-02-02 11:21:56 -05:00
]
)
async def case (
self ,
interaction : discord . Interaction ,
case : int ,
2024-05-06 21:04:08 -04:00
ephemeral : bool | None = None ,
2024-02-02 11:21:56 -05:00
evidenceformat : bool = False ,
changes : bool = False ,
2024-06-08 20:12:22 -04:00
raw : Choice [ str ] | None = None ,
2024-02-02 11:21:56 -05:00
) :
2023-12-14 18:35:25 -05:00
""" Check the details of a specific case.
Parameters
- - - - - - - - - - -
2023-12-14 20:16:25 -05:00
case : int
2023-12-14 18:35:25 -05:00
What case are you looking up ?
ephemeral : bool
2023-12-14 20:00:03 -05:00
Hide the command response
2024-06-08 20:12:22 -04:00
evidenceformat : bool
Display the evidence format of the case
2023-12-14 20:16:25 -05:00
changes : bool
List the changes made to the case
2024-06-08 20:12:22 -04:00
raw : bool
2023-12-15 10:41:38 -05:00
Export the case to a JSON file or codeblock """
2024-02-02 11:21:56 -05:00
permissions = check_permissions (
interaction . client . user , [ " embed_links " ] , interaction
)
2023-12-14 18:35:25 -05:00
if permissions :
2024-02-02 11:21:56 -05:00
await interaction . response . send_message (
error (
f " I do not have the ` { permissions } ` permission, required for this action. "
) ,
ephemeral = True ,
)
2023-12-14 18:35:25 -05:00
return
2023-12-15 10:02:54 -05:00
if ephemeral is None :
2024-02-02 11:21:56 -05:00
ephemeral = (
await config . user ( interaction . user ) . history_ephemeral ( )
2023-12-18 15:57:51 -05:00
or await config . guild ( interaction . guild ) . history_ephemeral ( )
2024-02-02 11:21:56 -05:00
or False
)
2023-12-15 10:02:54 -05:00
2024-05-06 15:59:43 -04:00
try :
2024-06-05 00:14:43 -04:00
mod = await Moderation . find_by_id ( interaction . client , case , interaction . guild . id )
2024-05-06 15:59:43 -04:00
except ValueError :
await interaction . response . send_message (
content = error ( f " Case # { case : , } does not exist! " ) , ephemeral = True
)
return
2023-12-14 19:59:22 -05:00
2024-06-08 20:12:22 -04:00
if raw :
if raw . value == " file " or len ( mod . to_json ( 2 ) ) > 1800 :
2024-05-06 15:59:43 -04:00
filename = (
str ( data_manager . cog_data_path ( cog_instance = self ) )
+ str ( os . sep )
+ f " moderation_ { interaction . guild . id } _case_ { case } .json "
)
2023-12-14 19:59:22 -05:00
2024-05-06 15:59:43 -04:00
with open ( filename , " w " , encoding = " utf-8 " ) as f :
mod . to_json ( 2 , f )
2024-06-08 20:12:22 -04:00
if raw . value == " codeblock " :
2024-05-06 15:59:43 -04:00
content = f " Case # { case : , } exported. \n " + warning (
" Case was too large to export as codeblock, so it has been uploaded as a `.json` file. "
2024-02-02 11:21:56 -05:00
)
2023-12-14 19:57:43 -05:00
else :
2024-05-06 15:59:43 -04:00
content = f " Case # { case : , } exported. "
await interaction . response . send_message (
content = content ,
file = discord . File (
filename ,
f " moderation_ { interaction . guild . id } _case_ { case } .json " ,
) ,
ephemeral = ephemeral ,
)
os . remove ( filename )
2023-12-14 18:35:25 -05:00
return
2024-05-06 15:59:43 -04:00
await interaction . response . send_message (
content = box ( mod . to_json ( 2 ) , ' json ' ) ,
ephemeral = ephemeral ,
)
return
if changes :
embed = await changes_factory (
interaction = interaction , moderation = mod
)
await interaction . response . send_message (
embed = embed , ephemeral = ephemeral
)
elif evidenceformat :
2024-05-06 16:47:21 -04:00
content = await evidenceformat_factory ( moderation = mod )
2024-05-06 15:59:43 -04:00
await interaction . response . send_message (
content = content , ephemeral = ephemeral
)
else :
embed = await case_factory (
interaction = interaction , moderation = mod
)
await interaction . response . send_message (
embed = embed , ephemeral = ephemeral
)
return
2023-12-14 19:38:35 -05:00
@app_commands.command ( name = " edit " )
2024-02-02 11:21:56 -05:00
async def edit (
self ,
interaction : discord . Interaction ,
case : int ,
2024-08-19 14:33:11 -04:00
reason : str | None = None ,
2024-05-06 21:04:08 -04:00
duration : str | None = None ,
2024-02-02 11:21:56 -05:00
) :
2023-12-14 19:38:35 -05:00
""" Edit the reason of a specific case.
Parameters
- - - - - - - - - - -
2023-12-14 20:16:25 -05:00
case : int
2023-12-14 19:38:35 -05:00
What case are you editing ?
reason : str
2023-12-16 16:59:48 -05:00
What is the new reason ?
duration : str
2024-02-02 11:21:56 -05:00
What is the new duration ? Does not reapply the moderation if it has already expired .
"""
permissions = check_permissions (
interaction . client . user , [ " embed_links " ] , interaction
)
2023-12-14 19:38:35 -05:00
if permissions :
2024-02-02 11:21:56 -05:00
await interaction . response . send_message (
error (
f " I do not have the ` { permissions } ` permission, required for this action. "
) ,
ephemeral = True ,
)
2023-12-14 19:38:35 -05:00
return
2024-05-06 15:59:43 -04:00
try :
2024-06-05 00:14:43 -04:00
moderation = await Moderation . find_by_id ( interaction . client , case , interaction . guild . id )
2024-08-14 15:21:07 -04:00
old_moderation = moderation . model_copy ( )
2024-05-06 15:59:43 -04:00
except ValueError :
await interaction . response . send_message (
content = error ( f " Case # { case : , } does not exist! " ) , ephemeral = True
)
return
if len ( moderation . changes ) > 25 :
return await interaction . response . send_message (
content = error (
" Due to limitations with Discord ' s embed system, you cannot edit a case more than 25 times. "
) ,
ephemeral = True ,
)
if duration :
2024-08-19 14:33:11 -04:00
try :
parsed_time = parse_relativedelta ( argument = duration )
if parsed_time is None :
raise commands . BadArgument ( )
2024-08-19 14:37:47 -04:00
moderation . duration = timedelta_from_relativedelta ( relativedelta = parsed_time )
2024-08-19 14:33:11 -04:00
except ( commands . BadArgument , ValueError ) :
2024-05-06 15:59:43 -04:00
return await interaction . response . send_message (
error ( " Please provide a valid duration! " ) , ephemeral = True
)
2024-08-14 15:57:53 -04:00
moderation . end_timestamp = moderation . timestamp + timedelta ( seconds = moderation . duration . total_seconds ( ) )
2024-05-06 15:59:43 -04:00
2024-07-12 15:22:24 -04:00
try :
2024-08-14 15:59:09 -04:00
success = await moderation . type . duration_edit_handler ( interaction = interaction , old_moderation = old_moderation , new_moderation = moderation )
2024-07-12 15:22:24 -04:00
except NotImplementedError :
return await interaction . response . send_message (
error ( " This case type does not support duration editing! " ) , ephemeral = True
)
if not success :
return
2024-05-06 15:59:43 -04:00
if reason :
moderation . reason = reason
2024-08-19 14:33:11 -04:00
if not reason and not duration :
return await interaction . response . send_message (
error ( " Please provide a new reason or duration to edit this case! " ) , ephemeral = True
)
2024-05-06 15:59:43 -04:00
if not moderation . changes :
moderation . changes . append ( Change . from_dict ( interaction . client , {
" type " : " ORIGINAL " ,
" timestamp " : old_moderation . timestamp ,
" reason " : old_moderation . reason ,
" user_id " : old_moderation . moderator_id ,
2024-08-19 17:21:33 -04:00
" duration " : timedelta_to_string ( old_moderation . duration ) if old_moderation . duration else None ,
2024-05-06 15:59:43 -04:00
" end_timestamp " : old_moderation . end_timestamp ,
} ) )
2024-08-19 14:33:11 -04:00
moderation . changes . append ( Change . from_dict ( interaction . client , {
" type " : " EDIT " ,
" timestamp " : int ( time . time ( ) ) ,
2024-08-19 17:19:01 -04:00
" reason " : reason if reason else None ,
2024-08-19 14:33:11 -04:00
" user_id " : interaction . user . id ,
2024-08-19 17:19:01 -04:00
" duration " : timedelta_to_string ( moderation . duration ) if duration else None ,
" end_timestamp " : moderation . end_timestamp if duration else None ,
2024-08-19 14:33:11 -04:00
} ) )
2024-05-06 15:59:43 -04:00
2024-06-05 00:14:43 -04:00
await moderation . update ( )
2024-05-06 15:59:43 -04:00
embed = await case_factory ( interaction = interaction , moderation = moderation )
2023-12-14 19:38:35 -05:00
2024-02-02 11:21:56 -05:00
await interaction . response . send_message (
2024-05-06 15:59:43 -04:00
content = f " ✅ Moderation # { case : , } edited! " ,
embed = embed ,
ephemeral = True ,
2024-02-02 11:21:56 -05:00
)
2024-08-14 15:23:40 -04:00
await log ( await self . bot . get_context ( interaction ) , case )
2024-05-06 15:59:43 -04:00
return
2023-12-14 18:35:25 -05:00
@tasks.loop ( minutes = 1 )
async def handle_expiry ( self ) :
2024-04-08 06:24:34 -04:00
await self . bot . wait_until_red_ready ( )
2024-01-01 12:14:53 -05:00
current_time = time . time ( )
2024-05-03 20:38:07 -04:00
global_unban_num = 0
global_addrole_num = 0
global_removerole_num = 0
2024-07-12 15:22:24 -04:00
global_other_num = 0
2023-12-14 18:35:25 -05:00
guilds : list [ discord . Guild ] = self . bot . guilds
for guild in guilds :
if not await self . bot . cog_disabled_in_guild ( self , guild ) :
2024-01-01 12:14:53 -05:00
time_per_guild = time . time ( )
2023-12-14 18:35:25 -05:00
2024-07-12 15:22:24 -04:00
query = f " SELECT * FROM moderation_ { guild . id } WHERE end_timestamp IS NOT NULL AND end_timestamp <= ? AND expired = 0 "
moderations = await Moderation . execute ( bot = self . bot , guild_id = guild . id , query = query , parameters = ( time . time ( ) , ) )
2023-12-14 18:35:25 -05:00
2024-05-03 20:37:01 -04:00
unban_num = 0
removerole_num = 0
addrole_num = 0
2024-07-12 15:22:24 -04:00
other_num = 0
for moderation in moderations :
2023-12-14 18:35:25 -05:00
try :
2024-08-13 01:26:50 -04:00
num = await moderation . type . expiry_handler ( moderation )
2024-07-12 15:22:24 -04:00
except NotImplementedError :
logger . warning ( " Expiry handler not implemented for expirable moderation type %s " , moderation . type . key )
2023-12-14 18:35:25 -05:00
continue
2024-09-08 12:40:38 -04:00
except Exception as e : # pylint: disable=broad-except
logger . exception ( " Expiry handler failed for moderation %s with the type %s " , moderation . id , moderation . type . key , exc_info = e )
continue
2024-07-12 15:22:24 -04:00
match moderation . type . key :
case " tempban " :
unban_num + = num
case " addrole " :
removerole_num + = num
case " removerole " :
addrole_num + = num
case _ :
other_num + = num if isinstance ( num , int ) else 0
2023-12-14 18:35:25 -05:00
2024-05-04 17:15:21 -04:00
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); "
2024-06-05 01:31:40 -04:00
await Moderation . execute ( bot = self . bot , guild_id = guild . id , query = expiry_query , parameters = ( time . time ( ) , ) , return_obj = False )
2024-05-03 20:09:48 -04:00
2024-01-01 12:14:53 -05:00
per_guild_completion_time = ( time . time ( ) - time_per_guild ) * 1000
2024-02-02 11:21:56 -05:00
logger . debug (
2024-07-12 15:22:24 -04:00
" Completed expiry loop for %s ( %s ) in %s ms with %s users unbanned, %s roles added, and %s roles removed ( %s other cases expired) " ,
2024-02-02 11:21:56 -05:00
guild . name ,
guild . id ,
f " { per_guild_completion_time : .6f } " ,
2024-05-03 20:37:01 -04:00
unban_num ,
addrole_num ,
removerole_num ,
2024-07-12 15:22:24 -04:00
other_num
2024-02-02 11:21:56 -05:00
)
2024-05-03 20:38:07 -04:00
global_unban_num = global_unban_num + unban_num
global_addrole_num = global_addrole_num + addrole_num
global_removerole_num = global_removerole_num + removerole_num
2024-07-12 15:22:24 -04:00
global_other_num = global_other_num + other_num
2024-01-01 12:14:53 -05:00
2023-12-14 18:35:25 -05:00
2024-01-01 12:14:53 -05:00
completion_time = ( time . time ( ) - current_time ) * 1000
2024-02-02 11:21:56 -05:00
logger . debug (
2024-07-12 15:22:24 -04:00
" Completed expiry loop in %s ms with %s users unbanned, %s roles added, and %s roles removed ( %s other cases expired) " ,
2024-02-02 11:21:56 -05:00
f " { completion_time : .6f } " ,
2024-05-03 20:38:07 -04:00
global_unban_num ,
global_addrole_num ,
global_removerole_num ,
2024-07-12 15:22:24 -04:00
global_other_num
2024-02-02 11:21:56 -05:00
)
2024-01-01 12:14:53 -05:00
2024-02-02 11:21:56 -05:00
########################################################################################################################
### Configuration Commands #
########################################################################################################################
2024-01-16 09:23:45 -05:00
@commands.group ( autohelp = True , aliases = [ " moderation " , " mod " ] )
async def aurora ( self , ctx : commands . Context ) :
""" Settings and miscellaneous commands for Aurora. """
@aurora.group ( autohelp = True , name = " settings " , aliases = [ " config " , " options " , " set " ] )
async def aurora_settings ( self , ctx : commands . Context ) :
""" Configure Aurora ' s settings. """
@aurora_settings.command ( name = " overrides " , aliases = [ " override " , " user " ] )
async def aurora_settings_overrides ( self , ctx : commands . Context ) :
""" Manage Aurora ' s user overriddable settings. """
2024-05-03 22:20:53 -04:00
msg = await ctx . send ( embed = await overrides_embed ( ctx ) )
2024-05-03 22:21:28 -04:00
await msg . edit ( view = Overrides ( ctx , msg , 60 ) )
2024-01-16 09:23:45 -05:00
@aurora_settings.command ( name = " guild " , aliases = [ " server " ] )
@commands.admin_or_permissions ( manage_guild = True )
@commands.guild_only ( )
async def aurora_settings_guild ( self , ctx : commands . Context ) :
""" Manage Aurora ' s guild settings. """
2024-05-03 22:20:53 -04:00
msg = await ctx . send ( embed = await guild_embed ( ctx ) )
2024-05-03 22:21:28 -04:00
await msg . edit ( view = Guild ( ctx , msg , 60 ) )
2024-01-16 09:23:45 -05:00
2024-08-12 17:39:13 -04:00
@aurora_settings.command ( name = " type " )
@commands.admin_or_permissions ( manage_guild = True )
@commands.guild_only ( )
async def aurora_settings_type ( self , ctx : commands . Context , moderation_type : str ) :
""" Manage configuration options for specific moderation types.
2024-08-12 17:48:01 -04:00
See [ the documentation ] ( https : / / seacogs . coastalcommits . com / Aurora / Types ) for a list of built - in moderation types , or run this command with a junk argument ( ` awasd ` or something ) to see a list of valid types . """
try :
registered_type = type_registry . get ( moderation_type )
except RegistryKeyError :
2024-08-12 17:39:13 -04:00
types = " `, ` " . join ( type_registry . keys ( ) )
2024-08-12 17:49:51 -04:00
await ctx . send ( error ( f " ` { moderation_type } ` is not a valid moderation type. \n Valid types are: \n ` { types } ` " ) )
2024-08-12 17:39:13 -04:00
return
msg = await ctx . send ( embed = await type_embed ( ctx , registered_type ) )
await msg . edit ( view = Types ( ctx , msg , registered_type ) )
2024-01-16 09:23:45 -05:00
@aurora_settings.command ( name = " addrole " , aliases = [ " removerole " ] )
@commands.admin_or_permissions ( manage_guild = True )
@commands.guild_only ( )
async def aurora_settings_addrole ( self , ctx : commands . Context ) :
""" Manage the addrole whitelist.
Roles added to this list are also applied to ` / removerole ` . """
2024-05-03 22:20:53 -04:00
msg = await ctx . send ( embed = await addrole_embed ( ctx ) )
2024-05-03 22:21:28 -04:00
await msg . edit ( view = Addrole ( ctx , msg , 60 ) )
2024-01-16 09:23:45 -05:00
@aurora_settings.command ( name = " immunity " )
@commands.admin_or_permissions ( manage_guild = True )
@commands.guild_only ( )
async def aurora_settings_immunity ( self , ctx : commands . Context ) :
""" Manage the immunity whitelist. """
2024-05-03 22:20:53 -04:00
msg = await ctx . send ( embed = await immune_embed ( ctx ) )
2024-05-03 22:21:28 -04:00
await msg . edit ( view = Immune ( ctx , msg , 60 ) )
2024-01-16 09:23:45 -05:00
@aurora.group ( autohelp = True , name = " import " )
@commands.admin ( )
@commands.guild_only ( )
async def aurora_import ( self , ctx : commands . Context ) :
""" Import moderation history from other bots. """
@aurora_import.command ( name = " aurora " )
@commands.admin ( )
async def aurora_import_aurora ( self , ctx : commands . Context ) :
""" Import moderation history from another bot using Aurora. """
if (
ctx . message . attachments
and ctx . message . attachments [ 0 ] . content_type
== " application/json; charset=utf-8 "
) :
2024-08-12 18:50:07 -04:00
file = await ctx . message . attachments [ 0 ] . read ( )
data : list [ dict ] = sorted ( json . loads ( file ) , key = lambda x : x [ " moderation_id " ] )
2024-01-16 09:23:45 -05:00
message = await ctx . send (
warning (
" Are you sure you want to import moderations from another bot? \n **This will overwrite any moderations that already exist in this guild ' s moderation table.** \n *The import process will block the rest of your bot until it is complete.* "
)
)
2024-08-12 18:50:07 -04:00
await message . edit ( view = ImportAuroraView ( 60 , ctx , message , data ) )
2024-01-16 09:23:45 -05:00
else :
await ctx . send ( error ( " Please provide a valid Aurora export file. " ) )
@aurora_import.command ( name = " galacticbot " )
@commands.admin ( )
async def aurora_import_galacticbot ( self , ctx : commands . Context ) :
""" Import moderation history from GalacticBot. """
if (
ctx . message . attachments
and ctx . message . attachments [ 0 ] . content_type
== " application/json; charset=utf-8 "
) :
message = await ctx . send (
warning (
" Are you sure you want to import GalacticBot moderations? \n **This will overwrite any moderations that already exist in this guild ' s moderation table.** \n *The import process will block the rest of your bot until it is complete.* "
)
)
await message . edit ( view = ImportGalacticBotView ( 60 , ctx , message ) )
else :
await ctx . send (
error ( " Please provide a valid GalacticBot moderation export file. " )
)
2024-08-12 20:46:40 -04:00
@aurora.group ( autohelp = True , name = " convert " )
async def aurora_convert ( self , ctx : commands . Context ) :
""" Convert strings to various Python objects. """
@aurora_convert.command ( aliases = [ " dt " ] )
2024-08-12 20:44:12 -04:00
async def datetime ( self , ctx : commands . Context , * , date : str ) - > None :
""" Convert a string to a datetime object.
This command converts a date to a [ ` datetime ` ] ( https : / / docs . python . org / 3 / library / datetime . html #datetime.datetime) Python object.
* * Example usage * *
` [ p ] aurora datetime 08 / 20 / 2024 `
* * Output * *
` 2024 - 08 - 20 12 : 00 : 00 ` """
try :
parsed_date = parse ( date )
await ctx . send ( f " ` { parsed_date } ` " )
except ( ParserError , OverflowError ) as e :
if e == ParserError :
await ctx . send ( error ( " Invalid date format! " ) )
if e == OverflowError :
await ctx . send ( error ( " Date is too far in the future! " ) )
2024-08-12 20:46:40 -04:00
@aurora_convert.command ( aliases = [ " td " ] )
2024-03-08 14:19:48 -05:00
async def timedelta ( self , ctx : commands . Context , * , duration : str ) - > None :
2024-03-08 14:21:26 -05:00
""" Convert a string to a timedelta.
This command converts a duration to a [ ` timedelta ` ] ( https : / / docs . python . org / 3 / library / datetime . html #datetime.timedelta) Python object.
2024-03-08 14:19:48 -05:00
You cannot convert years or months as they are not fixed units . Use ` [ p ] aurora relativedelta ` for that .
2023-12-14 18:35:25 -05:00
* * Example usage * *
2024-01-16 09:29:57 -05:00
` [ p ] aurora timedelta 1 day 15 hr 82 minutes 52 s `
2023-12-14 18:35:25 -05:00
* * Output * *
` 1 day , 16 : 22 : 52 ` """
2024-03-08 14:19:48 -05:00
parsed_time = parse_timedelta ( duration )
if parsed_time is None :
await ctx . send ( error ( " Please provide a convertible value! " ) )
2024-03-08 14:27:50 -05:00
return
2024-03-08 14:19:48 -05:00
await ctx . send ( f " ` { parsed_time } ` " )
2024-08-12 20:46:40 -04:00
@aurora_convert.command ( aliases = [ " rd " ] )
2024-03-08 14:19:48 -05:00
async def relativedelta ( self , ctx : commands . Context , * , duration : str ) - > None :
2024-03-08 14:21:26 -05:00
""" Convert a string to a relativedelta.
This command converts a duration to a [ ` relativedelta ` ] ( https : / / dateutil . readthedocs . io / en / stable / relativedelta . html ) Python object .
2024-03-08 14:19:48 -05:00
* * Example usage * *
` [ p ] aurora relativedelta 3 years 1 day 15 hr 82 minutes 52 s `
* * Output * *
` relativedelta ( years = + 3 , days = + 1 , hours = + 15 , minutes = + 82 , seconds = + 52 ) ` """
parsed_time = parse_relativedelta ( duration )
if parsed_time is None :
2024-01-05 04:21:05 -05:00
await ctx . send ( error ( " Please provide a convertible value! " ) )
2024-03-08 14:27:50 -05:00
return
2024-03-08 14:19:48 -05:00
await ctx . send ( f " ` { parsed_time } ` " )
2024-08-22 18:47:17 -04:00
@aurora.command ( name = " info " )
async def aurora_info ( self , ctx : commands . Context ) :
""" Get information about Aurora. """
embed = discord . Embed (
title = " Aurora Information " ,
color = await self . bot . get_embed_color ( ctx . channel ) ,
timestamp = datetime . now ( ) ,
)
embed . set_thumbnail ( url = self . bot . user . avatar . url )
embed . add_field (
name = " Version " ,
value = f " [ { self . __version__ } ](https://www.coastalcommits.com/cswimr/SeaCogs) "
)
embed . add_field (
name = " Author " ,
value = ' , ' . join ( self . __author__ )
)
if ctx . author . id in self . bot . owner_ids :
results = await Moderation . execute ( query = " SELECT name FROM sqlite_master WHERE type= ' table ' ; " , return_obj = False )
tables = [ table [ 0 ] for table in results ]
table_count = len ( tables )
row_count = 0
for table in tables :
count_query = f " SELECT COUNT() FROM { table } "
result = await Moderation . execute ( query = count_query , return_obj = False )
row_count + = result [ 0 ] [ 0 ]
filesize = os . path . getsize ( str ( data_manager . cog_data_path ( cog_instance = self ) / " aurora.db " ) ) / 1024
embed . add_field (
name = " Database Stats " ,
value = f " { bold ( ' Table Count: ' ) } { table_count : , } \n { bold ( ' Row Count: ' ) } { row_count : , } \n { bold ( ' File Size: ' ) } { filesize : ,.0f } KB " ,
)
embed . add_field (
name = " Moderation Types " ,
value = f " { len ( type_registry ) } registered types \n { box ( ' , ' . join ( type_registry . keys ( ) ) ) } " ,
inline = False
)
await ctx . send ( embed = embed )