From 376e39e3d38d774532f1e4536244b99c51b03012 Mon Sep 17 00:00:00 2001 From: SamuelCurrid Date: Thu, 12 Nov 2020 13:42:36 -0500 Subject: [PATCH 1/6] Created automod --- Automod.py | 10 ++++++++++ Gompei.py | 2 ++ 2 files changed, 12 insertions(+) create mode 100644 Automod.py diff --git a/Automod.py b/Automod.py new file mode 100644 index 0000000..032612a --- /dev/null +++ b/Automod.py @@ -0,0 +1,10 @@ +from discord.ext import commands, flags + + +class Automod(commands.Cog): + """ + Automatic moderation handler + """ + + def __init__(self, bot): + self.bot = bot \ No newline at end of file diff --git a/Gompei.py b/Gompei.py index 90889a3..7b691a3 100644 --- a/Gompei.py +++ b/Gompei.py @@ -7,10 +7,12 @@ from Minesweeper import Minesweeper from discord.ext import commands from datetime import datetime +from Automod import Automod from Hangman import Hangman from Logging import Logging from Voting import Voting + import discord import random import json From d8c23770751ebeb94bdf50aa7ac7a3f702484496 Mon Sep 17 00:00:00 2001 From: SamuelCurrid Date: Fri, 13 Nov 2020 11:14:52 -0500 Subject: [PATCH 2/6] Outlining structure --- Automod.py | 41 +++++++++++++++++++++++++++++++++++++++-- Gompei.py | 1 + config/automod.json | 7 +++++++ config/settings.json | 2 ++ 4 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 config/automod.json diff --git a/Automod.py b/Automod.py index 032612a..d543ef5 100644 --- a/Automod.py +++ b/Automod.py @@ -1,4 +1,8 @@ -from discord.ext import commands, flags +from GompeiFunctions import load_json +from Permissions import administrator_perms +from discord.ext import commands + +import os class Automod(commands.Cog): @@ -7,4 +11,37 @@ class Automod(commands.Cog): """ def __init__(self, bot): - self.bot = bot \ No newline at end of file + self.bot = bot + self.automod_settings = load_json(os.path.join("config", "automod.json")) + self.settings = load_json(os.path.join("config", "settings.json")) + + @commands.command(pass_context=True, aliases="am") + @commands.check(administrator_perms) + @commands.guild_only() + async def automod(self, ctx, command): + """ + Top level handler for automod + + :param ctx: Context object + :param command: automod command you'd like to use + """ + if command.lower() is "info": + await self.send_info(ctx) + + async def send_info(self, ctx): + response = "" + for setting in self.automod_settings: + response += setting.replace("_", " ").title() + ": " + if self.automod_settings[setting] is None: + response += "Disabled" + + + @commands.Cog.listener() + async def on_message_delete(self, message): + # Check for ghost pinging + return + + @commands.Cog.listener() + async def on_message(self, message): + # Check for excessive pings, check for rate limit, check for excessive white space, check for blacklisted phrases/words + return diff --git a/Gompei.py b/Gompei.py index 7b691a3..07df369 100644 --- a/Gompei.py +++ b/Gompei.py @@ -53,6 +53,7 @@ def get_prefix(client, message): gompei.add_cog(Logging(gompei)) gompei.add_cog(ReactionRoles(gompei)) gompei.add_cog(Voting(gompei)) +gompei.add_cog(Automod(gompei)) print("Cogs loaded") # Overwrite help command diff --git a/config/automod.json b/config/automod.json new file mode 100644 index 0000000..02428c6 --- /dev/null +++ b/config/automod.json @@ -0,0 +1,7 @@ +{ + "msg_rate": null, + "bad words": null, + "mentions": null, + "whitespace": null, + "ghost_ping": null +} \ No newline at end of file diff --git a/config/settings.json b/config/settings.json index 554bbf5..88f1fe6 100644 --- a/config/settings.json +++ b/config/settings.json @@ -3,6 +3,8 @@ "nitro_booster_id": null, "status": null, "dm_channel_id": null, + "mod_action_channel_id": null, + "muted_role_id": null, "prefix": ".", "access_roles": [], "opt_in_roles": [] From 343d0342cd4e1c8384ba64ea2029ce7e46cbc55a Mon Sep 17 00:00:00 2001 From: Samuel Currid Date: Fri, 28 May 2021 08:32:21 -0400 Subject: [PATCH 3/6] Automod framework --- Automod.py | 47 -------------- cogs/Automod.py | 159 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+), 47 deletions(-) delete mode 100644 Automod.py create mode 100644 cogs/Automod.py diff --git a/Automod.py b/Automod.py deleted file mode 100644 index d543ef5..0000000 --- a/Automod.py +++ /dev/null @@ -1,47 +0,0 @@ -from GompeiFunctions import load_json -from Permissions import administrator_perms -from discord.ext import commands - -import os - - -class Automod(commands.Cog): - """ - Automatic moderation handler - """ - - def __init__(self, bot): - self.bot = bot - self.automod_settings = load_json(os.path.join("config", "automod.json")) - self.settings = load_json(os.path.join("config", "settings.json")) - - @commands.command(pass_context=True, aliases="am") - @commands.check(administrator_perms) - @commands.guild_only() - async def automod(self, ctx, command): - """ - Top level handler for automod - - :param ctx: Context object - :param command: automod command you'd like to use - """ - if command.lower() is "info": - await self.send_info(ctx) - - async def send_info(self, ctx): - response = "" - for setting in self.automod_settings: - response += setting.replace("_", " ").title() + ": " - if self.automod_settings[setting] is None: - response += "Disabled" - - - @commands.Cog.listener() - async def on_message_delete(self, message): - # Check for ghost pinging - return - - @commands.Cog.listener() - async def on_message(self, message): - # Check for excessive pings, check for rate limit, check for excessive white space, check for blacklisted phrases/words - return diff --git a/cogs/Automod.py b/cogs/Automod.py new file mode 100644 index 0000000..b111bbf --- /dev/null +++ b/cogs/Automod.py @@ -0,0 +1,159 @@ +from cogs.Administration import Administration +from cogs.DirectMessages import DirectMessages +from GompeiFunctions import time_delta_string +from discord.ext import commands + +import typing + + +class Punishments: + """ + Punishment object + """ + # Punishment functions + punishments = { + "mute": Administration.mute, + "jail": Administration.jail, + "delete": Administration.delete_message, + "DM": DirectMessages.echo_pm, + "warn": Administration.warn + } + + def __init__(self): + self.punishments = [] + + +class Automod(commands.Cog): + """ + Automod tools + """ + + def __init__(self, bot): + self.bot = bot + + @commands.group() + def automod(self, ctx): + """ + Top level command for automod + + :param ctx: Context object + """ + # TODO Implement + pass + + @automod.command(name="info") + def automod_info(self, ctx): + """ + Sends current automod settings + + :param ctx: Context object + """ + # TODO Implement + pass + + @automod.command(name="help") + def automod_help(self, ctx, *, subcommand: str): + """ + Sends help information for automod + + :param ctx: Context object + :param subcommand: Specific command to show help for + """ + # TODO Implement + pass + + @automod.group(name="badWords") + def automod_bad_words(self, ctx): + """ + Top level command for bad words settings + + :param ctx: Context object + """ + # TODO Implement + # Currently only using strict matching + # Possible fuzzy implementation in the future + pass + + @automod_bad_words.command(name="add") + def automod_add_bad_word(self, ctx, *, word): + """ + Adds a word to the bad word list + + :param ctx: Context object + :param word: Word to add + """ + # TODO Implement + pass + + @automod_bad_words.command(name="remove") + def automod_remove_bad_word(self, ctx, *, word: typing.Optional[int, str]): + """ + Removes a word from the bad word list + + :param ctx: Context object + :param word: Word or index of word to remove + """ + # TODO Implement + pass + + @automod_bad_words.command(name="list") + def automod_list_bad_words(self, ctx): + """ + Lists out the current bad words + + :param ctx: Context object + """ + # TODO Implement + pass + + @automod.group(name="attachmentRate") + def automod_attachment_rate(self, ctx): + """ + Top level command for attachment rate settings + + :param ctx: Context object + """ + # TODO Implement + pass + + @automod_attachment_rate.command(name="setRate") + def automod_set_attachment_rate(self, ctx, *, rate: str): + """ + Sets the rate limit for attachments + + :param ctx: Context object + :param rate: Rate to set to + """ + # TODO Implement + pass + + @automod.group(name="mentionRate") + def automod_mention_rate(self, ctx): + """ + Top level command for mention rate settings + + :param ctx: Context object + """ + # TODO Implement + pass + + @automod.group(name="messageRate") + def automod_message_rate(self, ctx): + """ + Top level command for message rate settings + + :param ctx: Context object + """ + # TODO Implement + pass + + """ + POST CARL REPLACEMENT + - Whitespace abuse + - Div abuse + - Crashers + """ + + +def setup(bot): + bot.add_cog(Automod(bot)) From 8e19ac64df1f9994d47e34687a60859ecddf0c03 Mon Sep 17 00:00:00 2001 From: Samuel Currid Date: Sun, 30 May 2021 07:58:20 -0400 Subject: [PATCH 4/6] Adding automod cog --- Gompei.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Gompei.py b/Gompei.py index 440268f..380494c 100644 --- a/Gompei.py +++ b/Gompei.py @@ -35,6 +35,7 @@ def get_prefix(client, message): startup_cogs = [ "Administration", + "Automod", "DirectMessages", "EmbedBuilder", "Games", From 87d5d065e8f4741fa98c1f3682bbeba3b646857b Mon Sep 17 00:00:00 2001 From: Samuel Currid Date: Sun, 30 May 2021 07:58:48 -0400 Subject: [PATCH 5/6] More frameworks --- cogs/Automod.py | 290 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 256 insertions(+), 34 deletions(-) diff --git a/cogs/Automod.py b/cogs/Automod.py index b111bbf..810ed47 100644 --- a/cogs/Automod.py +++ b/cogs/Automod.py @@ -1,9 +1,16 @@ from cogs.Administration import Administration from cogs.DirectMessages import DirectMessages from GompeiFunctions import time_delta_string +from cogs.Permissions import moderator_perms from discord.ext import commands +from datetime import timedelta +from datetime import datetime +import discord +import pickle import typing +import os +import re class Punishments: @@ -14,7 +21,7 @@ class Punishments: punishments = { "mute": Administration.mute, "jail": Administration.jail, - "delete": Administration.delete_message, + # "delete": Administration.delete_message, "DM": DirectMessages.echo_pm, "warn": Administration.warn } @@ -30,84 +37,235 @@ class Automod(commands.Cog): def __init__(self, bot): self.bot = bot + self.settings = self.load_settings() + self.attachment_rate = {} + self.mention_rate = {} + self.message_rate = {} - @commands.group() - def automod(self, ctx): + def load_settings(self): + """ + Loads the automod settings + + :return: + """ + try: + return pickle.load(open(os.path.join("config", "automod.p"), "rb")) + except (OSError, IOError) as e: + return { + "ignore_mods": False, + "bad_words": { + "enabled": False, + "overwrites": {}, + "punishment": None, + "words": {} + }, + "attachment_rate": { + "enabled": False, + "overwrites": {}, + "punishment": None, + "attachments": 0, + "seconds": 0 + }, + "mention_rate": { + "enabled": False, + "overwrites": {}, + "punishment": None, + "mentions": 0, + "seconds": 0 + }, + "message_rate": { + "enabled": False, + "overwrites": {}, + "punishment": None, + "messages": 0, + "seconds": 0 + } + } + + def save_settings(self, settings): + """ + Saves the automod settings + """ + pickle.dump(settings, open(os.path.join("config", "automod.p"), "wb+")) + + @commands.group(case_insensitive=True, aliases=["a"]) + @commands.check(moderator_perms) + async def automod(self, ctx): """ Top level command for automod :param ctx: Context object """ - # TODO Implement - pass + if ctx.invoked_subcommand is None: + await self.automod_help(ctx, None) @automod.command(name="info") - def automod_info(self, ctx): + async def automod_info(self, ctx, subcommand: typing.Optional[str]): """ Sends current automod settings :param ctx: Context object """ - # TODO Implement - pass + + if subcommand is None: + now = datetime.now() + attach_time = time_delta_string(now, now + timedelta(seconds=self.settings["attachment_rate"]["seconds"])) + mention_time = time_delta_string(now, now + timedelta(seconds=self.settings["mention_rate"]["seconds"])) + message_time = time_delta_string(now, now + timedelta(seconds=self.settings["message_rate"]["seconds"])) + + embed = discord.Embed( + title="Automod Settings", + description=f"**__Attachment Rate__**\n" + f"Enabled: {self.settings['attachment_rate']['enabled']}\n" + f"Number: {self.settings['attachment_rate']['attachments']}\n" + f"Time: {attach_time}\n" + f"\n" + f"**__Bad Words__**\n" + f"Enabled: {self.settings['bad_words']['enabled']}\n" + f"\n" + f"**__Mention Rate__**\n" + f"Enabled: {self.settings['mention_rate']['enabled']}\n" + f"Number: {self.settings['mention_rate']['mentions']}\n" + f"Time: {mention_time}\n" + f"\n" + f"**__Message Rate__**\n" + f"Enabled: {self.settings['message_rate']['enabled']}\n" + f"Number: {self.settings['message_rate']['messages']}\n" + f"Time: {message_time}" + ) + + await ctx.send(embed=embed) + return + + # TODO Implement subcommand info @automod.command(name="help") - def automod_help(self, ctx, *, subcommand: str): + async def automod_help(self, ctx, subcommand: typing.Optional[str]): """ Sends help information for automod + Usage: .automod help + :param ctx: Context object :param subcommand: Specific command to show help for """ - # TODO Implement - pass - - @automod.group(name="badWords") - def automod_bad_words(self, ctx): + if subcommand is None: + embed = discord.Embed( + title="Automod Commands", + description="```\n" + "attachmentRate - Attachment rate commands\n" + "info - Displays current automod settings\n" + "badWords - Bad words commands\n" + "help [command] - Displays help info\n" + "mentionRate - Mention rate commands\n" + "messageRate - Message rate commands\n" + "```" + ) + await ctx.send(embed=embed) + return + + # TODO Implement subcommand help + + @automod.group(name="badWords", case_insensitive=True) + async def automod_bad_words(self, ctx): """ Top level command for bad words settings + Usage: .automod badWords ... + :param ctx: Context object """ - # TODO Implement - # Currently only using strict matching - # Possible fuzzy implementation in the future - pass + await self.automod_help(ctx, "badWords") @automod_bad_words.command(name="add") - def automod_add_bad_word(self, ctx, *, word): + async def automod_add_bad_word(self, ctx, *, word: str): """ Adds a word to the bad word list :param ctx: Context object :param word: Word to add """ - # TODO Implement - pass + if word.lower() in self.settings["bad_words"]["words"]: + await ctx.send( + f"{word} is already in the bad words list", + allowed_mentions=discord.AllowedMentions.none() + ) + return + + self.settings["bad_words"]["words"][word.lower()] = {} + self.save_settings(self.settings) + await ctx.send( + f"Successfully added {word} to the bad words list", + allowed_mentions=discord.AllowedMentions.none() + ) @automod_bad_words.command(name="remove") - def automod_remove_bad_word(self, ctx, *, word: typing.Optional[int, str]): + async def automod_remove_bad_word(self, ctx, *, word: typing.Union[int, str]): """ Removes a word from the bad word list :param ctx: Context object :param word: Word or index of word to remove """ - # TODO Implement - pass + if word.lower() not in self.settings["bad_words"]["words"]: + await ctx.send( + f"Did not find {word} in bad words list", + allowed_mentions=discord.AllowedMentions.none() + ) + return + + del self.settings["bad_words"]["words"][word.lower()] + self.save_settings(self.settings) + await ctx.send( + f"Successfully removed {word} from the bad words list" + ) @automod_bad_words.command(name="list") - def automod_list_bad_words(self, ctx): + async def automod_list_bad_words(self, ctx): """ Lists out the current bad words :param ctx: Context object """ - # TODO Implement - pass + embed = discord.Embed( + title="Automod Bad Words List", + description=f"{', '.join([x for x in self.settings['bad_words']['words']])}" + ) + await ctx.send(embed=embed) + + @automod_bad_words.command(name="enable") + async def automod_bad_words_enable(self, ctx): + """ + Enables automod for bad words - @automod.group(name="attachmentRate") - def automod_attachment_rate(self, ctx): + :param ctx: Context object + """ + if self.settings["bad_words"]["enabled"]: + await ctx.send("Automod for bad words is already enabled") + return + + self.settings["bad_words"]["enabled"] = True + self.save_settings(self.settings) + await ctx.send("Successfully enabled bad words") + + @automod_bad_words.command(name="disable") + async def automod_bad_words_disable(self, ctx): + """ + Disables automod for bad words + + :param ctx: Context object + """ + if not self.settings["bad_words"]["enabled"]: + await ctx.send("Automod for bad words is already disabled") + return + + self.settings["bad_words"]["enabled"] = False + self.save_settings(self.settings) + await ctx.send("Successfully disabled bad words") + + + @automod.group(name="attachmentRate", case_insensitive=True) + async def automod_attachment_rate(self, ctx): """ Top level command for attachment rate settings @@ -117,7 +275,7 @@ def automod_attachment_rate(self, ctx): pass @automod_attachment_rate.command(name="setRate") - def automod_set_attachment_rate(self, ctx, *, rate: str): + async def automod_set_attachment_rate(self, ctx, *, rate: str): """ Sets the rate limit for attachments @@ -127,8 +285,8 @@ def automod_set_attachment_rate(self, ctx, *, rate: str): # TODO Implement pass - @automod.group(name="mentionRate") - def automod_mention_rate(self, ctx): + @automod.group(name="mentionRate", case_insensitive=True) + async def automod_mention_rate(self, ctx): """ Top level command for mention rate settings @@ -137,8 +295,8 @@ def automod_mention_rate(self, ctx): # TODO Implement pass - @automod.group(name="messageRate") - def automod_message_rate(self, ctx): + @automod.group(name="messageRate", case_insensitive=True) + async def automod_message_rate(self, ctx): """ Top level command for message rate settings @@ -150,10 +308,74 @@ def automod_message_rate(self, ctx): """ POST CARL REPLACEMENT - Whitespace abuse + - Ghost ping abuse + - Username checks + - Nickname checks + - Status checks - Div abuse - Crashers """ + @commands.Cog.listener() + async def on_message(self, message): + """ + Checks automod settings on message + + :param message: Message object + """ + if message.author.bot: + return + + if self.settings["attachment_rate"]["enabled"]: + await self.attachment_rate_check(message) + if self.settings["bad_words"]["enabled"]: + await self.bad_word_check(message) + if self.settings["mention_rate"]["enabled"]: + await self.mention_rate_check(message) + if self.settings["message_rate"]["enabled"]: + await self.message_rate_check(message) + + async def attachment_rate_check(self, message): + """ + Checks to see if the attachment rate was reached on message + + :param message: Message to check + """ + # TODO Implement + pass + + async def bad_word_check(self, message): + """ + Checks to see if a bad word triggers for given message + TODO Fuzzy matching not working as intended + + :param message: Message to check + """ + msg = "".join(message.content.lower().split()).replace("-", "") + + for word in self.settings["bad_words"]["words"]: + if re.match(word, msg): + # TODO Punishment functions + pass + + async def mention_rate_check(self, message): + """ + Checks to see if mention rate was reached on a message + + :param message: Message to check + """ + # TODO Implement + pass + + async def message_rate_check(self, message): + """ + Checks ot see if message rate was reached on a message + + :param message: Message ot check + """ + # TODO Implement + pass + def setup(bot): bot.add_cog(Automod(bot)) From c8ff66e31ff13e5cb7fd1e667e65d66a9089bf33 Mon Sep 17 00:00:00 2001 From: Samuel Currid Date: Sun, 30 May 2021 07:59:11 -0400 Subject: [PATCH 6/6] Adding same datetime exception --- GompeiFunctions.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/GompeiFunctions.py b/GompeiFunctions.py index cb1c87a..026c1d8 100644 --- a/GompeiFunctions.py +++ b/GompeiFunctions.py @@ -26,6 +26,9 @@ def time_delta_string(before, after): :param after: datetime 2 :return: string """ + if after == before: + return "0 seconds" + delta = relativedelta.relativedelta(after, before) if delta.years > 0: