import datetime import string from typing import Optional import aiohttp import aiomysql import discord from decouple import config from discord import Embed, HTTPException from discord.ext import commands, tasks from discord.ext.commands import when_mentioned_or, is_owner, guild_only, has_permissions import settings from settings import blank_space, enso_embedmod_colours, enso_guild_ID, enso_newpeople_ID, get_prefix_for_guild, \ storage_prefix_for_guild, cache, del_cache # Global counter for statuses counter = 0 # Get DB information from .env password = config('DB_PASS') host = config('DB_HOST') user = config('DB_USER') port = config('DB_PORT') db = config('DB_NAME') disc_bots_gg_auth = config('DISCORD_BOTS_BOTS_AUTH') # Getting the Bot token from Environment Variables API_TOKEN = config('DISCORD_TOKEN') 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(get_prefix_for_guild(str(message.guild.id)))(bot, message) def get_version(): """Return the current version of the bot""" return "v1.7.2" # Bot Initiation client = commands.Bot( # Create a new bot command_prefix=get_prefix, # Set the prefix description='All current available commands within Ensō~Chan', # Set a description for the bot owner_id=154840866496839680, # Your unique User ID version=get_version) # Version number of Ensō~Chan client.remove_command("help") # Remove default help command # Setting up connection using pool/aiomysql async def create_connection(): client.db = await aiomysql.create_pool( host=host, port=int(port), user=user, password=password, db=db, loop=client.loop) async def startup_cache_log(): """Store the modlogs/prefixes in cache from the database on startup""" # Setup pool pool = client.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() # Store the guildID's, modlog channels and prefixes within cache for row in results: cache(guildid=row[0], channel=row[2], prefix=row[1]) # Make sure the connection is setup before the bot is ready client.loop.run_until_complete(create_connection()) client.loop.run_until_complete(startup_cache_log()) if __name__ == '__main__': for ext in settings.extensions(): client.load_extension(ext) async def post_bot_stats(): """Method To Update Guild Count On discord.bots.gg""" async with aiohttp.ClientSession() as session: await session.post(f"https://discord.bots.gg/api/v1/bots/{client.user.id}/stats", data={"guildCount": {len(client.guilds)}, "Content-Type": "application/json"}, headers={'Authorization': disc_bots_gg_auth}) print("Guild Count Updated!") @tasks.loop(minutes=5, reconnect=True) async def change_status(): """Creating Custom Statuses as a Background Task""" global counter # Waiting for the bot to ready await client.wait_until_ready() # Update Guild Count on discord.bots.gg await post_bot_stats() # Define array of statuses looping_statuses = [ discord.Activity( type=discord.ActivityType.watching, name=f"{len(client.users)} Weebs | {get_version()}"), discord.Activity( type=discord.ActivityType.watching, name=f"Hamothy | Real Life | {get_version()}"), discord.Activity( type=discord.ActivityType.watching, name=f"Hamothy Program | {get_version()}"), discord.Game(name=f"~help | {get_version()}") ] # Check if the counter is at the end of the array if counter == (len(looping_statuses) - 1): # Reset the loop counter = 0 else: # Increase the counter counter += 1 # Display the next status in the loop await client.change_presence(activity=looping_statuses[counter]) print("Status Changed!") # Start the background task(s) change_status.start() @client.event async def on_message(message): """Make sure bot messages are not tracked""" if message.author.bot: return # Processing the message await client.process_commands(message) @client.event async def on_ready(): """Displaying if Bot is Ready""" print("UvU Senpaiii I'm weady") @client.command(name="ping", aliases=["Ping"]) async def _ping(ctx): """Latency of the Bot (ms)""" await ctx.send(f"Pong! `{round(client.latency * 1000)}ms`") @client.command(name="leave", aliases=["Leave"], hidden=True) @is_owner() async def leave(ctx): """Leaves the guild""" await ctx.send("**Leaving the guild... Bye Bye uvu**") await ctx.guild.leave() @client.command(name="restart", hidden=True) @is_owner() async def restart(ctx): """Restart the Bot""" embed = Embed( description="**Success Senpai! My Reboot Had No Problems** ", colour=enso_embedmod_colours) await ctx.send(embed=embed) client.db.terminate() await client.db.wait_closed() await client.logout() @client.command(name="reloadusers", hidden=True) @is_owner() async def reload_db(ctx): """Reloads the database by inserting/updating all the records""" # Setup pool pool = client.db # 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}**") @client.command(name="prefix", aliases=["Prefix"]) @guild_only() @has_permissions(manage_guild=True) async def change_prefix(ctx, new: Optional[str] = None): """View/Change Guild Prefix""" # 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 storage_prefix_for_guild(client.db, 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!") # 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 `{get_prefix_for_guild(str(ctx.guild.id))}`**") @client.event async def on_guild_join(guild): """ Store users in a database Store prefix/modlogs in the cache """ # Store guildID, modlogs channel and prefix to cache cache(str(guild.id), channel=None, prefix="~") # Setup pool pool = client.db # 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""" + ", ".join( map(lambda m: f"({guild.id}, {m.id})", guild.members)) + """ ON DUPLICATE KEY UPDATE guildID = VALUES(guildID), discordID = VALUES(discordID)""" # Execute the query await cur.execute(insert) await conn.commit() print(cur.rowcount, f"Record(s) inserted successfully into Members from {guild.name}") @client.event async def on_guild_remove(guild): """ Remove users in the database for the guild Remove the modlogs/guild from the cache """ # Delete the key - value pairs for the guild del_cache(str(guild.id)) # Setup pool pool = client.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}") @client.event async def on_member_join(member): """ Bot event to insert new members into the database In the Enso guild, it will send an introduction embed """ # Get the guild guild = member.guild # Setup pool pool = client.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 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] try: if role_ids is not None: # 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}") else: pass except HTTPException: print(f"Roles Could Not Be Added 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 != enso_guild_ID: return # Set the channel id to "newpeople" new_people = guild.get_channel(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=enso_embedmod_colours, timestamp=datetime.datetime.utcnow()) embed.set_thumbnail(url=server_icon) embed.set_image(url=welcome_gif) embed.add_field( name=blank_space, value=f"Hello {member.mention}! We hope you enjoy your stay in this server! ", inline=False) embed.add_field( name=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=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) @client.event async def on_member_remove(member): """Storing User Roles within Database When User Leaves Guild""" role_ids = ", ".join([str(r.id) for r in member.roles]) # Setup pool pool = client.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") @client.event async def on_command_error(ctx, args2): """Event to detect and handle errors""" # if the user did not specify an user if isinstance(args2, commands.MissingRequiredArgument): await on_command_missing_argument(ctx) # if the user has spammed a command and invoked a cooldown elif isinstance(args2, commands.CommandOnCooldown): await on_command_cooldown(ctx, args2) # if the user tries to access a command that isn't available elif isinstance(args2, commands.CommandNotFound): await on_command_not_found(ctx) # if the user provides an argument that isn't recognised elif isinstance(args2, commands.BadArgument): await on_command_bad_argument(ctx) # if the user does not the correct permissions to call a command elif isinstance(args2, commands.MissingPermissions): await on_command_permission(ctx, args2) # if the bot is missing permissions needed elif isinstance(args2, commands.BotMissingPermissions): await on_bot_forbidden(ctx, args2) # if the bot is forbidden from performing the command elif isinstance(args2, commands.CommandInvokeError): await on_command_forbidden(ctx) # if the user tries to invoke a command that is only for the owner elif isinstance(args2, commands.NotOwner): await on_not_owner(ctx) async def on_bot_forbidden(ctx, args2): """Handles Missing Bot Permissions Errors""" # Convert list into string of the missing permissions missing_perms = string.capwords(", ".join(args2.missing_perms).replace("_", " ")) embed = Embed(description=f"❌ I Need **{missing_perms}** Permission(s) to Execute This Command! ❌", colour=enso_embedmod_colours) try: await ctx.send(embed=embed) except HTTPException: print("Error: Error Handling Message Could Not Be Sent") async def on_command_forbidden(ctx): """Handles Forbidden Error""" embed = Embed(description="**❌ I Don't Have Permissions To Execute This Command ❌**", colour=enso_embedmod_colours) try: await ctx.send(embed=embed) except HTTPException: print("Error: Error Handling Message Could Not Be Sent") async def on_command_bad_argument(ctx): """Handles Bad Argument Errors (Argument can't be read properly)""" embed = Embed(description="**❌ Uh oh! Couldn't find anyone to mention! Try again! ❌**", colour=enso_embedmod_colours) try: await ctx.send(embed=embed) except HTTPException: print("Error: Error Handling Message Could Not Be Sent") async def on_command_not_found(ctx): """Handles the command not found error""" embed = Embed(description=f"Command Not Found! ❌ Please use **{ctx.prefix}help** to see all commands", colour=enso_embedmod_colours) try: await ctx.send(embed=embed) except HTTPException: print("Error: Error Handling Message Could Not Be Sent") async def on_command_cooldown(ctx, error): """Handles Cooldown Errors""" embed = Embed(description=f"That command is on cooldown. Try again in **{error.retry_after:,.2f}** seconds", colour=enso_embedmod_colours) try: await ctx.send(embed=embed) except HTTPException: print("Error: Error Handling Message Could Not Be Sent") async def on_command_permission(ctx, args2): """Handles User Missing Permissions Errors""" # Convert list into string of the missing permissions missing_perms = string.capwords(", ".join(args2.missing_perms).replace("_", " ")) embed = Embed(description=f"❌ Uh oh! You Need **{missing_perms}** Permission(s) To Execute This Command! ❌", colour=enso_embedmod_colours) try: await ctx.send(embed=embed) except HTTPException: print("Error: Error Handling Message Could Not Be Sent") async def on_command_missing_argument(ctx): """Handles the missing argument error""" embed = Embed(description="Required Argument(s) Missing!" f"\nUse **{ctx.prefix}help** to find how to use **{ctx.command}**", colour=enso_embedmod_colours) try: await ctx.send(embed=embed) except HTTPException: print("Error: Error Handling Message Could Not Be Sent") async def on_not_owner(ctx): """Handles the error when the user is not the owner and tries to invoke owner only command""" embed = Embed(description="**❌ Owner Only Command ❌**", colour=enso_embedmod_colours) try: await ctx.send(embed=embed) except HTTPException: print("Error: Error Handling Message Could Not Be Sent") # Run the bot, allowing it to come online try: client.run(API_TOKEN) except discord.errors.LoginFailure as e: print("Login unsuccessful.") """ # Don't count messages that are taken in the dms if not isinstance(message.channel, DMChannel): # Using connection to the database with connection() as conn: # Make sure that mariaDB errors are handled properly try: msg_name = message.author.name msg_discrim = message.author.discriminator time = message.created_at # Get: guild_id = message.guild.id # Guild of the message msg_time = time.strftime('%Y-%m-%d %H:%M:%S') # Time of the Message msg_author = f"{msg_name}#{msg_discrim}" # DiscordID msg_content = message.content # Content of the message # Store the variables val = guild_id, msg_time, msg_author, msg_content, # If an attachment (link) has been sent if message.attachments: # Loop through all attachments for attachment in message.attachments: # Get the message content and the link that was used attach = "".join(f"Message: {message.content} Link: {attachment.url}") # Define the new variables to send val = guild_id, msg_time, msg_author, attach, # Define the Insert Into Statement inserting into the database insert_query = """"""INSERT INTO messages (guildID, messageTime, discordID, messageContent) VALUES (?, ?, ?, ?)"""""" cursor = conn.cursor() # Execute the SQL Query cursor.execute(insert_query, val) conn.commit() print(cursor.rowcount, "Record inserted successfully into Logs") except mariadb.Error as ex: print("Parameterized Query Failed: {}"(ex)) # Using database connection with connection() as conn: # Grab the guild and prefix information of the guild that the message was sent in select_query = """"""SELECT * FROM guilds WHERE guildID = (?)"""""" select_val = ctx.guild.id, # Using connection cursor with closing(conn.cursor()) as cursor: # Execute the query cursor.execute(select_query, select_val) result = cursor.fetchone() # Grab the guild prefix curr_prefix = result[1] # If no argument has been given, display the current prefix if not new: await ctx.send(f"**The current guild prefix is `{curr_prefix}`**") # Update the prefix for the guild else: colour_change.start() @client.command() @guild_only() @cooldown(1, 300, BucketType.guild) async def someone(ctx): """"""Tags Someone Randomly in the Server"""""" await ctx.send(random.choice(tuple(member.mention for member in ctx.guild.members if not member.bot))) if message.guild.id != enso_guild_ID: return else: nicknames = ["Ensō's Face", "Ensō's Leg", "Ensō's Thighs", "Ensō's Hands", "Ensō's Feet", "Ensō's Hair", "Ensō's Backbone", "Ensō's Knees", "Ensō's Fingers", "Ensō's Stomach", "Ensō's Heart", "Ensō's Nose", ] await message.author.edit(nick=random.choice(nicknames)) """