mirror of https://github.com/sgoudham/Enso-Bot.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
588 lines
24 KiB
Python
588 lines
24 KiB
Python
# 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 logging
|
|
import os
|
|
import random
|
|
|
|
import aiohttp
|
|
import aiomysql
|
|
import discord
|
|
from decouple import config
|
|
from discord import Colour, Embed
|
|
from discord.ext import commands, tasks
|
|
from discord.ext.commands import when_mentioned_or
|
|
|
|
from bot.libs.cache import MyCoolCache
|
|
|
|
logger = logging.getLogger('discord')
|
|
logger.setLevel(logging.DEBUG)
|
|
handler = logging.FileHandler(filename='discord.log', encoding='utf-8', mode='w')
|
|
handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s'))
|
|
logger.addHandler(handler)
|
|
|
|
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')
|
|
|
|
|
|
class Bot(commands.Bot):
|
|
def __init__(self, **options):
|
|
|
|
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)
|
|
|
|
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.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.remove_command("help") # Remove default help command
|
|
|
|
# Define variables that are for Enso only
|
|
self.hammyMention = '<@154840866496839680>'
|
|
self.hammy_role_ID = "<@&715412394968350756>"
|
|
self.blank_space = "\u200b"
|
|
self.enso_embedmod_colours = Colour(0x62167a)
|
|
self.enso_ensochancommands_Mention = "<#721449922838134876>"
|
|
self.enso_ensochancommands_ID = 721449922838134876
|
|
self.enso_verification_ID = 728034083678060594
|
|
self.enso_selfroles_ID = 722347423913213992
|
|
self.enso_guild_ID = 663651584399507476
|
|
self.enso_newpeople_ID = 669771571337887765
|
|
self.enso_modmail_ID = 728083016290926623
|
|
self.enso_feedback_ID = 739807803438268427
|
|
|
|
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)
|
|
|
|
async def create_connection():
|
|
"""Setting up connection using pool/aiomysql"""
|
|
|
|
self.db = await aiomysql.create_pool(
|
|
host=host,
|
|
port=int(port),
|
|
user=user,
|
|
password=password,
|
|
db=db,
|
|
loop=self.loop)
|
|
|
|
async def startup_cache_log():
|
|
"""Store the modlogs/prefixes in cache from the database on startup"""
|
|
|
|
# Setup pool
|
|
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()
|
|
|
|
# Store the guildID's, modlog channels and prefixes within cache
|
|
for row in results:
|
|
self.enso_cache[row[0]] = {"prefix": row[1], "modlogs": row[2], "roles_persist": row[3]}
|
|
|
|
# Make sure the connection is setup before the bot is ready
|
|
self.loop.run_until_complete(create_connection())
|
|
self.loop.run_until_complete(startup_cache_log())
|
|
|
|
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/{self.user.id}/stats",
|
|
data={"guildCount": {len(self.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 self.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(self.users)} Weebs | {self.version}"),
|
|
discord.Activity(
|
|
type=discord.ActivityType.watching,
|
|
name=f"Hamothy | Real Life | {self.version}"),
|
|
discord.Activity(
|
|
type=discord.ActivityType.watching,
|
|
name=f"Hamothy Program | {self.version}"),
|
|
discord.Game(name=f"~help | {self.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 self.change_presence(activity=looping_statuses[counter])
|
|
|
|
# Start the background task(s)
|
|
change_status.start()
|
|
|
|
# --------------------------------------------!Cache Section!-------------------------------------------------------
|
|
|
|
def store_cache(self, guild_id, prefix, channel, rolespersist):
|
|
"""Storing GuildID, Modlogs Channel and Prefix in Cache"""
|
|
|
|
self.enso_cache[guild_id] = {"prefix": prefix, "modlogs": channel, "roles_persist": rolespersist}
|
|
|
|
def del_cache(self, guild_id):
|
|
"""Deleting the entry of the guild within the cache"""
|
|
|
|
del self.enso_cache[guild_id]
|
|
|
|
# --------------------------------------------!End Cache Section!---------------------------------------------------
|
|
|
|
# --------------------------------------------!RolePersist Section!-------------------------------------------------
|
|
|
|
def get_roles_persist(self, guild_id):
|
|
"""Returning rolespersist value of the guild"""
|
|
|
|
return self.enso_cache[guild_id]["roles_persist"]
|
|
|
|
async def update_role_persist(self, guild_id, value, pool):
|
|
"""Update the rolepersist value of the guild (Enabled or Disabled)"""
|
|
|
|
self.enso_cache[guild_id]["roles_persist"] = value
|
|
|
|
# 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 rolespersist = (%s) WHERE guildID = (%s)"""
|
|
update_vals = value, guild_id,
|
|
|
|
# Execute the query
|
|
await cur.execute(update_query, update_vals)
|
|
await conn.commit()
|
|
|
|
# --------------------------------------------!End RolePersist Section!---------------------------------------------
|
|
|
|
# --------------------------------------------!ModLogs Section!-----------------------------------------------------
|
|
|
|
async def storage_modlog_for_guild(self, pool, 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
|
|
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}")
|
|
|
|
def remove_modlog_channel(self, guild_id):
|
|
"""Remove the value of modlog for the guild specified"""
|
|
|
|
self.enso_cache[guild_id]["modlogs"] = None
|
|
|
|
def get_modlog_for_guild(self, guild_id):
|
|
"""Get the modlog channel of the guild that the user is in"""
|
|
|
|
channel = self.enso_cache[guild_id]["modlogs"]
|
|
return channel
|
|
|
|
# --------------------------------------------!End ModLogs Section!-------------------------------------------------
|
|
|
|
# --------------------------------------------!Prefixes Section!----------------------------------------------------
|
|
|
|
async def storage_prefix_for_guild(self, pool, 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
|
|
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}")
|
|
|
|
# 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}`**")
|
|
|
|
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:
|
|
return prefix
|
|
return "~"
|
|
|
|
# --------------------------------------------!End Prefixes Section!------------------------------------------------
|
|
|
|
# --------------------------------------------!Roles/Colour/Embed Section!------------------------------------------
|
|
|
|
@staticmethod
|
|
def random_colour():
|
|
"""Generate a random hex colour"""
|
|
|
|
return Colour(random.randint(0, 0xFFFFFF))
|
|
|
|
async def generate_embed(self, ctx, desc):
|
|
"""Generate Embed"""
|
|
|
|
embed = Embed(description=desc,
|
|
colour=self.admin_colour)
|
|
|
|
await ctx.send(embed=embed)
|
|
|
|
async def storeRoles(self, target, ctx, member):
|
|
"""Storing User Roles within Database"""
|
|
|
|
pool = self.db
|
|
|
|
role_ids = ", ".join([str(r.id) for r in target.roles])
|
|
|
|
# Setup up pool connection and cursor
|
|
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}")
|
|
|
|
async def clearRoles(self, member):
|
|
"""Clear the roles when the user has been unmuted"""
|
|
|
|
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}")
|
|
|
|
# --------------------------------------------!End Roles/Colour/Embed Section!--------------------------------------
|
|
|
|
# --------------------------------------------!Events Section!------------------------------------------------------
|
|
|
|
async def on_message(self, message):
|
|
"""Make sure bot messages are not tracked"""
|
|
|
|
if message.author.bot: return
|
|
|
|
# Processing the message
|
|
await self.process_commands(message)
|
|
|
|
@staticmethod
|
|
async def on_ready():
|
|
"""Displaying if Bot is Ready"""
|
|
print("UvU Senpaiii I'm ready\n")
|
|
|
|
async def on_guild_join(self, guild):
|
|
"""
|
|
Store users in a database
|
|
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)
|
|
|
|
# Setup pool
|
|
pool = self.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}")
|
|
|
|
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
|
|
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}")
|
|
|
|
async def on_member_join(self, member):
|
|
"""
|
|
Bot event to insert new members into the database
|
|
In the Enso guild, it will send an introduction embed
|
|
"""
|
|
|
|
if member.bot: return
|
|
|
|
# Get the guild
|
|
guild = member.guild
|
|
|
|
# Setup pool
|
|
pool = self.db
|
|
|
|
role_persist = self.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(self.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 == 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)
|
|
|
|
async def on_member_remove(self, member):
|
|
"""Storing User Roles within Database When User Leaves Guild"""
|
|
|
|
if member.bot: return
|
|
|
|
role_ids = ", ".join([str(r.id) for r in member.roles if not r.managed])
|
|
|
|
# Setup pool
|
|
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")
|
|
|
|
# --------------------------------------------!End Events Section!----------------------------------------------
|
|
|
|
def execute(self):
|
|
"""Load the cogs and then run the bot"""
|
|
|
|
for file in os.listdir(f'.{os.sep}cogs'):
|
|
if file.endswith('.py'):
|
|
self.load_extension(f"cogs.{file[:-3]}")
|
|
|
|
# Run the bot, allowing it to come online
|
|
try:
|
|
self.run(API_TOKEN)
|
|
except discord.errors.LoginFailure as e:
|
|
print(e, "Login unsuccessful.")
|