def remove_ignored_item(self, item: int, ignore_type: str) -> None: """ Remove an item from the relevant ignore list Parameters ---------- item : int The id of the thing to unignore ignore_type : str A string representation of the ignored items overall container Raises ====== BaseASHException Invalid ignore ignore_type ValueError item is not of ignore_type int or int convertible Notes ===== This will silently ignore any attempts to remove an item not ignored. """ try: ignore_type = ignore_type.lower() except: raise ValueError("Expeced ignore_type of type: str") try: # TODO Handle more then just ints, take relevant objs as well if not isinstance(item, int): item = int(item) except ValueError: raise ValueError("Expected item of type: int") if ignore_type == "member": if item in self.options["ignore_users"]: index = self.options["ignore_users"].index(item) self.options["ignore_users"].pop(index) elif ignore_type == "channel": if item in self.options["ignore_channels"]: index = self.options["ignore_channels"].index(item) self.options["ignore_channels"].pop(index) elif ignore_type == "perm": if item in self.options["ignore_perms"]: index = self.options["ignore_perms"].index(item) self.options["ignore_perms"].pop(index) elif ignore_type == "guild": if item in self.options["ignore_guilds"]: index = self.options["ignore_guilds"].index(item) self.options["ignore_guilds"].pop(index) elif ignore_type == "role": if item in self.options["ignore_roles"]: index = self.options["ignore_roles"].index(item) self.options["ignore_roles"].pop(index) else: raise BaseASHException("Invalid ignore ignore_type") log.debug(f"Un-Ignored {ignore_type}: {item}")
def add_ignored_item(self, item: int, ignore_type: str) -> None: """ TODO Document this better with ignore_type notations Add an item to the relevant ignore list Parameters ---------- item : int The id of the thing to ignore ignore_type : str A string representation of the ignored items overall container Raises ====== BaseASHException Invalid ignore ignore_type ValueError item is not of ignore_type int or int convertible Notes ===== This will silently ignore any attempts to add an item already added. """ try: ignore_type = ignore_type.lower() except: raise ValueError("Expeced ignore_type of type: str") try: if not isinstance(item, int): item = int(item) except ValueError: raise ValueError("Expected item of type: int") if ignore_type == "member": if item not in self.options["ignore_users"]: self.options["ignore_users"].append(item) elif ignore_type == "channel": if item not in self.options["ignore_channels"]: self.options["ignore_channels"].append(item) elif ignore_type == "perm": if item not in self.options["ignore_perms"]: self.options["ignore_perms"].append(item) elif ignore_type == "guild": if item not in self.options["ignore_guilds"]: self.options["ignore_guilds"].append(item) elif ignore_type == "role": if item not in self.options["ignore_roles"]: self.options["ignore_roles"].append(item) else: raise BaseASHException("Invalid ignore ignore_type") log.debug(f"Ignored {ignore_type}: {item}")
def get_guild_options(self, guild_id: int) -> tuple: """ Get the options dictionary for a given guild, if the guild doesnt exist raise an exception Parameters ---------- guild_id : int The guild to get custom options for Returns ------- tuple The options for this guild as tuple[0] and tuple[1] is a bool which is used to say if the guild has custom options or not Be wary of the return value. It is in the format, (dict, boolean), where dict is the options and boolean is whether or not these options are custom Raises ------ BaseASHException This guild does not exist Notes ----- The value for tuple[1] is not checked/ensured at runtime. Be wary of this if you access guild and manually change options rather then using this libraries methods. Another thing to note is this returns a deepcopy of the options dictionary. This is to encourage usage of this libraries methods for changing options, rather then playing around with them yourself and potentially doing damage. """ guild = Guild(self.bot, guild_id, self.options) try: guild = next(iter(g for g in self.guilds if g == guild)) except StopIteration: raise BaseASHException("This guild does not exist") else: log.debug(f"Returned guild options for {guild_id}") return deepcopy(guild.options), guild.has_custom_options
def AddIgnoredItem(self, item: int, type: str) -> None: """ Add an item to the relevant ignore list Parameters ---------- item : int The id of the thing to ignore type : str A string representation of the ignored items overall container Raises ====== BaseASHException Invalid ignore type ValueError item is not of type int or int convertible Notes ===== This will silently ignore any attempts to add an item already added. """ type = type.lower() if not isinstance(item, int): item = int(item) if type == "user": if item not in self.options["ignoreUsers"]: self.options["ignoreUsers"].append(item) elif type == "channel": if item not in self.options["ignoreChannels"]: self.options["ignoreChannels"].append(item) elif type == "perm": if item not in self.options["ignorePerms"]: self.options["ignorePerms"].append(item) elif type == "guild": if item not in self.options["ignoreGuilds"]: self.options["ignoreGuilds"].append(item) elif type == "role": if item not in self.options["ignoreRoles"]: self.options["ignoreRoles"].append(item) else: raise BaseASHException("Invalid ignore type") self.logger.debug(f"Ignored {type}: {item}")
def RemoveIgnoredItem(self, item: int, type: str) -> None: """ Remove an item from the relevant ignore list Parameters ---------- item : int The id of the thing to unignore type : str A string representation of the ignored items overall container Raises ====== BaseASHException Invalid ignore type ValueError item is not of type int or int convertible Notes ===== This will silently ignore any attempts to remove an item not ignored. """ type = type.lower() if not isinstance(item, int): item = int(item) if type == "user": if item in self.options["ignoreUsers"]: index = self.options["ignoreUsers"].index(item) self.options["ignoreUsers"].pop(index) elif type == "channel": if item in self.options["ignoreChannels"]: index = self.options["ignoreChannels"].index(item) self.options["ignoreChannels"].pop(index) elif type == "perm": if item in self.options["ignorePerms"]: index = self.options["ignorePerms"].index(item) self.options["ignorePerms"].pop(index) else: raise BaseASHException("Invalid ignore type") self.logger.debug(f"Un-Ignored {type}: {item}")
def _ensure_options( self, warn_threshold=None, kick_threshold=None, ban_threshold=None, message_interval=None, guild_warn_message=None, guild_kick_message=None, guild_ban_message=None, user_kick_message=None, user_ban_message=None, user_failed_kick_message=None, user_failed_ban_message=None, message_duplicate_count=None, message_duplicate_accuracy=None, delete_spam=None, ignore_perms=None, ignore_users=None, ignore_channels=None, ignore_roles=None, ignore_guilds=None, ignore_bots=None, warn_only=None, no_punish=None, per_channel_spam=None, guild_warn_message_delete_after=None, user_kick_message_delete_after=None, guild_kick_message_delete_after=None, user_ban_message_delete_after=None, guild_ban_message_delete_after=None, ): """ Given the relevant arguments, validate and return the options dict Notes ===== For args, view this class's __init__ docstring """ if not isinstance(warn_threshold, int) and warn_threshold is not None: raise ValueError("Expected warn_threshold of type int") if not isinstance(kick_threshold, int) and kick_threshold is not None: raise ValueError("Expected kick_threshold of type int") if not isinstance(ban_threshold, int) and ban_threshold is not None: raise ValueError("Expected ban_threshold of type int") if not isinstance(message_interval, int) and message_interval is not None: raise ValueError("Expected message_interval of type int") if message_interval is not None and message_interval < 1000: raise BaseASHException( "Minimum message_interval is 1 seconds (1000 ms)") if (not isinstance(guild_warn_message, (str, dict)) and guild_warn_message is not None): raise ValueError("Expected guild_warn_message of type str or dict") if (not isinstance(guild_kick_message, (str, dict)) and guild_kick_message is not None): raise ValueError("Expected guild_kick_message of type str or dict") if (not isinstance(guild_ban_message, (str, dict)) and guild_ban_message is not None): raise ValueError("Expected guild_ban_message of type str or dict") if (not isinstance(user_kick_message, (str, dict)) and user_kick_message is not None): raise ValueError("Expected user_kick_message of type str or dict") if (not isinstance(user_ban_message, (str, dict)) and user_ban_message is not None): raise ValueError("Expected user_ban_message of type str or dict") if (not isinstance(user_failed_kick_message, (str, dict)) and user_failed_kick_message is not None): raise ValueError( "Expected user_failed_kick_message of type str or dict") if (not isinstance(user_failed_ban_message, (str, dict)) and user_failed_ban_message is not None): raise ValueError( "Expected user_failed_ban_message of type str or dict") if (not isinstance(message_duplicate_count, int) and message_duplicate_count is not None): raise ValueError("Expected message_duplicate_count of type int") # Convert message_duplicate_accuracy from int to float if exists if isinstance(message_duplicate_accuracy, int): message_duplicate_accuracy = float(message_duplicate_accuracy) if (not isinstance(message_duplicate_accuracy, float) and message_duplicate_accuracy is not None): raise ValueError( "Expected message_duplicate_accuracy of type float") if message_duplicate_accuracy is not None: if 1.0 > message_duplicate_accuracy or message_duplicate_accuracy > 100.0: # Only accept values between 1 and 100 raise ValueError( "Expected message_duplicate_accuracy between 1 and 100") if not isinstance(delete_spam, bool) and delete_spam is not None: raise ValueError("Expected delete_spam of type bool") if not isinstance(ignore_perms, list) and ignore_perms is not None: raise ValueError("Expected ignore_perms of type list") if not isinstance(ignore_users, list) and ignore_users is not None: raise ValueError("Expected ignore_users of type list") if not isinstance(ignore_channels, list) and ignore_channels is not None: raise ValueError("Expected ignore_channels of type list") if not isinstance(ignore_roles, list) and ignore_roles is not None: raise ValueError("Expected ignore_roles of type list") if not isinstance(ignore_guilds, list) and ignore_guilds is not None: raise ValueError("Expected ignore_guilds of type list") if not isinstance(ignore_bots, bool) and ignore_bots is not None: raise ValueError("Expected ignore_bots of type bool") if not isinstance(warn_only, bool) and warn_only is not None: raise ValueError("Expected warn_only of type bool") if not isinstance(no_punish, bool) and no_punish is not None: raise ValueError("Expected no_punish of type bool") if not isinstance(per_channel_spam, bool) and per_channel_spam is not None: raise ValueError("Expected per_channel_spam of type bool") if (not isinstance(guild_warn_message_delete_after, int) and guild_warn_message_delete_after is not None): raise ValueError( "Expected guild_warn_message_delete_after of type int") if (not isinstance(user_kick_message_delete_after, int) and user_kick_message_delete_after is not None): raise ValueError( "Expected user_kick_message_delete_after of type int") if (not isinstance(guild_kick_message_delete_after, int) and guild_kick_message_delete_after is not None): raise ValueError( "Expected guild_kick_message_delete_after of type int") if (not isinstance(user_ban_message_delete_after, int) and user_ban_message_delete_after is not None): raise ValueError( "Expected user_ban_message_delete_after of type int") if (not isinstance(guild_ban_message_delete_after, int) and guild_ban_message_delete_after is not None): raise ValueError( "Expected guild_ban_message_delete_after of type int") if warn_only and no_punish: raise BaseASHException( "Cannot do BOTH warn_only and no_punish. Pick one and try again" ) # Now we have ignore_type checked everything, lets do some logic if ignore_bots is None: ignore_bots = Static.DEFAULTS.get("ignore_bots") if ignore_roles is not None: placeholder_ignore_roles = [] for item in ignore_roles: if isinstance(item, discord.Role): placeholder_ignore_roles.append(item.id) elif isinstance(item, int): placeholder_ignore_roles.append(item) elif isinstance(item, str): placeholder_ignore_roles.append(item) else: raise ValueError( "Expected discord.Role or int or str for ignore_roles") ignore_roles = placeholder_ignore_roles if ignore_channels is not None: placeholder_ignore_channels = [] for item in ignore_channels: if isinstance(item, discord.TextChannel): placeholder_ignore_channels.extend([item.id]) else: placeholder_ignore_channels.append(item) ignore_channels = placeholder_ignore_channels if ignore_users is not None: placeholder_ignore_users = [] for item in ignore_users: if isinstance(item, discord.User) or isinstance( item, discord.Member): placeholder_ignore_users.append(item.id) else: placeholder_ignore_users.append(item) ignore_users = placeholder_ignore_users return { "warn_threshold": warn_threshold or Static.DEFAULTS.get("warn_threshold"), "kick_threshold": kick_threshold or Static.DEFAULTS.get("kick_threshold"), "ban_threshold": ban_threshold or Static.DEFAULTS.get("ban_threshold"), "message_interval": message_interval or Static.DEFAULTS.get("message_interval"), "guild_warn_message": guild_warn_message or Static.DEFAULTS.get("guild_warn_message"), "guild_kick_message": guild_kick_message or Static.DEFAULTS.get("guild_kick_message"), "guild_ban_message": guild_ban_message or Static.DEFAULTS.get("guild_ban_message"), "user_kick_message": user_kick_message or Static.DEFAULTS.get("user_kick_message"), "user_ban_message": user_ban_message or Static.DEFAULTS.get("user_ban_message"), "user_failed_kick_message": user_failed_kick_message or Static.DEFAULTS.get("user_failed_kick_message"), "user_failed_ban_message": user_failed_ban_message or Static.DEFAULTS.get("user_failed_ban_message"), "message_duplicate_count": message_duplicate_count or Static.DEFAULTS.get("message_duplicate_count"), "message_duplicate_accuracy": message_duplicate_accuracy or Static.DEFAULTS.get("message_duplicate_accuracy"), "delete_spam": delete_spam or Static.DEFAULTS.get("delete_spam"), "ignore_perms": ignore_perms or Static.DEFAULTS.get("ignore_perms"), "ignore_users": ignore_users or Static.DEFAULTS.get("ignore_users"), "ignore_channels": ignore_channels or Static.DEFAULTS.get("ignore_channels"), "ignore_roles": ignore_roles or Static.DEFAULTS.get("ignore_roles"), "ignore_guilds": ignore_guilds or Static.DEFAULTS.get("ignore_guilds"), "ignore_bots": ignore_bots, "warn_only": warn_only or Static.DEFAULTS.get("warn_only"), "no_punish": no_punish or Static.DEFAULTS.get("no_punish"), "per_channel_spam": per_channel_spam or Static.DEFAULTS.get("per_channel_spam"), "guild_warn_message_delete_after": guild_warn_message_delete_after or Static.DEFAULTS.get("guild_warn_message_delete_after"), "user_kick_message_delete_after": user_kick_message_delete_after or Static.DEFAULTS.get("user_kick_message_delete_after"), "guild_kick_message_delete_after": guild_kick_message_delete_after or Static.DEFAULTS.get("guild_kick_message_delete_after"), "user_ban_message_delete_after": user_ban_message_delete_after or Static.DEFAULTS.get("user_ban_message_delete_after"), "guild_ban_message_delete_after": guild_ban_message_delete_after or Static.DEFAULTS.get("guild_ban_message_delete_after"), }
def __init__( self, bot: commands.Bot, verboseLevel=0, *, warnThreshold=None, kickThreshold=None, banThreshold=None, messageInterval=None, warnMessage=None, kickMessage=None, banMessage=None, messageDuplicateCount=None, messageDuplicateAccuracy=None, ignorePerms=None, ignoreUsers=None, ignoreChannels=None, ignoreRoles=None, ignoreGuilds=None, ignoreBots=None, ): """ This is the first initialization of the entire spam handler, this is also where the initial options are set Parameters ---------- bot : commands.Bot The commands.Bot instance warnThreshold : int, optional This is the amount of messages in a row that result in a warning within the messageInterval kickThreshold : int, optional The amount of 'warns' before a kick occurs banThreshold : int, optional The amount of 'kicks' that occur before a ban occurs messageInterval : int, optional Amount of time a message is kept before being discarded. Essentially the amount of time (In milliseconds) a message can count towards spam warnMessage : str, optional The message to be sent upon warnThreshold being reached kickMessage : str, optional The message to be sent up kickThreshold being reached banMessage : str, optional The message to be sent up banThreshold being reached messageDuplicateCount : int, optional Amount of duplicate messages needed to trip a punishment messageDuplicateKick : int, optional Amount of duplicate messages needed within messageInterval to trip a kick messageDuplicateBan : int, optional Amount of duplicate messages needed within messageInterval to trip a ban messageDuplicateAccuracy : float, optional How 'close' messages need to be to be registered as duplicates (Out of 100) ignorePerms : list, optional The perms (ID Form), that bypass anti-spam ignoreUsers : list, optional The users (ID Form), that bypass anti-spam ignoreBots : bool, optional Should bots bypass anti-spam? """ # Just gotta casually type check everything. if not isinstance(bot, commands.Bot): raise ValueError("Expected channel of type: commands.Bot") if not isinstance(verboseLevel, int): raise ValueError("Verbosity should be an int between 0-5") if not isinstance(warnThreshold, int) and warnThreshold is not None: raise ValueError("Expected warnThreshold of type: int") if not isinstance(kickThreshold, int) and kickThreshold is not None: raise ValueError("Expected kickThreshold of type: int") if not isinstance(banThreshold, int) and banThreshold is not None: raise ValueError("Expected banThreshold of type: int") if not isinstance(messageInterval, int) and messageInterval is not None: raise ValueError("Expected messageInterval of type: int") if messageInterval is not None and messageInterval < 1000: raise BaseASHException( "Minimum messageInterval is 1 seconds (1000 ms)") if not isinstance(warnMessage, str) and warnMessage is not None: raise ValueError("Expected warnMessage of type: str") if not isinstance(kickMessage, str) and kickMessage is not None: raise ValueError("Expected kickMessage of type: str") if not isinstance(banMessage, str) and banMessage is not None: raise ValueError("Expected banMessage of type: str") if (not isinstance(messageDuplicateCount, int) and messageDuplicateCount is not None): raise ValueError("Expected messageDuplicateCount of type: int") if (not isinstance(messageDuplicateAccuracy, float) and messageDuplicateAccuracy is not None): raise ValueError("Expected messageDuplicateAccuracy of type: int") if messageDuplicateAccuracy is not None: if 1 > messageDuplicateAccuracy or messageDuplicateAccuracy > 100: # Only accept values between 1 and 100 raise ValueError( "Expected messageDuplicateAccuracy between 1 and 100") if not isinstance(ignorePerms, list) and ignorePerms is not None: raise ValueError("Expected ignorePerms of type: list") if not isinstance(ignoreUsers, list) and ignoreUsers is not None: raise ValueError("Expected ignoreUsers of type: list") if not isinstance(ignoreChannels, list) and ignoreChannels is not None: raise ValueError("Expected ignoreChannels of type: list") if not isinstance(ignoreRoles, list) and ignoreRoles is not None: raise ValueError("Expected ignoreRoles of type: list") if not isinstance(ignoreGuilds, list) and ignoreGuilds is not None: raise ValueError("Expected ignoreGuilds of type: list") if not isinstance(ignoreBots, bool) and ignoreBots is not None: raise ValueError("Expected ignoreBots of type: int") # Now we have type checked everything, lets do some logic if ignoreBots is None: ignoreBots = Static.DEFAULTS.get("ignoreBots") self.options = { "warnThreshold": warnThreshold or Static.DEFAULTS.get("warnThreshold"), "kickThreshold": kickThreshold or Static.DEFAULTS.get("kickThreshold"), "banThreshold": banThreshold or Static.DEFAULTS.get("banThreshold"), "messageInterval": messageInterval or Static.DEFAULTS.get("messageInterval"), "warnMessage": warnMessage or Static.DEFAULTS.get("warnMessage"), "kickMessage": kickMessage or Static.DEFAULTS.get("kickMessage"), "banMessage": banMessage or Static.DEFAULTS.get("banMessage"), "messageDuplicateCount": messageDuplicateCount or Static.DEFAULTS.get("messageDuplicateCount"), "messageDuplicateAccuracy": messageDuplicateAccuracy or Static.DEFAULTS.get("messageDuplicateAccuracy"), "ignorePerms": ignorePerms or Static.DEFAULTS.get("ignorePerms"), "ignoreUsers": ignoreUsers or Static.DEFAULTS.get("ignoreUsers"), "ignoreChannels": ignoreChannels or Static.DEFAULTS.get("ignoreChannels"), "ignoreRoles": ignoreRoles or Static.DEFAULTS.get("ignoreRoles"), "ignoreGuilds": ignoreGuilds or Static.DEFAULTS.get("ignoreGuilds"), "ignoreBots": ignoreBots, } self.bot = bot self._guilds = [] logging.basicConfig( format="%(asctime)s | %(levelname)s | %(module)s | %(message)s", datefmt="%d/%m/%Y %I:%M:%S %p", ) self.logger = logging.getLogger(__name__) if verboseLevel == 0: self.logger.setLevel(level=logging.NOTSET) elif verboseLevel == 1: self.logger.setLevel(level=logging.DEBUG) elif verboseLevel == 2: self.logger.setLevel(level=logging.INFO) elif verboseLevel == 3: self.logger.setLevel(level=logging.WARNING) elif verboseLevel == 4: self.logger.setLevel(level=logging.ERROR) elif verboseLevel == 5: self.logger.setLevel(level=logging.CRITICAL)