From 4003967af0189de83f0260992062b064dab4827f Mon Sep 17 00:00:00 2001 From: sgoudham Date: Fri, 21 Aug 2020 04:19:15 +0100 Subject: [PATCH] Refactored EVERYTHING No more converting to strings and back to ints Moving on from MariaD to PostGresSQL (THANK THE GODS) Storing modmail in cache Close to implementing circular queue for member cache FULLY REDID COMMENTARY Updated Version Number to 0.8.2 (Closer to v1 release!) Gonna test commands for the cache within main.py --- bot/__init__.py | 665 ++++++++++++++++++++++++++----------------- bot/libs/cache.py | 40 +-- cogs/enso.py | 19 +- cogs/guild.py | 444 +++++++++++++++-------------- cogs/help.py | 7 +- cogs/interactive.py | 138 +++++---- cogs/moderation.py | 132 +++++---- cogs/owner.py | 38 ++- cogs/relationship.py | 155 +++++----- main.py | 19 ++ 10 files changed, 937 insertions(+), 720 deletions(-) diff --git a/bot/__init__.py b/bot/__init__.py index d2101537..65afb06b 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -19,7 +19,7 @@ import os import random import aiohttp -import aiomysql +import asyncpg as asyncpg import discord from decouple import config from discord import Colour, Embed @@ -44,7 +44,7 @@ port = config('DB_PORT') db = config('DB_NAME') disc_bots_gg_auth = config('DISCORD_BOTS_BOTS_AUTH') -# Getting the Bot token from Environment Variables +# Getting the bot token from environment variables API_TOKEN = config('DISCORD_TOKEN') @@ -53,20 +53,21 @@ class Bot(commands.Bot): async def get_prefix(bot, message): """Allow the commands to be used with mentioning the bot""" + if message.guild is None: - return "~" - return when_mentioned_or(self.get_prefix_for_guild(str(message.guild.id)))(bot, message) + return "." + return when_mentioned_or(self.get_prefix_for_guild(message.guild.id))(bot, message) super().__init__(command_prefix=get_prefix, **options) self.db = None - self.description = 'All current available commands within Ensō~Chan', # Set a description for the bot + self.description = 'All current available commands within Ensō~Chan', self.owner_id = 154840866496839680 # Your unique User ID self.case_insensitive = True # Commands are now Case Insensitive self.admin_colour = Colour(0x62167a) # Admin Embed Colour - self.version = "0.7.2" # Version number of Ensō~Chan + self.version = "0.8.2" # Version number of Ensō~Chan self.remove_command("help") # Remove default help command - # Define variables that are for Enso only + # Instance variables for Enso self.hammyMention = '<@154840866496839680>' self.hammy_role_ID = "<@&715412394968350756>" self.blank_space = "\u200b" @@ -80,74 +81,66 @@ class Bot(commands.Bot): self.enso_modmail_ID = 728083016290926623 self.enso_feedback_ID = 739807803438268427 + # Instance variables for cache self.enso_cache = {} - self.member_cache = MyCoolCache(1000) - - async def check_cache(member_id, guild_id): - - pool = self.db - - # If the key is within the cache already - if (member_id, guild_id) in self.member_cache.cache: - return self.member_cache.cache[member_id, guild_id] - - else: - # fetch data from database - # Setup pool connection and cursor - async with pool.acquire() as conn: - async with conn.cursor() as author_cursor: - # Get the author's/members row from the Members Table - select_query = """SELECT * FROM members WHERE discordID = (%s) and guildID = (%s)""" - member_val = member_id, guild_id, - - # Execute The SQL Query - await author_cursor.execute(select_query, member_val) - result = await author_cursor.fetchone() - - # Store it in cache - dict_items = {"married": result[1], - "marriage_date": result[2], - "muted_roles": result[4], - "roles": result[5]} - self.member_cache.store_cache([member_id, guild_id], dict_items) + self.modmail_cache = {} + self.member_cache = MyCoolCache(2) async def create_connection(): """Setting up connection using pool/aiomysql""" - self.db = await aiomysql.create_pool( + self.db = await asyncpg.create_pool( host=host, port=int(port), user=user, password=password, - db=db, - loop=self.loop) + database=db, + loop=self.loop, + command_timeout=60) async def startup_cache_log(): - """Store the modlogs/prefixes in cache from the database on startup""" + """Store the guilds/modmail systems in cache from the database on startup""" - # Setup pool + # Setup up pool connection pool = self.db - - # Setup up pool connection and cursor async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Grab the prefix of the server from the database - select_query = """SELECT * FROM guilds""" - # Execute the query - await cur.execute(select_query) - results = await cur.fetchall() + # Query to get all records of guilds that the bot is in + try: + results = await conn.fetch("""SELECT * FROM guilds""") - # Store the guildID's, modlog channels and prefixes within cache + # Store the guilds information within cache + for row in results: + self.enso_cache[row[0]] = {"prefix": row[1], + "modlogs": row[2], + "roles_persist": row[3]} + # Catch errors + except asyncpg.PostgresError as e: + print("PostGres Error: Guild Records Could Not Be Loaded Into Cache On Startup", e) + + # Query to get all records of modmails within guilds + try: + results = await conn.fetch("""SELECT * FROM moderatormail""") + + # Store the information for modmail within cache for row in results: - self.enso_cache[row[0]] = {"prefix": row[1], "modlogs": row[2], "roles_persist": row[3]} + self.modmail_cache[row[0]] = {"modmail_channel_id": row[1], + "message_id": row[2], + "modmail_logging_channel_id": row[3]} + # Catch errors + except asyncpg.PostgresError as e: + print("PostGres Error: Modmail Records Could Not Be Loaded Into Cache On Startup", e) - # Make sure the connection is setup before the bot is ready + # Release connection back to pool + await pool.release(conn) + + # Establish Database Connection self.loop.run_until_complete(create_connection()) + # Load Information Into Cache self.loop.run_until_complete(startup_cache_log()) async def post_bot_stats(): - """Method To Update Guild Count On discord.bots.gg""" + """Update guild count on discord.bots.gg""" async with aiohttp.ClientSession() as session: await session.post(f"https://discord.bots.gg/api/v1/bots/{self.user.id}/stats", @@ -158,13 +151,13 @@ class Bot(commands.Bot): @tasks.loop(minutes=10, reconnect=True) async def change_status(): - """Creating Custom Statuses as a Background Task""" + """Creating custom statuses as background task""" global counter # Waiting for the bot to ready await self.wait_until_ready() - # Update Guild Count on discord.bots.gg + # Update guild count on discord.bots.gg await post_bot_stats() # Define array of statuses @@ -178,7 +171,7 @@ class Bot(commands.Bot): discord.Activity( type=discord.ActivityType.watching, name=f"Hamothy Program | {self.version}"), - discord.Game(name=f"~help | {self.version}") + discord.Game(name=f".help | {self.version}") ] # Check if the counter is at the end of the array @@ -197,10 +190,12 @@ class Bot(commands.Bot): # --------------------------------------------!Cache Section!------------------------------------------------------- - def store_cache(self, guild_id, prefix, channel, rolespersist): - """Storing GuildID, Modlogs Channel and Prefix in Cache""" + def store_cache(self, guild_id, prefix, modlogs, roles_persist): + """Storing guild information within cache""" - self.enso_cache[guild_id] = {"prefix": prefix, "modlogs": channel, "roles_persist": rolespersist} + self.enso_cache[guild_id] = {"prefix": prefix, + "modlogs": modlogs, + "roles_persist": roles_persist} def del_cache(self, guild_id): """Deleting the entry of the guild within the cache""" @@ -209,6 +204,35 @@ class Bot(commands.Bot): # --------------------------------------------!End Cache Section!--------------------------------------------------- + # --------------------------------------------!Modmail Section!----------------------------------------------------- + + def cache_store_modmail(self, guild_id, modmail_channel, message, modmail_logging_channel): + """Storing all modmail channels within cache""" + + self.modmail_cache[guild_id] = {"modmail_channel_id": modmail_channel, + "message_id": message, + "modmail_logging_channel_id": modmail_logging_channel} + + def get_modmail(self, guild_id): + """Returning the modmail system of the guild""" + + if guild_id in self.modmail_cache: + return self.modmail_cache[guild_id] + else: + return None + + def update_modmail(self, guild_id, channel_id): + """Update the modmail channel""" + + self.modmail_cache[guild_id]["roles_persist"] = channel_id + + def delete_modmail(self, guild_id): + """Deleting the modmail system of the guild within the Cache""" + + del self.modmail_cache[guild_id] + + # --------------------------------------------!EndModmail Section!-------------------------------------------------- + # --------------------------------------------!RolePersist Section!------------------------------------------------- def get_roles_persist(self, guild_id): @@ -216,57 +240,67 @@ class Bot(commands.Bot): return self.enso_cache[guild_id]["roles_persist"] - async def update_role_persist(self, guild_id, value, pool): + async def update_role_persist(self, guild_id, value): """Update the rolepersist value of the guild (Enabled or Disabled)""" - self.enso_cache[guild_id]["roles_persist"] = value - - # Setup up pool connection and cursor + # Setup up pool connection + pool = self.db async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Update the existing prefix within the database - update_query = """UPDATE guilds SET rolespersist = (%s) WHERE guildID = (%s)""" - update_vals = value, guild_id, - # Execute the query - await cur.execute(update_query, update_vals) - await conn.commit() + # Query for updating rolepersist values For guilds + try: + update_query = """UPDATE guilds SET roles_persist = $1 WHERE guild_id = $2""" + await conn.execute(update_query, value, guild_id) + + # Catch error + except asyncpg.PostgresError as e: + print(f"PostGres Error: RolePersist For Guild {guild_id} Could Not Be Updated", e) + + # Store in cache + else: + self.enso_cache[guild_id]["roles_persist"] = value + + # Release connection back to pool + finally: + await pool.release(conn) # --------------------------------------------!End RolePersist Section!--------------------------------------------- # --------------------------------------------!ModLogs Section!----------------------------------------------------- - async def storage_modlog_for_guild(self, pool, ctx, channel_id, setup): + async def storage_modlog_for_guild(self, ctx, channel_id, setup): """Updating the modlog within the dict and database""" - self.enso_cache[str(ctx.guild.id)]["modlogs"] = channel_id - - # Setup up pool connection and cursor + # Setup up pool connection + pool = self.db async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Update the existing modlogs channel within the database - update_query = """UPDATE guilds SET modlogs = (%s) WHERE guildID = (%s)""" - update_vals = channel_id, ctx.guild.id, - - # Execute the query - await cur.execute(update_query, update_vals) - await conn.commit() - - # Send custom confirmation messages to log based on the command update or setup - if setup: - print(cur.rowcount, f"Modlog channel for guild {ctx.guild.name} has been Setup") - else: - print(cur.rowcount, f"Modlog channel for guild {ctx.guild.name} has been Updated") - if setup: - # Send confirmation that modmail channel has been setup - await self.bot.generate_embed(ctx, desc=f"**Modlogs Channel** successfully setup in <#{channel_id}>" + - f"\nPlease refer to **{ctx.prefix}help** for any information") - else: - # Let the user know that the guild modlogs channel has been updated - channel = ctx.guild.get_channel(channel_id) - await self.generate_embed(ctx, - desc=f"Modlog Channel for **{ctx.guild.name}** has been updated to {channel.mention}") + # Query to update modlogs within the database + try: + update_query = """UPDATE guilds SET modlogs = $1 WHERE guild_id = $2""" + rowcount = await conn.execute(update_query, channel_id, ctx.guild.id) + + # Catch errors + except asyncpg.PostgresError as e: + print("PostGres Error: Modlogs Value In Guilds Table Could Not Be Updated/Setup", e) + + # Let the user know that modlogs channel has been updated/setup + else: + if setup: + print(rowcount, f"Modlog channel for guild {ctx.guild} has been Setup") + await self.generate_embed(ctx, desc=f"**Modlogs Channel** successfully setup in <#{channel_id}>" + + f"\nPlease refer to **{ctx.prefix}help** for any information") + else: + print(rowcount, f"Modlog channel for guild {ctx.guild} has been Updated") + await self.generate_embed(ctx, + desc=f"Modlog Channel for **{ctx.guild}** has been updated to <#{channel_id}>") + + # Store in cache + self.enso_cache[ctx.guild.id]["modlogs"] = channel_id + + # Release connection back to pool + finally: + await pool.release(conn) def remove_modlog_channel(self, guild_id): """Remove the value of modlog for the guild specified""" @@ -283,33 +317,42 @@ class Bot(commands.Bot): # --------------------------------------------!Prefixes Section!---------------------------------------------------- - async def storage_prefix_for_guild(self, pool, ctx, prefix): + async def storage_prefix_for_guild(self, ctx, prefix): """Updating the prefix within the dict and database when the method is called""" - self.enso_cache[str(ctx.guild.id)]["prefix"] = prefix - - # Setup up pool connection and cursor + # Setup up pool connection + pool = self.db async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Update the existing prefix within the database - update_query = """UPDATE guilds SET prefix = (%s) WHERE guildID = (%s)""" - update_vals = prefix, ctx.guild.id, - # Execute the query - await cur.execute(update_query, update_vals) - await conn.commit() - print(cur.rowcount, f"Guild prefix has been updated for guild {ctx.guild.name}") + # Query to update the existing prefix within the database + try: + update_query = """UPDATE guilds SET prefix = $1 WHERE guild_id = $2""" + + rowcount = await conn.execute(update_query, prefix, ctx.guild.id) + print(rowcount, f"Guild prefix has been updated for guild {ctx.guild}") + + # Catch errors + except asyncpg.PostgresError as e: + print(f"PostGres Error: Prefix For Guild {ctx.guild.id} Could Not Be Updated", e) - # Let the user know that the guild prefix has been updated - await self.generate_embed(ctx, desc=f"**Guild prefix has been updated to `{prefix}`**") + # Let the user know that the guild prefix has been updated + else: + await self.generate_embed(ctx, desc=f"Guild prefix has been updated to **{prefix}**") + + # Store in cache + self.enso_cache[ctx.guild.id]["prefix"] = prefix + + # Release connection back to pool + finally: + await pool.release(conn) def get_prefix_for_guild(self, guild_id): """Get the prefix of the guild that the user is in""" prefix = self.enso_cache[guild_id]["prefix"] - if prefix is not None: + if prefix: return prefix - return "~" + return "." # --------------------------------------------!End Prefixes Section!------------------------------------------------ @@ -322,7 +365,7 @@ class Bot(commands.Bot): return Colour(random.randint(0, 0xFFFFFF)) async def generate_embed(self, ctx, desc): - """Generate Embed""" + """Generate embed""" embed = Embed(description=desc, colour=self.admin_colour) @@ -330,40 +373,55 @@ class Bot(commands.Bot): await ctx.send(embed=embed) async def storeRoles(self, target, ctx, member): - """Storing User Roles within Database""" - - pool = self.db + """Storing user roles within database""" role_ids = ", ".join([str(r.id) for r in target.roles]) - # Setup up pool connection and cursor + # Setup up pool connection + pool = self.db async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Store the existing roles of the user within the database - update_query = """UPDATE members SET mutedroles = (%s) WHERE guildID = (%s) AND discordID = (%s)""" - update_vals = role_ids, ctx.guild.id, member.id - # Execute the query - await cur.execute(update_query, update_vals) - await conn.commit() - print(cur.rowcount, f"Roles Added For User {member} in {ctx.guild.name}") + # Query to store existing roles of the member within the database + try: + update_query = """UPDATE members SET muted_roles = $1 WHERE guild_id = $2 AND member_id = $3""" + rowcount = await conn.execute(update_query, role_ids, ctx.guild.id, member.id) + + # Catch errors + except asyncpg.PostgresError as e: + print(f"PostGres Error: Roles Could Not Be Stored For Member {member.id} in Guild {member.guild.id}", e) + + # Print success + else: + print(rowcount, f"Roles Added For User {member} in {ctx.guild}") + + # Release connection back to pool + finally: + await pool.release(conn) async def clearRoles(self, member): """Clear the roles when the user has been unmuted""" + # Setup up pool connection pool = self.db - - # Setup up pool connection and cursor async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Clear the existing roles of the user from the database - update_query = """UPDATE members SET mutedroles = NULL WHERE guildID = (%s) AND discordID = (%s)""" - update_vals = member.guild.id, member.id - # Execute the query - await cur.execute(update_query, update_vals) - await conn.commit() - print(cur.rowcount, f"Roles Cleared For User {member} in {member.guild.name}") + # Query to clear the existing role of the member from the database + try: + update_query = """UPDATE members SET muted_roles = NULL WHERE guild_id = $1 AND member_id = $2""" + rowcount = await conn.execute(update_query, member.guild.id, member.id) + + # Catch error + except asyncpg.PostgresError as e: + print(f"PostGres Error: Roles Could Not Be Cleared for Member {member.id} in Guild {member.guild.id}", + e) + + # Print success + else: + print(rowcount, f"Roles Cleared For User {member} in {member.guild.name}") + + # Release connection back to pool + finally: + await pool.release(conn) # --------------------------------------------!End Roles/Colour/Embed Section!-------------------------------------- @@ -379,7 +437,8 @@ class Bot(commands.Bot): @staticmethod async def on_ready(): - """Displaying if Bot is Ready""" + """Display Startup Message""" + print("UvU Senpaiii I'm ready\n") async def on_guild_join(self, guild): @@ -388,70 +447,81 @@ class Bot(commands.Bot): Store prefix/modlogs in the cache """ - # Store guildID, modlogs channel and prefix to cache - self.store_cache(str(guild.id), channel=None, prefix="~", rolespersist=0) + # Store every single record into an array + records = [(member.id, None, None, guild.id, None, None) for member in guild.members] - # Setup pool + # Setup up pool connection pool = self.db + async with pool.acquire() as conn: - # Grabbing the values to be inserted - records = ", ".join(map(lambda m: f"({guild.id}, {m.id})", guild.members)) + # Query to insert the guild information into guilds table + try: + insert_query = """INSERT INTO guilds VALUES ($1, $2, $3, $4) ON CONFLICT (guild_id) DO NOTHING""" + rowcount = await conn.execute(insert_query, guild.id, ".", None, 0) - # Setup up pool connection and cursor - async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Define the insert statement for inserting the guild into the guilds table - insert_query = """INSERT INTO guilds (guildID) VALUES (%s) ON DUPLICATE KEY UPDATE guildID = VALUES(guildID)""" - val = guild.id, - - # Execute the query - await cur.execute(insert_query, val) - await conn.commit() - print(cur.rowcount, f"Record(s) inserted successfully into Guilds from {guild.name}") - - async with conn.cursor() as cur: - # Define the insert statement that will insert the user's information - insert = """INSERT INTO members (guildID, discordID) VALUES {} - ON DUPLICATE KEY UPDATE guildID = VALUES(guildID), discordID = VALUES(discordID)""".format( - records) - - # Execute the query - await cur.execute(insert) - await conn.commit() - print(cur.rowcount, f"Record(s) inserted successfully into Members from {guild.name}") + # Catch errors + except asyncpg.PostgresError as e: + print(f"PostGres Error: Guild {guild.id} Could Not Be Inserted Into Guilds Table", e) + + # Print success + else: + print(rowcount, f"Record(s) inserted successfully into {guild}") + + # Query to insert all the member details to members table + try: + rowcount = await conn.copy_records_to_table("members", records=records) + + # Catch errors + except asyncpg.PostgresError as e: + print(f"PostGres Error: Members Could Not Be Inserted Into Members Table For Guild {guild.id}", e) + + # Store in cache + else: + print(rowcount, f"Record(s) inserted successfully into Members from {guild}") + self.store_cache(guild.id, modlogs=None, prefix=".", roles_persist=0) + + # Release connection back to pool + await pool.release(conn) async def on_guild_remove(self, guild): """ Remove users in the database for the guild Remove the modlogs/guild from the cache """ - # Delete the key - value pairs for the guild - self.del_cache(str(guild.id)) - # Setup pool + # Setup pool connection pool = self.db - - # Setup pool connection and cursor async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Delete the guild and prefix information as the bot leaves the server - delete_query = """DELETE FROM guilds WHERE guildID = %s""" - val = guild.id, - - # Execute the query - await cur.execute(delete_query, val) - await conn.commit() - print(cur.rowcount, f"Record deleted successfully from Guild {guild.name}") - - async with conn.cursor() as cur: - # Delete the record of the member as the bot leaves the server - delete_query = """DELETE FROM members WHERE guildID = %s""" - vals = guild.id, - - # Execute the query - await cur.execute(delete_query, vals) - await conn.commit() - print(cur.rowcount, f"Record(s) deleted successfully from Members from {guild.name}") + + # Delete the guild information as the bot leaves the server + try: + delete_query = """DELETE FROM guilds WHERE guild_id = $1""" + rowcount = await conn.execute(delete_query, guild.id) + + # Catch errors + except asyncpg.PostgresError as e: + print(f"PostGres Error: On Guild Remove Event Record Was Not Deleted For {guild.id}", e) + + # Delete the key - value pair for the guild + else: + print(rowcount, f"Record deleted successfully from Guild {guild}") + self.del_cache(guild.id) + + # Delete the record of the member as the bot leaves the server + try: + delete_query = """DELETE FROM members WHERE guild_id = $1""" + rowcount = await conn.execute(delete_query, guild.id) + + # Catch errors + except asyncpg.PostgresError as e: + print(f"PostGres Error: All Members Could Not Be Deleted From {guild.id}", e) + + # Print success + else: + print(rowcount, f"Record(s) deleted successfully from Members from {guild}") + + # Release connection back to pool + await pool.release(conn) async def on_member_join(self, member): """ @@ -459,120 +529,177 @@ class Bot(commands.Bot): In the Enso guild, it will send an introduction embed """ + # Ignoring bots if member.bot: return - # Get the guild + # Get the guild and role persist value of the guild guild = member.guild + role_persist = self.get_roles_persist(guild.id) - # Setup pool + # Setup pool connection pool = self.db + async with pool.acquire() as conn: - role_persist = self.get_roles_persist(str(guild.id)) + # Define the insert statement that will insert the user's information + try: + insert_query = """INSERT INTO members (guild_id, member_id) VALUES ($1, $2) + ON CONFLICT (guild_id, member_id) DO NOTHING""" - # Setup pool connection and cursor - async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Define the insert statement that will insert the user's information - insert_query = """INSERT INTO members (guildID, discordID) VALUES (%s, %s) - ON DUPLICATE KEY UPDATE guildID = VALUES(guildID), discordID = VALUES(discordID)""" - vals = member.guild.id, member.id, - - # Execute the SQL Query - await cur.execute(insert_query, vals) - await conn.commit() - print(cur.rowcount, f"{member} Joined {member.guild.name}, Record Inserted Into Members") - - async with conn.cursor() as cur: - # Get the roles of the user from the database - select_query = """SELECT * FROM members WHERE guildID = (%s) AND discordID = (%s)""" - vals = member.guild.id, member.id, - - # Execute the SQL Query - await cur.execute(select_query, vals) - result = await cur.fetchone() - role_ids = result[5] + rowcount = await conn.execute(insert_query, member.guild.id, member.id) + + # Catch errors + except asyncpg.PostgresError as e: + print(f"PostGres Error: Member {member.id} was not be able to be added to Guild {member.guild.id}", e) + + # Print success + else: + print(rowcount, f"{member} Joined {member.guild}, Record Inserted Into Members") + + # Get the roles of the user from the database + try: + select_query = """SELECT * FROM members WHERE guild_id = $1 AND member_id = $2""" + + user_joined = await conn.fetchrow(select_query, member.guild.id, member.id) + role_ids = user_joined[5] + + # Catch errors + except asyncpg.PostgresError as e: + print(f"PostGres Error: Member {member} Record Not Found", e) + # Give roles back to the user if role persist is enabled + else: if role_persist == 1: # Get Enso Chan bot = guild.get_member(self.user.id) # Check permissions of Enso - if bot.guild_permissions.manage_roles and role_ids is not None: + if bot.guild_permissions.manage_roles and role_ids: # Get all the roles of the user before they were muted from the database roles = [member.guild.get_role(int(id_)) for id_ in role_ids.split(", ") if len(id_)] # Give the member their roles back await member.edit(roles=roles) - print(f"{member} Had Their Roles Given Back In {member.guild.name}") + print(f"{member} Had Their Roles Given Back In {member.guild}") else: - print(f"Insufficient Permissions to Add Roles to {member} in {member.guild.name}") - - # Reset the roles entry for the database - update_query = """UPDATE members SET roles = NULL WHERE guildID = (%s) AND discordID = (%s)""" - update_vals = member.guild.id, member.id, - - # Execute the query - await cur.execute(update_query, update_vals) - await conn.commit() - print(cur.rowcount, f"Roles Cleared For {member} in {member.guild.name}") - - # Make sure the guild is Enso - if guild.id == self.enso_guild_ID: - # Set the channel id to "newpeople" - new_people = guild.get_channel(self.enso_newpeople_ID) - - # Set the enso server icon and the welcoming gif - server_icon = guild.icon_url - welcome_gif = "https://cdn.discordapp.com/attachments/669808733337157662/730186321913446521/NewPeople.gif" - - # Set up embed for the #newpeople channel - embed = Embed(title="\n**Welcome To Ensō!**", - colour=self.admin_colour, - timestamp=datetime.datetime.utcnow()) - - embed.set_thumbnail(url=server_icon) - embed.set_image(url=welcome_gif) - embed.add_field( - name=self.blank_space, - value=f"Hello {member.mention}! We hope you enjoy your stay in this server! ", - inline=False) - embed.add_field( - name=self.blank_space, - value=f"Be sure to check out our <#669815048658747392> channel to read the rules and <#683490529862090814> channel to get caught up with any changes! ", - inline=False) - embed.add_field( - name=self.blank_space, - value=f"Last but not least, feel free to go into <#669775971297132556> to introduce yourself!", - inline=False) - - # Send embed to #newpeople - await new_people.send(embed=embed) + print(f"Insufficient Permissions to Add Roles to Member {member.id} in Guild {member.guild.id}") + + # Reset the roles entry for the database + try: + update_query = """UPDATE members SET roles = NULL WHERE guild_id = $1 AND member_id = $2""" + rowcount = await conn.execute(update_query, member.guild.id, member.id) + + # Catch errors + except asyncpg.PostgresError as e: + print(f"PostGres Error: Clearing Member {member.id} Roles in Guild {member.guild.id}", e) + + # Print success + else: + print(rowcount, f"Roles Cleared For {member} in {member.guild}") + + # Release connection back to pool + await pool.release(conn) + + # Make sure the guild is Enso and send welcoming embed to the server + if guild.id == self.enso_guild_ID: + new_people = guild.get_channel(self.enso_newpeople_ID) + + server_icon = guild.icon_url + welcome_gif = "https://cdn.discordapp.com/attachments/669808733337157662/730186321913446521/NewPeople.gif" + + embed = Embed(title="\n**Welcome To Ensō!**", + colour=self.admin_colour, + timestamp=datetime.datetime.utcnow()) + + embed.set_thumbnail(url=server_icon) + embed.set_image(url=welcome_gif) + embed.add_field( + name=self.blank_space, + value=f"Hello {member.mention}! We hope you enjoy your stay in this server!", + inline=False) + embed.add_field( + name=self.blank_space, + value=f"Be sure to check out our <#669815048658747392> channel to read the rules and <#683490529862090814> channel to get caught up with any changes! ", + inline=False) + embed.add_field( + name=self.blank_space, + value=f"Last but not least, feel free to go into <#669775971297132556> to introduce yourself!", + inline=False) + + # Send embed to #newpeople + await new_people.send(embed=embed) async def on_member_remove(self, member): - """Storing User Roles within Database When User Leaves Guild""" + """Storing member roles within the database when the member leaves""" + # Ignoring bots if member.bot: return + # Store member roles within a string to insert into database role_ids = ", ".join([str(r.id) for r in member.roles if not r.managed]) - # Setup pool + # Setup pool connection pool = self.db - - # Setup pool connection and cursor async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Define the insert statement that will insert the user's information - update_query = """UPDATE members SET roles = (%s) WHERE guildID = (%s) AND discordID = (%s)""" - vals = role_ids, member.guild.id, member.id, - # Execute the SQL Query - await cur.execute(update_query, vals) - await conn.commit() - print(cur.rowcount, f"{member} Left {member.guild.name}, Roles stored into Members") + # Store member roles within the database + try: + update_query = """UPDATE members SET roles = $1 WHERE guild_id = $2 AND member_id = $3""" + rowcount = await conn.execute(update_query, role_ids, member.guild.id, member.id) + + # Catch Error + except asyncpg.PostgresError as e: + print(f"PostGres Error: Roles Could Not Be Added To {member} When Leaving {member.guild.id}", e) + + # Print success + else: + print(rowcount, f"{member} Left {member.guild.name}, Roles stored into Members") + + finally: + # Release connection back to pool + await pool.release(conn) # --------------------------------------------!End Events Section!---------------------------------------------- + async def check_cache(self, member_id, guild_id): + """Checks if member is in the member cache""" + + # Return key-value pair if member is already in the cache + if (member_id, guild_id) in self.member_cache.cache: + return self.member_cache.cache[member_id, guild_id] + + else: + + # Setup pool connection + pool = self.db + async with pool.acquire() as conn: + + # Get the author's/members row from the Members Table + try: + select_query = """SELECT * FROM members WHERE member_id = $1 and guild_id = $2""" + member_val = member_id, guild_id, + + result = await conn.fetchrow(select_query, member_val) + + # Catch errors + except asyncpg.PostgresError as e: + print(f"PostGres Error: Member {member_id} From Guild {guild_id}" + "Record Could Not Be Retrieved When Checking Cache", e) + + # Store it in cache + else: + dict_items = {"married": result[1], + "marriage_date": result[2], + "muted_roles": result[4], + "roles": result[5]} + self.member_cache.store_cache((member_id, guild_id), dict_items) + + return self.member_cache.cache[(member_id, guild_id)] + + # Release connection back to pool + finally: + await pool.release(conn) + def execute(self): """Load the cogs and then run the bot""" diff --git a/bot/libs/cache.py b/bot/libs/cache.py index 570c6560..93c29584 100644 --- a/bot/libs/cache.py +++ b/bot/libs/cache.py @@ -39,10 +39,10 @@ class CachingCircularQueue: def remove(self, value): # To my knowledge, this method can be called when deleting a single member and many members??? - # TODO: So this should only be called to remove a single member at a time + # So this should only be called to remove a single member at a time with self.threadLock: # Remove the value inside the array (value will be a tuple that is passed in) - # TODO: PRECONDITION, VALUE EXISTS IN CACHE, SO SHOULD EXIST IN LIST + # PRECONDITION, VALUE EXISTS IN CACHE, SO SHOULD EXIST IN LIST self.values.remove(value) # As you said, to ensure concurrency, set the current size back to the length of the array @@ -57,19 +57,25 @@ class MyCoolCache: def store_cache(self, key, dict_item): with self.threadLock: - # TODO: Changed == to >= just incase of concurrency issue meaning size exceeds maximum, thats my fault - if len(self.queue.values) >= self.MAX_SIZE: - key_to_delete = None - if key in self.cache: - if self.cache[key] is None: - key_to_delete = self.queue.push(key) - else: - key_to_delete = self.queue.push(key) + has_key = True + # Assume the key exists in the cache + if key in self.cache: + # If the key is None, aka removed + if self.cache[key] is None: + has_key = False + else: + # Or doesn't exist + has_key = False + + # Then we don't have the key. + # In this case, we have to check if adding a key will exceed max size + if not has_key: + key_to_delete = self.queue.push(key) + # If the key is not None, that means the queue was full. We must delete an item. + if key_to_delete is not None: self.cache[key_to_delete] = None - else: - self.cache[key] = dict_item - self.queue.push(key) + self.cache[key] = dict_item def remove_many(self, in_guild_id): # This method is to be used for when the bot has left a guild @@ -77,11 +83,9 @@ class MyCoolCache: # For every member within the cache for (member_id, guild_id) in self.cache: # if the guild_id passed in is equal to the guild_id within the cache - # TODO: Changed 'in' to ==, remember you're comparing one entry at a time if in_guild_id == guild_id: - # set that entry to be equal to none - # TODO: When removing a value from the cache due to a guild leave, permanently remove all values - # TODO: Yes it is expensive, however as this can run concurrently and we won't need the data available - # TODO: For this guild, it doesn't matter how long it takes, and will save in memory in the long term + # When removing a value from the cache due to a guild leave, permanently remove all values + # Yes it is expensive, however as this can run concurrently and we won't need the data available + # For this guild, it doesn't matter how long it takes, and will save in memory in the long term self.cache.pop((member_id, guild_id)) self.queue.remove((member_id, guild_id)) diff --git a/cogs/enso.py b/cogs/enso.py index 7d0b84ac..caee928f 100644 --- a/cogs/enso.py +++ b/cogs/enso.py @@ -120,7 +120,7 @@ class Enso(Cog): # Making sure this command only works in Enso if not ctx.guild.id == self.bot.enso_guild_ID: - await ctx.send("**Sorry! That command is only for a certain guild!**") + await self.bot.generate_embed(ctx, desc="**Sorry! That command is only for a certain guild!**") return # If the channel that the command has been sent is in the list of accepted channels @@ -143,8 +143,9 @@ class Enso(Cog): # Send the list of available members to the channel nice = string.capwords(', '.join(map(str, enso_people()))) # Send error message saying that the person isn't recognised - await ctx.send(f"Sorry! That person doesn't exist! Try the names listed below!" - f"\n{nice}") + await self.bot.generate_embed(ctx, + desc=f"Sorry! That person doesn't exist! Try the names listed below!" + f"\n{nice}") else: @@ -180,8 +181,8 @@ class Enso(Cog): # Send the list of available members to the channel nice = string.capwords(', '.join(map(str, enso_people()))) # Send error message saying that the person isn't recognised - await ctx.send(f"Try the names listed below!" - f"\n{nice}") + await self.bot.generate_embed(ctx, desc=f"Try the names listed below!" + f"\n{nice}") @command(name="rules") @cooldown(1, 5, BucketType.user) @@ -190,7 +191,7 @@ class Enso(Cog): # Making sure this command only works in Enso if not ctx.guild.id == self.bot.enso_guild_ID: - await ctx.send("**Sorry! That command is only for a certain guild!**") + await self.bot.generate_embed(ctx, desc="**Sorry! That command is only for a certain guild!**") return # Define Izzy's roles ID @@ -284,7 +285,7 @@ class Enso(Cog): """Leveled role/xp system for Ensō""" if not ctx.guild.id == self.bot.enso_guild_ID: - await ctx.send("**Sorry! That command is only for a certain guild!**") + await self.bot.generate_embed(ctx, desc="**Sorry! That command is only for a certain guild!**") return # Get the url of the leveled roles image @@ -400,7 +401,7 @@ class Enso(Cog): role = discord.utils.find(lambda r: r.name == payload.emoji.name, guild.roles) # if the role does exist - if role is not None: + if role: # Print to me that the role was found and display the id of the role print(role.name + " was found!") print(role.id) @@ -437,7 +438,7 @@ class Enso(Cog): role = discord.utils.find(lambda r: r.name == payload.emoji.name, guild.roles) # if the role does exist - if role is not None: + if role: # Find the member that has the role which the emoji is connected to member = discord.utils.find(lambda m: m.id == payload.user_id, guild.members) diff --git a/cogs/guild.py b/cogs/guild.py index 16934873..eb2e3fee 100644 --- a/cogs/guild.py +++ b/cogs/guild.py @@ -19,6 +19,7 @@ import datetime import io import random +import asyncpg import discord from discord import Embed, TextChannel from discord import File @@ -188,7 +189,7 @@ class Guild(Cog): async def rp_status(self, ctx): """Showing the status of the role persist within the guild""" - if self.bot.get_roles_persist(str(ctx.guild.id)) == 0: + if self.bot.get_roles_persist(ctx.guild.id) == 0: await self.bot.generate_embed(ctx, desc=f"**Role Persist is currently disabled within {ctx.guild}**") else: await self.bot.generate_embed(ctx, desc=f"**Role Persist is currently enabled within {ctx.guild}**") @@ -199,10 +200,8 @@ class Guild(Cog): async def rp_enable(self, ctx): """Enabling role persist within the guild""" - pool = self.bot.db - - if self.bot.get_roles_persist(str(ctx.guild.id)) == 0: - await self.bot.update_role_persist(str(ctx.guild.id), value=1, pool=pool) + if self.bot.get_roles_persist(ctx.guild.id) == 0: + await self.bot.update_role_persist(ctx.guild.id, value=1) await self.bot.generate_embed(ctx, desc=f"**Role Persist has been enabled within {ctx.guild}!**") else: await self.bot.generate_embed(ctx, desc=f"**Role Persist is already enabled within {ctx.guild}!**") @@ -213,10 +212,8 @@ class Guild(Cog): async def rp_disable(self, ctx): """Disabling role persist within the guild""" - pool = self.bot.db - - if self.bot.get_roles_persist(str(ctx.guild.id)) == 1: - await self.bot.update_role_persist(str(ctx.guild.id), value=0, pool=pool) + if self.bot.get_roles_persist(ctx.guild.id) == 1: + await self.bot.update_role_persist(ctx.guild.id, value=0) await self.bot.generate_embed(ctx, desc=f"**Role Persist has been disabled within {ctx.guild}!**") else: await self.bot.generate_embed(ctx, desc=f"**Role Persist is already disabled within {ctx.guild}!**") @@ -226,17 +223,17 @@ class Guild(Cog): @bot_has_permissions(administrator=True) async def modlogs(self, ctx): """ - Show Current Modlogs Channel (If Setup) + Show current modlogs channel """ - ml_channel = self.bot.get_modlog_for_guild(str(ctx.guild.id)) + ml_channel = self.bot.get_modlog_for_guild(ctx.guild.id) # Send current modlogs channel only if it is setup # Send error if no modlogs channel has been setup - if ml_channel is not None: + if ml_channel: # Get the modlog channel for the current guild - channel = ctx.guild.get_channel(int(ml_channel)) + channel = ctx.guild.get_channel(ml_channel) text = f"**The current modlogs channel is set to {channel.mention}**" await self.bot.generate_embed(ctx, desc=text) @@ -250,105 +247,114 @@ class Guild(Cog): @has_permissions(manage_guild=True) @bot_has_permissions(administrator=True) async def mlsetup(self, ctx, user_channel: TextChannel): - """Setup a Channel for the Kick/Ban/Mute Actions to be Logged In""" + """Setup a channel for Kick/Ban/Mute actions to be logged""" - # Setup pool + # Setup pool connection pool = self.bot.db - - # Setup pool connection and cursor async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Get the row of the guild - select_query = """SELECT * FROM guilds WHERE guildID = (%s)""" - val = ctx.guild.id, - # Execute the SQL Query - await cur.execute(select_query, val) - result = await cur.fetchone() + # Get the row of the guild from database + try: + select_query = """SELECT * FROM guilds WHERE guild_id = $1""" + result = await conn.fetchrow(select_query, ctx.guild.id) - # Throw error if the modlog channel already exists and then stop the function - if result[2] is not None: - text = "**Modlogs Channel** already set up!" \ - f"\nDo **{ctx.prefix}help modlogs** to find out more!" - await self.bot.generate_embed(ctx, desc=text) - return + # Catch errors + except asyncpg.PostgresError as e: + print("PostGres Error: Guild Record Could Not Be Retrieved For Modlog Setup", e) - else: - # Set up the modlogs channel within the guild - mod_log_setup = True - await self.bot.storage_modlog_for_guild(self.bot.db, ctx, user_channel.id, mod_log_setup) + # Throw error if the modlog channel already exists + else: + if result["modlogs"]: + text = "**Modlogs Channel** already set up!" \ + f"\nDo **{ctx.prefix}help modlogs** to find out more!" + await self.bot.generate_embed(ctx, desc=text) + + # Set up the modlogs channel within the guild + else: + mod_log_setup = True + await self.bot.storage_modlog_for_guild(ctx, user_channel.id, mod_log_setup) + + # Release the connection back to the pool + finally: + await pool.release(conn) @modlogs.command(name="update") @has_permissions(manage_guild=True) @bot_has_permissions(administrator=True) async def mlupdate(self, ctx, user_channel: TextChannel): - """Change the Channel that your Modlogs are Sent to""" + """Change the channel that your modlogs are sent to""" - # Setup pool + # Setup up pool connectionF pool = self.bot.db - - # Setup up pool connection and cursor async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Get the guilds row from the guilds table - select_query = """SELECT * FROM guilds WHERE guildID = (%s)""" - vals = ctx.guild.id, - - # Execute the SQL Query - await cur.execute(select_query, vals) - result = await cur.fetchone() - - # Throw error if the modlog channel already exists and then stop the function - if result[2] is None: - text = "**Modlogs Channel** not set up!" \ - f"\nDo **{ctx.prefix}help modlogs** to find out more!" - await self.bot.generate_embed(ctx, desc=text) - else: - # Update the modlog channel within the database and cache - mod_log_setup = False - await self.bot.storage_modlog_for_guild(self.bot.db, ctx, user_channel.id, mod_log_setup) + # Get the guilds row from the guilds table + try: + select_query = """SELECT * FROM guilds WHERE guild_id = $1""" + result = await conn.fetchrow(select_query, ctx.guild.id) + + # Catch errors + except asyncpg.PostgresError as e: + print("PostGres Error: Guild Record Could Not Be Retrieved For Modlog Update", e) + + # Throw error if the modlog channel already exists + else: + if result["married"] is None: + text = "**Modlogs Channel** not set up!" \ + f"\nDo **{ctx.prefix}help modlogs** to find out more!" + await self.bot.generate_embed(ctx, desc=text) + + # Update the modlog channel within the database and cache + else: + mod_log_setup = False + await self.bot.storage_modlog_for_guild(ctx, user_channel.id, mod_log_setup) + + # Release the connection back to the pool + finally: + await pool.release(conn) @modlogs.command("delete") @has_permissions(manage_guild=True) @bot_has_permissions(administrator=True) async def mldelete(self, ctx): - """Delete the Existing Modlogs System""" + """Delete the existing modlogs channel""" - # Setup pool + # Setup up pool connection pool = self.bot.db - - # Setup up pool connection and cursor async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Get the guilds row from the guilds table - select_query = """SELECT * FROM guilds WHERE guildID = (%s)""" - vals = ctx.guild.id, - - # Execute the SQL Query - await cur.execute(select_query, vals) - result = await cur.fetchone() - - # Throw error is modlogs error has not been setup before performing a delete action - if result[2] is None: - text = "**Modlogs Channel** not set up!" \ - f"\nDo **{ctx.prefix}help modlogs** to find out more!" - await self.bot.generate_embed(ctx, desc=text) - return - # Setup up pool connection and cursor - async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Update the existing prefix within the database - update_query = """UPDATE guilds SET modlogs = NULL WHERE guildID = (%s)""" - update_vals = ctx.guild.id, + # Get the guilds row from the guilds table + try: + select_query = """SELECT * FROM guilds WHERE guild_id = $1""" + result = await conn.fetchrow(select_query, ctx.guild.id) + + # Catch errors + except asyncpg.PostgresError as e: + print("PostGres Error: Guild Record Could Not Be Retrieved For Modlog Delete", e) + + # Throw error that modlogs have not been setup + else: + if result["married"] is None: + text = "**Modlogs Channel** not set up!" \ + f"\nDo **{ctx.prefix}help modlogs** to find out more!" + await self.bot.generate_embed(ctx, desc=text) + return + + # Update the existing modlogs for guild + try: + update_query = """UPDATE guilds SET modlogs = NULL WHERE guild_id = $1""" + await conn.execute(update_query, ctx.guild.id) + + # Catch errors + except asyncpg.PostgresError as e: + print(f"PostGres Error: Guild Modlogs Could Not Be Updated For {ctx.guild.id}", e) - # Execute the query - await cur.execute(update_query, update_vals) - await conn.commit() + # Delete channel from cache + else: + self.bot.remove_modlog_channel(ctx.guild.id) - # Delete channel from cache - self.bot.remove_modlog_channel(str(ctx.guild.id)) + # Release the connection back to the pool + await pool.release(conn) text = "**Modlogs System** successfully deleted!" \ f"\nDo **{ctx.prefix}help modlogs** to setup Modlogs again!" @@ -366,155 +372,195 @@ class Guild(Cog): @modmail.command(name="setup") @has_permissions(manage_guild=True) @bot_has_permissions(administrator=True) - async def mmsetup(self, ctx, user_channel: TextChannel, modmail_channel: TextChannel): + async def mmsetup(self, ctx, modmail: TextChannel, modmail_logging: TextChannel): """ Setup Modmail System First Argument: Input Channel(Mention or ID) where members can send modmail Second Argument: Input Channel(Mention or ID) where the members mail should be sent """ - # Setup pool + # Setup up pool connection pool = self.bot.db - - # Setup up pool connection and cursor async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Get the author's row from the Members Table - select_query = """SELECT * FROM moderatormail WHERE guildID = (%s)""" - val = ctx.guild.id, - - # Execute the SQL Query - await cur.execute(select_query, val) - result = await cur.fetchone() - - # Throw error if the guild already exists and then stop the function - if result is not None: - text = "**Modmail System** already set up!" \ - f"\nDo **{ctx.prefix}help modmail** to find out more!" - await self.bot.generate_embed(ctx, desc=text) - return + # Get the author's row from the members table + try: + select_query = """SELECT * FROM moderatormail WHERE guild_id = $1""" + result = await conn.fetchrow(select_query, ctx.guild.id) + + # Catch errors + except asyncpg.PostgresError as e: + print("PostGres Error: ModeratorMail Record Could Not Be Retrieved For Modmail Setup", e) + + # Throw error if the guild already exists + else: + if result: + text = "**Modmail System** already set up!" \ + f"\nDo **{ctx.prefix}help modmail** to find out more!" + await self.bot.generate_embed(ctx, desc=text) + return + + # Release the connection back to the pool + finally: + await pool.release(conn) + + # Set up embed to let the user how to start sending modmail desc = "React to this message if you want to send a message to the Staff Team!" \ "\n\n**React with ✅**" \ "\n\nWe encourage all suggestions/thoughts and opinions on the server!" \ "\nAs long as it is **valid** criticism." \ "\n\n\n**Purely negative feedback will not be considered.**" - # Set up embed to let the user how to start sending modmail ModMail = Embed(title="**Welcome to Modmail!**", description=desc, colour=self.bot.admin_colour, timestamp=datetime.datetime.utcnow()) ModMail.set_thumbnail(url=self.bot.user.avatar_url) - modmail_message = await user_channel.send(embed=ModMail) - - # Auto add the ✅ reaction + # Send modmail embed to the specified channel and auto add the ✅ reaction + modmail_message = await modmail.send(embed=ModMail) await modmail_message.add_reaction('✅') - # Setup up pool connection and cursor + # Setup up pool connection async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Define the insert statement that will insert information about the modmail channel - insert_query = """INSERT INTO moderatormail (guildID, channelID, messageID, modmailChannelID) VALUES (%s, %s, %s, %s)""" - vals = ctx.guild.id, user_channel.id, modmail_message.id, modmail_channel.id, - # Execute the SQL Query - await cur.execute(insert_query, vals) - await conn.commit() + # Insert the information about the modmail system into database + try: + insert_query = """INSERT INTO moderatormail (guild_id, modmail_channel_id, message_id, modmail_logging_channel_id) + VALUES ($1, $2, $3, $4)""" + await conn.execute(insert_query, ctx.guild.id, modmail.id, modmail_message.id, modmail_logging.id) + + # Catch errors + except asyncpg.PostgresError as e: + print(f"PostGres Error: Modmail System Record Could Not Be Inserted For Guild {ctx.guild.id}", e) + + # Send confirmation message + else: + text = "**Modmail System** is successfully set up!" \ + f"\nRefer to **{ctx.prefix}help modmail** for more information" + await self.bot.generate_embed(ctx, desc=text) - text = "**Modmail System** is successfully set up!" \ - f"\nRefer to **{ctx.prefix}help modmail** for more information" - await self.bot.generate_embed(ctx, desc=text) + # Store into cache + self.bot.cache_store_modmail(ctx.guild.id, modmail.id, modmail_message.id, modmail_logging.id) + + # Release connection back into pool + finally: + await pool.release(conn) @modmail.command(name="update") @has_permissions(manage_guild=True) @bot_has_permissions(administrator=True) - async def mmupdate(self, ctx, modmail_channel: TextChannel): + async def mmupdate(self, ctx, modmail_logging_channel: TextChannel): """ Update the Channel that the Modmail is logged to You can Mention or use the Channel ID """ - # Setup pool + # Setup up pool connection pool = self.bot.db + async with pool.acquire() as conn: + + # Get the moderatormail record from the guilds table + try: + select_query = """SELECT * FROM moderatormail WHERE guild_id = $1""" + result = await conn.fetchrow(select_query, ctx.guild.id) + + # Catch errors + except asyncpg.PostgresError as e: + print("PostGres Error: ModeratorMail Record Could Not Be Retrieved For Modmail Update", e) + + # Throw error if the guild already exists + else: + if not result: + text = "**Modmail System** not set up!" \ + f"\nDo **{ctx.prefix}help modmail** to find out more!" + await self.bot.generate_embed(ctx, desc=text) + return + + # Release connection back to pool + finally: + await pool.release(conn) # Setup up pool connection and cursor async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Get the author's row from the Members Table - select_query = """SELECT * FROM moderatormail WHERE guildID = (%s)""" - vals = ctx.guild.id, - - # Execute the SQL Query - await cur.execute(select_query, vals) - result = await cur.fetchone() - - # Throw error if the guild already exists and then stop the function - if result is None: - text = "**Modmail System** not set up!" \ - f"\nDo **{ctx.prefix}help modmail** to find out more!" + + # Update the modmail channel in the database + try: + update_query = """UPDATE moderatormail SET modmail_logging_channel_id = $1 WHERE guild_id = $2""" + await conn.execute(update_query, modmail_logging_channel.id, ctx.guild.id) + + # Catch errors + except asyncpg.PostgresError as e: + print(f"PostGres Error: Modmail System Record Could Not Be Updated For Guild {ctx.guild.id}", e) + + # Send confirmation that the channel has been updated + else: + text = "**Channel Updated**" \ + f"\nNew Modmail will be sent to {modmail_logging_channel.mention}" await self.bot.generate_embed(ctx, desc=text) - return - # Setup up pool connection and cursor - async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Define the update statement that will insert information about the modmail channel - update_query = """UPDATE moderatormail SET modmailChannelID = (%s) WHERE guildID = (%s)""" - vals = modmail_channel.id, ctx.guild.id - - # Execute the SQL Query - await cur.execute(update_query, vals) - await conn.commit() - - # Send confirmation that the channel has been updated - text = "**Channel Updated**" \ - f"\nNew Modmail will be sent to {modmail_channel.mention}" - await self.bot.generate_embed(ctx, desc=text) + # Update cache + self.bot.update_modmail(ctx.guild.id, modmail_logging_channel.id) + + # Release connection back to pool + finally: + await pool.release(conn) @modmail.command(name="delete") @has_permissions(manage_guild=True) @bot_has_permissions(administrator=True) async def mmdelete(self, ctx): - """Delete the Entire Modmail System from the Guild""" + """Delete the entire modmail system from the guild""" - # Setup pool + # Setup up pool connection pool = self.bot.db - - # Setup up pool connection and cursor async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Get the author's row from the Members Table - select_query = """SELECT * FROM moderatormail WHERE guildID = (%s)""" - vals = ctx.author.guild.id, - - # Execute the SQL Query - await cur.execute(select_query, vals) - result = await cur.fetchone() - - # Throw error if modmail system does not exist already - if result is None: - text = "**Modmail System** not set up!" \ - f"\nDo **{ctx.prefix}help modmail** to find out more!" - await self.bot.generate_embed(ctx, desc=text) - return - # Setup up pool connection and cursor + # Get the moderatormail record from the guilds table + try: + select_query = """SELECT * FROM moderatormail WHERE guild_id = $1""" + result = await conn.fetchrow(select_query, ctx.guild.id) + + # Catch errors + except asyncpg.PostgresError as e: + print("PostGres Error: ModeratorMail Record Could Not Be Retrieved For Modmail Delete", e) + + else: + # Throw error if modmail system does not exist already + if result is None: + text = "**Modmail System** not set up!" \ + f"\nDo **{ctx.prefix}help modmail** to find out more!" + await self.bot.generate_embed(ctx, desc=text) + return + + # Release connection back to pool + finally: + await pool.release(conn) + + # Setup up pool connection async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Define the delete statement to remove all information about the guild - delete_query = """DELETE FROM moderatormail WHERE guildID = (%s)""" - vals = ctx.author.guild.id, - # Execute the SQL Query - await cur.execute(delete_query, vals) - await conn.commit() + # Remove the moderatormail record from the database + try: + delete_query = """DELETE FROM moderatormail WHERE guild_id = $1""" + await conn.execute(delete_query, ctx.guild.id) + + # Catch errors + except asyncpg.PostgresError as e: + print(f"PostGres Error: ModeratorMail Record Could Not Be Deleted for Guild {ctx.guild.id}", e) # Sending confirmation message that the modmail system has been deleted - text = "**Modmail System** successfully deleted!" \ - f"\nDo **{ctx.prefix}help modmail** to find out more!" - await self.bot.generate_embed(ctx, desc=text) + else: + text = "**Modmail System** successfully deleted!" \ + f"\nDo **{ctx.prefix}help modmail** to find out more!" + await self.bot.generate_embed(ctx, desc=text) + + # Delete from cache + self.bot.delete_modmail(ctx.guild.id) + + # Release connection back to pool + finally: + await pool.release(conn) @Cog.listener() async def on_raw_reaction_add(self, payload): @@ -525,35 +571,17 @@ class Guild(Cog): if payload.member.bot or str(payload.emoji) not in ['✅', '❌']: return - # Find a role corresponding to the Emoji name. - guildid = payload.guild_id - - # Setup pool - pool = self.bot.db - - # Setup up pool connection and cursor - async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Get the author's row from the Members Table - select_query = """SELECT * FROM moderatormail WHERE guildID = (%s)""" - val = guildid, - - # Execute the SQL Query - await cur.execute(select_query, val) - result = await cur.fetchone() - - # Adding error handling - if result is None: - return - - # Define variables - guild_id = int(result[0]) - channel_id = int(result[1]) - message_id = int(result[2]) - modmail_channel_id = int(result[3]) + # Get the modmail information from cache + modmail = self.bot.get_modmail(payload.guild_id) + if modmail: + channel_id = modmail["modmail_channel_id"] + message_id = modmail["message_id"] + modmail_channel_id = modmail["modmail_logging_channel_id"] + else: + return # Bunch of checks to make sure it has the right guild, channel, message and reaction - if payload.guild_id == guild_id and payload.channel_id == channel_id and payload.message_id == message_id and payload.emoji.name == "✅": + if payload.channel_id == channel_id and payload.message_id == message_id and payload.emoji.name == "✅": # Get the guild guild = self.bot.get_guild(payload.guild_id) diff --git a/cogs/help.py b/cogs/help.py index 53e90d28..d73c947c 100644 --- a/cogs/help.py +++ b/cogs/help.py @@ -657,16 +657,17 @@ class Help(Cog): # As long as a new prefix has been given and is less than 5 characters if new and len(new) <= 5: # Store the new prefix in the dictionary and update the database - await self.bot.storage_prefix_for_guild(self.bot.db, ctx, new) + await self.bot.storage_prefix_for_guild(ctx, new) # Making sure that errors are handled if prefix is above 5 characters elif new and len(new) > 5: - await ctx.send("The guild prefix must be less than or equal to **5** characters!") + await self.bot.generate_embed(ctx, desc="The guild prefix must be less than or equal to **5** characters!") # if no prefix was provided elif not new: # Grab the current prefix for the guild within the cached dictionary - await ctx.send(f"**The current guild prefix is `{self.bot.get_prefix_for_guild(str(ctx.guild.id))}`**") + await self.bot.generate_embed(ctx, + desc=f"**The current guild prefix is `{self.bot.get_prefix_for_guild(ctx.guild.id)}`**") @command(name="support") async def support(self, ctx): diff --git a/cogs/interactive.py b/cogs/interactive.py index 877d46d8..91ae820a 100644 --- a/cogs/interactive.py +++ b/cogs/interactive.py @@ -17,6 +17,7 @@ import datetime import random +import asyncpg from discord import Embed, Member from discord.ext.commands import cooldown, command, BucketType, bot_has_permissions, Cog @@ -43,29 +44,16 @@ class Interactive(Cog): """Printing out that Cog is ready on startup""" print(f"{self.__class__.__name__} Cog has been loaded\n-----") + # TODO: MAKE EVERYTHING IN HERE ASYNCHRONOUS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + @command(name="kiss") @bot_has_permissions(embed_links=True) @cooldown(1, 3, BucketType.user) async def kiss(self, ctx, member: Member): - """Kiss your Partner""" + """Kiss your partner""" # Get the guild - guild = ctx.author.guild - - # Setup pool - pool = self.bot.db - - # Setup pool connection and cursor - async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Get the author's row from the Members Table - select_query = """SELECT * FROM members WHERE discordID = (%s) and guildID = (%s)""" - val = ctx.author.id, guild.id, - - # Execute the SQL Query - await cur.execute(select_query, val) - result = await cur.fetchone() - married_user = result[1] + guild = ctx.guild # Error handling to make sure that the user can kiss themselves if member.id == ctx.author.id: @@ -75,21 +63,35 @@ class Interactive(Cog): kiss = True title = f":kissing_heart: :kissing_heart: | **{ctx.author.display_name}** kissed **{member.display_name}**" - try: - # Make sure the user isn't trying to kiss someone else besides their partner - if married_user is None and kiss: - await ctx.send("Σ(‘◉⌓◉’) You need to be married in order to use this command! Baka!") - return - # Make sure that the married people can only kiss their partner - elif not str(member.id) == married_user and kiss: - await ctx.send("Σ(‘◉⌓◉’) You can only kiss your partner! Baka!") - return - except Exception as ex: - print(ex) - - # Surround with try/except to catch any exceptions that may occur - try: + # Setup pool connection + pool = self.bot.db + async with pool.acquire() as conn: + + # Get the author's row from the members table + try: + select_query = """SELECT * FROM members WHERE member_id = $1 and guild_id = $2""" + result = await conn.fetchrow(select_query, ctx.author.id, guild.id) + + # Catch errors + except asyncpg.PostgresError as e: + print("PostGres Error: Member Record Could Not Be Retrieved For Kiss Command", e) + + # Checking conditions to make sure user is married/kissing their partner + else: + married_user = result["married"] + + if married_user is None and kiss: + await ctx.send("Σ(‘◉⌓◉’) You need to be married in order to use this command! Baka!") + return + elif not member.id == married_user and kiss: + await ctx.send("Σ(‘◉⌓◉’) You can only kiss your partner! Baka!") + return + + # Release connection back to pool + finally: + await pool.release(conn) + try: # Open the file containing the kissing gifs with open('images/FunCommands/kissing.txt') as file: # Store content of the file in kissing_array @@ -116,25 +118,10 @@ class Interactive(Cog): @bot_has_permissions(embed_links=True) @cooldown(1, 3, BucketType.user) async def cuddle(self, ctx, member: Member): - """Cuddle your Partner""" + """Cuddle your partner""" # Get the guild - guild = ctx.author.guild - - # Setup pool - pool = self.bot.db - - # Setup pool connection and cursor - async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Get the author's row from the Members Table - select_query = """SELECT * FROM members WHERE discordID = (%s) and guildID = (%s)""" - val = ctx.author.id, guild.id - - # Execute the SQL Query - await cur.execute(select_query, val) - result = await cur.fetchone() - married_user = result[1] + guild = ctx.guild # Error handling to make sure that the user can cuddle themselves if member.id == ctx.author.id: @@ -144,19 +131,30 @@ class Interactive(Cog): cuddle = True title = f":blush: :blush: | **{ctx.author.display_name}** cuddled **{member.display_name}**" - try: - # Make sure the user isn't trying to cuddle someone else besides their partner - if married_user is None and cuddle: - await ctx.send("Σ(‘◉⌓◉’) You need to be married in order to use this command! Baka!") - return - # Make sure that the married people can only cuddle their partner - elif not str(member.id) == married_user and cuddle: - await ctx.send("Σ(‘◉⌓◉’) You can only cuddle your partner! Baka!") - return - except Exception as ex: - print(ex) - - # Surround with try/except to catch any exceptions that may occur + # Setup pool connection + pool = self.bot.db + async with pool.acquire() as conn: + + # Get the author's row from the members table + try: + select_query = """SELECT * FROM members WHERE member_id = $1 and guild_id = $2""" + result = await conn.fetchrow(select_query, ctx.author.id, guild.id) + + # Catch errors + except asyncpg.PostgresError as e: + print("PostGres Error: Member Record Could Not Be Retrieved For Cuddle Command", e) + + # Checking conditions to make sure user is married/cuddling their partner + else: + married_user = result["married"] + + if married_user is None and cuddle: + await ctx.send("Σ(‘◉⌓◉’) You need to be married in order to use this command! Baka!") + return + elif not member.id == married_user and cuddle: + await ctx.send("Σ(‘◉⌓◉’) You can only cuddle your partner! Baka!") + return + try: # Open the file containing the cuddling gifs @@ -185,14 +183,13 @@ class Interactive(Cog): @bot_has_permissions(embed_links=True) @cooldown(1, 3, BucketType.user) async def kill(self, ctx, member: Member): - """Kill a Member""" + """Kill a member""" if member is ctx.author: title = f":scream: :scream: | **{ctx.author.display_name}** killed **themselves**" else: title = f":scream: :scream: | **{ctx.author.display_name}** killed **{member.display_name}**" - # Surround with try/except to catch any exceptions that may occur try: # Open the file containing the killing gifs @@ -221,14 +218,13 @@ class Interactive(Cog): @bot_has_permissions(embed_links=True) @cooldown(1, 3, BucketType.user) async def slap(self, ctx, member: Member): - """Slap a Member""" + """Slap a member""" if member is ctx.author: title = f":cold_sweat: :cold_sweat: | **{ctx.author.display_name}** slapped **themselves**" else: title = f":cold_sweat: :cold_sweat: | **{ctx.author.display_name}** slapped **{member.display_name}**" - # Surround with try/except to catch any exceptions that may occur try: # Open the file containing the cuddling gifs @@ -257,14 +253,13 @@ class Interactive(Cog): @bot_has_permissions(embed_links=True) @cooldown(1, 3, BucketType.user) async def pat(self, ctx, member: Member): - """Pat a Member""" + """Pat a member""" if member is ctx.author: title = f"👉 👈 | **{ctx.author.display_name}** patted **themselves**" else: title = f"👉 👈 | **{ctx.author.display_name}** patted **{member.display_name}**" - # Surround with try/except to catch any exceptions that may occur try: # Open the file containing the patting gifs @@ -293,7 +288,7 @@ class Interactive(Cog): @bot_has_permissions(embed_links=True) @cooldown(1, 3, BucketType.user) async def lemon(self, ctx, member: Member): - """Give Lemon to Member""" + """Give Lemon to member""" if member is ctx.author: title = f":relaxed: :relaxed: | **{ctx.author.display_name}** gave a lemon to **themselves**" @@ -304,7 +299,6 @@ class Interactive(Cog): "https://media.discordapp.net/attachments/669812887564320769/720093575492272208/lemon2.gif", "https://media.discordapp.net/attachments/718484280925224981/719629805263257630/lemon.gif"] - # Surround with try/except to catch any exceptions that may occur try: # Get the member and the userAvatar @@ -328,14 +322,13 @@ class Interactive(Cog): @bot_has_permissions(embed_links=True) @cooldown(1, 3, BucketType.user) async def choke(self, ctx, member: Member): - """Choke a Member""" + """Choke a member""" if member is ctx.author: title = f":confounded: :confounded: | **{ctx.author.display_name}** choked **themselves**" else: title = f":confounded: :confounded: | **{ctx.author.display_name}** choked **{member.display_name}**" - # Surround with try/except to catch any exceptions that may occur try: # Open the file containing the choking gifs with open('images/FunCommands/choking.txt') as file: @@ -363,14 +356,13 @@ class Interactive(Cog): @bot_has_permissions(embed_links=True) @cooldown(1, 3, BucketType.user) async def hug(self, ctx, member: Member): - """Hug a Member""" + """Hug a member""" if member is ctx.author: title = f":smiling_face_with_3_hearts: :smiling_face_with_3_hearts: | **{ctx.author.display_name}** hugged **themselves**" else: title = f":smiling_face_with_3_hearts: :smiling_face_with_3_hearts: | **{ctx.author.display_name}** hugged **{member.display_name}**" - # Surround with try/except to catch any exceptions that may occur try: # Open the file containing the hug gifs diff --git a/cogs/moderation.py b/cogs/moderation.py index a2b268b2..7895c2f2 100644 --- a/cogs/moderation.py +++ b/cogs/moderation.py @@ -35,11 +35,11 @@ async def send_to_modlogs(self, ctx, target, reason, action): """ # Get the channel of the modlog within the guild - modlog = self.bot.get_modlog_for_guild(str(ctx.guild.id)) + modlog = self.bot.get_modlog_for_guild(ctx.guild.id) - if modlog is not None: + if modlog: - channel = ctx.guild.get_channel(int(modlog)) + channel = ctx.guild.get_channel(modlog) embed = Embed(title=f"Member {action}", colour=self.bot.admin_colour, @@ -87,30 +87,34 @@ async def ummute_members(self, ctx, targets, reason): """ - # Setup pool - pool = self.bot.db - for target in targets: if (ctx.guild.me.top_role.position > target.top_role.position and not target.guild_permissions.administrator): - # Setup up pool connection and cursor + # Setup up pool connection + pool = self.bot.db async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Get the roles of the user from the database - select_query = """SELECT * FROM members WHERE guildID = (%s) AND discordID = (%s)""" - select_vals = ctx.guild.id, target.id, - # Execute the SQL Query - await cur.execute(select_query, select_vals) - result = await cur.fetchone() - role_ids = result[4] + # Get the roles of the user from the database + try: + select_query = """SELECT * FROM members WHERE guild_id = $1 AND member_id = $2""" + result = await conn.fetchrow(select_query, ctx.guild.id, target.id) + + # Catch Errors + except Exception as e: + print("PostGres Error: Record Not Found For Unmuting Members", e) # Get all the roles of the user before they were muted from the database - roles = [ctx.guild.get_role(int(id_)) for id_ in role_ids.split(", ") if len(id_)] + else: + role_ids = result["muted_roles"] + roles = [ctx.guild.get_role(int(id_)) for id_ in role_ids.split(", ") if len(id_)] - # Clear all the roles of the user - await self.bot.clearRoles(member=target) + # Release connection back to pool + finally: + await pool.release(conn) + + # Clear all the roles of the user + await self.bot.clearRoles(member=target) await target.edit(roles=roles) @@ -159,7 +163,7 @@ async def mute_members(self, ctx, targets, reason, muted): embed = Embed(description=f"✅ **{target}** Was Muted! ✅", colour=self.bot.admin_colour) - if self.bot.get_roles_persist(str(ctx.guild.id)) == 0: + if self.bot.get_roles_persist(ctx.guild.id) == 0: embed.add_field(name="**WARNING: ROLE PERSIST NOT ENABLED**", value="The bot **will not give** the roles back to the user if they leave the server." "\nAllowing the user to bypass the Mute by leaving and rejoining." @@ -409,12 +413,12 @@ class Moderation(Cog): """Logging Bulk Message Deletion from Server""" # Get the channel within the cache - channel = self.bot.get_modlog_for_guild(str(payload.guild_id)) + channel = self.bot.get_modlog_for_guild(payload.guild_id) # When no modlogs channel is returned, do nothing - if channel is not None: + if channel: # Get the modlogs channel and channel that the messages were deleted in - modlogs_channel = self.bot.get_channel(int(channel)) + modlogs_channel = self.bot.get_channel(channel) deleted_msgs_channel = self.bot.get_channel(payload.channel_id) desc = f"**Bulk Delete in {deleted_msgs_channel.mention} | {len(payload.message_ids)} messages deleted**" @@ -431,13 +435,15 @@ class Moderation(Cog): async def on_member_remove(self, member): """Log Member Leaves from Server""" + if member == self.bot.user: return + # Get the channel within the cache - channel = self.bot.get_modlog_for_guild(str(member.guild.id)) + channel = self.bot.get_modlog_for_guild(member.guild.id) # When no modlogs channel is returned, do nothing - if channel is not None: + if channel: # Get the modlogs channel - modlogs_channel = self.bot.get_channel(int(channel)) + modlogs_channel = self.bot.get_channel(channel) embed = Embed(description=f"**{member.mention}** | **{member}**", colour=self.bot.admin_colour, @@ -453,12 +459,12 @@ class Moderation(Cog): """Log Member Joins to Server""" # Get the channel within the cache - channel = self.bot.get_modlog_for_guild(str(member.guild.id)) + channel = self.bot.get_modlog_for_guild(member.guild.id) # When no modlogs channel is returned, do nothing - if channel is not None: + if channel: # Get the modlogs channel - modlogs_channel = self.bot.get_channel(int(channel)) + modlogs_channel = self.bot.get_channel(channel) embed = Embed(description=f"**{member.mention}** | **{member}**", colour=self.bot.admin_colour, @@ -477,12 +483,12 @@ class Moderation(Cog): """Logs Member Bans to Server""" # Get the channel within the cache - channel = self.bot.get_modlog_for_guild(str(guild.id)) + channel = self.bot.get_modlog_for_guild(guild.id) # When no modlogs channel is returned, do nothing - if channel is not None: + if channel: # Get the modlogs channel - modlogs_channel = self.bot.get_channel(int(channel)) + modlogs_channel = self.bot.get_channel(channel) embed = Embed(description=f"{user.mention} | **{user}**", colour=self.bot.admin_colour, @@ -498,12 +504,12 @@ class Moderation(Cog): """Logs Member Unbans to Server""" # Get the channel within the cache - channel = self.bot.get_modlog_for_guild(str(guild.id)) + channel = self.bot.get_modlog_for_guild(guild.id) # When no modlogs channel is returned, do nothing - if channel is not None: + if channel: # Get the modlogs channel - modlogs_channel = self.bot.get_channel(int(channel)) + modlogs_channel = self.bot.get_channel(channel) embed = Embed(description=f"{user.mention} | **{user}**", colour=self.bot.admin_colour, @@ -519,12 +525,12 @@ class Moderation(Cog): """Logging Member Profile Updates""" # Get the channel within the cache - channel = self.bot.get_modlog_for_guild(str(after.guild.id)) + channel = self.bot.get_modlog_for_guild(after.guild.id) # When no modlogs channel is returned, do nothing - if channel is not None: + if channel: # Get the modlogs channel - modlogs_channel = self.bot.get_channel(int(channel)) + modlogs_channel = self.bot.get_channel(channel) # Logging Nickname Updates if before.nick != after.nick: @@ -586,19 +592,19 @@ class Moderation(Cog): async def on_message_edit(self, before, after): """Logging Message Edits (Within Cache)""" - msg_channel = self.bot.get_channel(int(after.channel.id)) + msg_channel = self.bot.get_channel(after.channel.id) # Get the channel within the cache if not isinstance(msg_channel, DMChannel): # Get the channel within the cache - channel = self.bot.get_modlog_for_guild(str(after.guild.id)) + channel = self.bot.get_modlog_for_guild(after.guild.id) else: return # When no modlogs channel is returned, do nothing - if channel is not None: + if channel: # Get the modlogs channel - modlogs_channel = self.bot.get_channel(int(channel)) + modlogs_channel = self.bot.get_channel(channel) # Logging Message Content Edits # Not logging any message edits from bots @@ -629,16 +635,17 @@ class Moderation(Cog): # Get the channel within the cache if not isinstance(msg_channel, DMChannel): - channel = self.bot.get_modlog_for_guild(payload.data["guild_id"]) + channel = self.bot.get_modlog_for_guild(int(payload.data["guild_id"])) else: return - # Only log this message if the message does not exist within the internal cache and modlogs channel is set up - if channel is not None and not payload.cached_message: + # Only log this message edit when the message does not exist within the internal cache + # and modlogs channel is set up + if channel and not payload.cached_message: # Get the modlogs channel - modlogs_channel = self.bot.get_channel(int(channel)) + modlogs_channel = self.bot.get_channel(channel) - desc = f"**Message Edited Within <#{payload.channel_id}>\nMessage Content Not Displayable**" + desc = f"**Message Edited Within {msg_channel.mention}\nMessage Content Not Displayable**" embed = Embed(description=desc, colour=self.bot.admin_colour, timestamp=datetime.datetime.utcnow()) @@ -652,12 +659,12 @@ class Moderation(Cog): """Logging Message Deletions (Within Cache)""" # Get the channel within the cache - channel = self.bot.get_modlog_for_guild(str(message.guild.id)) + channel = self.bot.get_modlog_for_guild(message.guild.id) # When no modlogs channel is returned, do nothing - if channel is not None and not message.author.bot: + if channel and not message.author.bot: # Get the modlogs channel - modlogs_channel = self.bot.get_channel(int(channel)) + modlogs_channel = self.bot.get_channel(channel) if not message.attachments: desc = f"**Message Sent By {message.author.mention} Deleted In {message.channel.mention}" \ @@ -677,6 +684,33 @@ class Moderation(Cog): await modlogs_channel.send(embed=embed) + @Cog.listener() + async def on_raw_message_delete(self, payload): + """Logging Message Deletions Not Stored Within Internal Cache""" + + msg_channel = self.bot.get_channel(payload.channel_id) + + # Get the channel within the cache + if not isinstance(msg_channel, DMChannel): + channel = self.bot.get_modlog_for_guild(payload.guild_id) + else: + return + + # Only log this message deletion when the message does not exist within the internal cache + # and modlogs channel is set up + if channel and not payload.cached_message: + # Get the modlogs channel + modlogs_channel = self.bot.get_channel(channel) + + desc = f"**Message Deleted Within {msg_channel.mention}\nMessage Content Not Displayable**" + embed = Embed(description=desc, + colour=self.bot.admin_colour, + timestamp=datetime.datetime.utcnow()) + embed.set_author(name=modlogs_channel.guild.name, icon_url=modlogs_channel.guild.icon_url) + embed.set_footer(text=f"Message ID: {payload.message_id}") + + await modlogs_channel.send(embed=embed) + def setup(bot): bot.add_cog(Moderation(bot)) diff --git a/cogs/owner.py b/cogs/owner.py index 3d62e3e5..72bd8ce5 100644 --- a/cogs/owner.py +++ b/cogs/owner.py @@ -13,7 +13,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . - +import asyncpg from discord import Member from discord.ext.commands import Cog, command, is_owner @@ -45,7 +45,7 @@ class Owner(Cog): @command(name="restart", hidden=True) @is_owner() async def restart(self, ctx): - """Restart the Bot""" + """Restart the bot""" await self.bot.generate_embed(ctx, desc="**Success Senpai!" "\nMy Reboot Had No Problems** ") @@ -62,21 +62,29 @@ class Owner(Cog): # Setup pool pool = self.bot.db + # Store every single record into an array + records = [(ctx.guild.id, member.id) for member in ctx.guild.members] + # Setup up pool connection and cursor async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Define the insert statement that will insert the user's information - insert = """INSERT INTO members (guildID, discordID) VALUES """ + ", ".join( - map(lambda m: f"({ctx.guild.id}, {m.id})", - ctx.guild.members)) + """ ON DUPLICATE KEY UPDATE guildID = VALUES(guildID), discordID = VALUES(discordID)""" - - # Execute the insert statement - await cur.execute(insert) - await conn.commit() - print(cur.rowcount, f"Record(s) inserted successfully into Members from {ctx.guild.name}") - - # Sending confirmation message - await ctx.send(f"Database Reloaded Successfully for **{ctx.guild.name}**") + # Define the insert statement that will insert the user's information + try: + insert_query = """INSERT INTO members (guild_id, member_id) VALUES ($1, $2) + ON CONFLICT (guild_id, member_id) DO NOTHING""" + + await conn.executemany(insert_query, records) + + # Catch errors + except asyncpg.PostgresError as e: + print(f"PostGres Error: Member(s) were not be able to be added to Guild", e) + + # Print success + else: + print(f"Record(s) Inserted Into Members") + + finally: + # Release connection back to pool + await pool.release(conn) def setup(bot): diff --git a/cogs/relationship.py b/cogs/relationship.py index 00dbbc09..e560bfa4 100644 --- a/cogs/relationship.py +++ b/cogs/relationship.py @@ -17,6 +17,7 @@ import asyncio import datetime +import asyncpg from discord import Member, Embed from discord.ext.commands import BucketType, command, cooldown, bot_has_permissions, Cog @@ -80,37 +81,35 @@ class Relationship(Cog): await self.bot.generate_embed(ctx, desc="**Senpaii! ˭̡̞(◞⁎˃ᆺ˂)◞*✰ You can't possibly marry yourself!**") return + # TODO: Probably will need to get rid of this in favour of getting from cache + # Setup pool connection and cursor async with pool.acquire() as conn: - async with conn.cursor() as author_cursor: - # Get the author's/members row from the Members Table - select_query = """SELECT * FROM members WHERE discordID = (%s) and guildID = (%s)""" - author_val = ctx.author.id, guild.id, - member_val = member.id, guild.id, - - # Execute the Author SQL Query - await author_cursor.execute(select_query, author_val) - author_result = await author_cursor.fetchone() - married_user = author_result[1] - - # Make sure that the person is not already married to someone else within the server - if married_user is not None: - member = guild.get_member(int(married_user)) - await self.bot.generate_embed(ctx, desc=f"**((╬◣﹏◢)) You're already married to {member.mention}!**") - return + # Get the author's/members row from the Members Table + select_query = """SELECT * FROM members WHERE member_id = $1 and guild_id = $2""" - # Set up new cursor for member row - async with conn.cursor() as member_cursor: - # Execute the Member SQL Query - await member_cursor.execute(select_query, member_val) - member_result = await member_cursor.fetchone() - target_user = member_result[1] - - if target_user is not None: - member = guild.get_member(int(target_user)) - await self.bot.generate_embed(ctx, - desc=f"**Sorry! That user is already married to {member.mention}**") - return + # Execute the Author SQL Query + db_author = await conn.fetchrow(select_query, ctx.author.id, guild.id) + married_user = db_author["married"] + + # Make sure that the person is not already married to someone else within the server + if married_user: + member = guild.get_member(married_user) + await self.bot.generate_embed(ctx, desc=f"**((╬◣﹏◢)) You're already married to {member.mention}!**") + return + + # Execute the Member SQL Query + db_member = await conn.fetchrow(select_query, member.id, guild.id) + target_user = db_member["married"] + + if target_user: + member = guild.get_member(target_user) + await self.bot.generate_embed(ctx, + desc=f"**Sorry! That user is already married to {member.mention}**") + return + + # Release connection back to pool + await pool.release(conn) # Send a message to the channel mentioning the author and the person they want to wed. await self.bot.generate_embed(ctx, desc=f"{ctx.author.mention} **Proposes To** {member.mention}" @@ -132,18 +131,20 @@ class Relationship(Cog): # Setup pool connection and cursor async with pool.acquire() as conn: - async with conn.cursor() as cur: - message_time = msg.created_at.strftime("%a, %b %d, %Y") - # Update the existing records in the database with the user that they are marrying along with the time of the accepted proposal - update_query = """UPDATE members SET married = (%s), marriedDate = (%s) WHERE discordID = (%s) AND guildID = (%s)""" - proposer = member.id, message_time, ctx.author.id, guild.id, - proposee = ctx.author.id, message_time, member.id, guild.id, - # Execute the SQL Query's - await cur.execute(update_query, proposer) - await cur.execute(update_query, proposee) - await conn.commit() - print(cur.rowcount, "2 people have been married!") + message_time = msg.created_at.strftime("%a, %b %d, %Y") + # Update the existing records in the database with the user that they are marrying along with the time of the accepted proposal + update_query = """UPDATE members SET married = $1, married_date = $2 WHERE member_id = $3 AND guild_id = $4""" + + # Execute the SQL Query's + try: + await conn.execute(update_query, member.id, message_time, ctx.author.id, guild.id) + await conn.execute(update_query, ctx.author.id, message_time, member.id, guild.id) + except asyncpg.PostgresError as e: + print(f"PostGres Error: {member.id} and {ctx.author.id} Could Not Be Married", e) + else: + # TODO: Update the cache that the users have been married + pass # Congratulate them! desc = f"**Congratulations! 。゚( ゚^∀^゚)゚。 {ctx.author.mention} and {member.mention} are now married to each other!**" @@ -180,17 +181,18 @@ class Relationship(Cog): await self.bot.generate_embed(ctx, desc="**Senpaii! ˭̡̞(◞⁎˃ᆺ˂)◞*✰ You can't possibly divorce yourself!**") return + # TODO: Probably will need to get rid of this in favour of getting from cache + # TODO: Not gonna bother refactoring this db statement + # Setup pool connection and cursor async with pool.acquire() as conn: async with conn.cursor() as cur: # Get the author's row from the Members Table - select_query = """SELECT * FROM members WHERE discordID = (%s) and guildID = (%s)""" - val = ctx.author.id, guild.id, + select_query = """SELECT * FROM members WHERE member_id = $1 and guild_id = $1""" # Execute the SQL Query - await cur.execute(select_query, val) - result = await cur.fetchone() - married_user = result[1] + result = await cur.fetchrow(select_query, ctx.author.id, guild.id) + married_user = result["married"] # Make sure that the person trying to divorce is actually married to the user if married_user is None: @@ -200,8 +202,8 @@ class Relationship(Cog): return # Make sure the person is married to the person that they're trying to divorce - elif married_user != str(member.id): - member = guild.get_member(int(married_user)) + elif married_user != member.id: + member = guild.get_member(married_user) desc = f"**(ノ ゜口゜)ノ You can only divorce the person that you're married!" \ f"\n That person is {member.mention}**" @@ -228,17 +230,19 @@ class Relationship(Cog): # Setup pool connection and cursor async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Update the existing records in the database with the user that they are marrying along with the time of the accepted proposal - update_query = """UPDATE members SET married = null, marriedDate = null WHERE discordID = (%s) and guildID = (%s)""" - divorcer = ctx.author.id, guild.id, - divorcee = member.id, guild.id, + # Update the existing records in the database with the user that they are marrying along with the time of the accepted proposal + update_query = """UPDATE members SET married = NULL, married_date = NULL WHERE member_id = $1 and guild_id = $2""" + + try: # Execute the SQL Query's - await cur.execute(update_query, divorcer) - await cur.execute(update_query, divorcee) - await conn.commit() - print(cur.rowcount, "2 Members have been divorced :(!") + await conn.execute(update_query, ctx.author.id, guild.id) + await conn.execute(update_query, member.id, guild.id) + except asyncpg.PostgresError as e: + print(f"PostGres Error: {member.id} and {ctx.author.id} Could Not Be Divorced", e) + else: + # TODO: Update the cache that the users have been divorced + pass # Congratulate them! desc = f"**૮( ´⁰▱๋⁰ )ა {ctx.author.mention} and {member.mention} are now divorced." \ @@ -277,29 +281,28 @@ class Relationship(Cog): # Setup pool pool = self.bot.db + # TODO: Replace this entire section with retrieving from cache + # Setup pool connection and cursor async with pool.acquire() as conn: - async with conn.cursor() as cur: - # Get the author's row from the Members Table - select_query = """SELECT * FROM members WHERE discordID = (%s) and guildID = (%s)""" - val = member.id, guild.id, - - # Execute the SQL Query - await cur.execute(select_query, val) - result = await cur.fetchone() - user = result[1] - marriage_date = result[2] - - # Set empty values for non-married users - if user is None: - married = False - marriedUser = "" - marriedDate = "" - # Set the member, date married and setting married status - else: - marriedUser = guild.get_member(int(user)) - marriedDate = marriage_date - married = True + # Get the author's row from the Members Table + select_query = """SELECT * FROM members WHERE member_id = $1 and guild_id = $2""" + + # Execute the SQL Query + result = await conn.fetchrow(select_query, member.id, guild.id) + user = result["married"] + marriage_date = result["married_date"] + + # Set empty values for non-married users + if not user: + married = False + marriedUser = "" + marriedDate = "" + # Set the member, date married and setting married status + else: + married = True + marriedUser = guild.get_member(user) + marriedDate = marriage_date # Get the current date of the message sent by the user currentDate = ctx.message.created_at.strftime("%a, %b %d, %Y") diff --git a/main.py b/main.py index ced34949..6d4656a5 100644 --- a/main.py +++ b/main.py @@ -14,10 +14,29 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from discord.ext.commands import is_owner from bot import Bot # Initiating Bot Object As Client client = Bot() + + +@client.command() +@is_owner() +async def test(ctx): + # Alt Account + testing1 = await client.check_cache(556665878662348811, 621621615930638336) + print(client.member_cache.cache) + + # My Account on the same guild as Alt + testing3 = await client.check_cache(154840866496839680, 621621615930638336) + print(client.member_cache.cache) + + # Me in another guild + testing4 = await client.check_cache(154840866496839680, 663651584399507476) + print(client.member_cache.cache) + + # Run the bot client.execute()