# Ensō~Chan - A Multi Purpose Discord Bot That Has Everything Your Server Needs!
# Copyright (C) 2020 Goudham Suresh
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import datetime
import string
import aiohttp
import aiomysql
import discord
from decouple import config
from discord import Embed , Forbidden
from discord . ext import commands , tasks
from discord . ext . commands import when_mentioned_or , is_owner
import settings
from settings import blank_space , enso_embedmod_colours , enso_guild_ID , enso_newpeople_ID , get_prefix_for_guild , \
cache , del_cache , get_roles_persist
# 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 ,
case_insensitive = True ) # 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 ] , prefix = row [ 1 ] , channel = row [ 2 ] , rolespersist = row [ 3 ] )
# 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 } )
await session . close ( )
@tasks.loop ( minutes = 10 , 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 ] )
# 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 " )
async def _ping ( ctx ) :
""" Latency of the Bot (ms) """
await ctx . send ( f " Pong! ` { round ( client . latency * 1000 ) } ms` " )
@client.command ( name = " 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** <a:ThumbsUp:737832825469796382> " ,
colour = enso_embedmod_colours )
await ctx . send ( embed = embed )
try :
client . db . terminate ( )
await client . db . wait_closed ( )
await client . logout ( )
except Exception as e :
print ( e )
@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.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 = " ~ " , rolespersist = 0 )
# Setup pool
pool = client . db
# Grabbing the values to be inserted
records = " , " . join ( map ( lambda m : f " ( { guild . id } , { m . id } ) " , guild . members ) )
# 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 } " )
@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
role_persist = get_roles_persist ( str ( guild . id ) )
# 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 ]
if role_persist == 1 :
# Get Enso Chan
bot = guild . get_member ( client . user . id )
# Check permissions of Enso
if bot . guild_permissions . manage_roles and 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 :
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 == enso_guild_ID :
# 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 if not r . managed ] )
# 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 """
# Get permissions for the bot within
perms = ctx . guild . me . permissions_in ( ctx . message . channel )
# if the user did not specify an user
if isinstance ( args2 , commands . MissingRequiredArgument ) :
await on_command_missing_argument ( ctx , perms )
# if the user has spammed a command and invoked a cooldown
elif isinstance ( args2 , commands . CommandOnCooldown ) :
await on_command_cooldown ( ctx , perms , args2 )
# if the user tries to access a command that isn't available
elif isinstance ( args2 , commands . CommandNotFound ) :
await on_command_not_found ( ctx , perms )
# if the user provides an argument that isn't recognised
elif isinstance ( args2 , commands . BadArgument ) :
await on_command_bad_argument ( ctx , perms )
# if the user does not the correct permissions to call a command
elif isinstance ( args2 , commands . MissingPermissions ) :
await on_command_permission ( ctx , perms , args2 )
# if the bot is missing permissions needed
elif isinstance ( args2 , commands . BotMissingPermissions ) :
await on_bot_forbidden ( ctx , perms , args2 )
# if the bot is forbidden from performing the command
elif isinstance ( args2 , Forbidden ) :
await on_command_forbidden ( ctx , perms )
# if the user tries to invoke a command that is only for the owner
elif isinstance ( args2 , commands . NotOwner ) :
await on_not_owner ( ctx , perms )
async def send_error ( ctx , perms , embed ) :
"""
Sending error message to the user
Only send error message if the channel permissions allow it
"""
if perms . send_messages and perms . embed_links :
await ctx . send ( embed = embed )
else :
print ( " Error: Error Handling Message Could Not Be Sent " )
async def on_bot_forbidden ( ctx , perms , 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 )
await send_error ( ctx , perms , embed )
async def on_command_forbidden ( ctx , perms ) :
""" Handles Forbidden Error """
embed = Embed ( description = " **❌ I Don ' t Have Permissions To Execute This Command ❌** " ,
colour = enso_embedmod_colours )
await send_error ( ctx , perms , embed )
async def on_command_bad_argument ( ctx , perms ) :
""" 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 )
await send_error ( ctx , perms , embed )
async def on_command_not_found ( ctx , perms ) :
""" 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 )
await send_error ( ctx , perms , embed )
async def on_command_cooldown ( ctx , perms , 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 )
await send_error ( ctx , perms , embed )
async def on_command_permission ( ctx , perms , 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 )
await send_error ( ctx , perms , embed )
async def on_command_missing_argument ( ctx , perms ) :
""" Handles the missing argument error """
embed = Embed ( description = " Required Argument(s) Missing! "
f " \n Use ** { ctx . prefix } help** to find how to use ** { ctx . command } ** " ,
colour = enso_embedmod_colours )
await send_error ( ctx , perms , embed )
async def on_not_owner ( ctx , perms ) :
""" 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 )
await send_error ( ctx , perms , embed )
# 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 ) )
"""