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()