2024-02-28 10:58:57 -05:00
# pylint: disable=cyclic-import
2024-05-04 16:54:12 -04:00
from datetime import datetime , timedelta
2024-06-03 23:46:22 -04:00
from typing import Optional , Tuple , Union
2023-12-18 17:24:40 -05:00
2024-06-08 20:12:22 -04:00
import aiosqlite
2024-03-08 14:19:48 -05:00
from dateutil . relativedelta import relativedelta as rd
2024-05-24 03:49:55 -04:00
from discord import File , Guild , Interaction , Member , SelectOption , TextChannel , User
2024-05-06 16:34:08 -04:00
from discord . errors import Forbidden
2024-05-03 21:35:29 -04:00
from redbot . core import commands , data_manager
2024-01-05 04:21:05 -05:00
from redbot . core . utils . chat_formatting import error
2023-12-18 17:24:40 -05:00
2024-08-12 18:13:04 -04:00
from . . models . type import Type
2024-05-06 21:39:43 -04:00
from . . utilities . config import config
2024-06-08 20:12:22 -04:00
from . . utilities . json import dumps
from . . utilities . logger import logger
2023-12-17 02:16:44 -05:00
def check_permissions (
user : User ,
2024-06-03 23:46:22 -04:00
permissions : Tuple [ str ] ,
2024-07-06 11:30:37 -04:00
ctx : commands . Context | Interaction | None = None ,
2024-05-06 21:04:08 -04:00
guild : Guild | None = None ,
2024-02-13 18:02:13 -05:00
) - > Union [ bool , str ] :
2023-12-17 02:16:44 -05:00
""" Checks if a user has a specific permission (or a list of permissions) in a channel. """
if ctx :
member = ctx . guild . get_member ( user . id )
resolved_permissions = ctx . channel . permissions_for ( member )
elif guild :
member = guild . get_member ( user . id )
resolved_permissions = member . guild_permissions
else :
raise ( KeyError )
for permission in permissions :
if (
not getattr ( resolved_permissions , permission , False )
2024-02-14 10:35:57 -05:00
and resolved_permissions . administrator is not True
2023-12-17 02:16:44 -05:00
) :
return permission
return False
async def check_moddable (
2024-08-12 18:13:04 -04:00
target : Union [ User , Member , TextChannel ] , ctx : commands . Context , permissions : Tuple [ str ] , moderation_type : Type ,
2024-02-13 18:02:13 -05:00
) - > bool :
2023-12-17 02:16:44 -05:00
""" Checks if a moderator can moderate a target. """
2024-05-24 03:49:55 -04:00
is_channel = isinstance ( target , TextChannel )
2024-08-12 18:13:04 -04:00
use_discord_permissions = await config . custom ( " types " , ctx . guild . id , moderation_type . key ) . use_discord_permissions ( )
if use_discord_permissions is None :
use_discord_permissions = await config . guild ( ctx . guild ) . use_discord_permissions ( )
2024-07-06 11:30:37 -04:00
if check_permissions ( ctx . bot . user , permissions , guild = ctx . guild ) :
await ctx . send (
2024-02-02 11:22:08 -05:00
error (
f " I do not have the ` { permissions } ` permission, required for this action. "
) ,
2023-12-17 02:16:44 -05:00
ephemeral = True ,
)
return False
2024-08-12 18:13:04 -04:00
if use_discord_permissions is True :
2024-07-06 11:30:37 -04:00
if check_permissions ( ctx . author , permissions , guild = ctx . guild ) :
await ctx . send (
2024-02-02 11:22:08 -05:00
error (
f " You do not have the ` { permissions } ` permission, required for this action. "
) ,
2023-12-17 02:16:44 -05:00
ephemeral = True ,
)
return False
2024-07-06 11:30:37 -04:00
if ctx . author . id == target . id :
await ctx . send (
2023-12-17 02:16:44 -05:00
content = " You cannot moderate yourself! " , ephemeral = True
)
return False
2024-05-24 03:49:55 -04:00
if not is_channel and target . bot :
2024-07-06 11:30:37 -04:00
await ctx . send (
2023-12-17 02:16:44 -05:00
content = " You cannot moderate bots! " , ephemeral = True
)
return False
if isinstance ( target , Member ) :
2024-07-06 11:30:37 -04:00
if ctx . author . top_role < = target . top_role and await config . guild ( ctx . guild ) . respect_hierarchy ( ) is True :
await ctx . send (
2024-02-02 11:22:08 -05:00
content = error (
" You cannot moderate members with a higher role than you! "
) ,
2023-12-17 02:16:44 -05:00
ephemeral = True ,
)
return False
if (
2024-07-06 11:30:37 -04:00
ctx . guild . get_member ( ctx . bot . user . id ) . top_role
2023-12-17 02:16:44 -05:00
< = target . top_role
) :
2024-07-06 11:30:37 -04:00
await ctx . send (
2024-02-02 11:22:08 -05:00
content = error (
" You cannot moderate members with a role higher than the bot! "
) ,
2023-12-17 02:16:44 -05:00
ephemeral = True ,
)
return False
immune_roles = await config . guild ( target . guild ) . immune_roles ( )
for role in target . roles :
if role . id in immune_roles :
2024-07-06 11:30:37 -04:00
await ctx . send (
2024-01-05 04:21:05 -05:00
content = error ( " You cannot moderate members with an immune role! " ) ,
2023-12-17 02:16:44 -05:00
ephemeral = True ,
)
return False
return True
2024-07-06 11:30:37 -04:00
async def log ( ctx : commands . Context , moderation_id : int , resolved : bool = False ) - > None :
2023-12-18 18:33:37 -05:00
""" This function sends a message to the guild ' s configured logging channel when an infraction takes place. """
2024-05-06 21:39:43 -04:00
from . . models . moderation import Moderation
from . factory import log_factory
2023-12-17 02:36:18 -05:00
2024-07-06 11:30:37 -04:00
logging_channel_id = await config . guild ( ctx . guild ) . log_channel ( )
2023-12-17 02:16:44 -05:00
if logging_channel_id != " " :
2024-07-06 11:30:37 -04:00
logging_channel = ctx . guild . get_channel ( logging_channel_id )
2023-12-17 02:16:44 -05:00
2024-05-06 16:34:08 -04:00
try :
2024-07-06 11:47:56 -04:00
moderation = await Moderation . find_by_id ( ctx . bot , moderation_id , ctx . guild . id )
2024-02-02 11:22:08 -05:00
embed = await log_factory (
2024-07-06 11:30:37 -04:00
ctx = ctx , moderation = moderation , resolved = resolved
2024-02-02 11:22:08 -05:00
)
2023-12-17 02:16:44 -05:00
try :
await logging_channel . send ( embed = embed )
except Forbidden :
return
2024-05-06 16:34:08 -04:00
except ValueError :
return
2023-12-18 18:33:37 -05:00
2024-02-02 11:22:08 -05:00
2024-07-06 11:30:37 -04:00
async def send_evidenceformat ( ctx : commands . Context , moderation_id : int ) - > None :
2023-12-18 18:33:37 -05:00
""" This function sends an ephemeral message to the moderator who took the moderation action, with a pre-made codeblock for use in the mod-evidence channel. """
2024-05-06 21:39:43 -04:00
from . . models . moderation import Moderation
from . factory import evidenceformat_factory
2023-12-18 18:33:37 -05:00
2024-02-02 11:22:08 -05:00
send_evidence_bool = (
2024-07-06 11:30:37 -04:00
await config . user ( ctx . author ) . auto_evidenceformat ( )
or await config . guild ( guild = ctx . guild ) . auto_evidenceformat ( )
2024-02-02 11:22:08 -05:00
or False
)
2024-07-06 18:58:04 -04:00
if send_evidence_bool is True :
moderation = await Moderation . find_by_id ( ctx . bot , moderation_id , ctx . guild . id )
content = await evidenceformat_factory ( moderation = moderation )
if not ctx . interaction :
await ctx . author . send ( content = content )
else :
await ctx . send ( content = content , ephemeral = True )
2023-12-30 04:10:25 -05:00
2024-02-02 11:22:08 -05:00
2024-04-05 10:42:13 -04:00
def get_bool_emoji ( value : Optional [ bool ] ) - > str :
2024-01-16 09:23:45 -05:00
""" Returns a unicode emoji based on a boolean value. """
2024-06-04 00:04:46 -04:00
match value :
case True :
return " \N{WHITE HEAVY CHECK MARK} "
case False :
return " \N{NO ENTRY SIGN} "
case _ :
return " \N{BLACK QUESTION MARK ORNAMENT} \N{VARIATION SELECTOR-16} "
2024-01-16 09:23:45 -05:00
2024-02-02 11:22:08 -05:00
2024-01-16 09:23:45 -05:00
def get_pagesize_str ( value : Union [ int , None ] ) - > str :
""" Returns a string based on a pagesize value. """
if value is None :
return " \N{BLACK QUESTION MARK ORNAMENT} \N{VARIATION SELECTOR-16} "
return str ( value ) + " cases per page "
2024-02-02 11:22:08 -05:00
2024-01-16 09:23:45 -05:00
def create_pagesize_options ( ) - > list [ SelectOption ] :
""" Returns a list of SelectOptions for pagesize configuration. """
options = [ ]
options . append (
SelectOption (
label = " Default " ,
value = " default " ,
description = " Reset the pagesize to the default value. " ,
)
)
for i in range ( 1 , 21 ) :
options . append (
SelectOption (
label = str ( i ) ,
value = str ( i ) ,
description = f " Set the pagesize to { i } . " ,
)
)
return options
2024-03-08 14:19:48 -05:00
2024-05-04 16:54:12 -04:00
def timedelta_from_relativedelta ( relativedelta : rd ) - > timedelta :
2024-03-08 14:19:48 -05:00
""" Converts a relativedelta object to a timedelta object. """
now = datetime . now ( )
then = now - relativedelta
return now - then
2024-05-03 21:35:29 -04:00
2024-06-04 23:31:52 -04:00
def timedelta_from_string ( string : str ) - > timedelta :
""" Converts a string to a timedelta object. """
hours , minutes , seconds = map ( int , string . split ( " : " ) )
return timedelta ( hours = hours , minutes = minutes , seconds = seconds )
2024-06-04 23:55:55 -04:00
def timedelta_to_string ( td : timedelta ) - > str :
2024-06-04 15:22:50 -04:00
""" Converts a timedelta object to a string. """
2024-06-04 23:55:55 -04:00
days = td . days * 24
hours , remainder = divmod ( td . seconds , 3600 )
2024-06-04 15:22:50 -04:00
minutes , seconds = divmod ( remainder , 60 )
2024-06-04 23:43:53 -04:00
return f " { days + hours } : { minutes : 02 } : { seconds : 02 } "
2024-06-04 15:22:50 -04:00
2024-05-03 21:35:29 -04:00
def get_footer_image ( coginstance : commands . Cog ) - > File :
""" Returns the footer image for the embeds. """
image_path = data_manager . bundled_data_path ( coginstance ) / " arrow.png "
return File ( image_path , filename = " arrow.png " , description = " arrow " )
2024-06-08 20:12:22 -04:00
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 )