diff --git a/bot.py b/bot.py index c0a6aed..9ab649f 100644 --- a/bot.py +++ b/bot.py @@ -13,7 +13,7 @@ from discord.ext import commands import cogs.fancyEmbeds as fEmbeds -import functions +from utils import utils # Logging config logging.basicConfig(format='[%(asctime)s] %(levelname)s: %(message)s', level=logging.INFO) @@ -158,7 +158,7 @@ async def send_cog_help(self,cog): filters = cursor.execute("SELECT * FROM message_filter").fetchall() filter_tuple = namedtuple("filter_tuple", ["enabled", "wildcard", "exact"]) for guild_filter in filters: - functions.update_filter(bot, guild_filter) + utils.update_filter(bot, guild_filter) #load prefixes into bot var bot.guild_prefixes = {} diff --git a/cogs/automod.py b/cogs/automod.py index e731572..2c62845 100644 --- a/cogs/automod.py +++ b/cogs/automod.py @@ -10,7 +10,9 @@ from discord.ext import commands import cogs.fancyEmbeds as fEmbeds -import functions +from utils import utils +from utils import checks +from utils.sql import sql async def word_filter_pre_invoke(self,ctx): @@ -40,7 +42,7 @@ def __init__(self, bot): self.connection = bot.connection @commands.group(aliases=["word_filter"], brief=":abcd: ") - @commands.check(functions.has_modrole) + @commands.check(checks.has_modrole) @commands.before_invoke(word_filter_pre_invoke) async def wordFilter(self,ctx): """Modifies the server message word filter.""" @@ -80,7 +82,7 @@ async def wordFilter_set_wild(self,ctx,*,new_filter=None): await ctx.send("Filter set.") async with self.connection.execute("SELECT * from message_filter WHERE guild=?",(ctx.guild.id,)) as cursor: current_filter = await cursor.fetchone() - functions.update_filter(self.bot, current_filter) + utils.update_filter(self.bot, current_filter) @wordFilter_set.command(name="exact", brief=":ballpoint_pen: ") async def wordFilter_set_exact(self,ctx,*,new_filter=None): @@ -92,7 +94,7 @@ async def wordFilter_set_exact(self,ctx,*,new_filter=None): cursor = await self.connection.execute("SELECT * from message_filter WHERE guild=?",(ctx.guild.id,)).fetchone() current_filter = await cursor.fetchone() await cursor.close() - functions.update_filter(self.bot, current_filter) + utils.update_filter(self.bot, current_filter) @wordFilter.command(name="add", brief=":pencil: ") async def wordFilter_add(self,ctx,*words): @@ -130,7 +132,7 @@ async def wordFilter_add(self,ctx,*words): current_filter = await cursor.execute("SELECT * from message_filter WHERE guild=?",(ctx.guild.id,)) current_filter = await current_filter.fetchone() await cursor.close() - functions.update_filter(self.bot, current_filter) + utils.update_filter(self.bot, current_filter) await ctx.send("Added to filter.") @wordFilter.command(name="remove",aliases=["del","delete"], brief=":x: ") @@ -171,7 +173,7 @@ async def wordFilter_remove(self,ctx,*words): current_filter = await cursor.execute("SELECT * from message_filter WHERE guild=?",(ctx.guild.id,)) current_filter = await current_filter.fetchone() await cursor.close() - functions.update_filter(self.bot, current_filter) + utils.update_filter(self.bot, current_filter) await ctx.send(f"Removed from filter. {'The following words were not found so not removed: ' if notFoundWords else ''}{' '.join(notFoundWords) if notFoundWords else ''}") @wordFilter.command(name="get",aliases=["list"], brief=":notepad_spiral: ") @@ -202,11 +204,11 @@ async def wordFilter_toggle(self,ctx): current_filter = await cursor.execute("SELECT * from message_filter WHERE guild=?",(ctx.guild.id,)) current_filter = await current_filter.fetchone() await cursor.close() - functions.update_filter(self.bot, current_filter) + utils.update_filter(self.bot, current_filter) await ctx.send(f"Filter now {'enabled' if enabled == 1 else 'disabled'}.") @commands.group(name="spamFilter",aliases=["spam_filter"], brief=":loudspeaker: ") - @commands.check(functions.has_modrole) + @commands.check(checks.has_modrole) @commands.before_invoke(spam_filter_pre_invoke) async def spamFilter(self,ctx): """Set various filters to help reduce spam!""" @@ -292,7 +294,7 @@ async def spamFilter_repeatingLimit(self,ctx,limit:int=None): await ctx.send(f"Character repeat limit now {limit if limit > -1 else 'disabled'}.") @commands.group(aliases=["name_filter"], brief=":name_badge: ") - @commands.check(functions.has_modrole) + @commands.check(checks.has_modrole) async def nameFilter(self,ctx): """Modifies the name filter.""" if ctx.invoked_subcommand is None: @@ -329,7 +331,7 @@ async def nameFilter_setnames(self, ctx): #used to make sure custom nickname follows guild filter (preventing an infinite loop) and doesn't exceed character limit def valid_nick(nick): - if functions.filter_check(self.bot, nick, ctx.guild.id): + if checks.filter_check(self.bot, nick, ctx.guild.id): return "failed filter" elif len(nick) > 32: return "too long" @@ -390,7 +392,7 @@ async def check_message(self,message): return if isinstance(message.channel, discord.channel.DMChannel): return - if functions.has_modrole(message, self.bot) or functions.has_adminrole(message, self.bot): + if checks.has_modrole(message, self.bot) or checks.has_adminrole(message, self.bot): return if message.guild.id not in self.bot.guild_filters: return @@ -556,7 +558,7 @@ async def on_member_join(self, member): #checks if username is appropriate if not await SqlCommands.namefilter_enabled(member.guild.id): return - if functions.filter_check(self.bot, member.display_name, member.guild.id): + if checks.filter_check(self.bot, member.display_name, member.guild.id): try: new_name = await SqlCommands.get_new_nick(member.guild.id, "username") await member.edit(nick=new_name) @@ -639,7 +641,7 @@ async def on_member_update(self, before, after): #Checks if member has an appropriate nick when they update it if not await SqlCommands.namefilter_enabled(after.guild.id): return - if functions.filter_check(self.bot, after.display_name, after.guild.id): + if checks.filter_check(self.bot, after.display_name, after.guild.id): try: new_name = await SqlCommands.get_new_nick(after.guild.id, "nickname") await after.edit(nick=new_name) @@ -673,7 +675,7 @@ async def on_user_update(self, before, after): member = guild.get_member(after.id) if not await SqlCommands.namefilter_enabled(guild.id): continue - if not member.nick and functions.filter_check(self.bot, member.display_name, member.guild.id): + if not member.nick and checks.filter_check(self.bot, member.display_name, member.guild.id): try: new_name = await SqlCommands.get_new_nick(after.guild.id, "username") await after.edit(nick=new_name) @@ -701,4 +703,4 @@ async def on_user_update(self, before, after): def setup(bot): global SqlCommands bot.add_cog(AutoMod(bot)) - SqlCommands = functions.Sql(bot) + SqlCommands = sql.sql(bot) diff --git a/cogs/community.py b/cogs/community.py index 9165db9..f1bb47c 100644 --- a/cogs/community.py +++ b/cogs/community.py @@ -9,8 +9,8 @@ from discord.ext import commands, tasks from PIL import Image -import functions import cogs.fancyEmbeds as fEmbeds +from utils import checks class Community(commands.Cog): @@ -64,7 +64,7 @@ async def get_worm(self,id,colour=False): return discord.File(arr,filename="worm.png"),wormColour @commands.group(brief=":ballot_box: ") - @commands.check(functions.has_modrole) + @commands.check(checks.has_modrole) async def poll(self, ctx): """Create polls!""" if ctx.invoked_subcommand is None: diff --git a/cogs/fancyEmbeds.py b/cogs/fancyEmbeds.py index 18f30d5..ae2da71 100644 --- a/cogs/fancyEmbeds.py +++ b/cogs/fancyEmbeds.py @@ -7,7 +7,7 @@ import discord from discord.ext import commands -import functions +from utils import checks #Add cog to the bot def setup(bot): @@ -149,7 +149,7 @@ def addFooter(self, embed, footer, bot): #Embed command group @commands.group(help="Manage how embeds are sent.", brief=":page_facing_up: ") - @commands.check(functions.has_modrole) + @commands.check(checks.has_modrole) async def embed(self, ctx): if ctx.invoked_subcommand is None: await ctx.send_help(ctx.command) diff --git a/cogs/moderation.py b/cogs/moderation.py index 0aa330f..e6d9f76 100644 --- a/cogs/moderation.py +++ b/cogs/moderation.py @@ -6,7 +6,9 @@ from discord.ext import commands, tasks import cogs.fancyEmbeds as fEmbeds -import functions +from utils.time import timeconverters, InSeconds +from utils import checks +from utils.sql import sql class Moderation(commands.Cog): @@ -21,7 +23,7 @@ def __init__(self, bot): self.connection = bot.connection @commands.group(help="Purge command.", brief=":x: ") - @commands.check(functions.has_modrole) + @commands.check(checks.has_modrole) async def purge(self,ctx): if ctx.invoked_subcommand is None: await ctx.send_help(ctx.command) @@ -42,7 +44,7 @@ def filter_check(message): #ban @commands.command(help="bans a user", brief=":hammer: ") - @commands.check(functions.has_modrole) + @commands.check(checks.has_modrole) async def ban(self, ctx, member : discord.Member, *, reason=None): if not ctx.guild.me.guild_permissions.ban_members: await ctx.send("I don't have permissions to ban people.") @@ -97,7 +99,7 @@ async def ban(self, ctx, member : discord.Member, *, reason=None): await channel.send(embed=embed) @commands.command(help="kicks a user", brief=":boot: ") - @commands.check(functions.has_modrole) + @commands.check(checks.has_modrole) async def kick(self, ctx, member : discord.Member, *, reason=None): if not ctx.guild.me.guild_permissions.kick_members: await ctx.send("I don't have permissions to kick people.") @@ -153,7 +155,7 @@ async def kick(self, ctx, member : discord.Member, *, reason=None): #unban @commands.command(help="unbans a user", brief=":key: ") - @commands.check(functions.has_modrole) + @commands.check(checks.has_modrole) async def unban(self, ctx, user : discord.User): unbanTime = time.time() @@ -196,8 +198,8 @@ async def unban(self, ctx, user : discord.User): #gravel @commands.command(help="Gravels a user", brief=":mute: ") - @commands.check(functions.has_modrole) - async def gravel(self, ctx, member : discord.Member, graveltime: functions.InSeconds, *, reason=None): + @commands.check(checks.has_modrole) + async def gravel(self, ctx, member : discord.Member, graveltime: InSeconds, *, reason=None): if reason is None: reason = "No reason specified" roleid = await SqlCommands.get_role(ctx.guild.id, "gravel") @@ -248,8 +250,8 @@ async def gravel(self, ctx, member : discord.Member, graveltime: functions.InSec await channel.send(embed=embed) @commands.command(help="Mutes a user", brief=":mute: ") - @commands.check(functions.has_modrole) - async def mute(self, ctx, member : discord.Member, mutetime: functions.InSeconds, *, reason=None): + @commands.check(checks.has_modrole) + async def mute(self, ctx, member : discord.Member, mutetime: InSeconds, *, reason=None): now = time.time() if reason is None: reason = "No reason specified" @@ -302,7 +304,7 @@ async def mute(self, ctx, member : discord.Member, mutetime: functions.InSeconds await channel.send(embed=embed) @commands.command(help="warns a user", brief=":warning: ") - @commands.check(functions.has_modrole) + @commands.check(checks.has_modrole) async def warn(self, ctx, member : discord.Member, *, reason): style = fEmbeds.fancyEmbeds.getActiveStyle(self, ctx.guild.id) @@ -341,7 +343,7 @@ async def warn(self, ctx, member : discord.Member, *, reason): await channel.send(embed=embed) @commands.command(help="Shows a user's modlogs", brief=":file_folder: ") - @commands.check(functions.has_modrole) + @commands.check(checks.has_modrole) async def modlogs(self, ctx, member : discord.User): style = fEmbeds.fancyEmbeds.getActiveStyle(self, ctx.guild.id) @@ -373,7 +375,7 @@ async def modlogs(self, ctx, member : discord.User): await ctx.send(embed = logEmbed) @commands.command(help="Shows information on a case", brief=":notepad_spiral: ") - @commands.check(functions.has_modrole) + @commands.check(checks.has_modrole) async def case(self, ctx, case:int): cursor = await self.connection.execute("SELECT id_in_guild, guild, user, type, reason, started, expires, moderator FROM caselog WHERE id = ?", (case,)) caseinfo = await cursor.fetchone() @@ -407,7 +409,7 @@ async def case(self, ctx, case:int): await ctx.send(embed = logEmbed) @commands.command(help="Unmutes a User", brief=":sound: ") - @commands.check(functions.has_modrole) + @commands.check(checks.has_modrole) async def unmute(self, ctx, member : discord.Member): mod = str(ctx.author) unmutetime = time.time() @@ -444,7 +446,7 @@ async def unmute(self, ctx, member : discord.Member): await channel.send(embed=embed) @commands.command(help="Ungravels a User", brief=":sound: ") - @commands.check(functions.has_modrole) + @commands.check(checks.has_modrole) async def ungravel(self, ctx, member : discord.Member): mod = str(ctx.author) ungraveltime = time.time() @@ -533,9 +535,9 @@ async def memberinfo(self, ctx, member: discord.Member): else: rolefieldname = f"Roles - {len(mentionedroles)}" - if functions.has_modrole_no_ctx(member, self.bot): + if checks.has_modrole_no_ctx(member, self.bot): ack = ack + "Server Moderator, " - if functions.has_adminrole_no_ctx(member, self.bot): + if checks.has_adminrole_no_ctx(member, self.bot): ack = ack + "Server Administrator, " if member == member.guild.owner: ack = ack + "Server Owner, " @@ -741,7 +743,7 @@ async def serverinfo(self, ctx): await ctx.send(embed=embed) @commands.command(brief=":card_box: ") - @commands.check(functions.has_modrole) + @commands.check(checks.has_modrole) async def moderations(self, ctx): """Shows all active moderations in the current guild.""" @@ -775,7 +777,7 @@ async def moderations(self, ctx): await ctx.send(embed=modEmbed) @commands.command(brief=":mute: ", help="Server mutes a user, preventing them from talking in VC", aliases=["servermute", "voicemute"]) - @commands.check(functions.has_modrole) + @commands.check(checks.has_modrole) async def server_mute(self, ctx, member: discord.Member): if member.voice == None: await ctx.send("This user isn't currently in a voice channel!") @@ -787,7 +789,7 @@ async def server_mute(self, ctx, member: discord.Member): await ctx.send(f"Sounds good! I server muted {member.name}") @commands.command(brief=":speaker: ", help="Unmutes a user that is server muted", aliases=["serverunmute", "voiceunmute", "unmutevoice"]) - @commands.check(functions.has_modrole) + @commands.check(checks.has_modrole) async def server_unmute(self, ctx, member: discord.Member): if member.voice == None: await ctx.send("This user isn't currently in a voice channel!") @@ -844,7 +846,7 @@ async def bot_check_once(self,ctx): self.bot.cooldowns[ctx.guild.id] = {} if ctx.guild.id not in self.bot.pending_cooldowns.keys(): self.bot.pending_cooldowns[ctx.guild.id] = {} - if functions.has_modrole(ctx) or functions.has_adminrole(ctx): + if checks.has_modrole(ctx) or checks.has_adminrole(ctx): return True now = datetime.datetime.now() if now < self.bot.cooldowns[ctx.guild.id].get(ctx.author.id,now): @@ -875,9 +877,9 @@ async def before_invoke(self,ctx): # There is no way to put a global check behin self.bot.cooldowns[ctx.guild.id][cooldown[0][0]] = cooldown[0][1] SqlCommands = None -TimeConversions = functions.timeconverters() +TimeConversions = timeconverters() def setup(bot): global SqlCommands - SqlCommands = functions.Sql(bot) + SqlCommands = sql.sql(bot) bot.add_cog(Moderation(bot)) diff --git a/cogs/owner.py b/cogs/owner.py index cc0ddb5..909df51 100644 --- a/cogs/owner.py +++ b/cogs/owner.py @@ -12,7 +12,9 @@ from pathlib import Path import discord -import functions +from utils import utils +from utils import checks +from utils.sql.db import backups from discord.ext import commands, tasks import cogs.fancyEmbeds as fEmbeds @@ -40,7 +42,7 @@ def __init__(self, bot): async def shutdown(self,ctx): """Shuts the bot down!""" await ctx.send("👋 Goodbye") - await functions.close_bot(self.bot) + await utils.close_bot(self.bot) @commands.command(brief=":arrows_counterclockwise: ") @commands.is_owner() @@ -48,7 +50,7 @@ async def restart(self,ctx): """Restarts the bot!""" await ctx.send("🏃‍♂️ Be right back!") self.bot.restart = True - await functions.close_bot(self.bot) + await utils.close_bot(self.bot) @commands.group(aliases = ['c'], brief=":gear: ") @commands.is_owner() @@ -224,7 +226,7 @@ async def create(self, ctx): if os.path.isfile("resources/backups/tempbackupfile.db"): await ctx.send("A backup is already in the process of being made! Please wait a moment before trying this again") return() - await functions.make_backup(self.connection, self.bot.kept_backups) + await backups.make_backup(self.connection, self.bot.kept_backups) root_directory = Path('resources/backups') #functions in f-string gets size, count of everything in "backups" folder, 1 is subtracted from count because of gitkeep await ctx.send(f"Sounds good! I made a backup of your database. Currently, your {(len(os.listdir('resources/backups')))-1} backup(s) take up {round((sum(f.stat().st_size for f in root_directory.glob('**/*') if f.is_file())/1000),2)} kilobytes of space") @@ -321,7 +323,7 @@ async def auto_backup(self): if os.path.isfile("resources/backups/tempbackupfile.db"): logging.warning("Unable to automatically create backup, database backup is already in process. If this problem persists, please contact SushiInYourFace") return() #should probably log this occurance, as it may signal something going wrong - await functions.make_backup(self.connection, self.bot.kept_backups) + await backups.make_backup(self.connection, self.bot.kept_backups) logging.info("Database backup created.") @auto_backup.before_loop @@ -341,7 +343,7 @@ async def on_message(self,message): cursor = await self.connection.execute("SELECT command_usage FROM role_ids WHERE guild = ?", (message.guild.id,)) commandRole = await cursor.fetchone() member_roles = [role.id for role in message.author.roles] - if (not commandRole or commandRole[0] in member_roles) or (functions.has_adminrole(message,self.bot) or functions.has_modrole(message,self.bot)): # Only people with commands role/mod should be able to do this + if (not commandRole or commandRole[0] in member_roles) or (checks.has_adminrole(message,self.bot) or checks.has_modrole(message,self.bot)): # Only people with commands role/mod should be able to do this if re.match(r"^<@."+str(self.bot.user.id)+r">$",message.content): # making sure the mention is the only content (^ means start of str, $ end) prefix = self.bot.guild_prefixes.get(message.guild.id,"!") await message.channel.send(f"My prefix here is `{prefix}`",delete_after=8) diff --git a/cogs/utilities.py b/cogs/utilities.py index d4b6d5a..7dcb041 100644 --- a/cogs/utilities.py +++ b/cogs/utilities.py @@ -9,7 +9,7 @@ from discord.ext import commands import cogs.fancyEmbeds as fEmbeds -import functions +from utils import checks class Utilities(commands.Cog): """Adds utilities for users!""" @@ -20,7 +20,7 @@ def __init__(self, bot): self.setup_running = [] @commands.group(name="setup", help="setup some (or all) features of the bot", aliases=["su",], brief=":wrench: ") - @commands.check_any(commands.has_permissions(administrator=True),commands.check(functions.has_adminrole)) + @commands.check_any(commands.has_permissions(administrator=True),commands.check(checks.has_adminrole)) async def setup(self,ctx): if ctx.invoked_subcommand is None: await ctx.invoke(self.bot.get_command('setup all')) @@ -364,7 +364,7 @@ async def tag_tagname(self,ctx): return @tag.group(name="add",aliases=["new","set"], brief=":memo: ") - @commands.check(functions.has_modrole) + @commands.check(checks.has_modrole) async def tag_add(self,ctx): """Sets a tags text assosiation.""" if ctx.invoked_subcommand is None: @@ -432,7 +432,7 @@ async def tag_add_embed(self,ctx,tag,*,embed=None): await ctx.send("Tag updated.") @tag.command(name="remove",aliases=["delete","del"], brief=":scissors: ") - @commands.check(functions.has_modrole) + @commands.check(checks.has_modrole) async def tag_remove(self,ctx,tag): """Removes a tag text assosiation.""" cursor = await self.connection.execute("SELECT tags FROM tags WHERE guild = ?",(ctx.guild.id,)) @@ -471,13 +471,13 @@ async def tags(self,ctx): await ctx.invoke(self.bot.get_command("tag list")) @commands.command(brief=":ping_pong: ") - @commands.check(functions.has_modrole) + @commands.check(checks.has_modrole) async def ping(self, ctx): """Pong!""" await ctx.send(f"Pong! {round((self.bot.latency*1000),4)} ms") @commands.command(brief=":speech_balloon: ") - @commands.check(functions.has_modrole) + @commands.check(checks.has_modrole) async def embed_message(self, ctx, *, text): """Sends a message to the channel the command is used in, contained within an embed.""" embed = fEmbeds.fancyEmbeds.makeEmbed(self, ctx.guild.id, desc=text, useColor=0) @@ -485,7 +485,7 @@ async def embed_message(self, ctx, *, text): await ctx.message.delete() @commands.command(brief=":speech_balloon: ") - @commands.check(functions.has_modrole) + @commands.check(checks.has_modrole) async def message(self, ctx, *, text): """Sends a message to the channel the command is used in.""" await ctx.send(text) diff --git a/functions.py b/functions.py deleted file mode 100644 index c7b635e..0000000 --- a/functions.py +++ /dev/null @@ -1,319 +0,0 @@ -import datetime -import gzip -import os -import re -import shutil -import time -from collections import namedtuple -from typing import Union -import sqlite3 - -import aiosqlite -from discord.ext import commands - - -async def close_bot(bot): - #Shuts down the bot completely, closing the database connection in the process - await bot.connection.close() - await bot.close() - - -def has_modrole(ctx, bot=None): - if not bot: - modrole = ctx.bot.modrole.get(ctx.guild.id) - trialrole = ctx.bot.modrole.get(ctx.guild.id) - else: - modrole = bot.modrole.get(ctx.guild.id) - trialrole = bot.trialrole.get(ctx.guild.id) - member_roles = [role.id for role in ctx.author.roles] - if modrole is None and trialrole is None: - return False - elif modrole in member_roles or trialrole in member_roles: - return True - else: - return False - -#For when you can't use context -def has_modrole_no_ctx(member, bot): - modrole = bot.modrole.get(member.guild.id) - member_roles = [role.id for role in member.roles] - if modrole is None: - return False - elif modrole in member_roles: - return True - else: - return False - -def has_adminrole(ctx, bot=None): - if not bot: - adminrole = ctx.bot.adminrole.get(ctx.guild.id) - else: - adminrole = bot.adminrole.get(ctx.guild.id) - member_roles = [role.id for role in ctx.author.roles] - if adminrole is None: - return False - elif adminrole in member_roles: - return True - else: - return False - -#For when you can't use context -def has_adminrole_no_ctx(member, bot): - adminrole = bot.adminrole.get(member.guild.id) - member_roles = [role.id for role in member.roles] - if adminrole is None: - return False - elif adminrole in member_roles: - return True - else: - return False - -def filter_check(bot, message, guildID: int): - #returns a boolean depending on whether a message should be filtered according to the rules of a guild - should_filter = False - try: - guild_filter = bot.guild_filters[guildID] - except KeyError: - print("The bot tried to reference filters for a guild it does not have stored in memory. Please contact SushiInYourFace if this problem persists") - return False - formatted_content = re.sub(r"[^\w ]|_", "", message).lower() - spaceless_content = re.sub(r"[^\w]|_", "", message) - if guild_filter.wildcard: - if guild_filter.wildcard.search(spaceless_content): - should_filter = True - if guild_filter.exact: - if guild_filter.exact.search(formatted_content): - should_filter = True - return should_filter - -#adds a guild's up-to-date regexes to the bot -def update_filter(bot, guild_filter): - filter_tuple = namedtuple("filter_tuple", ["enabled", "wildcard", "exact"]) - enabled = True if guild_filter[1] == 1 else False - #getting lists - bannedWilds = guild_filter[2].split(";") - bannedExacts = guild_filter[3].split(";") - if "" in bannedWilds: - bannedWilds.remove("") - if "" in bannedExacts: - bannedExacts.remove("") - #creating regexes - if bannedWilds: - wilds_pattern = "|".join(bannedWilds) - wilds_re = re.compile(wilds_pattern) - else: - wilds_re = None - if bannedExacts: - exacts_pattern = "|".join(bannedExacts) - exacts_re = re.compile(r"\b(?:%s)\b" % exacts_pattern) - else: - exacts_re = None - guild_tuple = filter_tuple(enabled=enabled, wildcard=wilds_re, exact=exacts_re) - bot.guild_filters[guild_filter[0]] = guild_tuple - -async def make_backup(connection, kept_backups): - """Creates a backup file of the current SQL database""" - backup = sqlite3.connect("resources/backups/tempbackupfile.db") - with backup: - await connection.backup(backup, pages=1) #actual backup happens here - backup.close() - timestamp = datetime.datetime.now().strftime('%m_%d_%Y-%H_%M_%S') - fname = f'resources/backups/{timestamp}.db.gz' - with gzip.open(fname, 'wb') as f_out: - with open("resources/backups/tempbackupfile.db", "rb") as f_in: - shutil.copyfileobj(f_in, f_out) - os.remove("resources/backups/tempbackupfile.db") - if kept_backups != 0: - #list of all files except gitkeep, sorted chronologically - files = sorted([f for f in os.listdir('resources/backups') if os.path.isfile(os.path.join('resources/backups',f)) and f != ".gitkeep"]) - while len(files) > kept_backups: - oldest_file = files[0] - os.remove(f"resources/backups/{oldest_file}") - files = sorted([f for f in os.listdir('resources/backups') if os.path.isfile(os.path.join('resources/backups',f)) and f != ".gitkeep"]) - - -class Sql: - def __init__(self, bot): - self.bot = bot - self.connection: aiosqlite.Connection = self.bot.connection - async def newest_case(self): - caseNumber = None - async with self.connection.execute("SELECT id FROM caselog ORDER BY id DESC LIMIT 1") as cursor: - caseNumber = await cursor.fetchone() - if caseNumber is None: - caseNumber = 0 - else: - caseNumber = caseNumber[0] - caseNumber += 1 - return caseNumber - - async def newest_guild_case(self, guild): - async with self.connection.execute("SELECT id_in_guild FROM caselog WHERE guild = ? ORDER BY id DESC LIMIT 1", (guild,)) as cursor: - guildCaseNumber = await cursor.fetchone() - if guildCaseNumber is None: - guildCaseNumber = 0 - else: - guildCaseNumber = guildCaseNumber[0] - guildCaseNumber += 1 - return guildCaseNumber - - - async def new_case(self, user, guild, casetype, reason, started, expires, mod): - caseID = await self.newest_case() - id_in_guild = await self.newest_guild_case(guild) - cursor = await self.connection.cursor() - if expires != -1: - #checks if user already has an active case of the same type, and removes it if it is less severe - unexpired_cases = await cursor.execute("SELECT id FROM caselog WHERE guild=? AND user=? AND type=? AND expires >=? AND expires <=? ", (guild,user, casetype, time.time(), expires)) - #should only ever be <=1 case that meets these criteria, but better safe than sorry - if unexpired_cases is not None: - unexpired_cases = await unexpired_cases.fetchall() - for case in unexpired_cases: - await cursor.execute("DELETE FROM active_cases WHERE id = ?", (case[0],)) - await cursor.execute("INSERT INTO active_cases(id, expiration) VALUES(?,?)", (caseID, expires)) - await cursor.execute("INSERT INTO caselog(id, id_in_guild, guild, user, type, reason, started, expires, moderator) VALUES(?,?,?,?,?,?,?,?,?)", (caseID, id_in_guild, guild, user, casetype, reason, started, expires, mod)) - await self.connection.commit() - await cursor.close() - - async def get_role(self, guild, role): - if role == "gravel": - cursor = await self.connection.execute("SELECT gravel FROM role_ids WHERE guild = ?", (guild,)) - elif role == "muted": - cursor = await self.connection.execute("SELECT muted FROM role_ids WHERE guild = ?", (guild,)) - roleid = await cursor.fetchone() - await cursor.close() - return roleid[0] - - async def namefilter_enabled(self, guild): - #checks if filter is enabled - cursor = await self.connection.cursor() - filter_status = await cursor.execute("SELECT enabled FROM name_filtering WHERE guild = ?",(guild,)) - try: - filter_status = await filter_status.fetchone() - except AttributeError: - return - if filter_status is not None: - await cursor.close() - return bool(filter_status[0]) #casts the 0 or 1 stored to a boolean - else: - #guild hasn't set up name filtering, create a row in the table for them and disable the filter - await cursor.execute("INSERT INTO name_filtering(guild, enabled) VALUES(?,?)",(guild, 0)) - await self.connection.commit() - await cursor.close() - return False - - async def get_new_nick(self, guild, flagged_nametype): - #returns a replacement nickname when when the bot flags one as needing to be changed - async with self.connection.execute("SELECT * FROM custom_names WHERE guild=?",(guild,)) as cursor: - server_nicks = await cursor.fetchone() - no_table = False - if server_nicks is None: - no_table = True - #Server does not have a table for custom nicknames yet - await cursor.execute("INSERT INTO custom_names(guild, nickname, username) VALUES(?,?,?)",(guild, "I had a bad nickname", "I had a bad username")) - self.connection.commit() - if flagged_nametype == "nickname": - return server_nicks[1] if not no_table else "I had a bad nickname" - elif flagged_nametype == "username": - return server_nicks[2] if not no_table else "I had a bad username" - else: - return "I had a bad name" - -class timeconverters: - def secondsconverter(self, value, startType): - if startType == "s": - return value - elif startType == "m": - return value * 60 - elif startType == "h": - return value * 3600 - elif startType == "d": - return value * 86400 - return None - - def fromseconds(self, seconds): - if seconds >= 86400: - days = seconds//86400 - return str(days) + (" Day" if days==1 else " Days") - elif seconds >= 3600: - hours = seconds//3600 - return str(hours) + (" Hour" if hours==1 else " Hours") - elif seconds >= 60: - minutes = seconds//60 - return str(minutes) + (" Minute" if minutes==1 else " Minutes") - else: - return str(seconds) + (" Second" if seconds==1 else " Seconds") - -class InSeconds(commands.Converter): - async def convert(self,ctx,argument): - try: - int(str(argument)[-1]) - except: # The last character cannot be an int so convert - try: - argument = timeconverters().secondsconverter(int(str(argument[:-1])),str(argument)[-1]) - except ValueError: - raise commands.BadArgument("That isn't a timeframe!\nExamples of valid timeframes: `20s`, `1h`") - if argument is None: - raise commands.BadArgument(f"I couldn't understand the units.\nSupported units are: `s`, `m`, `h`, `d`\nExamples: `20s`, `1h`") - return argument - else: # The last character can be int so we assume it's in seconds - try: - return int(argument) - except ValueError: - raise commands.BadArgument("That isn't a timeframe!\nExamples of valid timeframes: `20s`, `1h`") - -class DiscordTimestamp(): - """ - A class useful for creating timestamps for display in discord. - Timestamp can be a UNIX timestamp, datetime or timedelta - Relative means time is added to current time. - """ - def __init__(self,timestamp: Union[datetime.datetime,datetime.timedelta,int], relative: bool=False): - if isinstance(timestamp,datetime.datetime): - timestamp = timestamp.timestamp() - elif isinstance(timestamp,datetime.timedelta) and not relative: - timestamp = timestamp.total_seconds() - if relative: - if isinstance(timestamp,int): - timestamp = (datetime.datetime.now() + datetime.timedelta(seconds=timestamp)).timestamp() - else: - timestamp = (datetime.datetime.now() + timestamp).timestamp() - self.timestamp = int(timestamp) - - def _to_discord(self,t: str): - return f"" - - @property - def date(self): - """A date displayed in Discord, `10/07/2021` or `07/10/2021`.""" - return self._to_discord("d") - - @property - def date_full(self): - """A date displayed in readable format in Discord, `10 July 2021` or `July 10, 2021`.""" - return self._to_discord("D") - - @property - def time(self): - """A time displayed in Discord, `18:21` or `6:21 PM`.""" - return self._to_discord("t") - - @property - def time_full(self): - """A time displayed with seconds in Discord, `18:21:21` or `6:21:21 PM`.""" - return self._to_discord("T") - - @property - def date_time(self): - """A time displayed with date and time in Discord, `10 July 2021 18:21` or `July 10, 2021 6:21 PM`.""" - return self._to_discord("f") - - @property - def date_time_full(self): - """A time displayed with date, time and weekday in Discord, `Saturday, 10 July 2021 18:21` or `Saturday, July 10, 2021 6:21 PM`.""" - return self._to_discord("F") - - @property - def relative(self): - """A time displayed as relative in Discord, `10 minutes ago` if before current time or `in 10 minutes` if in the future.""" - return self._to_discord("R") diff --git a/utils/checks.py b/utils/checks.py new file mode 100644 index 0000000..15b8725 --- /dev/null +++ b/utils/checks.py @@ -0,0 +1,69 @@ +import re + +def has_modrole(ctx, bot=None): + if not bot: + modrole = ctx.bot.modrole.get(ctx.guild.id) + trialrole = ctx.bot.modrole.get(ctx.guild.id) + else: + modrole = bot.modrole.get(ctx.guild.id) + trialrole = bot.trialrole.get(ctx.guild.id) + member_roles = [role.id for role in ctx.author.roles] + if modrole is None and trialrole is None: + return False + elif modrole in member_roles or trialrole in member_roles: + return True + else: + return False + +#For when you can't use context +def has_modrole_no_ctx(member, bot): + modrole = bot.modrole.get(member.guild.id) + member_roles = [role.id for role in member.roles] + if modrole is None: + return False + elif modrole in member_roles: + return True + else: + return False + +def has_adminrole(ctx, bot=None): + if not bot: + adminrole = ctx.bot.adminrole.get(ctx.guild.id) + else: + adminrole = bot.adminrole.get(ctx.guild.id) + member_roles = [role.id for role in ctx.author.roles] + if adminrole is None: + return False + elif adminrole in member_roles: + return True + else: + return False + +#For when you can't use context +def has_adminrole_no_ctx(member, bot): + adminrole = bot.adminrole.get(member.guild.id) + member_roles = [role.id for role in member.roles] + if adminrole is None: + return False + elif adminrole in member_roles: + return True + else: + return False + +def filter_check(bot, message, guildID: int): + #returns a boolean depending on whether a message should be filtered according to the rules of a guild + should_filter = False + try: + guild_filter = bot.guild_filters[guildID] + except KeyError: + print("The bot tried to reference filters for a guild it does not have stored in memory. Please contact SushiInYourFace if this problem persists") + return False + formatted_content = re.sub(r"[^\w ]|_", "", message).lower() + spaceless_content = re.sub(r"[^\w]|_", "", message) + if guild_filter.wildcard: + if guild_filter.wildcard.search(spaceless_content): + should_filter = True + if guild_filter.exact: + if guild_filter.exact.search(formatted_content): + should_filter = True + return should_filter \ No newline at end of file diff --git a/utils/sql/db.py b/utils/sql/db.py new file mode 100644 index 0000000..d68e7fe --- /dev/null +++ b/utils/sql/db.py @@ -0,0 +1,26 @@ +import datetime +import gzip +import os +import shutil +import sqlite3 + +class backups: + async def make_backup(connection, kept_backups): + """Creates a backup file of the current SQL database""" + backup = sqlite3.connect("resources/backups/tempbackupfile.db") + with backup: + await connection.backup(backup, pages=1) #actual backup happens here + backup.close() + timestamp = datetime.datetime.now().strftime('%m_%d_%Y-%H_%M_%S') + fname = f'resources/backups/{timestamp}.db.gz' + with gzip.open(fname, 'wb') as f_out: + with open("resources/backups/tempbackupfile.db", "rb") as f_in: + shutil.copyfileobj(f_in, f_out) + os.remove("resources/backups/tempbackupfile.db") + if kept_backups != 0: + #list of all files except gitkeep, sorted chronologically + files = sorted([f for f in os.listdir('resources/backups') if os.path.isfile(os.path.join('resources/backups',f)) and f != ".gitkeep"]) + while len(files) > kept_backups: + oldest_file = files[0] + os.remove(f"resources/backups/{oldest_file}") + files = sorted([f for f in os.listdir('resources/backups') if os.path.isfile(os.path.join('resources/backups',f)) and f != ".gitkeep"]) \ No newline at end of file diff --git a/utils/sql/sql.py b/utils/sql/sql.py new file mode 100644 index 0000000..21c6b79 --- /dev/null +++ b/utils/sql/sql.py @@ -0,0 +1,88 @@ +import time + +import aiosqlite +from discord.ext import commands + +class sql: + def __init__(self, bot): + self.bot = bot + self.connection: aiosqlite.Connection = self.bot.connection + + async def newest_case(self): + caseNumber = None + async with self.connection.execute("SELECT id FROM caselog ORDER BY id DESC LIMIT 1") as cursor: + caseNumber = await cursor.fetchone() + if caseNumber is None: + caseNumber = 0 + else: + caseNumber = caseNumber[0] + caseNumber += 1 + return caseNumber + async def newest_guild_case(self, guild): + async with self.connection.execute("SELECT id_in_guild FROM caselog WHERE guild = ? ORDER BY id DESC LIMIT 1", (guild,)) as cursor: + guildCaseNumber = await cursor.fetchone() + if guildCaseNumber is None: + guildCaseNumber = 0 + else: + guildCaseNumber = guildCaseNumber[0] + guildCaseNumber += 1 + return guildCaseNumber + async def new_case(self, user, guild, casetype, reason, started, expires, mod): + caseID = await self.newest_case() + id_in_guild = await self.newest_guild_case(guild) + cursor = await self.connection.cursor() + if expires != -1: + #checks if user already has an active case of the same type, and removes it if it is less severe + unexpired_cases = await cursor.execute("SELECT id FROM caselog WHERE guild=? AND user=? AND type=? AND expires >=? AND expires <=? ", (guild,user, casetype, time.time(), expires)) + #should only ever be <=1 case that meets these criteria, but better safe than sorry + if unexpired_cases is not None: + unexpired_cases = await unexpired_cases.fetchall() + for case in unexpired_cases: + await cursor.execute("DELETE FROM active_cases WHERE id = ?", (case[0],)) + await cursor.execute("INSERT INTO active_cases(id, expiration) VALUES(?,?)", (caseID, expires)) + await cursor.execute("INSERT INTO caselog(id, id_in_guild, guild, user, type, reason, started, expires, moderator) VALUES(?,?,?,?,?,?,?,?,?)", (caseID, id_in_guild, guild, user, casetype, reason, started, expires, mod)) + await self.connection.commit() + await cursor.close() + + async def get_role(self, guild, role): + if role == "gravel": + cursor = await self.connection.execute("SELECT gravel FROM role_ids WHERE guild = ?", (guild,)) + elif role == "muted": + cursor = await self.connection.execute("SELECT muted FROM role_ids WHERE guild = ?", (guild,)) + roleid = await cursor.fetchone() + await cursor.close() + return roleid[0] + + async def namefilter_enabled(self, guild): + #checks if filter is enabled + cursor = await self.connection.cursor() + filter_status = await cursor.execute("SELECT enabled FROM name_filtering WHERE guild = ?",(guild,)) + try: + filter_status = await filter_status.fetchone() + except AttributeError: + return + if filter_status is not None: + await cursor.close() + return bool(filter_status[0]) #casts the 0 or 1 stored to a boolean + else: + #guild hasn't set up name filtering, create a row in the table for them and disable the filter + await cursor.execute("INSERT INTO name_filtering(guild, enabled) VALUES(?,?)",(guild, 0)) + await self.connection.commit() + await cursor.close() + return False + async def get_new_nick(self, guild, flagged_nametype): + #returns a replacement nickname when when the bot flags one as needing to be changed + async with self.connection.execute("SELECT * FROM custom_names WHERE guild=?",(guild,)) as cursor: + server_nicks = await cursor.fetchone() + no_table = False + if server_nicks is None: + no_table = True + #Server does not have a table for custom nicknames yet + await cursor.execute("INSERT INTO custom_names(guild, nickname, username) VALUES(?,?,?)",(guild, "I had a bad nickname", "I had a bad username")) + self.connection.commit() + if flagged_nametype == "nickname": + return server_nicks[1] if not no_table else "I had a bad nickname" + elif flagged_nametype == "username": + return server_nicks[2] if not no_table else "I had a bad username" + else: + return "I had a bad name" \ No newline at end of file diff --git a/utils/time.py b/utils/time.py new file mode 100644 index 0000000..8946c93 --- /dev/null +++ b/utils/time.py @@ -0,0 +1,103 @@ +import datetime +from typing import Union + +from discord.ext import commands + +class timeconverters: + def secondsconverter(self, value, startType): + if startType == "s": + return value + elif startType == "m": + return value * 60 + elif startType == "h": + return value * 3600 + elif startType == "d": + return value * 86400 + return None + + def fromseconds(self, seconds): + if seconds >= 86400: + days = seconds//86400 + return str(days) + (" Day" if days==1 else " Days") + elif seconds >= 3600: + hours = seconds//3600 + return str(hours) + (" Hour" if hours==1 else " Hours") + elif seconds >= 60: + minutes = seconds//60 + return str(minutes) + (" Minute" if minutes==1 else " Minutes") + else: + return str(seconds) + (" Second" if seconds==1 else " Seconds") + +class InSeconds(commands.Converter): + async def convert(self,ctx,argument): + try: + int(str(argument)[-1]) + except: # The last character cannot be an int so convert + try: + argument = timeconverters().secondsconverter(int(str(argument[:-1])),str(argument)[-1]) + except ValueError: + raise commands.BadArgument("That isn't a timeframe!\nExamples of valid timeframes: `20s`, `1h`") + if argument is None: + raise commands.BadArgument(f"I couldn't understand the units.\nSupported units are: `s`, `m`, `h`, `d`\nExamples: `20s`, `1h`") + return argument + else: # The last character can be int so we assume it's in seconds + try: + return int(argument) + except ValueError: + raise commands.BadArgument("That isn't a timeframe!\nExamples of valid timeframes: `20s`, `1h`") + +class DiscordTimestamp(): + """ + A class useful for creating timestamps for display in discord. + Timestamp can be a UNIX timestamp, datetime or timedelta. + Relative means time is added to current time. + """ + def __init__(self,timestamp: Union[datetime.datetime,datetime.timedelta,int], relative: bool=False): + if isinstance(timestamp,datetime.datetime): + timestamp = timestamp.timestamp() + elif isinstance(timestamp,datetime.timedelta) and not relative: + timestamp = timestamp.total_seconds() + if relative: + if isinstance(timestamp,int): + timestamp = (datetime.datetime.now() + datetime.timedelta(seconds=timestamp)).timestamp() + else: + timestamp = (datetime.datetime.now() + timestamp).timestamp() + self.timestamp = int(timestamp) + + def _to_discord(self,t: str): + return f"" + + @property + def date(self): + """A date displayed in Discord, `10/07/2021` or `07/10/2021`.""" + return self._to_discord("d") + + @property + def date_full(self): + """A date displayed in readable format in Discord, `10 July 2021` or `July 10, 2021`.""" + return self._to_discord("D") + + @property + def time(self): + """A time displayed in Discord, `18:21` or `6:21 PM`.""" + return self._to_discord("t") + + @property + def time_full(self): + """A time displayed with seconds in Discord, `18:21:21` or `6:21:21 PM`.""" + return self._to_discord("T") + + @property + def date_time(self): + """A time displayed with date and time in Discord, `10 July 2021 18:21` or `July 10, 2021 6:21 PM`.""" + return self._to_discord("f") + + @property + def date_time_full(self): + """A time displayed with date, time and weekday in Discord, `Saturday, 10 July 2021 18:21` or `Saturday, July 10, 2021 6:21 PM`.""" + return self._to_discord("F") + + @property + def relative(self): + """A time displayed as relative in Discord, `10 minutes ago` if before current time or `in 10 minutes` if in the future.""" + return self._to_discord("R") \ No newline at end of file diff --git a/utils/utils.py b/utils/utils.py new file mode 100644 index 0000000..30ee382 --- /dev/null +++ b/utils/utils.py @@ -0,0 +1,33 @@ +import re +from collections import namedtuple + + +async def close_bot(bot): + #Shuts down the bot completely, closing the database connection in the process + await bot.connection.close() + await bot.close() + +#adds a guild's up-to-date regexes to the bot +def update_filter(bot, guild_filter): + filter_tuple = namedtuple("filter_tuple", ["enabled", "wildcard", "exact"]) + enabled = True if guild_filter[1] == 1 else False + #getting lists + bannedWilds = guild_filter[2].split(";") + bannedExacts = guild_filter[3].split(";") + if "" in bannedWilds: + bannedWilds.remove("") + if "" in bannedExacts: + bannedExacts.remove("") + #creating regexes + if bannedWilds: + wilds_pattern = "|".join(bannedWilds) + wilds_re = re.compile(wilds_pattern) + else: + wilds_re = None + if bannedExacts: + exacts_pattern = "|".join(bannedExacts) + exacts_re = re.compile(r"\b(?:%s)\b" % exacts_pattern) + else: + exacts_re = None + guild_tuple = filter_tuple(enabled=enabled, wildcard=wilds_re, exact=exacts_re) + bot.guild_filters[guild_filter[0]] = guild_tuple \ No newline at end of file