Remove old python code

java-rewrite
Hammy 3 years ago
parent bc514200b0
commit 2d24ad86db

@ -1,622 +0,0 @@
# Ensō~Chan - A Multi Purpose Discord Bot That Has Everything Your Server Needs!
# Copyright (C) 2020 Hamothy
# 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 os
import pathlib
import random
import asyncpg as asyncpg
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
# Counter for cycling 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')
# 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(message.guild.id))(bot, message)
intents = discord.Intents.default()
intents.members = True
super().__init__(intents=intents, command_prefix=get_prefix, case_insensitive=True, **options)
self.db = None
self.line_count = None
self.description = 'All current available commands within Ensō~Chan',
self.owner_id = 154840866496839680 # Your unique User ID
self.admin_colour = Colour(0x62167a) # Admin Embed Colour
self.version = "0.8.2" # Version number of Ensō~Chan
self.remove_command("help") # Remove default help command
# Instance variables for Enso
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
# Cross/Tick Emojis
self.cross = "<:xMark:746834944629932032>"
self.tick = "<:greenTick:746834932936212570>"
# Instance variables for cache
self.enso_cache = {}
self.modmail_cache = {}
self.starboard_cache = {}
self.starboard_messages_cache = {}
self.member_cache = MyCoolCache(100)
async def create_connection():
"""Setting up connection using asyncpg"""
self.db = await asyncpg.create_pool(
host=host,
port=int(port),
user=user,
password=password,
database=db,
loop=self.loop)
async def line_count():
"""Getting the line count of the project"""
code = 0
comments = 0
blank = 0
file_amount = 0
ENV = "venv"
for path, _, files in os.walk("."):
if ".local" in path:
continue
for name in files:
file_dir = str(pathlib.PurePath(path, name))
# Ignoring the venv directory
if not name.endswith(".py") or ENV in file_dir:
continue
file_amount += 1
with open(file_dir, "r", encoding="utf-8") as file:
for line in file:
if line.strip().startswith("#"):
comments += 1
elif not line.strip():
blank += 1
else:
code += 1
# Adding up the total lines of code
total = comments + blank + code
self.line_count = f"Code: {code}\n" \
f"Commentary: {comments}\n" \
f"Blank: {blank}\n" \
f"Total: {total}\n" \
f"Files: {file_amount}"
async def startup_cache_log():
"""Store the guilds/modmail systems in cache from the database on startup"""
# Setup up pool connection
pool = self.db
async with pool.acquire() as conn:
# Query to get all records of guilds that the bot is in
try:
results = await conn.fetch("""SELECT * FROM guilds""")
# Store the guilds information within cache
for row in results:
self.enso_cache[row["guild_id"]] = {"prefix": row["prefix"],
"modlogs": row["modlogs"],
"roles_persist": row["roles_persist"]}
# 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.modmail_cache[row["guild_id"]] = {"modmail_channel_id": row["modmail_channel_id"],
"message_id": row["message_id"],
"modmail_logging_channel_id":
row["modmail_logging_channel_id"]}
# Catch errors
except asyncpg.PostgresError as e:
print("PostGres Error: Modmail Records Could Not Be Loaded Into Cache On Startup", e)
# Query to get all records of starboards within guilds
try:
results = await conn.fetch("SELECT * FROM starboard")
# Store the information for starboard within cache
for row in results:
self.starboard_cache[row["guild_id"]] = {"channel_id": row["channel_id"],
"min_stars": row["min_stars"]}
# Catch errors
except asyncpg.PostgresError as e:
print("PostGres Error: Starboard Records Could Not Be Loaded Into Cache On Startup", e)
# Query to get all records of starboard messages within guilds
try:
results = await conn.fetch("SELECT * FROM starboard_messages")
for row in results:
self.starboard_messages_cache[(row["root_message_id"], row["guild_id"])] = {
"star_message_id": row["star_message_id"],
"stars": row["stars"]}
# Catch errors
except asyncpg.PostgresError as e:
print("PostGres Error: Starboard Records Could Not Be Loaded Into Cache On Startup", e)
# Establish Database Connection
self.loop.run_until_complete(create_connection())
# Get line count of project
self.loop.run_until_complete(line_count())
# Load Information Into Cache
self.loop.run_until_complete(startup_cache_log())
@tasks.loop(minutes=2, reconnect=True)
async def change_status():
"""Creating custom statuses as background task"""
global counter
# Waiting for the bot to ready
await self.wait_until_ready()
# 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, modlogs, roles_persist):
"""Storing guild information within cache"""
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"""
del self.enso_cache[guild_id]
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"""
result = await conn.fetchrow(select_query, member_id, guild_id)
# 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["married"],
"married_date": result["married_date"],
"muted_roles": result["muted_roles"],
"roles": result["roles"]}
self.member_cache.store_cache((member_id, guild_id), dict_items)
return self.member_cache.cache[member_id, guild_id]
# --------------------------------------------!End Cache Section!---------------------------------------------------
# --------------------------------------------!Starboard Section!---------------------------------------------------
def cache_store_starboard(self, guild_id, channel_id, min_stars):
"""Storing starboard within cache"""
self.starboard_cache[guild_id] = {"channel_id": channel_id,
"min_stars": min_stars}
def get_starboard_channel(self, guild_id):
"""Returning the starboard channel of the guild"""
starboard = self.starboard_cache.get(guild_id)
return starboard.get("channel_id") if starboard else None
def get_starboard_min_stars(self, guild_id):
"""Returning the starboard minimum stars of the guild"""
starboard = self.starboard_cache.get(guild_id)
return starboard.get("min_stars") if starboard else None
def update_starboard_channel(self, guild_id, channel_id):
"""Update the starboard channel"""
self.starboard_cache[guild_id]["channel_id"] = channel_id
def update_starboard_min_stars(self, guild_id, min_stars):
"""Update the starboard minimum stars"""
self.starboard_cache[guild_id]["min_stars"] = min_stars
def delete_starboard(self, guild_id):
"""Deleting the starboard of the guild"""
del self.starboard_cache[guild_id]
def delete_starboard_messages(self, in_guild_id):
# Array to store keys to be removed
keys_to_remove = []
# For every starboard message in cache
for (root_msg_id, guild_id) in self.starboard_messages_cache:
# if the guild_id passed in is equal to the guild_id within the cache
if in_guild_id == guild_id:
# Store key within array
keys_to_remove.append((root_msg_id, guild_id))
# Iterate through the array and then pop the keys from cache
for key in keys_to_remove:
self.starboard_messages_cache.pop(key)
def cache_store_starboard_message(self, root_message_id, guild_id, star_message_id):
"""Store the starboard messages within cache"""
self.starboard_messages_cache[root_message_id, guild_id] = {"star_message_id": star_message_id,
"stars": 1}
def update_starboard_message_id(self, root_message_id, guild_id, star_message_id):
"""Update the stored starboard message"""
self.starboard_messages_cache[root_message_id, guild_id]["star_message_id"] = star_message_id
def del_starboard_star_message_id(self, root_message_id, guild_id):
"""Set the star message id to None"""
self.starboard_messages_cache[root_message_id, guild_id]["star_message_id"] = None
def update_starboard_message_stars(self, root_message_id, guild_id, reactions):
"""Update the stored starboard message"""
self.starboard_messages_cache[root_message_id, guild_id]["stars"] = reactions
def check_root_message_id(self, root_message_id, guild_id):
"""Check if the original message is stored within the cache"""
# Return value if message is already in the cache
if (root_message_id, guild_id) in self.starboard_messages_cache:
return True
else:
return False
async def check_starboard_messages_cache(self, root_message_id, guild_id):
"""Check if the message is already in the cache"""
# Return value if message is already in the cache
if (root_message_id, guild_id) in self.starboard_messages_cache:
return self.starboard_messages_cache[root_message_id, guild_id]["star_message_id"], \
self.starboard_messages_cache[root_message_id, guild_id]["stars"]
else:
# Setup pool connection
pool = self.db
async with pool.acquire() as conn:
# Get the starboard row from the starboard_messages table
try:
select_query = """SELECT * FROM starboard_messages WHERE root_message_id = $1 AND guild_id = $2"""
result = await conn.fetchrow(select_query, root_message_id, guild_id)
# Catch errors
except asyncpg.PostgresError as e:
print(e)
# Store it in cache
else:
if result:
self.starboard_messages_cache[root_message_id, guild_id] = {
"star_message_id": result["star_message_id"],
"stars": result["stars"]}
# Returning as separate variables for better readability
star_message_id = self.starboard_messages_cache[root_message_id, guild_id]["star_message_id"]
stars = self.starboard_messages_cache[root_message_id, guild_id]["stars"]
return star_message_id, stars
else:
return None, 0
# --------------------------------------------!EndStarboard 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"""
return self.modmail_cache.get(guild_id)
def update_modmail(self, guild_id, channel_id):
"""Update the modmail channel"""
self.modmail_cache[guild_id]["modmail_logging_channel_id"] = 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):
"""Returning rolespersist value of the guild"""
role_persist = self.enso_cache.get(guild_id)
return role_persist.get("roles_persist") if role_persist else None
async def update_role_persist(self, guild_id, value):
"""Update the rolepersist value of the guild (Enabled or Disabled)"""
# Setup up pool connection
pool = self.db
async with pool.acquire() as conn:
# 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
# --------------------------------------------!End RolePersist Section!---------------------------------------------
# --------------------------------------------!ModLogs Section!-----------------------------------------------------
async def storage_modlog_for_guild(self, ctx, channel_id, setup):
"""Updating the modlog within the dict and database"""
# Setup up pool connection
pool = self.db
async with pool.acquire() as conn:
# 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
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"""
modlogs = self.enso_cache.get(guild_id)
return modlogs.get("modlogs") if modlogs else None
# --------------------------------------------!End ModLogs Section!-------------------------------------------------
# --------------------------------------------!Prefixes Section!----------------------------------------------------
async def storage_prefix_for_guild(self, ctx, prefix):
"""Updating the prefix within the dict and database when the method is called"""
# Setup up pool connection
pool = self.db
async with pool.acquire() as conn:
# 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)
# 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
else:
print(rowcount, f"Guild prefix has been updated for guild {ctx.guild}")
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
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:
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 store_roles(self, target, ctx, member):
"""Storing user roles within database"""
role_ids = ", ".join([str(r.id) for r in target.roles])
# Setup up pool connection
pool = self.db
async with pool.acquire() as conn:
# 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)
result = await self.check_cache(member.id, member.guild.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
# Update cache
else:
result["muted_roles"] = role_ids
print(rowcount, f"Roles Added For User {member} in {ctx.guild}")
async def clear_roles(self, member):
"""Clear the roles when the user has been unmuted"""
# Setup up pool connection
pool = self.db
async with pool.acquire() as conn:
# Query to clear the existing role of the member from the database
try:
update = """UPDATE members SET muted_roles = NULL WHERE guild_id = $1 AND member_id = $2"""
rowcount = await conn.execute(update, member.guild.id, member.id)
result = await self.check_cache(member.id, member.guild.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
# Update cache
else:
result["muted_roles"] = None
print(rowcount, f"Roles Cleared For User {member} in {member.guild.name}")
# --------------------------------------------!End Roles/Colour/Embed 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.")

@ -1,159 +0,0 @@
# Ensō~Chan - A Multi Purpose Discord Bot That Has Everything Your Server Needs!
# Copyright (C) 2020 Hamothy
# 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/>.
# Built by karan#7508
# Edited by Hamothy#5619
# TODO: UPDATE ALL COMMENTARY TO REFLECT OUR WORK
import threading
class CachingCircularQueue:
# When this lock is enabled, only the function it was called in can change
# The state of this class
threadLock = threading.Lock()
def __init__(self, size):
# The current queue
self.values = []
# The maximum size of the queue
self.MAX_SIZE = size
def push(self, value):
# thread safe
with self.threadLock:
# If the current size is less than the max size:
if len(self.values) < self.MAX_SIZE:
# Add the value to the end of the queue
self.values.append(value)
# Return None as the default return value of Push
return None
else:
# If the queue is full, add the value to the end of the list
self.values.append(value)
# Then remove and return the item at the start of the list
# Therefore we don't need to touch the size
return self.values.pop(0)
def pop(self):
# thread safe
with self.threadLock:
# should never try to pop an empty queue, you fucked up
assert len(self.values) > 0
# decrement the size
# return the first value of the array
return self.values.pop(0)
def remove(self, value):
# To my knowledge, this method can be called when deleting a single member and many members???
# 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)
# PRECONDITION, VALUE EXISTS IN CACHE, SO SHOULD EXIST IN LIST
if value in self.values:
self.values.remove(value)
class MyCoolCache:
threadLock = threading.Lock()
def __init__(self, size):
self.MAX_SIZE = size
self.queue = CachingCircularQueue(size)
self.cache = {}
def get_size(self):
"""Return size of cache and queue"""
return self.MAX_SIZE, len(self.cache), len(self.queue.values)
def change_array_size(self, input_size):
"""Dynamically change the size of the array"""
# Making it thread-safe
with self.threadLock:
# Increase the size of the array and the cache to the new size
if input_size > self.MAX_SIZE:
self.queue.MAX_SIZE = input_size
self.MAX_SIZE = input_size
# Increase the size of the array while
# it's greater than the queue and
# less than the max value of the cache
elif self.MAX_SIZE > input_size > len(self.queue.values):
self.queue.MAX_SIZE = input_size
self.MAX_SIZE = input_size
# Decrease the size of the queue and pop values in cache
else:
# Split Array into 2 and iterate through the queue and delete things
for value in self.queue.values[input_size:]:
self.cache.pop(value)
# Make sure only the records up until the size specified are stored
self.queue.values = self.queue.values[:input_size]
# Set max size of queue and cache to be the length of the new queue
self.queue.MAX_SIZE = len(self.queue.values)
self.MAX_SIZE = input_size
def store_cache(self, key, dict_item):
"""Store the value in queue/cache"""
with self.threadLock:
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
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
with self.threadLock:
# Array to store keys to be removed
keys_to_remove = []
# 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
if in_guild_id == guild_id:
# 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
# Store key within array
keys_to_remove.append((member_id, guild_id))
# Remove the key from the queue
self.queue.remove((member_id, guild_id))
# Iterate through the array and then pop the keys from cache
for key in keys_to_remove:
self.cache.pop(key)

@ -1,584 +0,0 @@
# Ensō~Chan - A Multi Purpose Discord Bot That Has Everything Your Server Needs!
# Copyright (C) 2020 Hamothy
# 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 aiohttp
from decouple import config
from discord import Embed
from discord.ext.commands import Cog, group, bot_has_permissions, command, BadArgument, MissingRequiredArgument
from cogs.libs.paginators import SimpleMenu, MWLMenu
my_waifu_list_auth = config('MYWAIFULIST_AUTH')
class WaifuCommandNotFound(Exception):
"""Exception raised for errors when user does not use the right arguments for waifu command
Attributes:
waifu -- input command which cause the error
message -- explanation of the error
"""
def __init__(self, waifu, ctx):
self.command = waifu
self.bot = ctx.bot
self.message = f"Error! Use **{ctx.prefix}help {self.command}** to see {self.command} commands"
super().__init__(self.message)
def __str__(self):
return f'{self.command} -> {self.message}'
def store_in_dict(_dict, api):
"""Store the waifu data in dicts"""
# Store all the shows with the name as the key
for item in api:
_dict[item["id"]] = {}
for value in item:
store_dict(_dict, item, value)
def store_dict(dict_, key, value):
"""Method to store waifu's in the new dict"""
dict_[key["id"]][value] = key[value]
async def get_from_api(self, ctx, url):
"""Retrieving data from API"""
url = f"https://mywaifulist.moe/api/v1/{url}"
# Retrieve random or daily waifu from API
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=self.headers) as resp:
# Store waifu's in dict when request is successful, else send an error
if resp.status == 200:
api_dict = await resp.json()
_dict = api_dict["data"]
# Send error if something went wrong internally/while grabbing data from API
else:
await self.bot.generate_embed(ctx, desc="**Something went wrong with MyWaifuList!**")
return _dict
async def post_from_api(self, i, ctx, term, _dict, url):
"""Posting information to the API and getting data back"""
data = {"term": term,
'content-type': "application/json"}
# Searching API for waifu(s)
async with aiohttp.ClientSession() as session:
async with session.post(url, data=data, headers=self.headers) as resp:
# Store waifu's in dict when request is successful, else send an error
if resp.status == 200:
api_data = await resp.json()
# Send error if something went wrong internally/while grabbing data from API
else:
await self.bot.generate_embed(ctx, desc="**Something went wrong with MyWaifuList!**")
# As long as data is returned
# Store all data from the api in dict
if len(api_data["data"]) > 1:
for item in api_data["data"]:
_dict[item["id"]] = {}
for value in item:
store_dict(_dict, item, value)
# Get the instance of the bot
bot = ctx.guild.get_member(self.bot.user.id)
# Get the permissions of the channel
perms = bot.permissions_in(ctx.message.channel)
# Send the menu to the display
menu = MWLMenu(i, perms, _dict, self.bot)
return menu
# Don't bother with pagination if only 1 item is returned by the API
elif len(api_data["data"]) == 1:
if api_data["data"][0]["type"] in ["Waifu", "Husbando"]:
await ctx.send(embed=waifu_embed(self, api_data["data"][0], False))
return False
else:
await ctx.send(embed=anime_embed(self, api_data["data"][0], False))
return False
# When no waifu has been retrieved, send error message to the user
else:
await self.bot.generate_embed(ctx, desc="**Waifu/Anime Not Found!\n"
"Tips for a good search:\n"
"- Use the full name!\n"
f"- Use `{ctx.prefix}bsearch` to try and get better results!**")
return False
def anime_embed(self, anime, detailed):
"""Generate embed of single anime's"""
# Get all the data to be displayed in the embed
name = anime["name"]
anime_id = anime["id"]
og_name = anime["original_name"]
picture = anime["display_picture"]
url = anime["url"]
_type = anime["type"]
romaji_name = anime["romaji_name"]
# Only setting the description if original name is returned from the API
desc = og_name if og_name else Embed.Empty
embed = Embed(title=name, description=desc,
url=url,
colour=self.bot.random_colour())
embed.set_author(name=f"{_type} | ID: {anime_id}")
embed.set_image(url=picture)
if romaji_name:
embed.set_footer(text=f"{romaji_name} | Powered by MyWaifuList")
else:
embed.set_footer(text="Powered By MyWaifuList")
if detailed:
release_date = anime["release_date"]
airing_start = anime["airing_start"]
airing_end = anime["airing_end"]
episode_count = anime["episode_count"]
# Only setting the series date information if they exist
rel_date = release_date if release_date else self.bot.cross
air_start = airing_start if airing_start else self.bot.cross
air_end = airing_end if airing_end else self.bot.cross
ep_count = episode_count if episode_count else self.bot.cross
fields = [("Airing Start Date", air_start, True),
("Airing End Date", air_end, True),
("\u200b", "\u200b", True),
("Release Date", rel_date, True),
("Episode Count", ep_count, True),
("\u200b", "\u200b", True)]
# Add fields to the embed
for name, value, inline in fields:
embed.add_field(name=name, value=value, inline=inline)
return embed
def waifu_embed(self, waifu, _type):
"""Generate embed of single waifu's"""
# Get all the data to be displayed in the embed
name = waifu["name"]
og_name = waifu["original_name"]
picture = waifu["display_picture"]
url = waifu["url"]
waifu_id = waifu["id"]
likes = waifu["likes"]
trash = waifu["trash"]
waifu_type = waifu["type"]
# Set different values for description based on the command
if _type == "random":
author = f"Random {waifu_type} | ID: {waifu_id}"
elif _type == "daily":
author = f"Daily {waifu_type} | ID: {waifu_id}"
else:
author = f"{waifu_type} | ID: {waifu_id}"
desc = og_name if waifu["original_name"] else Embed.Empty
embed = Embed(title=name, description=desc,
colour=self.bot.random_colour(),
url=url)
embed.set_author(name=author)
embed.set_image(url=picture)
embed.set_footer(text=f"❤️ {likes} 🗑️ {trash} | Powered by MyWaifuList")
return embed
async def detailed_waifu_embed(self, waifu, author, ctx):
"""Generate embed of single waifu's (detailed)"""
not_found = "https://media.discordapp.net/attachments/741072426984538122/748586578074664980/DzEZ4UsXgAAcFjN.png?width=423&height=658"
# Get all the data to be displayed in the embed
name = waifu["name"]
waifu_id = waifu["id"]
url = waifu["url"]
picture = waifu["display_picture"]
likes = waifu["likes"]
trash = waifu["trash"]
og_name = waifu["original_name"]
romaji_name = waifu["romaji_name"]
age = waifu["age"]
b_day = waifu["birthday_day"]
b_month = waifu["birthday_month"]
b_year = waifu["birthday_year"]
popularity_rank = waifu["popularity_rank"]
like_rank = waifu["like_rank"]
trash_rank = waifu["trash_rank"]
height = waifu["height"]
weight = waifu["weight"]
waist = waifu["waist"]
bust = waifu["bust"]
hip = waifu["hip"]
# Only setting up description if waifu og_name has a value
desc = f"**Waifu ID:** {waifu_id}\n"
desc += f"**Original Name:** {og_name}\n" if og_name else f"**Original Name:** {self.bot.cross}\n"
desc += f"**Romaji Name:** {romaji_name}\n" if romaji_name else f"**Romaji Name:** {self.bot.cross}\n"
desc += f"\n**Age:** {age}\n" if age else f"**Age:** {self.bot.cross}\n"
desc += f"**Birthday-Day:** {b_day}\n" if b_day else f"**Birthday-Day:** {self.bot.cross}\n"
desc += f"**Birthday-Month:** {b_month}\n" if b_month else f"**Birthday-Month:** {self.bot.cross}\n"
desc += f"**Birthday-Year:** {b_year}\n" if b_year else f"**Birthday-Year:** {self.bot.cross}\n"
height = f"**Height:** {height}cm" if height != "0.00" else f"**Height:** {self.bot.cross}"
weight = f"**Weight:** {weight}kg" if weight != "0.00" else f"**Weight:** {self.bot.cross}"
waist = f"**Waist:** {waist}cm" if waist != "0.00" else f"**Waist:** {self.bot.cross}"
bust = f"**Bust:** {bust}cm" if bust != "0.00" else f"**Bust:** {self.bot.cross}"
hip = f"**Hip:** {hip}cm" if hip != "0.00" else f"**Hip:** {self.bot.cross}"
# Only setting up the ranks if they are returned
pop_rank_string = f"**Popularity Rank:** {popularity_rank}\n" if popularity_rank else f"**Popularity Rank:** {self.bot.cross}\n"
like_rank_string = f"**Like Rank:** {like_rank}\n" if like_rank else f"**Like Rank:** {self.bot.cross}\n"
trash_rank_string = f"**Trash Rank:** {trash_rank}\n" if trash_rank else f"**Trash Rank:** {self.bot.cross}\n"
fields = [("Measurements",
f"{height}"
f"\n{weight}"
f"\n{waist}"
f"\n{bust}"
f"\n{hip}", True),
("Ranks",
f"{pop_rank_string}"
f"{like_rank_string}"
f"{trash_rank_string}", True)]
# Only using image if it can be displayed, else display 404 image
picture_url = picture if picture.endswith((".jpeg", ".png", ".jpg")) else not_found
# Different titles depending on if author was given or not
title = f"True Love | {name}" if author else f"Detailed Waifu | {name}"
detailed = Embed(title=title, description=desc,
colour=self.bot.random_colour(),
url=url)
detailed.set_image(url=picture_url)
detailed.set_footer(text=f"❤️ {likes} 🗑️ {trash} | Powered by MyWaifuList")
# Add fields to the embed
for name, value, inline in fields:
detailed.add_field(name=name, value=value, inline=inline)
# Get the permissions of the channel
perms = ctx.guild.me.permissions_in(ctx.message.channel)
if author:
menu = SimpleMenu(0, "User Information", perms, [author, detailed], self)
await menu.start(ctx)
else:
return detailed
async def user_embed(self, user, ctx):
"""Generate embed of user profile information"""
love = False
# Get all the data to be displayed in the embed
name = user["name"]
avatar = user["avatar"] if user[
"avatar"] else "https://media.discordapp.net/attachments/741072426984538122/748586578074664980/DzEZ4UsXgAAcFjN.png?width=423&height=658"
joined = user["joined"]
id = user["id"]
waifus_created = user["waifus_created"]
waifus_liked = user["waifus_liked"]
waifus_trashed = user["waifus_trashed"]
main_love = user["true_love"]
profile_url = f"https://mywaifulist.moe/user/{id}"
date_time_obj = datetime.datetime.strptime(joined, '%Y-%m-%d %H:%M:%S')
joined_at = date_time_obj.strftime("%a, %b %d, %Y\n%I:%M:%S %p")
desc = f"**Waifu's Created:** {waifus_created}" \
f"\n**Waifu's Liked:** {waifus_liked}" \
f"\n**Waifu's Trashed:** {waifus_trashed}"
author = Embed(title=name, description=desc,
colour=self.bot.random_colour(),
url=profile_url)
author.add_field(name="Joined Date", value=joined_at, inline=False)
author.set_thumbnail(url=avatar)
author.set_footer(text=f"User ID: {id} | Powered by MyWaifuList")
if main_love["slug"]:
love = True
slug = main_love["slug"]
# Get true love details from the API
true_love = await get_from_api(self, ctx, f"waifu/{slug}")
await detailed_waifu_embed(self, true_love, author, ctx)
return author, love
class Anime(Cog):
"""
Search MyWaifuList for Waifu's, Anime's and more!
Please keep in mind that this API is in ALPHA (And it is a community driven website.)
Searches might not return fully detailed results and images may be missing
"""
# TODO: ADD AIRING SHOWS BY SEASON COMMAND
# TODO: ADD USER WAIFUS COMMAND
def __init__(self, bot):
self.bot = bot
self.headers = {'apikey': my_waifu_list_auth}
@Cog.listener()
async def on_ready(self):
"""Printing out that Cog is ready on startup"""
print(f"{self.__class__.__name__} Cog has been loaded!\n-----")
@group(name="airing", invoke_without_command=True, case_insensitive=True, usage="`anime|best|popular|trash`")
@bot_has_permissions(embed_links=True, add_reactions=True)
async def airing(self, ctx):
"""
Display's airing anime and waifu's within those anime's
"""
error = WaifuCommandNotFound(ctx.command, ctx)
await self.bot.generate_embed(ctx, desc=error.message)
@airing.command(name="trash", aliases=["worst", "garbage"])
@bot_has_permissions(embed_links=True, add_reactions=True)
async def airing_trash(self, ctx):
"""Get the worst waifus from the airing anime"""
# Variables to set up the reaction menu
i = 0
airing_trash = {}
trash_waifus = await get_from_api(self, ctx, "airing/trash")
store_in_dict(airing_trash, trash_waifus)
# Get the instance of the bot
bot = ctx.guild.get_member(self.bot.user.id)
# Get the permissions of the channel
perms = bot.permissions_in(ctx.message.channel)
# Send the menu to the display
menu = MWLMenu(i, perms, airing_trash, self.bot)
await menu.start(ctx)
@airing.command(name="popular", aliases=["pop"])
@bot_has_permissions(embed_links=True, add_reactions=True)
async def airing_popular(self, ctx):
"""Get the most popular waifus from the airing anime"""
# Variables to setup the reaction menu
i = 0
airing_popular = {}
popular_waifus = await get_from_api(self, ctx, "airing/popular")
store_in_dict(airing_popular, popular_waifus)
# Get the instance of the bot
bot = ctx.guild.get_member(self.bot.user.id)
# Get the permissions of the channel
perms = bot.permissions_in(ctx.message.channel)
# Send the menu to the display
menu = MWLMenu(i, perms, airing_popular, self.bot)
await menu.start(ctx)
@airing.command(name="best")
@bot_has_permissions(embed_links=True, add_reactions=True)
async def airing_best(self, ctx):
"""Get the best waifus from the airing anime"""
# Local Variable i to allow the pages to be modified
i = 0
airing_best = {}
best_waifus = await get_from_api(self, ctx, "airing/best")
store_in_dict(airing_best, best_waifus)
# Get the instance of the bot
bot = ctx.guild.get_member(self.bot.user.id)
# Get the permissions of the channel
perms = bot.permissions_in(ctx.message.channel)
# Send the menu to the display
menu = MWLMenu(i, perms, airing_best, self.bot)
await menu.start(ctx)
@airing.command(name="anime", aliases=["show", "series"])
@bot_has_permissions(embed_links=True, add_reactions=True)
async def airing_anime(self, ctx):
"""Display the current airing anime"""
# Local Variable i to allow the pages to be modified
i = 0
anime_dict = {}
animes = await get_from_api(self, ctx, "airing")
store_in_dict(anime_dict, animes)
# Get the instance of the bot
bot = ctx.guild.get_member(self.bot.user.id)
# Get the permissions of the channel
perms = bot.permissions_in(ctx.message.channel)
# Send the menu to the display
menu = MWLMenu(i, perms, anime_dict, self.bot)
await menu.start(ctx)
@group(name="waifu", invoke_without_command=True, case_insensitive=True, usage="`daily|random`")
@bot_has_permissions(embed_links=True)
async def waifu(self, ctx):
"""
Waifu's that are retrieved from MyWaifuList
"""
error = WaifuCommandNotFound(ctx.command, ctx)
await self.bot.generate_embed(ctx, desc=error.message)
@waifu.command(name="daily")
@bot_has_permissions(embed_links=True)
async def daily_waifu(self, ctx):
"""Returns the Daily Waifu from MyWaifuList"""
waifu = await get_from_api(self, ctx, "meta/daily")
await ctx.send(embed=waifu_embed(self, waifu, "daily"))
@waifu.command(name="random", aliases=["rnd"])
@bot_has_permissions(embed_links=True)
async def random_waifu(self, ctx):
"""Returning a Random Waifu from MyWaifuList"""
waifu = await get_from_api(self, ctx, "meta/random")
await ctx.send(embed=waifu_embed(self, waifu, "random"))
@group(name="anime", aliases=["series", "shows"],
invoke_without_command=True, case_insensitive=True,
usage="`<MWLAnimeID>|waifu <MWLWaifuID>`")
@bot_has_permissions(embed_links=True)
async def anime(self, ctx, term: int):
"""Returning information about a given series (MWL ID ONLY)"""
anime = await get_from_api(self, ctx, f"series/{term}")
await ctx.send(embed=anime_embed(self, anime, True))
@anime.command(name="waifu", usage="`<MWLAnimeID>`")
@bot_has_permissions(embed_links=True)
async def anime_waifus(self, ctx, term: int):
"""Return the waifu's of the given anime (MWL ID ONLY)"""
i = 0
anime_waifus = {}
waifus = await get_from_api(self, ctx, f"series/{term}/waifus")
if len(waifus) > 0:
for item in waifus:
if item["type"] in ["Waifu", "Husbando"]:
anime_waifus[item["id"]] = {}
for value in item:
store_dict(anime_waifus, item, value)
# Get the instance of the bot
bot = ctx.guild.get_member(self.bot.user.id)
# Get the permissions of the channel
perms = bot.permissions_in(ctx.message.channel)
# Send the menu to the display
menu = MWLMenu(i, perms, anime_waifus, self.bot)
await menu.start(ctx)
else:
await self.bot.generate_embed(ctx, desc="**No Waifu's/Husbando's To Be Displayed!**")
@command("detailedwaifu", aliases=["dwaifu"], usage="`<MWLWaifuID>`")
@bot_has_permissions(embed_links=True)
async def detailed_waifu(self, ctx, term: int):
"""Returns detailed information about a waifu (MWL ID ONLY)"""
waifu = await get_from_api(self, ctx, f"waifu/{term}")
embed = await detailed_waifu_embed(self, waifu, None, ctx)
await ctx.send(embed=embed)
@command(name="profile", aliases=["user"], usage="`<MWLUserID>`")
@bot_has_permissions(embed_links=True)
async def mwl_user_profile(self, ctx, term: int):
"""Returning the MWL User Profile requested"""
user = await get_from_api(self, ctx, f"user/{term}")
embed, love = await user_embed(self, user, ctx)
if not love:
await ctx.send(embed=embed)
@command(name="search", aliases=["lookup"], usage="`<waifu|anime>`")
@bot_has_permissions(embed_links=True, add_reactions=True)
async def search(self, ctx, *, term: str):
"""Search the entire website! (Anime|Manga|Waifus|Husbandos)"""
# Local Variable i to allow the index of the embeds to be modified
i = 0
anime_or_waifu = {}
url = "https://mywaifulist.moe/api/v1/search/"
if menu := await post_from_api(self, i, ctx, term, anime_or_waifu, url):
await menu.start(ctx)
@command(name="betasearch", aliases=["bsearch", "betalookup", "blookup"], usage="`<waifu|anime>`")
@bot_has_permissions(embed_links=True, add_reactions=True)
async def beta_search(self, ctx, *, term: str):
"""Search the entire website - more aggressive searching! (Anime|Manga|Waifus|Husbandos)"""
# Local Variable i to allow the index of the embeds to be modified
i = 0
anime_or_waifu = {}
url = "https://mywaifulist.moe/api/v1/search/beta"
if menu := await post_from_api(self, i, ctx, term, anime_or_waifu, url):
await menu.start(ctx)
@anime.error
@anime_waifus.error
@mwl_user_profile.error
@detailed_waifu.error
async def mlsetup_command_error(self, ctx, exc):
"""Catching error if ID is not recognised"""
if isinstance(exc, BadArgument):
text = "**MyWaifuList ID Not Detected... Aborting Process**"
await self.bot.generate_embed(ctx, desc=text)
elif isinstance(exc, MissingRequiredArgument):
text = "Required Argument(s) Missing!" \
f"\nUse **{ctx.prefix}help** to find how to use **{ctx.command}**"
await self.bot.generate_embed(ctx, desc=text)
def setup(bot):
bot.add_cog(Anime(bot))

@ -1,77 +0,0 @@
# Ensō~Chan - A Multi Purpose Discord Bot That Has Everything Your Server Needs!
# Copyright (C) 2020 Hamothy
# 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 aiohttp
import dbl
import statcord
from decouple import config
from discord.ext import commands, tasks
statcord_auth = config("STATCORD_AUTH")
disforge_auth = config('DISFORGE_AUTH')
disc_bots_gg_auth = config('DISCORD_BOTS_BOTS_AUTH')
top_gg_auth = config('TOP_GG_AUTH')
async def post_bot_stats(self):
"""Update guild count on bot lists"""
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.post(f"https://disforge.com/api/botstats/{self.user.id}",
data={"servers": {len(self.guilds)}},
headers={'Authorization': disforge_auth})
class BotLists(commands.Cog):
"""Handles interactions with the top.gg API"""
def __init__(self, bot):
self.bot = bot
self.token = top_gg_auth
self.dblpy = dbl.DBLClient(self.bot, self.token)
self.key = f"statcord.com-{statcord_auth}"
self.api = statcord.Client(self.bot, self.key)
self.api.start_loop()
@tasks.loop(hours=1, reconnect=True)
async def post_updates():
"""Post updates to botlists"""
await self.bot.wait_until_ready()
try:
await post_bot_stats(self.bot)
await self.dblpy.post_guild_count()
except Exception as e:
print(e)
else:
print("Server count posted successfully")
# Start the background task(s)
post_updates.start()
@commands.Cog.listener()
async def on_command(self, ctx):
self.api.command_run(ctx)
def setup(bot):
bot.add_cog(BotLists(bot))

@ -1,162 +0,0 @@
# Ensō~Chan - A Multi Purpose Discord Bot That Has Everything Your Server Needs!
# Copyright (C) 2020 Hamothy
# 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 string
from discord import Forbidden, Embed
from discord.ext import commands
from discord.ext.commands import Cog
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)
elif perms.send_messages:
await ctx.send(
"Error Message Not Sent.\nMake sure that the bot has the **Embed Links** Permission")
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"{ctx.bot.cross} I Need **{missing_perms}** Permission(s) to Execute This Command! {ctx.bot.cross}",
colour=ctx.bot.admin_colour)
await send_error(ctx, perms, embed)
async def on_command_forbidden(ctx, perms):
"""Handles Forbidden Error"""
embed = Embed(description=f"**{ctx.bot.cross} I Don't Have Permissions To Execute This Command {ctx.bot.cross}**",
colour=ctx.bot.admin_colour)
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=f"**{ctx.bot.cross} Uh oh! Couldn't find anyone to mention! Try again! {ctx.bot.cross}**",
colour=ctx.bot.admin_colour)
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! {ctx.bot.cross} Please use **{ctx.prefix}help** to see all commands",
colour=ctx.bot.admin_colour)
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=ctx.bot.admin_colour)
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"{ctx.bot.cross} Uh oh! You Need **{missing_perms}** Permission(s) To Execute This Command! {ctx.bot.cross}",
colour=ctx.bot.admin_colour)
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"\nUse **{ctx.prefix}help** to find how to use **{ctx.command}**",
colour=ctx.bot.admin_colour)
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=f"**{ctx.bot.cross} Owner Only Command {ctx.bot.cross}**",
colour=ctx.bot.admin_colour)
await send_error(ctx, perms, embed)
class Errors(Cog):
"""Global Error Event Handler!"""
def __init__(self, bot):
self.bot = bot
@Cog.listener()
async def on_command_error(self, ctx, args2):
"""Event to detect and handle errors"""
# Get permissions for the bot within
perms = ctx.guild.me.permissions_in(ctx.message.channel)
# Ignore global event handler if command already has error handler event
if hasattr(ctx.command, "on_error"): return
# 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)
def setup(bot):
bot.add_cog(Errors(bot))

@ -1,313 +0,0 @@
import datetime
import asyncpg
from discord.ext.commands import Cog
class Events(Cog):
"""Handling all global events"""
def __init__(self, bot):
self.bot = bot
@Cog.listener()
async def on_ready(self):
"""Display startup message"""
print("UvU Senpaiii I'm ready\n")
@Cog.listener()
async def on_guild_join(self, guild):
"""
Store users in a database
Store prefix/modlogs in the cache
"""
# Store every single record into an array
records = [(member.id, None, None, guild.id, None, None, None, 0) for member in guild.members]
# Setup up pool connection
pool = self.bot.db
async with pool.acquire() as conn:
# Insert the guild information into guilds table
try:
insert = """INSERT INTO guilds VALUES ($1, $2, $3, $4) ON CONFLICT (guild_id) DO NOTHING"""
rowcount = await conn.execute(insert, guild.id, ".", None, 0)
# 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}")
self.bot.store_cache(guild.id, modlogs=None, prefix=".", roles_persist=0)
# 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}")
@Cog.listener()
async def on_guild_remove(self, guild):
"""
Remove users in the database for the guild
Remove the modlogs/guild from the cache
"""
# Setup pool connection
pool = self.bot.db
async with pool.acquire() as conn:
# Delete the guild information as the bot leaves the server
try:
delete = """DELETE FROM guilds WHERE guild_id = $1"""
rowcount = await conn.execute(delete, 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.bot.del_cache(guild.id)
# Delete all records of members from that guild
try:
delete = """DELETE FROM members WHERE guild_id = $1"""
rowcount = await conn.execute(delete, 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}")
# Remove any/all members stored in cache from that guild
self.bot.member_cache.remove_many(guild.id)
# Delete any starboard information upon leaving the guild
try:
delete = """DELETE FROM starboard WHERE guild_id = $1"""
rowcount = await conn.execute(delete, guild.id)
# Catch errors
except asyncpg.PostgresError as e:
print(
f"PostGres Error: On Guild Remove Event Starboard/Starboard Messages Were Not Deleted For {guild.id}",
e)
# Delete all information about the starboard and any messages stored
else:
print(rowcount, f"Starboard deleted successfully from Guild {guild}")
if self.bot.get_starboard_channel(guild.id):
self.bot.delete_starboard(guild.id)
self.bot.delete_starboard_messages(guild.id)
@Cog.listener()
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
"""
# Ignoring bots
if member.bot: return
# Get the guild and role persist value of the guild
guild = member.guild
role_persist = self.bot.get_roles_persist(guild.id)
# Setup pool connection
pool = self.bot.db
async with pool.acquire() as conn:
# Define the insert statement that will insert the user's information
# On conflict, set the left values to null
try:
insert = """INSERT INTO members (guild_id, member_id) VALUES ($1, $2)
ON CONFLICT (guild_id, member_id) DO UPDATE SET left_at = NULL, has_left = 0"""
rowcount = await conn.execute(insert, 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 {member.guild} | {member.guild.id}",
e)
# Print success
else:
print(rowcount, f"{member} Joined {member.guild}, Record Inserted Into Members")
print(rowcount, f"Roles Cleared For {member} in {member.guild}")
# 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["roles"]
# Catch errors
except asyncpg.PostgresError as e:
print(f"PostGres Error: {member} | {member.id} 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.bot.user.id)
# Set flag for what value role_ids is
flag = role_ids
# Check permissions of Enso
if bot.guild_permissions.manage_roles and flag:
# 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}")
# Don't give roles if user has no roles to be given
elif bot.guild_permissions.manage_roles and not flag:
print(f"Member {member} | {member.id} Had No Roles To Be Given")
# No permissions to give roles in the server
else:
print(
f"Insufficient Permissions to Add Roles to {member} | {member.id} in {member.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
# Update cache
else:
print(rowcount, f"Roles Cleared For {member} in {member.guild}")
@Cog.listener()
async def on_member_remove(self, member):
"""Storing member roles within the database when the member leaves"""
# Ignoring bots
if member.bot: return
# Get the datetime of when the user left the guild
left_at = datetime.datetime.utcnow()
# 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 connection
pool = self.bot.db
async with pool.acquire() as conn:
# Store member roles within the database
try:
update = """UPDATE members SET roles = $1, left_at = $2, has_left = 1
WHERE guild_id = $3 AND member_id = $4"""
rowcount = await conn.execute(update, role_ids, left_at, 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")
@Cog.listener()
async def on_guild_channel_delete(self, channel):
"""Deleting modlogs/modmail channel if it's deleted in the guild"""
# Get the modlogs channel (channel or none)
modlogs = self.bot.get_modlog_for_guild(channel.guild.id)
# Get the starboard (record or none)
starboard = self.bot.get_starboard_channel(channel.guild.id)
# Get the modmail record - (normal and logging channels)
modmail_record = self.bot.get_modmail(channel.guild.id)
modmail_channel = modmail_record.get("modmail_channel_id") if modmail_record else None
modmail_logging_channel = modmail_record.get("modmail_logging_channel_id") if modmail_record else None
# Get pool
pool = self.bot.db
# Delete the modlogs system from the database when modlogs channel is deleted
if channel.id == modlogs:
# Setup pool connection
async with pool.acquire() as conn:
# Set channel to none
try:
update = """UPDATE guilds SET modlogs = NULL WHERE guild_id = $1"""
await conn.execute(update, channel.guild.id)
# Catch errors
except asyncpg.PostgresError as e:
print(f"PostGres Error: Guild Modlogs Could Not Be Deleted For {channel.guild.id}", e)
# Delete channel from cache
else:
self.bot.remove_modlog_channel(channel.guild.id)
# Delete all of the starboard information when the channel is deleted from the guild
if channel.id == starboard:
# Setup pool connection
async with pool.acquire() as conn:
# Delete any starboard information upon leaving the guild
try:
delete = """DELETE FROM starboard WHERE guild_id = $1"""
rowcount = await conn.execute(delete, channel.guild.id)
# Catch errors
except asyncpg.PostgresError as e:
print(
f"PostGres Error: On Guild Remove Event Starboard/Starboard Messages Were Not Deleted For {channel.guild.id}",
e)
# Delete all information about the starboard and any messages stored
else:
print(rowcount, f"Starboard deleted successfully from Guild {channel.guild}")
self.bot.delete_starboard(channel.guild.id)
self.bot.delete_starboard_messages(channel.guild.id)
# If modmail channels are deleted, delete the entire system
if channel.id == modmail_channel or channel.id == modmail_logging_channel:
# Set up pool connection
async with pool.acquire() as conn:
# Remove the moderatormail record from the database
try:
delete = """DELETE FROM moderatormail WHERE guild_id = $1"""
await conn.execute(delete, channel.guild.id)
# Catch errors
except asyncpg.PostgresError as e:
print(f"PostGres Error: ModeratorMail Record Could Not Be Deleted for Guild {channel.guild.id}", e)
# Delete from cache
else:
self.bot.delete_modmail(channel.guild.id)
def setup(bot):
bot.add_cog(Events(bot))

@ -1,531 +0,0 @@
# Ensō~Chan - A Multi Purpose Discord Bot That Has Everything Your Server Needs!
# Copyright (C) 2020 Hamothy
# 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 io
import random
import string
import textwrap
import urllib.parse
from typing import Optional
import aiohttp
import discord
from PIL import Image, ImageDraw, ImageFont
from PIL.ImageOps import invert
from aiohttp import request
from discord import Member, Embed
from discord.ext import commands
from discord.ext.commands import BucketType, cooldown, command, Cog
from discord.ext.commands import is_owner, bot_has_permissions
from owotext import OwO
def generate_meme(image_path, top_text, bottom_text='', font_path='images/homies/impact/Impacted.ttf', font_size=9):
get_image = Image.open(image_path)
draw = ImageDraw.Draw(get_image)
image_width, image_height = get_image.size
# Load font
font = ImageFont.truetype(font=font_path, size=int(image_height * font_size) // 100)
# Convert text to uppercase
top_text = top_text.upper()
bottom_text = bottom_text.upper()
# Text wrapping
char_width, char_height = font.getsize('A')
chars_per_line = image_width // char_width
top_lines = textwrap.wrap(top_text, width=chars_per_line)
bottom_lines = textwrap.wrap(bottom_text, width=chars_per_line)
# Draw top lines
y = 10
for line in top_lines:
line_width, line_height = font.getsize(line)
x = (image_width - line_width) / 2
draw.text((x, y), line, fill='white', font=font)
y += line_height
# Draw bottom lines
y = image_height - char_height * len(bottom_lines) - 15
for line in bottom_lines:
line_width, line_height = font.getsize(line)
x = (image_width - line_width) / 2
draw.text((x, y), line, fill='white', font=font)
y += line_height
# Save meme as bytes
file = io.BytesIO()
get_image.save(file, format='PNG')
file.seek(0)
return file
# Set up the cog
class Fun(Cog):
"""Fun Commands! (8ball, Doggo etc!)"""
def __init__(self, bot):
self.bot = bot
@Cog.listener()
async def on_ready(self):
"""Printing out that Cog is ready on startup"""
print(f"{self.__class__.__name__} Cog has been loaded\n-----")
@command(name="attack", hidden=True)
@is_owner()
async def attack(self, ctx, member: Member):
"""Throw Insults at Members"""
# Set up array of insults to throw at people
responses = [
f"{member.mention} is stinky",
f"{member.mention} is ugly",
f"{member.mention} has a gigantic nose",
f"{member.mention} gets no views on their tiktok",
f"{member.mention} is obviously compensating for something :eyes:",
f"{member.mention} DIE DIE DIE :knife: :skull:",
f"{member.mention} is so annoying smh :rolling_eyes:",
f"I'd say {member.mention} was dropped as a child but they would have to be held to be dropped in the first place",
f"I hate {member.mention}",
f"{member.mention} close your legs, it smells like clam chowder :face_vomiting: :face_vomiting: :nauseated_face: :nauseated_face:",
f"I bet {member.mention} can't reach the wall cabinets without a booster chair",
f"{member.mention} Browses 4Chan and Reddit all day looking for love",
f"{member.mention} Your forehead could be used as a landing pad",
f"I bet {member.mention} likes eating watermelon with the rind.",
f"{member.mention} You were the first creation to make god say oops",
f"{member.mention} You have delusions of adequacy",
f"{member.mention} I treasure the time I don't spend with you",
f"Don't be ashamed of yourself {member.mention}, that's your parent's job",
f"I don't have the energy to pretend I like {member.mention} today",
f"I know this was made for me to insult but its kinda hard to be a hateful cunt like {member.mention} :star_struck::star_struck:",
f"#{member.mention}IsOverParty",
f"I hope {member.mention} drops dead with a curable disease that doctors simply didnt feel like curing :)",
f"{member.mention} You know there's no vaccine for stupidity right?",
f"{member.mention} You are not very epic at all",
f"You make Kpop Fancams 24/7 for validation on the internet {member.mention}",
f"Your mother wanted to drop you on the head when you were little {member.mention}",
f"{member.mention} You're the CEO of Racism",
f"{member.mention} has no common sense"
]
# Sending out a random insult from the array "responses"
await ctx.send(random.choice(responses))
@command(name="compliment", aliases=["comp"])
async def compliment(self, ctx, member: Member):
"""Give Compliments to Members"""
# Set up array of compliments to throw at people
responses = [
f"{member.mention} is the most adorable uwu <:awie:676201100793085952> <:awie:676201100793085952> <:awie:676201100793085952>",
f"{member.mention} You have my ENTIRE HEART <:blushlook1:677310734123663363> <:blushlook2:679524467248201769>",
f"{member.mention} Hun you're CUTE uwu :pleading_face: :flushed: :pleading_face: :flushed: :pleading_face:",
f"I love {member.mention} so so much :heartbeat: :heartbeat: :heartbeat: ",
f"My heart is full of love for you {member.mention} <:Kawaii:676203363922214953> <:Kawaii:676203363922214953>",
f"{member.mention} I admire your greatness so much that I consider making a fan club to become your #1 fan (´꒳`)",
f"{member.mention} has no flaws, only special effects :))",
f"{member.mention}'s smile is brighter than sunlight, so smile more often ( ◠‿◠ )",
f"{member.mention} Your smile is so beautiful it blinds me :heart_eyes: :heart_eyes:",
f"Being on a journey all my life, I will never meet a person as amazing as you are {member.mention}",
f"Such a pleasure to be on the same server with {member.mention} <:boneappleteeth:676202300573876252> <:boneappleteeth:676202300573876252>",
f"With {member.mention}, even the worst day will be filled with joy <:hug:718248629034549299> <:hug:718248629034549299>",
f"There's no better antidepressant than {member.mention}",
f"{member.mention} You're great, keep going Σd(˘ꇴ˘๑)",
f"I'd simp for {member.mention} anyday :flushed: :heart_eyes: :flushed: ",
f"{member.mention} Even the ugliest clothes won't ruin your look (。•̀ᴗ -)☆",
f"{member.mention} Youre that “nothing” when people ask me what Im thinking about <:Kawaii:676203363922214953> <:Kawaii:676203363922214953>",
f"{member.mention} Somehow you make time stop and fly at the same time <:awie:676201100793085952> <:blushlook1:677310734123663363>",
f"{member.mention} is a whole ass SWAGMEAL <:Kawaii:676203363922214953> <:Kawaii:676203363922214953>",
f"After meeting {member.mention}, I couldn't imagine living my life without them",
f"Take me into your arms and tell me you love me <:blushlook1:677310734123663363> <:blushlook2:679524467248201769> {member.mention}",
f"{member.mention} I would spend eternity cuddling with you :flushed: :flushed:",
f"Would you want to go on an e-date together? :pleading_face: :point_right: :point_left: {member.mention}",
f"Let me shoot my shot to you :see_no_evil: :see_no_evil: {member.mention}",
f"Your existence makes me feel so much better {member.mention}",
f"You're so hot, even hotter than hell :heart_eyes: {member.mention}",
f"{member.mention} Youre so cute that Taz will simp for you anytime :flushed: :heart_eyes: :flushed:",
f"{member.mention} The thought of you leaving me is too much to bear. Stay with me forever :pleading_face: :pleading_face:",
f"You're... You're SHREKTACULAR :heart_eyes: :flushed: :heart_eyes: {member.mention}",
f"{member.mention} Your beauty renders me speechless... :heart_eyes: :heart_eyes:",
f"Your taste in music is impeccable {member.mention}",
f"{member.mention} I can't stop thinking about you :see_no_evil: :see_no_evil:",
f"{member.mention} Your wedding will be wonderful, but the y is silent <a:huh:676195228872474643> <a:huh:676195228872474643>",
f"{member.mention} I would give up my lifelong goals just to have a chance with you <a:huh:676195228872474643> <a:huh:676195228872474643>",
f"{member.mention} Will you be the **yee** to my **haw**? :pleading_face: :pleading_face:",
f"{member.mention} is the definition of perfection :heart_eyes: :heart_eyes:",
f"{member.mention} My love for you is bigger than the amount of code Hammy has written <:Kawaii:676203363922214953> <:Kawaii:676203363922214953> <:Kawaii:676203363922214953>",
f"{member.mention} Why jump off a cliff when you can jump into my arms :flushed: :flushed:"
]
# Sending out a random compliment from the array "responses"
await ctx.send(random.choice(responses))
@command(name="flip")
async def flip(self, ctx):
"""Flip a Coin (Huge pp/Smol pp)"""
# Define array with only 2 entries to create 50/50 chance
pp_array = ["Smol pp", "Huge pp"]
# Send out one of the responses stored in the array
await ctx.send(f"{ctx.author.mention} {random.choice(pp_array)}")
@command(name="digby", hidden=True)
@bot_has_permissions(embed_links=True)
async def digby(self, ctx):
"""Pictures of Digby!"""
# Surround with try/except to catch any exceptions that may occur
try:
# Open the file containing the digby images
with open('images/FunCommands/digby.txt') as file:
# Store content of the file in digby_array
digby_array = file.readlines()
# Set member as the author
member = ctx.message.author
# Get the member avatar
userAvatar = member.avatar_url
# Set up the embed to display a random image of digby
embed = Embed(
title=f"**A cute picture of Digby!**",
colour=self.bot.random_colour(),
timestamp=datetime.datetime.utcnow())
embed.set_image(url=random.choice(digby_array))
embed.set_footer(text=f"Requested by {member}", icon_url=userAvatar)
# Send the embedded message to the user
await ctx.send(embed=embed)
except FileNotFoundError as e:
print(e)
@command(name="doggo")
@bot_has_permissions(embed_links=True)
@cooldown(1, 1, BucketType.user)
async def doggo(self, ctx, breed: Optional[str] = None):
"""Pictures of Doggos!"""
# Set member as the author
member = ctx.message.author
# Get the member avatar
userAvatar = member.avatar_url
# Initialise array to store doggo pics
b_list = []
# If a breed if specified
if breed:
# Get the lowercase string input
lowercase_breed = breed.lower()
# If the user wants to know what breeds there are
if lowercase_breed == "breeds":
# Get the list of breeds
breed_url = "https://dog.ceo/api/breeds/list/all"
# Using API, retrieve the full list of breeds available
async with request("GET", breed_url, headers={}) as response:
if response.status == 200:
data = await response.json()
breed_link = data["message"]
# Store every Doggo in an array
for doggo in breed_link:
b_list.append(doggo)
# Join together all the breeds into a string
doggo_string = string.capwords(", ".join(b_list))
# Tell the user to try the breeds listed below
desc = f"Try the Breeds listed below!\n{doggo_string}"
await self.bot.generate_embed(ctx, desc=desc)
# If no breed has been specified
else:
# Grab a random image of a doggo with the breed specified
image_url = f"https://dog.ceo/api/breed/{lowercase_breed}/images/random"
# Using API, retrieve the image of a doggo of the breed specified
async with request("GET", image_url, headers={}) as response:
if response.status == 200:
data = await response.json()
image_link = data["message"]
# Set up the embed for a doggo image
doggo_embed = Embed(
title=f"**It's a {lowercase_breed.capitalize()} Doggo!!** ",
colour=self.bot.random_colour(),
timestamp=datetime.datetime.utcnow())
doggo_embed.set_image(url=image_link)
doggo_embed.set_footer(text=f"Requested by {member}", icon_url=userAvatar)
# Send the doggo image
await ctx.send(embed=doggo_embed)
else:
# Send error message that Doggo was not found!
desc = f"Doggo Not Found!\nPlease do **{ctx.prefix}doggo breeds** to see the full list of Doggos!"
await self.bot.generate_embed(ctx, desc=desc)
else:
# Grab a random image of a doggo of any breed
image_url = "https://dog.ceo/api/breeds/image/random"
# Using API, retrieve the image of a doggo of any breed
async with request("GET", image_url, headers={}) as response:
if response.status == 200:
data = await response.json()
image_link = data["message"]
# Set up the embed for a random doggo image
doggo_embed = Embed(
title=f"**Doggo!** ",
colour=self.bot.random_colour(),
timestamp=datetime.datetime.utcnow())
doggo_embed.set_image(url=image_link)
doggo_embed.set_footer(text=f"Requested by {member}", icon_url=userAvatar)
# Send random doggo image to the channel
await ctx.send(embed=doggo_embed)
else:
# Send error message that Doggo was not found!
desc = f"Doggo Not Found!\nPlease do **{ctx.prefix}doggo breeds** to see the full list of Doggos!"
await self.bot.generate_embed(ctx, desc=desc)
@command(name="8ball")
async def _8ball(self, ctx, *, question):
"""8ball Responses!"""
try:
# Make the text readable to the api
eightball_question = urllib.parse.quote(question)
# Using API, make a connection to 8ball API
async with request("GET", f"https://8ball.delegator.com/magic/JSON/{eightball_question}",
headers={}) as response:
# With a successful connection
# Get the answer
if response.status == 200:
data = await response.json()
api_question = data["magic"]
api_answer = api_question["answer"]
await ctx.send(api_answer)
except commands.BadArgument as e:
raise e
@command(name="kpop")
async def get_kpop_member(self, ctx):
"""Retrieve a random kpop member"""
# Request profile information from API
url = "https://apis.duncte123.me/kpop"
async with aiohttp.ClientSession() as session:
async with await session.get(url=url) as response:
# When successful, read data from json
if response.status == 200:
kpop = await response.json()
name = kpop["data"]["name"]
band = kpop["data"]["band"]
image_url = kpop["data"]["img"]
id = kpop["data"]["id"]
elif response.status == 422 or response.status == 404:
await self.bot.generate_embed(ctx, desc="**Kpop Member Not Found!**")
return
elif response.status == 429:
await self.bot.generate_embed(ctx,
desc="**You are being rate limited! You have spammed it too much :(**")
return
embed = Embed(title=name,
description=band,
colour=self.bot.random_colour(),
timestamp=datetime.datetime.utcnow())
embed.set_image(url=image_url)
embed.set_footer(text=f"Internal ID: {id}")
await ctx.send(embed=embed)
@command(name="insta", aliases=["instagram"])
async def insta_info(self, ctx, *, user_name):
"""Retrieve a persons Instagram profile information"""
with ctx.typing():
# Request profile information from API
url = f"https://apis.duncte123.me/insta/{user_name}"
async with aiohttp.ClientSession() as session:
async with await session.get(url=url) as response:
# When successful, read data from json
if response.status == 200:
insta = await response.json()
data = insta["user"]
images = insta["images"]
private = data["is_private"]
verified = data["is_verified"]
full_name = data["full_name"]
username = data["username"]
pfp = data["profile_pic_url"]
followers = data["followers"]["count"]
following = data["following"]["count"]
uploads = data["uploads"]["count"]
biography = data["biography"]
# When profile isn't private, grab the information of their last post
if not private:
image_url = images[0]["url"]
image_caption = images[0]["caption"]
# Send error if no instagram profile was found with given username
elif response.status == 422:
await self.bot.generate_embed(ctx, desc="**Instagram Username Not Found!**")
return
elif response.status == 429:
await self.bot.generate_embed(ctx,
desc="**You are being rate limited! You have spammed it too much :(**")
return
# Setting bools to ticks/cross emojis
verif = self.bot.tick if verified else self.bot.cross
priv = self.bot.tick if private else self.bot.cross
# Set the page url to the last post or the profile based on privacy settings
page_url = images[0]["page_url"] if not private else f"https://www.instagram.com/{username}/"
desc = f"**Full Name:** {full_name}" \
f"\n**Bio:** {biography}" \
f"\n\n**Verified?:** {verif} | **Private?:** {priv}" \
f"\n**Following:** {following} | **Followers:** {followers}" \
f"\n**Upload Count:** {uploads}"
embed = discord.Embed(title=f"{username}'s Instagram",
description=desc,
url=page_url,
colour=self.bot.random_colour(),
timestamp=datetime.datetime.utcnow())
embed.set_thumbnail(url=pfp)
embed.set_footer(text=f"Requested By {ctx.author}", icon_url=ctx.author.avatar_url)
# When profile is not private, display the last post with the caption
if not private:
embed.add_field(name="My Latest Post",
value=f"**Caption:** {image_caption}",
inline=False)
embed.set_image(url=image_url)
await ctx.send(embed=embed)
@command(name="homies")
@cooldown(1, 5, BucketType.user)
@bot_has_permissions(attach_files=True)
async def homies(self, ctx, *, text):
"""Summoning the Homies"""
# Make sure the text entered is less than 20 characters
if len(text) >= 20:
await self.bot.generate_embed(ctx, desc="Please make sure the prompt is below **20** characters!")
else:
# Define the text to be drawn on the top and the bottom
top_text = f"Ayo fuck {text}"
bottom_text = f"All my homies hate {text}"
# Call the method to generate the image
file = generate_meme('images/homies/AllMyHomies.jpg', top_text=top_text, bottom_text=bottom_text)
# Send the bytes object as an image file
await ctx.send(file=discord.File(file, "homies.png"))
@command(name="grayscale", aliases=["gs"])
@cooldown(1, 2, BucketType.user)
@bot_has_permissions(attach_files=True)
async def grayscale(self, ctx):
"""Display grayscale version of image uploaded"""
if ctx.message.attachments:
for attachments in ctx.message.attachments:
attach = await attachments.read()
image = Image.open(io.BytesIO(attach)).convert('LA')
file = io.BytesIO()
image.save(file, format='PNG')
file.seek(0)
await ctx.message.delete()
await ctx.send(file=discord.File(file, "gs.png"))
else:
await self.bot.generate_embed(ctx, desc="**Image Not Detected!**")
@command(name="invert", aliases=["negative"])
@cooldown(1, 2, BucketType.user)
@bot_has_permissions(attach_files=True)
async def invert(self, ctx):
"""Display inverted version of image uploaded"""
if ctx.message.attachments:
for attachments in ctx.message.attachments:
attach = await attachments.read()
image = Image.open(io.BytesIO(attach)).convert('RGB')
inverted = invert(image)
# Save new grayscale image as bytes
file = io.BytesIO()
inverted.save(file, format='PNG')
file.seek(0)
await ctx.message.delete()
# Send Grayscale Image
await ctx.send(file=discord.File(file, "inverted.png"))
else:
await self.bot.generate_embed(ctx, desc="**Image Not Detected!**")
@command(name="owo", aliases=["uwu"])
@bot_has_permissions(manage_messages=True)
async def owo(self, ctx, *, text):
"""Converts given text to 'OwO' format"""
# Delete the message sent by the user
await ctx.message.delete()
# Convert to "OwO" text
uwu = OwO()
owo = uwu.whatsthis(text)
# Send the text back
await ctx.message.channel.send(owo)
def setup(bot):
bot.add_cog(Fun(bot))

@ -1,543 +0,0 @@
# Ensō~Chan - A Multi Purpose Discord Bot That Has Everything Your Server Needs!
# Copyright (C) 2020 Hamothy
# 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 asyncpg
from discord import Embed, TextChannel
from discord.ext.commands import has_permissions, Cog, group, bot_has_permissions, BadArgument, MissingRequiredArgument, \
command, MissingPermissions
from cogs.libs.modmail import Modmail
from cogs.libs.starboard import Starboard
async def get_starboard_from_db(self, ctx, action):
"""Get the starboard record from DB"""
# Setup up pool connection
pool = self.bot.db
async with pool.acquire() as conn:
# Get the row of the guild from the starboard table
try:
select_query = """SELECT * FROM starboard WHERE guild_id = $1"""
result = await conn.fetchrow(select_query, ctx.guild.id)
# Catch errors
except asyncpg.PostgresError as e:
print("PostGres Error: Starboard Record Could Not Be Retrieved For Starboard", e)
# Throw error if the guild already exists
else:
if action == "setup" and result:
text = "**Starboard** Already Setup!" \
f"\nDo **{ctx.prefix}help starboard** to find out more!"
await self.bot.generate_embed(ctx, desc=text)
return None
elif (action == "update" or action == "delete") and not result:
text = "**Starboard** Not Setup!" \
f"\nDo **{ctx.prefix}help starboard** to find out more!"
await self.bot.generate_embed(ctx, desc=text)
return None
return not None
async def get_modlogs_from_db(self, ctx, action):
"""Get the starboard record from DB"""
# Setup up pool connection
pool = self.bot.db
async with pool.acquire() as conn:
# Get the entire row of the guild 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: Modlog Record Could Not Be Retrieved For Modlogs", e)
# Throw error if the modlogs already exists
else:
if action == "setup" and result["modlogs"]:
text = "**Modlogs** Already Setup!" \
f"\nDo **{ctx.prefix}help modlogs** to find out more!"
await self.bot.generate_embed(ctx, desc=text)
return None
elif (action == "update" or action == "delete") and not result["modlogs"]:
text = "**Modlogs** Not Setup!" \
f"\nDo **{ctx.prefix}help modlogs** to find out more!"
await self.bot.generate_embed(ctx, desc=text)
return None
return not None
async def get_modmail_from_db(self, ctx, action):
"""Get the starboard record from DB"""
# Setup up pool connection
pool = self.bot.db
async with pool.acquire() as conn:
# Get the entire row of the guild 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: Modlog Record Could Not Be Retrieved For Modlogs", e)
# Throw error if the modlogs already exists
else:
if action == "setup" and result:
text = "**Modmail** Already Setup!" \
f"\nDo **{ctx.prefix}help modmail** to find out more!"
await self.bot.generate_embed(ctx, desc=text)
return None
elif (action == "update" or action == "delete") and not result:
text = "**Modmail** Not Setup!" \
f"\nDo **{ctx.prefix}help modmail** to find out more!"
await self.bot.generate_embed(ctx, desc=text)
return None
return not None
# Set up the Cog
class Guild(Cog):
"""All Guild Systems (Modmail/Modlogs/RolePersist)"""
def __init__(self, bot):
self.bot = bot
self.modmail = Modmail(self.bot)
self.starboard = Starboard(self.bot)
@Cog.listener()
async def on_ready(self):
"""Printing out that Cog is ready on startup"""
print(f"{self.__class__.__name__} Cog has been loaded\n-----")
@command(name="modstatus", aliases=["logsstatus"])
@bot_has_permissions(embed_links=True)
async def all_statuses(self, ctx):
"""Status of all the moderation systems (Modlogs/Modmail/RolePersist)"""
desc = ""
# Get status of mod
if self.bot.get_roles_persist(ctx.guild.id) == 0:
desc += f"**{self.bot.cross} Role Persist**\n"
else:
desc += f"**{self.bot.tick} Role Persist**\n"
# Get status of modlogs
if ml_channel := self.bot.get_modlog_for_guild(ctx.guild.id):
channel = ctx.guild.get_channel(ml_channel)
desc += f"**{self.bot.tick} Modlogs | {channel.mention}**\n"
else:
desc += f"**{self.bot.cross} Modlogs**\n"
# Get status of modmail
if modmail := self.bot.get_modmail(ctx.guild.id):
modmail_channel = ctx.guild.get_channel(modmail["modmail_channel_id"])
modmail_logging = ctx.guild.get_channel(modmail["modmail_logging_channel_id"])
desc += f"**{self.bot.tick} Modmail | Channel: {modmail_channel.mention} | Logging: {modmail_logging.mention}**\n"
else:
desc += f"**{self.bot.cross} Modmail**\n"
if starboard := self.bot.get_starboard_channel(ctx.guild.id):
channel = self.bot.get_channel(starboard)
min_stars = self.bot.get_starboard_min_stars(ctx.guild.id)
desc += f"**{self.bot.tick} Starboard | Channel: {channel.mention} | Minimum Stars: {min_stars} :star:**\n"
else:
desc += f"**{self.bot.cross} Starboard**\n"
embed = Embed(title="Moderation Systems",
description=desc,
colour=self.bot.random_colour(),
timestamp=datetime.datetime.utcnow())
embed.set_footer(text=f"Requested by {ctx.author}", icon_url=ctx.author.avatar_url)
await ctx.send(embed=embed)
@group(name="rolepersist", case_insensitive=True, usage="`<enable|disable>`")
@has_permissions(manage_guild=True)
@bot_has_permissions(manage_roles=True)
async def roles_persist(self, ctx):
"""Role Persist! Keep user roles when they leave/join!"""
@roles_persist.command(name="enable")
async def rp_enable(self, ctx):
"""Enabling role persist within the guild"""
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}!**")
@roles_persist.command(name="disable")
async def rp_disable(self, ctx):
"""Disabling role persist within the guild"""
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}!**")
@group(name="starboard", case_insensitive=True, usage="`<setup|update|delete|stars>`")
@bot_has_permissions(embed_links=True)
@has_permissions(manage_guild=True)
async def starboard(self, ctx):
"""
Starboard! Let the community star messages!
"""
@starboard.command(name="stars")
@bot_has_permissions(embed_links=True)
async def sb_min_stars(self, ctx, stars: int):
"""Update the minimum amount of stars needed for the message to appear on the starboard"""
if await get_starboard_from_db(self, ctx, "update") and stars > 0:
# Setup up pool connection
pool = self.bot.db
async with pool.acquire() as conn:
# Update the starboard min_stars in the database
try:
update_query = """UPDATE starboard SET min_stars = $1 WHERE guild_id = $2"""
await conn.execute(update_query, stars, ctx.guild.id)
# Catch errors
except asyncpg.PostgresError as e:
print(f"PostGres Error: Starboard Record Could Not Be Updated For Guild {ctx.guild.id}", e)
# Send confirmation that the channel has been updated
else:
star_channel = self.bot.get_starboard_channel(ctx.guild.id)
channel = self.bot.get_channel(star_channel)
text = "**Minimum Stars Updated!**" \
f"\nMessages will now need {stars} :star: to appear in {channel.mention}"
await self.bot.generate_embed(ctx, desc=text)
# Update cache
self.bot.update_starboard_min_stars(ctx.guild.id, stars)
elif stars <= 0:
await self.bot.generate_embed(ctx, desc="Minimum Stars Must Be Over or Equal to 1!")
@starboard.command(name="setup", usage="`<#channel>`")
@bot_has_permissions(embed_links=True)
async def sb_setup(self, ctx, starboard_channel: TextChannel):
"""
Setup Starboard
First Argument: Input Channel(Mention or ID) where starred messages will be sent
"""
if await get_starboard_from_db(self, ctx, "setup"):
# Setup up pool connection
pool = self.bot.db
async with pool.acquire() as conn:
# Insert the information about the starboard into database
try:
insert_query = """INSERT INTO starboard (guild_id, channel_id) VALUES ($1, $2)"""
await conn.execute(insert_query, ctx.guild.id, starboard_channel.id)
# Catch errors
except asyncpg.PostgresError as e:
print(f"PostGres Error: Starboard Record Could Not Be Inserted For Guild {ctx.guild.id}", e)
# Send confirmation message
else:
text = "**Starboard** is successfully set up!" \
f"\nRefer to **{ctx.prefix}help starboard** for more information"
await self.bot.generate_embed(ctx, desc=text)
# Store into cache
self.bot.cache_store_starboard(ctx.guild.id, starboard_channel.id, 1)
@starboard.command(name="update", usage="`<channel>`")
@bot_has_permissions(embed_links=True)
async def sb_update(self, ctx, starboard_channel: TextChannel):
"""
Update the channel that the starred messages are sent to
You can Mention or use the Channel ID
"""
if await get_starboard_from_db(self, ctx, "update"):
# Setup up pool connection
pool = self.bot.db
async with pool.acquire() as conn:
# Update the starboard channel in the database
try:
update_query = """UPDATE starboard SET channel_id = $1 WHERE guild_id = $2"""
await conn.execute(update_query, starboard_channel.id, ctx.guild.id)
# Catch errors
except asyncpg.PostgresError as e:
print(f"PostGres Error: Starboard 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 Starred Messages will be sent to {starboard_channel.mention}"
await self.bot.generate_embed(ctx, desc=text)
# Update cache
self.bot.update_starboard_channel(ctx.guild.id, starboard_channel.id)
@starboard.command(name="delete")
@bot_has_permissions(embed_links=True)
async def sb_delete(self, ctx):
"""Delete the starboard from the guild"""
if await get_starboard_from_db(self, ctx, "delete"):
# Setup up pool connection
pool = self.bot.db
async with pool.acquire() as conn:
# Remove the starboard record from the database
try:
delete_query = """DELETE FROM starboard WHERE guild_id = $1"""
await conn.execute(delete_query, ctx.guild.id)
# Catch errors
except asyncpg.PostgresError as e:
print(f"PostGres Error: Starboard Record Could Not Be Deleted for Guild {ctx.guild.id}", e)
# Sending confirmation message that the starboard has been deleted
else:
text = "**Starboard** successfully deleted!" \
f"\nDo **{ctx.prefix}help starboard** to find out more!"
await self.bot.generate_embed(ctx, desc=text)
# Delete from cache
self.bot.delete_starboard(ctx.guild.id)
@group(name="modlogs", case_insensitive=True, usage="`<setup|update|delete>`")
@has_permissions(manage_guild=True)
@bot_has_permissions(embed_links=True)
async def modlogs(self, ctx):
"""
Log updates in your server! (Nicknames/Deleted Msgs/etc!)
"""
@modlogs.command(name="setup", usage="`<#channel>`")
async def mlsetup(self, ctx, user_channel: TextChannel):
"""Setup a channel for Kick/Ban/Mute actions to be logged"""
if await get_modlogs_from_db(self, ctx, "setup"):
# Set up the modlogs channel within the guild
mod_log_setup = True
await self.bot.storage_modlog_for_guild(ctx, user_channel.id, mod_log_setup)
@modlogs.command(name="update", usage="`<#channel>`")
async def mlupdate(self, ctx, user_channel: TextChannel):
"""Change the channel that your modlogs are sent to"""
if await get_modlogs_from_db(self, ctx, "update"):
# Update the modlog channel within the database and cache
mod_log_setup = False
await self.bot.storage_modlog_for_guild(ctx, user_channel.id, mod_log_setup)
@modlogs.command("delete")
async def mldelete(self, ctx):
"""Delete the existing modlogs channel"""
if await get_modlogs_from_db(self, ctx, "delete"):
# Setup up pool connection
pool = self.bot.db
async with pool.acquire() as conn:
# Update the existing modlogs for guild
try:
update = """UPDATE guilds SET modlogs = NULL WHERE guild_id = $1"""
await conn.execute(update, ctx.guild.id)
# Catch errors
except asyncpg.PostgresError as e:
print(f"PostGres Error: Guild Modlogs Could Not Be Deleted For {ctx.guild.id}", e)
# Delete channel from cache
else:
text = "**Modlogs System** successfully deleted!" \
f"\nDo **{ctx.prefix}help modlogs** to setup Modlogs again!"
await self.bot.generate_embed(ctx, desc=text)
self.bot.remove_modlog_channel(ctx.guild.id)
@group(name="modmail", case_insensitive=True, usage="`<setup|update|delete>`")
@bot_has_permissions(manage_channels=True, embed_links=True, add_reactions=True, manage_messages=True,
attach_files=True, read_message_history=True, manage_roles=True)
@has_permissions(manage_guild=True)
async def mod_mail(self, ctx):
"""
Modmail! Allow your members to send mail to the staff team!
"""
@mod_mail.command(name="setup")
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
"""
if await get_modmail_from_db(self, ctx, "setup"):
# 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.**"
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)
# 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
pool = self.bot.db
async with pool.acquire() as conn:
# 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)
# Store into cache
self.bot.cache_store_modmail(ctx.guild.id, modmail.id, modmail_message.id, modmail_logging.id)
@mod_mail.command(name="update")
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
"""
if await get_modmail_from_db(self, ctx, "update"):
pool = self.bot.db
async with pool.acquire() as conn:
# 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)
# Update cache
self.bot.update_modmail(ctx.guild.id, modmail_logging_channel.id)
@mod_mail.command(name="delete")
async def mmdelete(self, ctx):
"""Delete the entire modmail system from the guild"""
if await get_modmail_from_db(self, ctx, "delete"):
# Setup up pool connection
pool = self.bot.db
async with pool.acquire() as conn:
# 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
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)
@Cog.listener()
async def on_raw_reaction_add(self, payload):
"""Listening for reactions relating to modmail/starboard"""
await self.modmail.modmail(payload)
await self.starboard.send_starboard_and_update_db(payload, action="added")
@Cog.listener()
async def on_raw_reaction_remove(self, payload):
"""Editing the message if a star reaction was removed"""
await self.starboard.send_starboard_and_update_db(payload, action="removed")
@mlsetup.error
@mlupdate.error
@mmsetup.error
@mmupdate.error
@sb_setup.error
@sb_update.error
async def mlsetup_command_error(self, ctx, exc):
"""Catching error if channel is not recognised"""
if isinstance(exc, BadArgument):
text = "**Channel Not Detected... Aborting Process**"
await self.bot.generate_embed(ctx, desc=text)
elif isinstance(exc, MissingRequiredArgument):
text = "Required Argument(s) Missing!" \
f"\nUse **{ctx.prefix}help** to find how to use **{ctx.command}**"
await self.bot.generate_embed(ctx, desc=text)
elif isinstance(exc, MissingPermissions):
missing_perms = string.capwords(", ".join(exc.missing_perms).replace("_", " "))
text = f"{self.bot.cross} Uh oh! You Need **{missing_perms}** Permission(s) To Execute This Command! {self.bot.cross}"
await self.bot.generate_embed(ctx, desc=text)
def setup(bot):
bot.add_cog(Guild(bot))

@ -1,803 +0,0 @@
# Ensō~Chan - A Multi Purpose Discord Bot That Has Everything Your Server Needs!
# Copyright (C) 2020 Hamothy
# 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/>.
# Help paginator by Rapptz
# Edited by F4stZ4p
# Edited by Hamothy
import asyncio
import datetime
from typing import Optional
import discord
from discord import Embed, DMChannel
from discord.ext import commands
from discord.ext.commands import Cog, command, has_permissions, guild_only, bot_has_permissions
class CannotPaginate(Exception):
pass
class Pages:
"""Implements a paginator that queries the user for the
pagination interface.
Pages are 1-index based, not 0-index based.
If the user does not reply within 2 minutes then the pagination
interface exits automatically.
Parameters
------------
ctx: Context
The context of the command.
entries: List[str]
A list of entries to paginate.
per_page: int
How many entries show up per page.
show_entry_count: bool
Whether to show an entry count in the footer.
Attributes
-----------
embed: discord.Embed
The embed object that is being used to send pagination info.
Feel free to modify this externally. Only the description,
footer fields, and colour are internally modified.
permissions: discord.Permissions
Our permissions for the channel.
"""
def __init__(self, ctx, *, entries, per_page=8, show_entry_count=True):
self.bot = ctx.bot
self.prefix = ctx.prefix
self.entries = entries
self.message = ctx.message
self.channel = ctx.channel
self.author = ctx.author
self.per_page = per_page
pages, left_over = divmod(len(self.entries), self.per_page)
if left_over:
pages += 1
self.maximum_pages = pages
self.embed = discord.Embed(colour=self.bot.admin_colour,
timestamp=datetime.datetime.utcnow())
self.paginating = len(entries) > per_page
self.show_entry_count = show_entry_count
self.reaction_emojis = [
('\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}', self.first_page),
('\N{BLACK LEFT-POINTING TRIANGLE}', self.previous_page),
('\N{BLACK RIGHT-POINTING TRIANGLE}', self.next_page),
('\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}', self.last_page),
('\N{INPUT SYMBOL FOR NUMBERS}', self.numbered_page),
('\N{BLACK SQUARE FOR STOP}', self.stop_pages),
('\N{INFORMATION SOURCE}', self.show_help),
]
if ctx.guild is not None:
self.permissions = self.channel.permissions_for(ctx.guild.me)
else:
self.permissions = self.channel.permissions_for(ctx.bot.user)
if not self.permissions.embed_links:
raise CannotPaginate('Bot does not have Embed Links permission')
if not self.permissions.send_messages:
raise CannotPaginate('Bot Cannot Send Messages')
if self.paginating:
# verify we can actually use the pagination session
if not self.permissions.add_reactions:
raise CannotPaginate('Bot does not have Add Reactions permission')
if not self.permissions.read_message_history:
raise CannotPaginate('Bot does not have Read Message History permission')
def get_page(self, page):
base = (page - 1) * self.per_page
return self.entries[base:base + self.per_page]
async def show_page(self, page, *, first=False):
self.current_page = page
entries = self.get_page(page)
p = []
for index, entry in enumerate(entries, 1 + ((page - 1) * self.per_page)):
p.append(f'{index}. {entry}')
if self.maximum_pages > 1:
if self.show_entry_count:
text = f'Page {page}/{self.maximum_pages} ({len(self.entries)} entries)'
else:
text = f'Page {page}/{self.maximum_pages}'
self.embed.set_footer(text=text)
if not self.paginating:
self.embed.description = '\n'.join(p)
return await self.channel.send(embed=self.embed)
if not first:
self.embed.description = '\n'.join(p)
await self.message.edit(embed=self.embed)
return
p.append('')
p.append('Confused? React with \N{INFORMATION SOURCE} for more info.')
self.embed.description = '\n'.join(p)
self.message = await self.channel.send(embed=self.embed)
for (reaction, _) in self.reaction_emojis:
if self.maximum_pages == 2 and reaction in ('\u23ed', '\u23ee'):
# no |<< or >>| buttons if we only have two pages
# we can't forbid it if someone ends up using it but remove
# it from the default set
continue
await self.message.add_reaction(reaction)
async def checked_show_page(self, page):
if page != 0 and page <= self.maximum_pages:
await self.show_page(page)
async def first_page(self):
"""Show First Page"""
await self.show_page(1)
async def last_page(self):
"""Show Last Page"""
await self.show_page(self.maximum_pages)
async def next_page(self):
"""Show Next Page"""
await self.checked_show_page(self.current_page + 1)
async def previous_page(self):
"""Show Previous Page"""
await self.checked_show_page(self.current_page - 1)
async def show_current_page(self):
if self.paginating:
await self.show_page(self.current_page)
async def numbered_page(self):
"""Go to Given Page"""
to_delete = []
to_delete.append(await self.channel.send('What page do you want to go to?'))
def message_check(m):
return m.author == self.author and \
self.channel == m.channel and \
m.content.isdigit()
try:
msg = await self.bot.wait_for('message', check=message_check, timeout=30.0)
except asyncio.TimeoutError:
to_delete.append(await self.channel.send('Took too long.'))
await asyncio.sleep(5)
else:
page = int(msg.content)
to_delete.append(msg)
if page != 0 and page <= self.maximum_pages:
await self.show_page(page)
else:
to_delete.append(await self.channel.send(f'Invalid page given. ({page}/{self.maximum_pages})'))
await asyncio.sleep(5)
try:
await self.channel.delete_messages(to_delete)
except Exception:
pass
async def show_help(self):
"""shows this message"""
messages = ['Welcome to the interactive paginator!\n',
'This interactively allows you to see pages of text by navigating with '
'reactions. They are as follows:\n']
for (emoji, func) in self.reaction_emojis:
messages.append(f'{emoji} {func.__doc__}')
self.embed.description = '\n'.join(messages)
self.embed.clear_fields()
self.embed.set_footer(text=f'We were on page {self.current_page} before this message.')
await self.message.edit(embed=self.embed)
async def go_back_to_current_page():
await asyncio.sleep(60.0)
await self.show_current_page()
self.bot.loop.create_task(go_back_to_current_page())
async def stop_pages(self):
"""Deletes Help Message"""
await self.message.delete()
self.paginating = False
def react_check(self, reaction, user):
if user is None or user.id != self.author.id:
return False
if reaction.message.id != self.message.id:
return False
for (emoji, func) in self.reaction_emojis:
if reaction.emoji == emoji:
self.match = func
return True
return False
async def paginate(self):
"""Actually paginate the entries and run the interactive loop if necessary."""
first_page = self.show_page(1, first=True)
if not self.paginating:
await first_page
else:
# allow us to react to reactions right away if we're paginating
self.bot.loop.create_task(first_page)
while self.paginating:
try:
reaction, user = await self.bot.wait_for('reaction_add', check=self.react_check, timeout=120.0)
except asyncio.TimeoutError:
self.paginating = False
try:
await self.message.clear_reactions()
except:
pass
finally:
break
try:
await self.message.remove_reaction(reaction, user)
except:
pass # can't remove it so don't bother doing so
await self.match()
class FieldPages(Pages):
"""Similar to Pages except entries should be a list of
tuples having (key, value) to show as embed fields instead.
"""
async def show_page(self, page, *, first=False):
self.current_page = page
entries = self.get_page(page)
self.embed.clear_fields()
self.embed.description = discord.Embed.Empty
for key, value in entries:
self.embed.add_field(name=key, value=value, inline=False)
if self.maximum_pages > 1:
if self.show_entry_count:
text = f'Page {page}/{self.maximum_pages} ({len(self.entries)} entries)'
else:
text = f'Page {page}/{self.maximum_pages}'
self.embed.set_footer(text=text)
if not self.paginating:
return await self.channel.send(embed=self.embed)
if not first:
await self.message.edit(embed=self.embed)
return
self.message = await self.channel.send(embed=self.embed)
for (reaction, _) in self.reaction_emojis:
if self.maximum_pages == 2 and reaction in ('\u23ed', '\u23ee'):
# no |<< or >>| buttons if we only have two pages
# we can't forbid it if someone ends up using it but remove
# it from the default set
continue
await self.message.add_reaction(reaction)
import itertools
import inspect
import re
# ?help
# ?help Cog
# ?help command
# -> could be a subcommand
_mention = re.compile(r'<@!?([0-9]{1,19})>')
def cleanup_prefix(bot, prefix):
m = _mention.match(prefix)
if m:
user = bot.get_user(int(m.group(1)))
if user:
return f'@{user.name} '
return prefix
async def _can_run(cmd, ctx):
try:
return await cmd.can_run(ctx)
except:
return False
def _command_signature(cmd):
# this is modified from discord.py source
# which I wrote myself lmao
result = [cmd.qualified_name]
if cmd.usage:
result.append(cmd.usage)
return ' '.join(result)
params = cmd.clean_params
if not params:
return ' '.join(result)
for name, param in params.items():
if param.default is not param.empty:
# We don't want None or '' to trigger the [name=value] case and instead it should
# do [name] since [name=None] or [name=] are not exactly useful for the user.
should_print = param.default if isinstance(param.default, str) else param.default is not None
if should_print:
result.append(f'[{name}={param.default!r}]')
else:
result.append(f'`[{name}]`')
elif param.kind == param.VAR_POSITIONAL:
result.append(f'`[{name}...]`')
else:
result.append(f'`<{name}>`')
return ' '.join(result)
class HelpPaginator(Pages):
def __init__(self, ctx, entries, *, per_page=10):
super().__init__(ctx, entries=entries, per_page=per_page)
self.reaction_emojis.append(('\N{WHITE QUESTION MARK ORNAMENT}', self.show_bot_help))
self.total = len(entries)
@classmethod
async def from_cog(cls, ctx, cog):
cog_name = cog.__class__.__name__
if ctx.guild is None:
icon = ctx.author.avatar_url
else:
icon = ctx.guild.icon_url
# get the commands
entries = sorted(Cog.get_commands(cog), key=lambda c: c.name)
# remove the ones we can't run
entries = [cmd for cmd in entries if not cmd.hidden]
self = cls(ctx, entries)
self.title = f'(っ◔◡◔)っ {cog_name} (っ◔◡◔)っ'
self.embed.set_thumbnail(url=icon)
self.description = inspect.getdoc(cog)
self.prefix = cleanup_prefix(ctx.bot, ctx.prefix)
return self
@classmethod
async def from_command(cls, ctx, command):
try:
entries = sorted(command.commands, key=lambda c: c.name)
except AttributeError:
entries = []
else:
entries = [cmd for cmd in entries if not cmd.hidden]
self = cls(ctx, entries)
if not isinstance(command, discord.ext.commands.Group):
if command.aliases:
aliases = " | ".join(command.aliases)
if command.usage:
self.title = f"{command.qualified_name} | {aliases} {command.signature}"
elif command.signature:
self.title = f"{command.qualified_name} | {aliases} `{command.signature}`"
else:
self.title = f"{command.qualified_name} | {aliases}"
else:
if command.usage:
self.title = f"{command.qualified_name} | {command.signature}"
elif command.signature:
self.title = f"{command.qualified_name} `{command.signature}`"
else:
self.title = f"{command.qualified_name}"
else:
if command.aliases:
aliases = " | ".join(command.aliases)
self.title = f"{command.name} | {aliases}"
else:
self.title = command.name
if command.description:
self.description = f'{command.description}\n\n{command.help}'
else:
self.description = command.help or 'No help given.'
self.prefix = cleanup_prefix(ctx.bot, ctx.prefix)
return self
@classmethod
async def from_bot(cls, ctx):
def key(c):
return c.cog_name or '\u200bMisc'
entries = sorted(ctx.bot.commands, key=key)
nested_pages = []
per_page = 8
# 0: (cog, desc, commands) (max len == 9)
# 1: (cog, desc, commands) (max len == 9)
# ...
for cog, commands in itertools.groupby(entries, key=key):
plausible = [cmd for cmd in commands if not cmd.hidden]
if len(plausible) == 0:
continue
description = ctx.bot.get_cog(cog)
if description is None:
description = discord.Embed.Empty
else:
description = inspect.getdoc(description) or discord.Embed.Empty
nested_pages.extend(
(cog, description, plausible[i:i + per_page]) for i in range(0, len(plausible), per_page))
if ctx.guild is None:
icon = ctx.author.avatar_url
else:
icon = ctx.guild.icon_url
self = cls(ctx, nested_pages, per_page=1) # this forces the pagination session
self.prefix = cleanup_prefix(ctx.bot, ctx.prefix)
self.embed.set_thumbnail(url=icon)
# swap the get_page implementation with one that supports our style of pagination
self.get_page = self.get_bot_page
self._is_bot = True
# replace the actual total
self.total = sum(len(o) for _, _, o in nested_pages)
return self
def get_bot_page(self, page):
cog, description, commands = self.entries[page - 1]
self.title = f'{cog} Commands'
self.description = description
return commands
async def show_page(self, page, *, first=False):
self.current_page = page
entries = self.get_page(page)
self.embed.clear_fields()
self.embed.description = self.description
self.embed.title = self.title
self.embed.set_footer(text=f'"{self.prefix}help command | module" For More Information!')
signature = _command_signature
for entry in entries:
self.embed.add_field(name=signature(entry), value=entry.short_doc or "No help given", inline=False)
if self.maximum_pages:
self.embed.set_author(name=f'Page {page}/{self.maximum_pages} ({self.total} commands)')
if not self.paginating:
return await self.channel.send(embed=self.embed)
if not first:
await self.message.edit(embed=self.embed)
return
self.message = await self.channel.send(embed=self.embed)
for (reaction, _) in self.reaction_emojis:
if self.maximum_pages == 2 and reaction in ('\u23ed', '\u23ee'):
# no |<< or >>| buttons if we only have two pages
# we can't forbid it if someone ends up using it but remove
# it from the default set
continue
await self.message.add_reaction(reaction)
async def show_help(self):
"""Shows This Message"""
self.embed.title = 'Paginator help'
self.embed.description = 'Hello! Welcome to the help page.'
messages = [f'{emoji} {func.__doc__}' for emoji, func in self.reaction_emojis]
self.embed.clear_fields()
self.embed.add_field(name='What are these reactions for?', value='\n'.join(messages), inline=False)
self.embed.set_footer(text=f'We were on page {self.current_page} before this message.')
await self.message.edit(embed=self.embed)
async def go_back_to_current_page():
await asyncio.sleep(30.0)
await self.show_current_page()
self.bot.loop.create_task(go_back_to_current_page())
async def show_bot_help(self):
"""Information On The Bot"""
self.embed.title = 'Using Ensō~Chan'
self.embed.description = 'Hiya! This is the Help Page!'
self.embed.clear_fields()
entries = (
('`<argument>`', 'This means the argument is **required**.'),
('`[argument]`', 'This means the argument is **optional**.'),
('`[A|B]`', 'This means the it can be **either A or B**.'),
('`[argument...]`', 'This means you can have multiple arguments.\n' \
'Now that you know the basics, it should be noted that...\n' \
'**You do not type in the brackets!**')
)
self.embed.add_field(name='How do I use Ensō~Chan', value='Reading the signature is pretty straightforward')
for name, value in entries:
self.embed.add_field(name=name, value=value, inline=False)
self.embed.set_footer(text=f'We were on page {self.current_page} before this message.')
await self.message.edit(embed=self.embed)
async def go_back_to_current_page():
await asyncio.sleep(30.0)
await self.show_current_page()
self.bot.loop.create_task(go_back_to_current_page())
def send_feedback(self, message, author):
"""Preparing Embed to send to the support server"""
embed = Embed(title="Feedback!",
colour=self.bot.admin_colour,
timestamp=datetime.datetime.utcnow())
embed.set_thumbnail(url=author.avatar_url)
embed.set_footer(text=f"Send By {author}")
fields = [("Member", f"{author.mention} | {author}", False),
("Message", message.content, False)]
for name, value, inline in fields:
embed.add_field(name=name, value=value, inline=inline)
return embed
def message_sent_confirmation(self):
"""Preparing Embed to be sent to the user after the message has been received successfully"""
ConfirmationEmbed = Embed(title="Thank you for your feedback!",
description=f"**Message relayed to {self.bot.hammyMention}**",
colour=self.bot.admin_colour,
timestamp=datetime.datetime.utcnow())
ConfirmationEmbed.set_footer(text=f"Thanks Once Again! ~ Hammy")
return ConfirmationEmbed
def error_handling(self, author):
"""Preparing embed to send if the message is not suitable"""
ErrorHandlingEmbed = Embed(
title="Uh Oh! Please make sure the message is below **1024** characters!",
colour=self.bot.admin_colour,
timestamp=datetime.datetime.utcnow())
ErrorHandlingEmbed.set_footer(text=f"Sent To {author}")
return ErrorHandlingEmbed
# Set up the Cog
class Help(Cog):
"""Help Commands!"""
def __init__(self, bot):
self.bot = bot
@command(name='help', usage="`[command|cog]`")
async def _help(self, ctx, *, cmd: Optional[str] = None):
"""Shows help about a command or the bot"""
try:
if cmd is None:
p = await HelpPaginator.from_bot(ctx)
else:
entity = ctx.bot.get_cog(cmd) or ctx.bot.get_command(cmd)
if entity is None:
clean = cmd.replace('@', '@\u200b')
return await self.bot.generate_embed(ctx, desc=f"Command or Category **{clean}** Not Found.")
elif isinstance(entity, commands.Command):
p = await HelpPaginator.from_command(ctx, entity)
else:
p = await HelpPaginator.from_cog(ctx, entity)
await p.paginate()
except Exception as ex:
await ctx.send(f"**{ex}**")
@command(name="prefix")
@guild_only()
@has_permissions(manage_guild=True)
async def change_prefix(self, 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 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 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
prefix = self.bot.get_prefix_for_guild(ctx.guild.id)
await self.bot.generate_embed(ctx, desc=f"**The current guild prefix is `{prefix}`**")
@command(name="invite", aliases=["inv"])
@has_permissions(embed_links=True)
async def get_invite(self, ctx):
"""Give the invite of the bot to the user"""
embed = Embed(title="Invite For Ensō~Chan",
description="Click the link above to invite Ensō~Chan into your server!",
url="https://discord.com/oauth2/authorize?client_id=716701699145728094&permissions=1543892087&scope=bot",
colour=self.bot.random_colour(),
timestamp=datetime.datetime.utcnow())
embed.set_footer(text="Thank you for considering to invite Ensō~Chan!")
await ctx.send(embed=embed)
@command(name="source")
@bot_has_permissions(embed_links=True)
async def _bot_source(self, ctx):
"""Link to the source code for Enso!"""
embed = Embed(title=f"<:github:741000905364603010> Source Code | Ensō~Chan {self.bot.version}",
description="**Click above me to view my source code!**",
url="https://github.com/sgoudham/Enso-Bot",
colour=self.bot.admin_colour,
timestamp=datetime.datetime.utcnow())
embed.set_thumbnail(url=self.bot.user.avatar_url)
embed.add_field(name="Developer", value=f"{self.bot.hammyMention} | Hamothy#5619", inline=False)
embed.set_footer(text=f"Requested by {ctx.author}", icon_url=ctx.author.avatar_url)
await ctx.send(embed=embed)
@command(name="vote", aliases=["upvote"])
@bot_has_permissions(embed_links=True)
async def upvote(self, ctx):
"""Upvote the bot on top.gg!"""
desc = "Click the link above to upvote me!\nIt would greatly help me out as it allows the bot to be " \
"noticed more on the website!\nIt's free and takes a maximum of 30 seconds to do. Thanks so much!"
embed = Embed(title="Upvote me on top.gg!",
description=desc,
url="https://top.gg/bot/716701699145728094/vote",
colour=self.bot.random_colour(),
timestamp=datetime.datetime.utcnow())
embed.set_thumbnail(url=self.bot.user.avatar_url)
embed.add_field(name="Developer", value=f"{self.bot.hammyMention} | Hamothy#5619", inline=False)
await ctx.send(embed=embed)
@command(name="support")
@bot_has_permissions(embed_links=True)
async def support(self, ctx):
"""Joining Support Server And Sending Feedback"""
embed = Embed(title="Support Server!",
description=f"Do **{ctx.prefix}feedback** to send me feedback about the bot and/or report any issues "
f"that you are having!",
url="https://discord.gg/SZ5nexg",
colour=self.bot.admin_colour,
timestamp=datetime.datetime.utcnow())
embed.set_thumbnail(url=ctx.bot.user.avatar_url)
embed.set_footer(text=f"Requested by {ctx.author}", icon_url=ctx.author.avatar_url)
fields = [("Developer", f"{self.bot.hammyMention} | Hamothy#5619", False),
("Data Collection",
"\nData Stored:" +
"\n- User ID" +
"\n- Guild ID" +
"\n\n If you wish to delete this data being stored about you. Follow steps outlined below:" +
"\n\n1) **Enable Developer Mode**" +
"\n2) Note down your **User ID** and the **Guild ID** (You must have left this guild or are planning to leave)" +
f"\n3) Join support server and notify me or use **{ctx.prefix}feedback** to notify me", False)]
# Add fields to the embed
for name, value, inline in fields:
embed.add_field(name=name, value=value, inline=inline)
await ctx.send(embed=embed)
@command(name="feedback")
@bot_has_permissions(embed_links=True)
async def feedback(self, ctx):
"""Sending Feedback to Support Server"""
# Get the #feedback channel within the support server
channel = self.bot.get_channel(self.bot.enso_feedback_ID)
embed = Embed(title="Provide Feedback!",
description=f"Hiya! Please respond to this message with the feedback you want to provide!"
f"\n(You have **5 minutes** to respond. Make sure it is a **single message** and under **1024** characters!)",
url="https://discord.gg/SZ5nexg",
colour=self.bot.admin_colour,
timestamp=datetime.datetime.utcnow())
embed.set_footer(text=f"Requested by {ctx.author}", icon_url=ctx.author.avatar_url)
helper = await ctx.author.send(embed=embed)
def check(m):
"""Ensure that the feedback received is from the author's DMs"""
return m.author == ctx.author and isinstance(m.channel, DMChannel)
try:
# Wait for feedback from author
msg = await ctx.bot.wait_for('message', check=check, timeout=300.0)
# Make sure sure that the message is below 1024 characters
while len(msg.content) > 1024 and check(msg):
await ctx.author.send(embed=error_handling(self, ctx.author))
# Wait for feedback again
msg = await ctx.bot.wait_for('message', check=check, timeout=300.0)
# Once message is below 1024 characters
# Send confirmation message to author to let them know feedback has been sent
# Send the feedback entered from the author into support server
if len(msg.content) < 1024 and check(msg):
await ctx.author.send(embed=message_sent_confirmation(self))
await channel.send(embed=send_feedback(self, msg, ctx.author))
# Edit current embed to show error that the feedback timed out
except asyncio.TimeoutError:
embed = Embed(title="(。T ω T。) You waited too long",
description=f"Do **{ctx.prefix}feedback** to try again!",
colour=self.bot.admin_colour,
timestamp=datetime.datetime.utcnow())
embed.set_footer(text=f"Sent To {ctx.author}", icon_url=ctx.author.avatar_url)
# Send out an error message if the user waited too long
await helper.edit(embed=embed)
def setup(bot):
bot.add_cog(Help(bot))

@ -1,490 +0,0 @@
# Ensō~Chan - A Multi Purpose Discord Bot That Has Everything Your Server Needs!
# Copyright (C) 2020 Hamothy
# 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 io
import string
from asyncio.subprocess import Process
from platform import python_version
from time import time
from typing import Optional, Union
from PIL import Image
from PIL.ImageOps import invert
from discord import Embed, Role, File
from discord import Member, TextChannel
from discord import __version__ as discord_version
from discord.ext.commands import bot_has_permissions, guild_only, Cog, group, cooldown, BucketType
from discord.ext.commands import command
from psutil import Process, virtual_memory
from cogs.libs.functions import string_list, get_region, perms, detect_perms
from cogs.libs.paginators import SimpleMenu
def add_perms(embed, _list):
"""Add all the permission in the list to embed fields"""
i = 0
while i < len(_list):
embed.add_field(name=str(_list[i].split(":")[0]).strip(),
value=f"<{_list[i].split('<')[1]}",
inline=True)
i += 1
return embed
class Info(Cog):
"""(User/Server/Bot etc) Information!"""
def __init__(self, bot):
self.bot = bot
@Cog.listener()
async def on_ready(self):
"""Printing out that Cog is ready on startup"""
print(f"{self.__class__.__name__} Cog has been loaded\n-----")
@command(name="ping")
async def ping(self, ctx):
"""Latency of the Bot (ms)"""
await self.bot.generate_embed(ctx, desc=f"Pong! **{round(self.bot.latency * 1000)}ms**")
@command(name="permissions", aliases=["perms"], usage="`[member|role]`")
@guild_only()
@bot_has_permissions(embed_links=True, add_reactions=True)
async def perms(self, ctx, *, item: Optional[Union[Member, Role]]):
"""View all permissions for any Member/Role!"""
# Defaults to author if no argument is given
item = item if item else ctx.author
if isinstance(item, Member):
# Iterating through list of perms
perms = [f"{perm.title().replace('_', ' ')}: {self.bot.tick if value else self.bot.cross}" for perm, value
in item.guild_permissions]
else:
# Iterating through list of perms
perms = [f"{perm.title().replace('_', ' ')}: {self.bot.tick if value else self.bot.cross}" for perm, value
in item.permissions]
middle = len(perms) // 2
f_half = perms[:middle]
s_half = perms[middle:]
first_page = Embed(description=f"**Item:** {item}",
colour=self.bot.admin_colour,
timestamp=datetime.datetime.utcnow())
first_page.set_footer(text=f"ID: {item.id}")
second_page = Embed(description=f"**Item:** {item}",
colour=self.bot.admin_colour,
timestamp=datetime.datetime.utcnow())
second_page.set_footer(text=f"ID: {item.id}")
# Add permissions to both of the embeds
first = add_perms(first_page, f_half)
second = add_perms(second_page, s_half)
# Get the permissions of the channel
perms = ctx.guild.me.permissions_in(ctx.message.channel)
menu = SimpleMenu(0, item, perms, [first, second], self)
await menu.start(ctx)
@command(name="roleinfo", aliases=["ri"])
@guild_only()
async def role_info(self, ctx, *, role: Role):
"""Retrieve information about any role!"""
# Returns the permissions that the role has within the guild
filtered = filter(lambda x: x[1], role.permissions)
# Replace all "_" with " " in each item and join them together
_perms = ",".join(map(lambda x: x[0].replace("_", " "), filtered))
# Capitalise every word in the array and filter out the permissions that are defined within the frozenset
permission = string.capwords("".join(detect_perms(_perms, perms)))
# Get all members within role
member = string_list(role.members, 30, "Member")
# Using emotes to represent bools
mentionable = self.bot.tick if role.mentionable else self.bot.cross
hoisted = self.bot.tick if role.hoist else self.bot.cross
managed = self.bot.tick if role.managed else self.bot.cross
# Description of the embed
desc = f"{role.mention} **|** @{role} **<-- Colour:** {str(role.colour)}" \
f"\n**Position -->** #{role.position} / {len(ctx.guild.roles)}" \
f"\n** ID -->** {role.id}"
# Set up Embed
embed = Embed(title=f"@{role.name} Information",
description=desc,
colour=role.colour,
timestamp=datetime.datetime.utcnow())
embed.set_thumbnail(url=ctx.guild.icon_url)
embed.set_footer(text=f"ID: {role.id}")
# Setting up fields
fields = [
("Creation At", role.created_at.strftime("%a, %b %d, %Y\n%I:%M:%S %p"), True),
(f"Members ({len(role.members)})",
f"\nHumans: {len(list(filter(lambda m: not m.bot, role.members)))}" +
f"\nBots: {len(list(filter(lambda m: m.bot, role.members)))}", True),
(f"Misc",
f"\nMentionable?: {mentionable}"
f"\nHoisted?: {hoisted}"
f"\nManaged?: {managed}", True),
(f"List of Members ({len(role.members)})", member or "No Members In Role", False),
("Key Permissions", permission or "No Key Permissions", False)
]
# Add fields to the embed
for name, value, inline in fields:
embed.add_field(name=name, value=value, inline=inline)
await ctx.send(embed=embed)
@command(name="rolelist", aliases=["rl"])
@guild_only()
async def role_list(self, ctx):
"""Retrieve list of all roles in the server!"""
# More readable name
guild_roles = ctx.guild.roles
# Get all guild roles
role = string_list(guild_roles, 50, "Role")
embed = Embed(title=f"{ctx.guild}'s Roles --> {len(ctx.guild.roles)}",
description=role or "Guild Has No Roles",
color=self.bot.random_colour(),
timestamp=datetime.datetime.utcnow())
embed.set_footer(text=f"Guild ID: {ctx.guild.id}", icon_url=ctx.guild.icon_url)
await ctx.send(embed=embed)
@command(name="userinfo", aliases=["ui"])
@guild_only()
@bot_has_permissions(embed_links=True)
async def user_info(self, ctx, member: Optional[Member] = None):
"""User Information! (Created At/Joined/Roles etc)"""
# Use member when mentioned
# Use author if no member is mentioned
member = ctx.author if not member else member
# Get the member avatar
userAvatar = member.avatar_url
# Get total member roles
role = string_list(member.roles, 20, "Role")
# Returns the permissions that the user has within the guild
filtered = filter(lambda x: x[1], member.guild_permissions)
# Replace all "_" with " " in each item and join them together
perms = ",".join(map(lambda x: x[0].replace("_", " "), filtered))
# Capitalise every word in the array and filter out the permissions that are defined within the frozenset
permission = string.capwords("".join(map(str, detect_perms(perms, perms))))
embed = Embed(
title=f"**User Information**",
colour=self.bot.random_colour(),
timestamp=datetime.datetime.utcnow())
embed.set_thumbnail(url=userAvatar)
embed.set_footer(text=f"ID: {member.id}", icon_url=userAvatar)
embed_fields = [("Name", member.mention, True),
("Tag", member.name, True),
("Discrim", f"#{member.discriminator}", True),
("Registered", member.created_at.strftime("%a, %b %d, %Y\n%I:%M:%S %p"), True),
("Joined", member.joined_at.strftime("%a, %b %d, %Y\n%I:%M:%S %p"), True),
("Top Role", member.top_role.mention, False),
("Roles", role or "No Roles", False),
("Key Permissions", permission or "No Key Permissions", False),
("Status", str(member.status).title(), True),
("Boosting Server", bool(member.premium_since), True),
("Bot", member.bot, True)]
# Add fields to the embed
for name, value, inline in embed_fields:
embed.add_field(name=name, value=value, inline=inline)
await ctx.send(embed=embed)
@command(name="serverinfo", aliases=["si", "guildinfo", "gi"])
@guild_only()
@bot_has_permissions(embed_links=True)
async def server_info(self, ctx):
"""Guild Information! (Owner/Roles/Emojis etc)"""
# Define guild icon and id
guild_icon = ctx.guild.icon_url
guild_id = ctx.guild.id
# Getting permissions of the bot within the channel
perms = ctx.guild.me.permissions_in(ctx.message.channel)
# Retrieve the top role of the guild
top_role = ctx.guild.roles[-1]
# Get total guild roles
role_string = string_list(ctx.guild.roles, 20, "Role")
# Get total emojis
emojis = string_list(ctx.guild.emojis, 20, "Emoji")
# Defining a dictionary of the statuses
member_status = {
"online": 0,
"idle": 0,
"dnd": 0,
"offline": 0
}
# Iterating over the members and then storing the numbers in the dictionary
for m in ctx.guild.members:
member_status[str(m.status)] += 1
# Storing the statuses in an array
statuses = [member_status["online"],
member_status["idle"],
member_status["dnd"],
member_status["offline"]]
# Set up embed to display all the server information
embed = Embed(title="**Server Information**",
colour=self.bot.random_colour(),
timestamp=datetime.datetime.utcnow())
embed.set_thumbnail(url=guild_icon)
embed.set_footer(text=f"ID: {guild_id}", icon_url=guild_icon)
# Get the list of banned users from the server
bans = len(await ctx.guild.bans()) if perms.ban_members else f"No Perms {self.bot.cross}"
# Get the list of invites created for the server
invites = len(await ctx.guild.invites()) if perms.manage_guild else f"No Perms {self.bot.cross}"
# Define fields to be added into the embed
fields = [("Owner", ctx.guild.owner.mention, True),
("Created", ctx.guild.created_at.strftime("%a, %b %d, %Y\n%I:%M:%S %p"), False),
("Region", get_region(str(ctx.guild.region)), False),
("Statuses", f"<a:online:753214525272096831> {statuses[0]} "
f"<a:idle:753214548756004924> {statuses[1]} "
f"<a:dnd:753214555999567953> {statuses[2]} "
f"<a:offline:753214562970501171> {statuses[3]} ", False),
(f"Members ({len(ctx.guild.members)})",
f"\nHumans: {len(list(filter(lambda m: not m.bot, ctx.guild.members)))}"
f"\nBots: {len(list(filter(lambda m: m.bot, ctx.guild.members)))}"
f"\nBanned: {bans}", True),
(f"Channels ({len(ctx.guild.channels)})",
f"\nText: {len(ctx.guild.text_channels)}"
f"\nVoice: {len(ctx.guild.voice_channels)}"
f"\nCategories: {len(ctx.guild.categories)}", True),
("Misc",
f"Invites: {invites}"
f"\nVerif Level: {ctx.guild.verification_level.name.capitalize()}"
f"\nNitro Boosters: {len(ctx.guild.premium_subscribers)}", True),
("Top Role", top_role.mention, False),
(f"Roles ({len(ctx.guild.roles)})", role_string or "No Roles In Guild", True),
(f"Emojis ({len(ctx.guild.emojis)})", emojis or "No Emojis In Guild", False)]
# Add fields to the embed
for name, value, inline in fields:
embed.add_field(name=name, value=value, inline=inline)
await ctx.send(embed=embed)
@command(name="channelinfo", aliases=["chinfo"])
@guild_only()
@bot_has_permissions(embed_links=True)
async def channel_info(self, ctx, channel: Optional[TextChannel] = None):
"""Channel Statistics! (Category/Created At etc)"""
# Get information about the channel
channel = ctx.channel if not channel else channel
perms_synced = self.bot.tick if channel.permissions_synced else self.bot.cross
nsfw = self.bot.tick if channel.is_nsfw() else self.bot.cross
# Set up Embed
desc = f"**Guild -->** {ctx.guild}" \
f"\n**Position -->** {f'#{channel.position} / {len(ctx.guild.channels)}'}"
embed = Embed(title=f"Statistics For #{channel.name}",
description=desc,
timestamp=datetime.datetime.utcnow(),
colour=self.bot.random_colour())
embed.set_thumbnail(url=ctx.guild.icon_url)
embed.set_footer(text=f"ID: {channel.id}")
# Setting up fields
fields = [("Category", channel.category or self.bot.cross, True),
("Topic", channel.topic or self.bot.cross, True),
("\u200b", "\u200b", True),
("Perms Synced?", perms_synced, True),
("Nsfw?", nsfw, True),
("Creation At", channel.created_at.strftime("%a, %b %d, %Y\n%I:%M:%S %p"), False)]
# Add fields to the embed
for name, value, inline in fields:
embed.add_field(name=name, value=value, inline=inline)
await ctx.send(embed=embed)
@command(name="about")
@bot_has_permissions(embed_links=True)
async def checking_bot_stats(self, ctx):
"""Bot Statistics! (CPU/Mem Usage etc)"""
stats = Embed(title=f"<:github:741000905364603010> Source Code | Ensō~Chan {self.bot.version}",
url="https://github.com/sgoudham/Enso-Bot",
colour=self.bot.admin_colour,
timestamp=datetime.datetime.utcnow())
stats.set_thumbnail(url=self.bot.user.avatar_url)
stats.set_footer(text=f"Requested by {ctx.author}", icon_url=ctx.author.avatar_url)
# Grabbing technical statistics of the bot
proc = Process()
with proc.oneshot():
uptime = datetime.timedelta(seconds=time() - proc.create_time())
mem_total = virtual_memory().total / (1024 ** 2)
mem_of_total = proc.memory_percent()
mem_usage = mem_total * (mem_of_total / 100)
uptime_hours, uptime_remainder = divmod(uptime.seconds, 3600)
uptime_minutes, uptime_seconds = divmod(uptime_remainder, 60)
frmt_uptime = f'{int(uptime_hours):01} Hour(s), {int(uptime_minutes):01} Minute(s), {int(uptime_seconds):01} Second(s)'
# Grabbing total number of channels across all guilds in which the bot is present in
channels = map(lambda m: len(m.channels), self.bot.guilds)
# Setting up fields
fields = [
("Developer", f"{self.bot.hammyMention} | Hamothy#5619", False),
("Language | Library",
f"<:python:747224674319990895> Python {python_version()} | <:discord:747224665553895544> Discord.py {discord_version}",
False),
("<:discord:747224665553895544> Support Server",
"[Here!](https://discord.com/invite/SZ5nexg)", True),
("<:invite:740998357643952139> Invite Link",
"[Here!](https://top.gg/bot/716701699145728094)", True),
("❗ Current Prefix", ctx.prefix, True),
("Discord Stats",
f"Guilds: {len(self.bot.guilds)}"
f"\nChannels: {sum(list(channels))}"
f"\nEmojis: {len(self.bot.emojis)}"
f"\nCommands: {len(self.bot.commands)}"
f"\nUsers: {len(self.bot.users):,}", True),
("Line Count", self.bot.line_count, True),
("Uptime", frmt_uptime, False),
("Memory Usage", f"{mem_usage:,.2f} / {mem_total:,.2f} MiB ({mem_of_total:.2f}%)", False)]
# Add fields to the embed
for name, value, inline in fields:
stats.add_field(name=name, value=value, inline=inline)
await ctx.send(embed=stats)
@group(name="avatar", invoke_without_command=True, case_insensitive=True,
usage="`[member]|greyscale|invert`")
@bot_has_permissions(embed_links=True)
async def get_user_avatar(self, ctx, *, member: Optional[Member] = None):
"""
Displaying Member's Avatar
Member can be mentioned and their avatar will be displayed
"""
# Get member mentioned or set to author
member = ctx.author if not member else member
# Get the member avatar
userAvatar = str(member.avatar_url)
embed = Embed(colour=self.bot.admin_colour,
timestamp=datetime.datetime.utcnow())
embed.set_author(name=f"{member}'s Avatar", icon_url=member.avatar_url, url=userAvatar)
embed.set_image(url=userAvatar)
embed.set_footer(text=f"Requested by {ctx.author}", icon_url=ctx.author.avatar_url)
await ctx.send(embed=embed)
@get_user_avatar.command(name="greyscale", aliases=["gs"])
@bot_has_permissions(embed_links=True)
@cooldown(1, 2, BucketType.user)
async def greyscale_user_avatar(self, ctx, *, member: Optional[Member] = None):
"""Get the greyscale avatar of the member"""
# Get member mentioned or set to author
member = ctx.author if not member else member
attach = await member.avatar_url.read()
image = Image.open(io.BytesIO(attach)).convert('LA')
# Save new grayscale image as bytes
file = io.BytesIO()
image.save(file, format='PNG')
file.seek(0)
# Send image in an embed
f = File(file, "greyscale.png")
embed = Embed(colour=self.bot.admin_colour,
timestamp=datetime.datetime.utcnow())
embed.set_author(name=f"{member}'s Avatar | Greyscale", icon_url=member.avatar_url)
embed.set_image(url="attachment://greyscale.png")
embed.set_footer(text=f"Requested by {ctx.author}", icon_url=ctx.author.avatar_url)
await ctx.send(file=f, embed=embed)
@get_user_avatar.command(name="invert", aliases=["negative", "inverted"])
@bot_has_permissions(embed_links=True)
@cooldown(1, 2, BucketType.user)
async def invert_user_avatar(self, ctx, *, member: Optional[Member] = None):
"""Get the inverted avatar of the member"""
# Get member mentioned or set to author
member = ctx.author if not member else member
# Invert the image
attach = await member.avatar_url.read()
image = Image.open(io.BytesIO(attach)).convert('RGB')
inverted = invert(image)
# Save new inverted image as bytes
file = io.BytesIO()
inverted.save(file, format='PNG')
file.seek(0)
# Send image in an embed
f = File(file, "inverted.png")
embed = Embed(colour=self.bot.admin_colour,
timestamp=datetime.datetime.utcnow())
embed.set_author(name=f"{member}'s Avatar | Inverted", icon_url=member.avatar_url)
embed.set_image(url="attachment://inverted.png")
embed.set_footer(text=f"Requested by {ctx.author}", icon_url=ctx.author.avatar_url)
await ctx.send(file=f, embed=embed)
def setup(bot):
bot.add_cog(Info(bot))

@ -1,295 +0,0 @@
# Ensō~Chan - A Multi Purpose Discord Bot That Has Everything Your Server Needs!
# Copyright (C) 2020 Hamothy
# 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 random
from discord import Embed, Member
from discord.ext.commands import cooldown, command, BucketType, bot_has_permissions, Cog
def random_line(file):
"""Return a random line from the chosen file"""
lines = open(f'images/FunCommands/{file}.txt').read().splitlines()
return random.choice(lines)
# Gets the member and user avatar
def getMember(ctx):
# Set member as the author
member = ctx.message.author
# Get the member avatar
userAvatar = member.avatar_url
return member, userAvatar
# Set up the Cog
class Interactive(Cog):
"""Interactive Commands! (E.G Kiss/Hug/Cuddle)"""
def __init__(self, bot):
self.bot = bot
@Cog.listener()
async def on_ready(self):
"""Printing out that Cog is ready on startup"""
print(f"{self.__class__.__name__} Cog has been loaded\n-----")
@command(name="kiss")
@bot_has_permissions(embed_links=True)
@cooldown(1, 1, BucketType.user)
async def kiss(self, ctx, member: Member):
"""Kiss your partner"""
# Get the guild
guild = ctx.guild
# Error handling to make sure that the user can kiss themselves
if member.id == ctx.author.id:
kiss = False
title = f":kissing_heart: :kissing_heart: | **{ctx.author.display_name}** kissed **themselves**"
else:
kiss = True
title = f":kissing_heart: :kissing_heart: | **{ctx.author.display_name}** kissed **{member.display_name}**"
# Get author record from cache/database
result = await self.bot.check_cache(ctx.author.id, guild.id)
married_user = result["married"]
if married_user is None and kiss:
await self.bot.generate_embed(ctx,
desc="Σ(‘◉⌓◉’) You need to be married in order to use this command! Baka!")
return
elif not member.id == married_user and kiss:
await self.bot.generate_embed(ctx, desc="Σ(‘◉⌓◉’) You can only kiss your partner! Baka!")
return
# Get the member and the userAvatar
member, userAvatar = getMember(ctx)
# Set up the embed to display a random kissing gif
embed = Embed(
title=title,
colour=self.bot.random_colour(),
timestamp=datetime.datetime.utcnow())
embed.set_image(url=random_line("kissing"))
embed.set_footer(text=f"Requested by {member}", icon_url=userAvatar)
# Send the embedded message to the user
await ctx.send(embed=embed)
@command(name="cuddle")
@bot_has_permissions(embed_links=True)
@cooldown(1, 1, BucketType.user)
async def cuddle(self, ctx, member: Member):
"""Cuddle your partner"""
# Get the guild
guild = ctx.guild
# Error handling to make sure that the user can cuddle themselves
if member.id == ctx.author.id:
cuddle = False
title = f":blush: :blush: | **{ctx.author.display_name}** cuddled **themselves**"
else:
cuddle = True
title = f":blush: :blush: | **{ctx.author.display_name}** cuddled **{member.display_name}**"
# Get author record from cache/database
result = await self.bot.check_cache(ctx.author.id, guild.id)
married_user = result["married"]
if married_user is None and cuddle:
await self.bot.generate_embed(ctx,
desc="Σ(‘◉⌓◉’) You need to be married in order to use this command! Baka!")
return
elif not member.id == married_user and cuddle:
await self.bot.generate_embed(ctx, desc="Σ(‘◉⌓◉’) You can only cuddle your partner! Baka!")
return
# Get the member and the userAvatar
member, userAvatar = getMember(ctx)
# Set up the embed to display a random cuddling gif
embed = Embed(
title=title,
colour=self.bot.random_colour(),
timestamp=datetime.datetime.utcnow())
embed.set_image(url=random_line("cuddling"))
embed.set_footer(text=f"Requested by {member}", icon_url=userAvatar)
# Send the embedded message to the user
await ctx.send(embed=embed)
@command(name="kill")
@bot_has_permissions(embed_links=True)
@cooldown(1, 1, BucketType.user)
async def kill(self, ctx, member: 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}**"
# Get the member and the userAvatar
member, userAvatar = getMember(ctx)
# Set up the embed to display a random killing gif
embed = Embed(
title=title,
colour=self.bot.random_colour(),
timestamp=datetime.datetime.utcnow())
embed.set_image(url=random_line("killing"))
embed.set_footer(text=f"Requested by {member}", icon_url=userAvatar)
# Send the embedded message to the user
await ctx.send(embed=embed)
@command(name="slap")
@bot_has_permissions(embed_links=True)
@cooldown(1, 1, BucketType.user)
async def slap(self, ctx, member: 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}**"
# Get the member and the userAvatar
member, userAvatar = getMember(ctx)
# Set up the embed to display a random slapping gif
embed = Embed(
title=title,
colour=self.bot.random_colour(),
timestamp=datetime.datetime.utcnow())
embed.set_image(url=random_line("slapping"))
embed.set_footer(text=f"Requested by {member}", icon_url=userAvatar)
# Send the embedded message to the user
await ctx.send(embed=embed)
@command(name="pat")
@bot_has_permissions(embed_links=True)
@cooldown(1, 1, BucketType.user)
async def pat(self, ctx, member: 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}**"
# Get the member and the userAvatar
member, userAvatar = getMember(ctx)
# Set up the embed to display a random patting gif
embed = Embed(
title=title,
colour=self.bot.random_colour(),
timestamp=datetime.datetime.utcnow())
embed.set_image(url=random_line("patting"))
embed.set_footer(text=f"Requested by {member}", icon_url=userAvatar)
# Send the embedded message to the user
await ctx.send(embed=embed)
@command(name="lemon")
@bot_has_permissions(embed_links=True)
@cooldown(1, 1, BucketType.user)
async def lemon(self, ctx, member: Member):
"""Give Lemon to member"""
if member is ctx.author:
title = f":relaxed: :relaxed: | **{ctx.author.display_name}** gave a lemon to **themselves**"
else:
title = f":relaxed: :relaxed: | **{ctx.author.display_name}** gave a lemon to **{member.display_name}**"
lemon_array = ["https://media.discordapp.net/attachments/669812887564320769/720093589056520202/lemon.gif",
"https://media.discordapp.net/attachments/669812887564320769/720093575492272208/lemon2.gif",
"https://media.discordapp.net/attachments/718484280925224981/719629805263257630/lemon.gif"]
# Get the member and the userAvatar
member, userAvatar = getMember(ctx)
# Set up the embed to display a random lemon gif
embed = Embed(
title=title,
colour=self.bot.random_colour(),
timestamp=datetime.datetime.utcnow())
embed.set_image(url=random.choice(lemon_array))
embed.set_footer(text=f"Requested by {member}", icon_url=userAvatar)
# Send the embedded message to the user
await ctx.send(embed=embed)
@command(name="choke")
@bot_has_permissions(embed_links=True)
@cooldown(1, 1, BucketType.user)
async def choke(self, ctx, member: 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}**"
# Get the member and the userAvatar
member, userAvatar = getMember(ctx)
# Set up the embed to display a random choking gif
embed = Embed(
title=title,
colour=self.bot.random_colour(),
timestamp=datetime.datetime.utcnow())
embed.set_image(url=random_line("choking"))
embed.set_footer(text=f"Requested by {member}", icon_url=userAvatar)
# Send the embedded message to the user
await ctx.send(embed=embed)
@command(name="hug")
@bot_has_permissions(embed_links=True)
@cooldown(1, 1, BucketType.user)
async def hug(self, ctx, member: 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}**"
# Get the member and the userAvatar
member, userAvatar = getMember(ctx)
# Set up the embed to display a random hugging gif
embed = Embed(
title=title,
colour=self.bot.random_colour(),
timestamp=datetime.datetime.utcnow())
embed.set_image(url=random_line("hugging"))
embed.set_footer(text=f"Requested by {member}", icon_url=userAvatar)
# Send the embedded message to the user
await ctx.send(embed=embed)
def setup(bot):
bot.add_cog(Interactive(bot))

@ -1,137 +0,0 @@
# Ensō~Chan - A Multi Purpose Discord Bot That Has Everything Your Server Needs!
# Copyright (C) 2020 Hamothy
# 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/>.
# Using frozenset
# Permissions to filter through
perms = frozenset(
{
"create instant invite",
"add reactions",
"view audit log",
"priority speaker",
"stream",
"read messages",
"send messages",
"send tts messages",
"embed links",
"attach links",
"read message history",
"external emojis",
"view guild insights",
"connect",
"speak",
"use voice activation",
"change nickname"
}
)
# List of regions mapped to emojis
region = {
"eu-central": ":flag_eu: Central Europe",
"europe": ":flag_eu: Central Europe",
"singapore": ":flag_sg: Singapore",
"india": ":flag_in: India",
"japan": ":flag_jp: Japan",
"us-central": ":flag_us: U.S. Central",
"sydney": ":flag_au: Sydney",
"us-east": ":flag_us: U.S. East",
"us-south": ":flag_us: U.S. South",
"us-west": ":flag_us: U.S. West",
"eu-west": ":flag_eu: Western Europe",
"vip-us-east": ":flag_us: VIP U.S. East",
"london": ":flag_gb: London",
"amsterdam": ":flag_nl: Amsterdam",
"hongkong": ":flag_hk: Hong Kong",
"russia": ":flag_ru: Russia",
"southafrica": ":flag_za: South Africa",
"brazil": ":flag_br: Brazil"
}
# List of content filters and their descriptions
filters = {
"disabled": "<:xMark:746834944629932032>",
"no_role": "<:greenTick:746834932936212570> For Members Without Roles",
"all_members": "<:greenTick:746834932936212570> For All Members"
}
# List of default notifications settings for guild
notifs = {
"all_messages": "<:greenTick:746834932936212570> For All Messages",
"only_mentions": "<:greenTick:746834932936212570> For All Mentions"
}
def detect_perms(message, fset):
"""Filter out permissions that are not important"""
# Split the message individual permissions
message = message.split(",")
# Filter the permission out if it's in the frozenset
filtered = filter(lambda perm: perm not in fset, message)
return ", ".join(filtered)
def string_list(types, n, instance):
"""Return objects in nicely formatted strings"""
if len(types) > n:
# Retrieve the length of the remaining roles
length = len(types) - n
if instance == "Emoji":
# Store the first 20 emojis in a string
string = f"{' '.join(map(str, (types[:n])))} and **{length}** more..."
else:
# Store the first n roles/members in a string called "roles" (highest to lowest)
string = f"{' **|** '.join(_type.mention for _type in list(reversed(types))[:n])} and **{length}** more"
else:
if instance == "Role":
# Display all roles as it is lower than n provided
string = f"{' **|** '.join(role.mention for role in list(reversed(types[1:])))}"
elif instance == "Emoji":
# Display all the emojis in the server as it is less than 20
string = " ".join(map(str, types))
else:
# Display all members as it is lower than n provided
string = f"{' **|** '.join(role.mention for role in list(reversed(types)))}"
return string
def get_region(disc_region):
"""Return Nicer Looking Region String"""
for key in region:
if key == disc_region:
return region[key]
def get_content_filter(content_filter):
"""Return nicer looking content filter string"""
for key in filters:
if key == content_filter:
return filters[key]
def get_notifs(notif):
"""Return nicer looking notification string"""
for key in notifs:
if key == notif:
return notifs[key]

@ -1,309 +0,0 @@
# Ensō~Chan - A Multi Purpose Discord Bot That Has Everything Your Server Needs!
# Copyright (C) 2020 Hamothy
# 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 asyncio
import datetime
import io
import random
import discord
from discord import Embed, File
class Modmail:
"""Methods for sending modmail!"""
def __init__(self, bot):
self.bot = bot
self.anon = None
self.avatars = ["https://cdn.discordapp.com/embed/avatars/0.png",
"https://cdn.discordapp.com/embed/avatars/1.png",
"https://cdn.discordapp.com/embed/avatars/2.png",
"https://cdn.discordapp.com/embed/avatars/3.png",
"https://cdn.discordapp.com/embed/avatars/4.png"]
def anon_or_not(self, author):
"""Method to ask the user if they want to be anonymous or not"""
# Set up embed to let the user how to start sending modmail
AnonModMailEmbed = Embed(title="**Want to send it Anonymously?**",
colour=self.bot.admin_colour,
timestamp=datetime.datetime.utcnow())
AnonModMailEmbed.set_thumbnail(url=author.avatar_url)
AnonModMailEmbed.set_footer(text=f"Sent by {author}")
fields = [(self.bot.blank_space, "**We understand that for some things,"
"you may want to remain Anonymous."
"\nUse the reactions below to choose!**", False),
(self.bot.blank_space, "**Use :white_check_mark: for** `Yes`", True),
(self.bot.blank_space, "**Use :x: for** `No`", True),
(self.bot.blank_space, self.bot.blank_space, True),
(self.bot.blank_space,
"The Staff will not know who is sending this"
"\nPurely negative feedback will not be considered.", True)]
for name, value, inline in fields:
AnonModMailEmbed.add_field(name=name, value=value, inline=inline)
return AnonModMailEmbed
def send_instructions(self, author):
"""Method to send an embed to to let the user know to type into chat"""
SendModMailEmbed = Embed(title="**Please enter a message for it to be sent to the staff!**",
colour=self.bot.admin_colour,
timestamp=datetime.datetime.utcnow())
SendModMailEmbed.set_thumbnail(url=author.avatar_url)
SendModMailEmbed.set_footer(text=f"Sent by {author}")
fields = [("**Make sure that the message is above **50** and below **1024** characters!**",
"**Include as much detail as possible :P**",
False)]
for name, value, inline in fields:
SendModMailEmbed.add_field(name=name, value=value, inline=inline)
return SendModMailEmbed
def error_handling(self, author):
"""Method to let the user know that the message must be above 50 characters"""
ErrorHandlingEmbed = Embed(
title="Uh Oh! Please make sure the message is above **50** and below **1024** characters!",
colour=self.bot.admin_colour,
timestamp=datetime.datetime.utcnow())
ErrorHandlingEmbed.set_thumbnail(url=author.avatar_url)
ErrorHandlingEmbed.set_footer(text=f"Sent by {author}")
fields = [("Please enter in a message which is above **50** and below **1024** characters!",
"**This helps us reduce spam and allows you to include more detail in your mail!**",
False)]
for name, value, inline in fields:
ErrorHandlingEmbed.add_field(name=name, value=value, inline=inline)
return ErrorHandlingEmbed
def message_sent_confirmation(self, author):
"""Method to send an embed into chat to let the user know that their mail has been sent successfully"""
ConfirmationEmbed = Embed(title="**Message relayed to Staff!!**",
colour=self.bot.admin_colour,
timestamp=datetime.datetime.utcnow())
ConfirmationEmbed.set_thumbnail(url=author.avatar_url)
ConfirmationEmbed.set_footer(text=f"Sent by {author}")
fields = [("Thank you for your input! The staff team appreciate it very much!",
f"\n As mentioned previously, please don't be hesitant to DM the Staff for anything! :P",
False)]
for name, value, inline in fields:
ConfirmationEmbed.add_field(name=name, value=value, inline=inline)
return ConfirmationEmbed
def send_modmail(self, msg, author):
"""Method to actually allow the message to be sent to modmail logging channel"""
embed = Embed(title="Modmail",
colour=self.bot.admin_colour,
timestamp=datetime.datetime.utcnow())
if self.anon:
embed.set_thumbnail(url=random.choice(self.avatars))
embed.set_footer(text=f"Sent By Anon Member")
fields = [("Member", "Anon Member", False),
("Message", msg.content, False)]
else:
embed.set_thumbnail(url=author.avatar_url)
embed.set_footer(text=f"Sent By {author}")
fields = [("Member", author, False),
("Message", msg.content, False)]
for name, value, inline in fields:
embed.add_field(name=name, value=value, inline=inline)
return embed
async def wait_for_msg(self, check, user_channel):
"""
Method to check if the user actually types in a message
If not, delete the channel
"""
try:
# Wait for the message from the author
mod_message = await self.bot.wait_for('message', check=check, timeout=300.0)
# Delete channel if user does not send a message within 5 minutes
except asyncio.TimeoutError:
await user_channel.delete()
return None
else:
return mod_message
async def modmail(self, payload):
"""Listen for reactions for modmail channel/starboard"""
# Don't count reactions that are made by the bot
# Don't count other reactions other than ✅ and ❌
if payload.member.bot or str(payload.emoji) not in ['', '']:
return
# 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.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)
# Get the member
member = guild.get_member(payload.user_id)
# Get the setup modmail channel
channel = guild.get_channel(payload.channel_id)
# Get the modmail logging channel
modmail_channel = guild.get_channel(modmail_channel_id)
# Fetch the message and remove the reaction
reaction = await channel.fetch_message(message_id)
await reaction.remove_reaction('', member)
# Setting up the channel permissions for the new channel that will be created
overwrites = {
guild.default_role: discord.PermissionOverwrite(read_messages=False, send_messages=False),
guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True, embed_links=True,
add_reactions=True, manage_messages=True),
member: discord.PermissionOverwrite(read_messages=True, send_messages=True)
}
# Saving this for later within when discord.py 1.4 comes out
# user_channel = await guild.create_category_channel("Member", overwrites=overwrites, position=7)
# Create the text channel
user_channel = await guild.create_text_channel("Member", overwrites=overwrites,
position=0)
# Mention the user to make sure that they get pinged
mention = await user_channel.send(member.mention)
await mention.delete()
try:
# Send the embed if they want to remain anonymous or not
Anon_or_Not = await user_channel.send(embed=self.anon_or_not(member))
# Add reactions to the message
await Anon_or_Not.add_reaction('')
await Anon_or_Not.add_reaction('')
# Checking if the user reacted with ✅ with response to sending staff a message
def emoji_check(reaction, user):
return user == member and str(reaction.emoji) in ['', '']
try:
# Wait for the user to add a reaction
reaction, user = await self.bot.wait_for('reaction_add', check=emoji_check, timeout=60.0)
# Delete channel if user does not react within 60 seconds
except asyncio.TimeoutError as ex:
print(ex)
await user_channel.delete()
else:
# Making sure that the reply is from the author
def check(m):
return m.author == payload.member and user_channel.id == instructions.channel.id
# Checking if user wants to be Anonymous or not
if str(reaction.emoji) == "":
self.anon = True
if str(reaction.emoji) == "":
self.anon = False
# Delete the old embed
await Anon_or_Not.delete()
# Tell the user to type their mail into the chat
instructions = await user_channel.send(embed=self.send_instructions(member))
# Wait for the message from the author
msg = await self.wait_for_msg(check, user_channel)
if not msg: return
# Making sure that the message is below 50 characters and the message was sent in the channel
while len(msg.content) <= 50 and msg.channel == user_channel:
await user_channel.send(embed=self.error_handling(member))
# Wait for the message from the author
msg = await self.wait_for_msg(check, user_channel)
if not msg: return
# As long as the message is above 50 characters and in the correct channel
if len(msg.content) > 50 and msg.channel == user_channel:
# Delete the previous embed
await instructions.delete()
# Store all text in the channel in a bytesio object
text = ""
async for message in user_channel.history(limit=300):
text += "".join(f"{message.created_at} : {message.content}\n")
text_bytes = str.encode(text)
file = io.BytesIO(text_bytes)
file_name = "Anon.txt" if self.anon else f"{member.name}.txt"
# Send the message to the modmail channel
await modmail_channel.send(embed=self.send_modmail(msg, member),
file=File(file, file_name))
# Make sure the user knows that their message has been sent
await user_channel.send(embed=self.message_sent_confirmation(member))
# Let the user read the message for 5 seconds
await asyncio.sleep(5)
# Delete the channel and then stop the function
await user_channel.delete()
# If the user types anywhere else, delete the channel
else:
await user_channel.delete()
except Exception as ex:
print(ex)
# Send out an error message if the user waited too long
await user_channel.send(
"Sorry! Something seems to have gone wrong and the modmail will be aborting."
"\nRemember to make sure it's under **1024** characters!!")
await asyncio.sleep(5)
await user_channel.delete()

@ -1,395 +0,0 @@
# Ensō~Chan - A Multi Purpose Discord Bot That Has Everything Your Server Needs!
# Copyright (C) 2020 Hamothy
# 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 asyncio
import datetime
from discord import Embed
from discord.ext import menus
def search(self, bot):
"""Method to generate embed of multiple waifu's"""
not_found = "https://media.discordapp.net/attachments/741072426984538122/748586578074664980/DzEZ4UsXgAAcFjN.png?width=423&height=658"
embeds = []
for key in self._dict.values():
# Only setting up description if waifu og_name has a value
desc = key['original_name'] if key["original_name"] else Embed.Empty
# Only using image if it can be displayed, else display 404 image
url = key["display_picture"] if key["display_picture"].endswith((".jpeg", ".png", ".jpg")) else not_found
embed = Embed(title=key["name"], description=desc,
colour=bot.random_colour(),
url=key["url"])
embed.set_image(url=url)
embed.set_author(name=f"{key['type']} | ID: {key['id']}")
if key["type"] in ["Waifu", "Husbando"]:
embed.set_footer(text=f"❤️ {key['likes']} 🗑️ {key['trash']} | Powered by MyWaifuList")
else:
embed.set_footer(text=f"{key['romaji_name']} | Powered by MyWaifuList" if key[
"romaji_name"] else "Powered by MyWaifuList")
embeds.append(embed)
return embeds
class SimpleMenu(menus.Menu):
def __init__(self, i, item, perms, embeds, bot):
super().__init__(timeout=125.0, clear_reactions_after=True)
self.i = i
self.perms = perms
self.dicts = embeds
self.type = item
self.bot = bot
async def remove_reaction(self, reaction):
"""Remove the reaction given"""
if self.perms.manage_messages:
await self.message.remove_reaction(reaction, self.ctx.author)
@staticmethod
def check(m, payload):
"""Simple check to make sure that the reaction is performed by the user"""
return m.author == payload.member and m.channel.id == payload.channel_id
@staticmethod
def get_page(self):
"""
Return the current page index
"""
cur_page = self.i + 1
pages = len(self.dicts)
return cur_page, pages
async def send_initial_message(self, ctx, channel):
"""Set the first embed to the first element in the pages[]"""
initial = self.dicts[self.i]
cur_page, pages = self.get_page(self)
pages = len(self.dicts)
initial.set_author(name=f"{self.type} | Page {cur_page}/{pages}")
# Send embed
return await channel.send(embed=initial)
@menus.button('\N{LEFTWARDS BLACK ARROW}')
async def on_left_arrow(self, payload):
"""Reaction to allow user to go to the previous page in the embed"""
# Do nothing if the check does not return true
if self.check(self.ctx, payload):
# Set self.i to (i - 1) remainder length of the array
self.i = (self.i - 1) % len(self.dicts)
prev_page = self.dicts[self.i]
cur_page, pages = self.get_page(self)
prev_page.set_author(name=f"{self.type} | Page {cur_page}/{pages}")
# Send the embed and remove the reaction of the user
await self.message.edit(embed=prev_page)
await self.remove_reaction("")
@menus.button('\N{BLACK RIGHTWARDS ARROW}')
async def on_right_arrow(self, payload):
"""Reaction to allow user to go to the next page in the embed"""
# Do nothing if the check does not return true
if self.check(self.ctx, payload):
# Set self.i to (i + 1) remainder length of the array
self.i = (self.i + 1) % len(self.dicts)
next_page = self.dicts[self.i]
cur_page, pages = self.get_page(self)
next_page.set_author(name=f"{self.type} | Page {cur_page}/{pages}")
# Send the embed and remove the reaction of the user
await self.message.edit(embed=next_page)
await self.remove_reaction("")
@menus.button('\N{BLACK SQUARE FOR STOP}\ufe0f')
async def on_stop(self, payload):
"""Reaction to allow user to make the embed disappear"""
# Do nothing if the check does not return true
if self.check(self.ctx, payload):
# Edit the embed and tell the member that the session has been closed
embed = Embed(description="**Pagination Session Has Been Closed**",
colour=self.bot.random_colour())
await self.message.edit(embed=embed)
self.stop()
class MWLMenu(menus.Menu):
"""Setup menus for MyWaifuList results"""
def __init__(self, i, perms, _dict, bot):
super().__init__(timeout=125.0, clear_reactions_after=True)
self.i = i
self.perms = perms
self._dict = _dict
self.bot = bot
self.dicts = search(self, bot)
async def remove_reaction(self, reaction):
"""Remove the reaction given"""
if self.perms.manage_messages:
await self.message.remove_reaction(reaction, self.ctx.author)
@staticmethod
def check(m, payload):
"""Simple check to make sure that the reaction is performed by the user"""
return m.author == payload.member and m.channel.id == payload.channel_id
@staticmethod
def get_page(self):
"""
Return the current page index
"""
cur_page = self.i + 1
pages = len(self.dicts)
return cur_page, pages
@staticmethod
def set_author(embed, cur_page, pages):
"""
Returns the author for the first initial embed
The reason why it's different is because I need to retrieve the previous author that I set for the
embed (to get the type from the API)
"""
__type = embed.author.name
embed.remove_author()
return embed.set_author(name=f"{__type} | Page {cur_page}/{pages}")
@staticmethod
def set_author_after(embed, cur_page, pages):
"""
Returns the author for all the pages when the user reacts to go back and forwards
This needs to be another method because the previous author is gonna be different to the one
specified at the start "multiple_dict_generators()"
"""
author = embed.author.name
tv_type = author.split("|")
__type = f"{tv_type[0].strip()} | {tv_type[1].strip()}"
return embed.set_author(name=f"{__type} | Page {cur_page}/{pages}")
async def send_initial_message(self, ctx, channel):
"""Set the first embed to the first element in the pages[]"""
initial = self.dicts[self.i]
cur_page, pages = self.get_page(self)
initial = self.set_author(initial, cur_page, pages)
# Send embed
return await channel.send(embed=initial)
@menus.button('\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}')
async def on_first_page_arrow(self, payload):
"""Reaction to allow the user to return to the first page"""
# Do nothing if the check does not return true
if self.check(self.ctx, payload):
# Send the embed and remove the reaction of the user
if self.i == 0:
await self.remove_reaction("\U000023ee")
return
self.i = 0 % len(self.dicts)
first_page = self.dicts[self.i]
cur_page, pages = self.get_page(self)
first_page = self.set_author_after(first_page, cur_page, pages)
await self.message.edit(embed=first_page)
await self.remove_reaction("\U000023ee")
@menus.button('\N{LEFTWARDS BLACK ARROW}')
async def on_left_arrow(self, payload):
"""Reaction to allow user to go to the previous page in the embed"""
# Do nothing if the check does not return true
if self.check(self.ctx, payload):
# Set self.i to (i - 1) remainder length of the array
self.i = (self.i - 1) % len(self.dicts)
prev_page = self.dicts[self.i]
cur_page, pages = self.get_page(self)
prev_page = self.set_author_after(prev_page, cur_page, pages)
# Send the embed and remove the reaction of the user
await self.message.edit(embed=prev_page)
await self.remove_reaction("")
@menus.button('\N{BLACK RIGHTWARDS ARROW}')
async def on_right_arrow(self, payload):
"""Reaction to allow user to go to the next page in the embed"""
# Do nothing if the check does not return true
if self.check(self.ctx, payload):
# Set self.i to (i + 1) remainder length of the array
self.i = (self.i + 1) % len(self.dicts)
next_page = self.dicts[self.i]
cur_page, pages = self.get_page(self)
next_page = self.set_author_after(next_page, cur_page, pages)
# Send the embed and remove the reaction of the user
await self.message.edit(embed=next_page)
await self.remove_reaction("")
@menus.button('\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}')
async def on_last_page_arrow(self, payload):
"""Reaction to allow the user to go to the last page in the embed"""
# Do nothing if the check does not return true
if self.check(self.ctx, payload):
# Send the embed and remove the reaction of the user
if self.i == len(self.dicts) - 1:
await self.remove_reaction("\U000023ed")
return
self.i = len(self.dicts) - 1
last_page = self.dicts[self.i]
cur_page, pages = self.get_page(self)
last_page = self.set_author_after(last_page, cur_page, pages)
await self.message.edit(embed=last_page)
await self.remove_reaction("\U000023ed")
@menus.button('\N{INPUT SYMBOL FOR NUMBERS}')
async def on_numbered_page(self, payload):
"""Reaction to allow users to input page numbers"""
# Do nothing if the check does not return true
if self.check(self.ctx, payload):
embed = Embed(description="**What Page Would You Like To Go To? (Only Numbers!)**",
colour=self.bot.random_colour())
message = await self.ctx.send(embed=embed)
def check(m):
"""Simple check to make sure that the reaction is performed by the user"""
return m.author == payload.member and m.channel.id == payload.channel_id
try:
# Wait for the message from the mentioned user
msg = await self.bot.wait_for('message', check=check, timeout=20.0)
# Catch timeout error
except asyncio.TimeoutError as ex:
print(ex)
await self.remove_reaction("\U0001f522")
embed = Embed(description="**You Waited Too Long D:**",
colour=self.bot.random_colour())
await message.edit(embed=embed)
await asyncio.sleep(2.5)
await message.delete()
else:
# As long as the number entered is within the page numbers, go to that page
try:
if 0 < int(msg.content) <= len(self.dicts):
await message.delete()
await msg.delete()
self.i = int(msg.content) - 1
number_page = self.dicts[self.i]
cur_page, pages = self.get_page(self)
last_page = self.set_author_after(number_page, cur_page, pages)
await self.message.edit(embed=last_page)
await self.remove_reaction("\U0001f522")
# Delete the message and remove the reaction if out of bounds
else:
await message.delete()
await msg.delete()
await self.remove_reaction("\U0001f522")
except Exception as e:
print(e)
await message.delete()
await msg.delete()
await self.remove_reaction("\U0001f522")
@menus.button('\N{INFORMATION SOURCE}')
async def on_information(self, payload):
"""Show's information about the pagination session"""
# Do nothing if the check does not return true
if self.check(self.ctx, payload):
messages = ['Welcome to the Waifu/Anime Pagination Session!',
'This interactively allows you to see pages of text by navigating with '
'reactions. They are as follows:\n']
reaction_emojis = [
('\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}', "Takes You To The First Page"),
('\N{BLACK LEFT-POINTING TRIANGLE}', "Takes You To The Previous Page"),
('\N{BLACK RIGHT-POINTING TRIANGLE}', "Takes You To The Next Page"),
('\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}', "Takes You To The Last Page"),
('\N{INPUT SYMBOL FOR NUMBERS}', "Enter Page Number To Go To"),
('\N{INFORMATION SOURCE}', "Shows This Message"),
('\N{BLACK SQUARE FOR STOP}', "Closes The Pagination Session")
]
for value, func in reaction_emojis:
messages.append(f"{value}, {func}")
embed = Embed(description='\n'.join(messages),
colour=self.bot.random_colour(),
timestamp=datetime.datetime.utcnow())
embed.set_footer(text=f'We Were On Page {self.i + 1} Before This Message')
await self.message.edit(embed=embed)
await self.remove_reaction("\U00002139")
@menus.button('\N{BLACK SQUARE FOR STOP}\ufe0f')
async def on_stop(self, payload):
"""Reaction to allow user to make the embed disappear"""
# Do nothing if the check does not return true
if self.check(self.ctx, payload):
# Edit the embed and tell the member that the session has been closed
embed = Embed(description="**Waifu/Anime Reaction Session Has Been Closed**",
colour=self.bot.random_colour())
await self.message.edit(embed=embed)
self.stop()

@ -1,241 +0,0 @@
# Ensō~Chan - A Multi Purpose Discord Bot That Has Everything Your Server Needs!
# Copyright (C) 2020 Hamothy
# 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 asyncpg
from discord import Embed
class Starboard:
def __init__(self, bot):
self.bot = bot
async def send_starboard_message(self, payload, new_stars, msg_id, channel, message, embed):
"""Send the message to the starboard for the first time"""
# When the message stars is larger than the minimum, send to the starboard and store in database/cache
if new_stars >= self.bot.get_starboard_min_stars(payload.guild_id) and not msg_id:
star_message = await channel.send(embed=embed)
# Only insert the record into database when it doesn't exist in cache
if not self.bot.check_root_message_id(message.id, payload.guild_id):
# Setup up pool connection
pool = self.bot.db
async with pool.acquire() as conn:
# Insert the starboard message in the database
try:
insert = """INSERT INTO starboard_messages (root_message_id, guild_id, star_message_id)
VALUES ($1, $2, $3)"""
await conn.execute(insert, message.id, payload.guild_id, star_message.id)
# Catch errors
except asyncpg.PostgresError as e:
print(
f"PostGres Error: Starboard_Message Record Could Not Be Inserted For Guild {payload.guild_id}",
e)
# Update cache
else:
self.bot.cache_store_starboard_message(message.id, payload.guild_id, star_message.id)
else:
# Setup up pool connection
pool = self.bot.db
async with pool.acquire() as conn:
# Update the stars that the message has in the database and then store the message id's
try:
update = """UPDATE starboard_messages SET stars = $1, star_message_id = $2 WHERE root_message_id = $3 AND guild_id = $4"""
await conn.execute(update, new_stars, star_message.id, message.id, payload.guild_id)
# Catch errors
except asyncpg.PostgresError as e:
print(
f"PostGres Error: Starboard_Message Record Could Not Be Updated For Guild {payload.guild_id}",
e)
# Update cache
else:
self.bot.update_starboard_message_id(message.id, payload.guild_id, star_message.id)
self.bot.update_starboard_message_stars(message.id, payload.guild_id, new_stars)
# Store the message into the database so the reactions can be tracked
else:
# Only insert into the database when the message doesn't exist in cache
if not self.bot.check_root_message_id(message.id, payload.guild_id):
# Setup up pool connection
pool = self.bot.db
async with pool.acquire() as conn:
# Insert the starboard message in the database
try:
insert = """INSERT INTO starboard_messages (root_message_id, guild_id) VALUES ($1, $2)"""
await conn.execute(insert, message.id, payload.guild_id)
# Catch errors
except asyncpg.PostgresError as e:
print(
f"PostGres Error: Starboard_Message Record Could Not Be Inserted For Guild {payload.guild_id}",
e)
# Update cache
else:
self.bot.cache_store_starboard_message(message.id, payload.guild_id, None)
# When the message is already in the database/cache, update the amount of reactions
else:
# Setup up pool connection
pool = self.bot.db
async with pool.acquire() as conn:
# Update the stars that the message has in the database and then store the message id's
try:
update = """UPDATE starboard_messages SET stars = $1 WHERE root_message_id = $2 AND guild_id = $3"""
await conn.execute(update, new_stars, message.id, payload.guild_id)
# Catch errors
except asyncpg.PostgresError as e:
print(
f"PostGres Error: Starboard_Message Record Could Not Be Updated For Guild {payload.guild_id}",
e)
# Update cache
else:
self.bot.update_starboard_message_stars(message.id, payload.guild_id, new_stars)
async def edit_starboard_message(self, payload, new_stars, msg_id, channel, message, embed):
"""Edit the message which is already on the starboard"""
min_stars = self.bot.get_starboard_min_stars(payload.guild_id)
# When the message has finally reached the minimum amount of stars, send the message to the starboard
if new_stars >= min_stars and not msg_id:
star_message = await channel.send(embed=embed)
# Setup up pool connection
pool = self.bot.db
async with pool.acquire() as conn:
# Update the stars that the message has in the database and then store the message id's
try:
update = """UPDATE starboard_messages SET stars = $1, star_message_id = $2
WHERE root_message_id = $3 AND guild_id = $4"""
await conn.execute(update, new_stars, star_message.id, message.id, payload.guild_id)
# Catch errors
except asyncpg.PostgresError as e:
print(f"PostGres Error: Starboard_Message Record Could Not Be Updated For Guild {payload.guild_id}",
e)
# Update cache
else:
self.bot.cache_store_starboard_message(message.id, payload.guild_id, star_message.id)
self.bot.update_starboard_message_stars(message.id, payload.guild_id, new_stars)
elif new_stars < min_stars and msg_id:
star_message = await channel.fetch_message(msg_id)
await star_message.delete()
# Setup up pool connection
pool = self.bot.db
async with pool.acquire() as conn:
# Update the stars that the message has in the database and set the star message id to None
try:
update = """UPDATE starboard_messages
SET stars = $1, star_message_id = NULL
WHERE root_message_id = $2 AND guild_id = $3"""
await conn.execute(update, new_stars, message.id, payload.guild_id)
# Catch errors
except asyncpg.PostgresError as e:
print(
f"PostGres Error: Starboard_Message Record Could Not Be Updated For Guild {payload.guild_id}",
e)
# Update cache
else:
self.bot.del_starboard_star_message_id(message.id, payload.guild_id)
self.bot.update_starboard_message_stars(message.id, payload.guild_id, new_stars)
# When the message already exists on the starboard but doesn't have enough reactions anymore
elif msg_id:
star_message = await channel.fetch_message(msg_id)
await star_message.edit(embed=embed)
# Setup up pool connection
pool = self.bot.db
async with pool.acquire() as conn:
# Update the stars that the message has in the database and set the star message id to None
try:
update = """UPDATE starboard_messages SET stars = $1 WHERE root_message_id = $2 AND guild_id = $3"""
await conn.execute(update, new_stars, message.id, payload.guild_id)
# Catch errors
except asyncpg.PostgresError as e:
print(
f"PostGres Error: Starboard_Message Record Could Not Be Updated For Guild {payload.guild_id}",
e)
# Update cache
else:
self.bot.update_starboard_message_id(message.id, payload.guild_id, star_message.id)
self.bot.update_starboard_message_stars(message.id, payload.guild_id, new_stars)
elif not msg_id:
self.bot.update_starboard_message_stars(message.id, payload.guild_id, new_stars)
async def send_starboard_and_update_db(self, payload, action):
"""Send the starboard embed and update database/cache"""
if (starboard := self.bot.get_starboard_channel(payload.guild_id)) and payload.emoji.name == "":
message = await self.bot.get_channel(payload.channel_id).fetch_message(payload.message_id)
if not message.author.bot and payload.user_id != message.author.id:
channel = self.bot.get_channel(starboard)
msg_id, stars = await self.bot.check_starboard_messages_cache(message.id, payload.guild_id)
new_stars = stars + 1 if action == "added" else stars - 1
embed = Embed(title=f"Starred Message | {new_stars} :star:",
description=f"{message.content or 'View Attachment'}",
colour=message.author.colour,
timestamp=datetime.datetime.utcnow())
embed.set_author(name=message.author, icon_url=message.author.avatar_url)
embed.set_footer(text=f"ID: {message.id}")
embed.add_field(name="Original",
value=f"**Channel:** {message.channel.mention}\n[Jump To Message]({message.jump_url})",
inline=False)
# Send spoiler attachments as links
if message.attachments:
file = message.attachments[0]
spoiler = file.is_spoiler()
if not spoiler and file.url.endswith(('png', 'jpeg', 'jpg', 'gif', 'webp')):
embed.set_image(url=file.url)
elif spoiler:
embed.add_field(name='Attachment', value=f'||[{file.filename}]({file.url})||', inline=False)
else:
embed.add_field(name='Attachment', value=f'[{file.filename}]({file.url})', inline=False)
# When the message has no previous stars, send a new starboard message or update the amount of stars it has
if not stars:
await self.send_starboard_message(payload, new_stars, msg_id, channel, message, embed)
# When the message has stars already from the cache/database. Delete or edit the message
else:
await self.edit_starboard_message(payload, new_stars, msg_id, channel, message, embed)

File diff suppressed because it is too large Load Diff

@ -1,246 +0,0 @@
# Ensō~Chan - A Multi Purpose Discord Bot That Has Everything Your Server Needs!
# Copyright (C) 2020 Hamothy
# 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 asyncio
import inspect
import io
import textwrap
import traceback
from contextlib import redirect_stdout
from typing import Optional
import asyncpg
from discord import Member
from discord.ext.commands import Cog, command, is_owner
def cleanup_code(content):
"""Automatically removes code blocks from the code."""
# remove ```py\n```
if content.startswith('```') and content.endswith('```'):
return '\n'.join(content.split('\n')[1:-1])
# remove `foo`
return content.strip('` \n')
def paginate(text: str):
"""Simple generator that paginates text."""
last = 0
pages = []
for curr in range(0, len(text)):
if curr % 1980 == 0:
pages.append(text[last:curr])
last = curr
appd_index = curr
if appd_index != len(text) - 1:
pages.append(text[last:curr])
return list(filter(lambda a: a != '', pages))
def get_syntax_error(e):
if e.text is None:
return f'```py\n{e.__class__.__name__}: {e}\n```'
return f'```py\n{e.text}{"^":>{e.offset}}\n{e.__class__.__name__}: {e}```'
class Owner(Cog):
"""Commands for Ensō server"""
def __init__(self, bot):
self.bot = bot
@command(name="dm", hidden=True)
@is_owner()
async def dm(self, ctx, member: Member, *, text):
"""DM users"""
# Delete the message sent instantly
await ctx.message.delete()
# Send the message typed the mentioned user
await member.send(text)
@command(name="leave", hidden=True)
@is_owner()
async def leave(self, ctx):
"""Leaves the guild"""
await self.bot.generate_embed(ctx, desc="**Leaving the guild... Bye Bye uvu**")
await ctx.guild.leave()
@command(name="forceprefix", hidden=True)
@is_owner()
async def override_prefix(self, ctx, new: Optional[str] = None):
"""Override the prefix in any given guild (Owner only)"""
# 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(ctx, new)
# Making sure that errors are handled if prefix is above 5 characters
elif new and len(new) > 5:
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
prefix = self.bot.get_prefix_for_guild(ctx.guild.id)
await self.bot.generate_embed(ctx, desc=f"**The current guild prefix is `{prefix}`**")
@command(name="restart", hidden=True)
@is_owner()
async def restart(self, ctx):
"""Restart the bot"""
# Close the database connection
try:
await asyncio.wait_for(self.bot.db.close(), timeout=1.0)
# Catch errors
except asyncio.TimeoutError:
await self.bot.generate_embed(ctx, desc="**Database Connection Timed Out!")
# Shutdown the bot
else:
await self.bot.generate_embed(ctx, desc="**Success Senpai!"
"\nMy Reboot Had No Problems** <a:ThumbsUp:737832825469796382>")
await self.bot.logout()
@command(name="reloadusers", hidden=True)
@is_owner()
async def reload_db(self, ctx):
"""Reloads the database by inserting/updating all the records"""
# Store every single record into an array
records = [(ctx.guild.id, member.id) for member in ctx.guild.members]
# Setup up pool connection
pool = self.bot.db
async with pool.acquire() as conn:
# 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")
@command(name="cache", hidden=True)
@is_owner()
async def set_cache(self, ctx, size: Optional[int]):
"""Allow me to dynamically set the cache max size"""
# Change the size of the cache
if size:
try:
self.bot.member_cache.change_array_size(size)
# Catch errors
except Exception as e:
print(e)
# Let me that it's successful
else:
await self.bot.generate_embed(ctx, desc=f"Cache Now Storing To **{size}** Records")
# Display the length of the current queue and cache and the max length of the cache
else:
max_cache_len, cache_len, queue_len = self.bot.member_cache.get_size()
await self.bot.generate_embed(ctx, desc=f"Current Records Stored Within Cache: **{cache_len}**"
f"\nCurrent Queue Length: **{queue_len}**"
f"\nMax Size Of Cache: **{max_cache_len}**")
@command(name="eval", hidden=True)
@is_owner()
async def _eval(self, ctx, *, body):
"""
Evaluates python code
Gracefully yoinked from (https://github.com/fourjr/eval-bot)"""
env = {
'ctx': ctx,
'self': self,
'bot': self.bot,
'channel': ctx.channel,
'author': ctx.author,
'guild': ctx.guild,
'message': ctx.message,
'source': inspect.getsource
}
env.update(globals())
body = cleanup_code(body)
stdout = io.StringIO()
err = out = None
to_compile = f'async def func():\n{textwrap.indent(body, " ")}'
try:
exec(to_compile, env)
except Exception as e:
err = await ctx.send(f'```py\n{e.__class__.__name__}: {e}\n```')
return await ctx.message.add_reaction('\u2049')
func = env['func']
try:
with redirect_stdout(stdout):
ret = await func()
except Exception as e:
value = stdout.getvalue()
err = await ctx.send(f'```py\n{value}{traceback.format_exc()}\n```')
else:
value = stdout.getvalue()
if ret is None:
if value:
try:
out = await ctx.send(f'```py\n{value}\n```')
except:
paginated_text = paginate(value)
for page in paginated_text:
if page == paginated_text[-1]:
out = await ctx.send(f'```py\n{page}\n```')
break
await ctx.send(f'```py\n{page}\n```')
else:
try:
out = await ctx.send(f'```py\n{value}{ret}\n```')
except:
paginated_text = paginate(f"{value}{ret}")
for page in paginated_text:
if page == paginated_text[-1]:
out = await ctx.send(f'```py\n{page}\n```')
break
await ctx.send(f'```py\n{page}\n```')
if out:
await ctx.message.add_reaction('\u2705') # tick
elif err:
await ctx.message.add_reaction('\u2049') # x
else:
await ctx.message.add_reaction('\u2705')
def setup(bot):
bot.add_cog(Owner(bot))

@ -1,293 +0,0 @@
# Ensō~Chan - A Multi Purpose Discord Bot That Has Everything Your Server Needs!
# Copyright (C) 2020 Hamothy
# 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 asyncio
import datetime
import asyncpg
from discord import Member, Embed
from discord.ext.commands import command, bot_has_permissions, Cog
# Sets up the embed for the marriage info
def marriageInfo(self, target, marriedUser, marriedDate, currentDate, married):
# Make sure that non-users can still use the marriage
if not married:
# Set up the fields for the embed
fields = [("Married To", "No One", False),
("Marriage Date", "N/A", False),
("Days Married", "N/A", False)]
else:
# Calculate the days married
marriedTime = datetime.datetime.strptime(marriedDate, "%a, %b %d, %Y")
currentTime = datetime.datetime.strptime(currentDate, "%a, %b %d, %Y")
delta = currentTime - marriedTime
# Set up the fields for the embed
fields = [("Married To", marriedUser.mention, False),
("Marriage Date", marriedDate, False),
("Days Married", delta.days, False)]
# Set the title, colour, timestamp and thumbnail
embed = Embed(title=f"{target.name}'s Marriage Information",
colour=self.bot.random_colour(),
timestamp=datetime.datetime.utcnow())
embed.set_thumbnail(url=target.avatar_url)
# Add fields to the embed
for name, value, inline in fields:
embed.add_field(name=name, value=value, inline=inline)
return embed
# Set up the Cog
class Relationship(Cog):
"""Marry/Divorce etc!"""
def __init__(self, bot):
self.bot = bot
@Cog.listener()
async def on_ready(self):
"""Printing out that Cog is ready on startup"""
print(f"{self.__class__.__name__} Cog has been loaded\n-----")
@command(name="marry")
async def marry(self, ctx, member: Member):
"""Wed your Lover!"""
# Getting the guild of the user
guild = ctx.guild
# Make sure that the user cannot marry themselves
if member.id == ctx.author.id:
await self.bot.generate_embed(ctx, desc="**Senpaii! ˭̡̞(◞⁎˃ᆺ˂)◞*✰ You can't possibly marry yourself!**")
return
# Get the author from the cache
db_author = await self.bot.check_cache(ctx.author.id, ctx.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
# Get the member mentioned from the cache
db_member = await self.bot.check_cache(member.id, ctx.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
# 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}"
f"\n**Do you accept??**"
f"\nRespond with [**Y**es/**N**o]")
# A check that makes sure that the reply is not from the author
# and that the reply is in the same channel as the proposal
def check(m):
return m.author == member and m.channel == ctx.channel
try:
# Wait for the message from the mentioned user
msg = await self.bot.wait_for('message', check=check, timeout=90.0)
# if the person says yes
if msg.content.lower() in ['y', 'yes', 'yea']:
# Setup pool connection
pool = self.bot.db
async with pool.acquire() as conn:
# Get the time of marriage
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
try:
update_query = """UPDATE members SET married = $1, married_date = $2 WHERE member_id = $3 AND guild_id = $4"""
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)
# Catch errors
except asyncpg.PostgresError as e:
print(f"PostGres Error: {member.id} and {ctx.author.id} Could Not Be Married", e)
# Update cache new details
else:
db_author["married"] = member.id
db_author["married_date"] = message_time
db_member["married"] = ctx.author.id
db_member["married_date"] = message_time
# Congratulate them!
desc = f"**Congratulations! 。゚( ゚^∀^゚)゚。 {ctx.author.mention} and {member.mention} are now married to each other!**"
await self.bot.generate_embed(ctx, desc=desc)
# if the person says no
elif msg.content.lower() in ['n', 'no', 'nah']:
# Try to console the person and wish them the best in their life
desc = f"**{ctx.author.mention} It's okay king. Pick up your crown and move on (◕‿◕✿)**"
await self.bot.generate_embed(ctx, desc=desc)
else:
# Abort the process as the message sent did not make sense
await self.bot.generate_embed(ctx, desc="**Senpaiiii! (。╯︵╰。) Speak English Please**")
except asyncio.TimeoutError as ex:
print(ex)
# Send out an error message if the user waited too long
await self.bot.generate_embed(ctx, desc=f"**(。T ω T。) {member.mention} waited too long**")
@command(name="divorce")
async def divorce(self, ctx, member: Member):
"""Divorce your Partner!"""
# Getting the guild of the user
guild = ctx.guild
# Make sure that the user cannot divorce themselves
if member.id == ctx.author.id:
await self.bot.generate_embed(ctx, desc="**Senpaii! ˭̡̞(◞⁎˃ᆺ˂)◞*✰ You can't possibly divorce yourself!**")
return
# Get the author from the cache
db_author = await self.bot.check_cache(ctx.author.id, ctx.guild.id)
married_user = db_author["married"]
# Make sure that the person trying to divorce is actually married to the user
if married_user is None:
desc = "**((╬◣﹏◢)) You must be married in order to divorce someone! Baka!**"
await self.bot.generate_embed(ctx, desc=desc)
return
# Make sure the person is married to the person that they're trying to divorce
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}**"
await self.bot.generate_embed(ctx, desc=desc)
return
# 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} **Wishes to Divorce** {member.mention}"
f"\n**Are you willing to break this sacred bond?**"
f"\nRespond with [**Y**es/**N**o]")
# A check that makes sure that the reply is not from the author
# and that the reply is in the same channel as the proposal
def check(m):
return m.author == member and m.channel == ctx.channel
# Surround with try/except to catch any exceptions that may occur
try:
# Wait for the message from the mentioned user
msg = await self.bot.wait_for('message', check=check, timeout=90.0)
# if the person says yes
if msg.content.lower() in ['y', 'yes', 'yea']:
# Setup pool connection
pool = self.bot.db
async with pool.acquire() as conn:
# Update the existing records in the database that the author and member are divorced
try:
update_query = """UPDATE members SET married = NULL, married_date = NULL WHERE member_id = $1 and guild_id = $2"""
await conn.execute(update_query, ctx.author.id, guild.id)
await conn.execute(update_query, member.id, guild.id)
# Catch errors
except asyncpg.PostgresError as e:
print(f"PostGres Error: {member.id} and {ctx.author.id} Could Not Be Divorced", e)
# Update cache with new details
else:
db_author["married"] = None
db_author["married_date"] = None
db_member = await self.bot.check_cache(member.id, ctx.guild.id)
db_member["married"] = None
db_member["married_date"] = None
# Congratulate them!
desc = f"**૮( ´⁰▱๋⁰ )ა {ctx.author.mention} and {member.mention} are now divorced." \
f"\nI hope you two can find happiness in life with other people**"
await self.bot.generate_embed(ctx, desc=desc)
# if the person says no
elif msg.content.lower() in ['n', 'no', 'nah']:
# Try to console the person and wish them the best in their life
desc = f"**Sorry {ctx.author.mention} but you're gonna need {member.mention}'s consent to move forward with this!**"
await self.bot.generate_embed(ctx, desc=desc)
else:
# Abort the process as the message sent did not make sense
await self.bot.generate_embed(ctx, desc="**Senpaiiii! (。╯︵╰。) Speak English Please**")
except asyncio.TimeoutError as ex:
print(ex)
# Send out an error message if the user waited too long
await self.bot.generate_embed(ctx, desc=f"**(。T ω T。) {member.mention} waited too long**")
@command(name="marriageinfo", aliases=["minfo"])
@bot_has_permissions(embed_links=True)
async def m_info(self, ctx, member: Member = None):
"""Marriage Information!"""
# Choose author if no other user has been mentioned
member = ctx.author if not member else member
# Getting the guild of the user
guild = member.guild
# Get the author from the cache
result = await self.bot.check_cache(member.id, ctx.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")
# Get the marriage info embed and then send it to the display
embed = marriageInfo(self, member, marriedUser, marriedDate, currentDate, married)
await ctx.send(embed=embed)
def setup(bot):
bot.add_cog(Relationship(bot))

@ -1,20 +0,0 @@
https://cdn.discordapp.com/attachments/716424509858644018/729562288624762971/unknown.gif
https://cdn.discordapp.com/attachments/716424509858644018/729563027531104336/tenor_2.gif
https://cdn.discordapp.com/attachments/716424509858644018/729562295134322718/unknown.gif
https://cdn.discordapp.com/attachments/716424509858644018/729565034547642409/giphy_1.gif
https://cdn.discordapp.com/attachments/716424509858644018/729565035013341195/DBvbJ4grTZuOgR3YyWCliY7znqMF1sehfeBTvW4pd3yUZF6Uy-1Ad36eR_Ho11Im1eEWqB8TQcM6mCjpd3LhLg.gif
https://cdn.discordapp.com/attachments/716424509858644018/729565035226988595/giphy_2.gif
https://cdn.discordapp.com/attachments/716424509858644018/729565197110345818/d4qelwv-b77885ba-b71e-463b-baf1-82146cf2eddd.gif
https://cdn.discordapp.com/attachments/716424509858644018/729565431261691954/tenor_3.gif
https://cdn.discordapp.com/attachments/716424509858644018/729566244579049533/0b7033dafc792ff9-anime-choking-gifs-tenor.gif
https://cdn.discordapp.com/attachments/716424509858644018/729566778954350672/SourGrizzledGavial-size_restricted.gif
https://cdn.discordapp.com/attachments/716424509858644018/729567686551404644/He_didnt_strangle_her_.gif
https://cdn.discordapp.com/attachments/716424509858644018/729568394508238918/tenor_5.gif
https://cdn.discordapp.com/attachments/716424509858644018/729568521083945042/tenor_6.gif
https://cdn.discordapp.com/attachments/716424509858644018/729569562399604786/tumblr_myp7nzBpkB1torue8o1_500.gif
https://cdn.discordapp.com/attachments/716424509858644018/729569673561112646/2uCh694.gif
https://cdn.discordapp.com/attachments/716424509858644018/731279769848643744/original.gif
https://cdn.discordapp.com/attachments/716424509858644018/731281689971523594/tenor_9.gif
https://cdn.discordapp.com/attachments/716424509858644018/731281690319913020/79o.gif
https://cdn.discordapp.com/attachments/716424509858644018/731282061024952340/tumblr_n3xfkdCYN71ssisofo2_500.gif
https://images-ext-2.discordapp.net/external/jgTnvfjNup8F8F-Yj7jrOFfhCVocYePg-LVxOt4VFHE/https/zippy.gfycat.com/AmazingIdealAustralianfreshwatercrocodile.mp4

@ -1,82 +0,0 @@
https://media.tenor.co/images/012cc6d6cb65c3c98bd5505ab2e1c42a/tenor.gif
https://media.tenor.co/images/3b205574d0352d4d61687f835276566d/tenor.gif
https://media.tenor.co/images/864d525effe7da722203e209efafafec/tenor.gif
https://media.tenor.co/images/9af57b60dca6860724a0ff6c1689c246/tenor.gif
https://media.tenor.co/images/9c055cf13ecd8255834775c0af48f2c3/tenor.gif
https://media.tenor.co/images/826fedbb36f0e844089e10b37fa41643/tenor.gif
https://media.tenor.co/images/75f0c4deb31845f1e6ec33e3d7611095/tenor.gif
https://media.tenor.co/images/52471e5ee4d2c953127091efac41de23/tenor.gif
https://media.tenor.co/images/c445e2665d12cfda0921291d919cbe9a/tenor.gif
https://media.tenor.co/images/8cbe0edadc12ca1056d5eb685a4c27f6/tenor.gif
https://media.tenor.co/images/9af57b60dca6860724a0ff6c1689c246/tenor.gif
https://media.tenor.co/images/c51d8a4505e1dfef709efd4739d09faa/tenor.gif
https://media.tenor.co/images/1a65319302b9e1c86a99a39e9a81084e/tenor.gif
https://media.tenor.co/images/8f8ba3baeecdf28f3e0fa7d4ce1a8586/tenor.gif
https://media.tenor.co/images/f349135baa0c6b0bf72d1746d4b949bf/tenor.gif
https://media.tenor.co/images/9fa3577b5aeb65dc898f5cc75b82a659/tenor.gif
https://media.tenor.co/images/d0c2e7382742f1faf8fcb44db268615f/tenor.gif
https://media.tenor.co/images/826fedbb36f0e844089e10b37fa41643/tenor.gif
https://media.tenor.co/images/adeb030aaa5a2a3d16abdc58be4d1448/tenor.gif
https://media.tenor.co/images/c51d8a4505e1dfef709efd4739d09faa/tenor.gif
https://media.tenor.co/images/6d73b0a9cadef5310be4b6160d2f959a/tenor.gif
https://media.tenor.co/images/7b8f5e852e6a53c1fe36816d3ccf9326/tenor.gif
https://media.tenor.co/images/ec14f1673479a72db6083c0be2bee335/tenor.gif
https://media.tenor.co/images/f65bd4bb3441b9d647d86b04eee6542e/tenor.gif
https://media.tenor.com/images/d309f16ec3299431393db58b3b7935e5/tenor.gif
https://media.tenor.com/images/a8cbc11ee331c62aaf03420d99696da0/tenor.gif
https://media.tenor.com/images/b2010973cddee5baf2d3ee369897709c/tenor.gif
https://media.tenor.com/images/acdf6187a51161b74c88e2fad6991041/tenor.gif
https://media.tenor.com/images/43fce3d874179afb2d9d74a7402dcff4/tenor.gif
https://media.tenor.com/images/3b8a361475bb17f157c1b4c2988b1d79/tenor.gif
https://media.tenor.com/images/4179058caa9eef3e7c6b21b8888b9cc9/tenor.gif
https://media.tenor.com/images/bba22a37d85c5760a64c2497d5fdd4d2/tenor.gif
https://media.tenor.com/images/26105169827cd8ca702a809d32db38c9/tenor.gif
https://media.tenor.com/images/4f6bf30970f0ffdd755981d43eee3f7c/tenor.gif
https://media.tenor.com/images/48f3a68df98b66f72d3c6b01efd6c6fd/tenor.gif
https://media.tenor.com/images/8cab4f4c73547d077c56066461c40a5e/tenor.gif
https://media.tenor.com/images/b43e90f82f1b7f85683a9c6610f0045e/tenor.gif
https://media.tenor.com/images/ea1dc7c4ad4835b02f7d8f4062d76006/tenor.gif
https://media.tenor.com/images/26f3a326707440dc8bf65c0573826e22/tenor.gif
https://media.tenor.com/images/a3862661163e420d57538b1b08aa5972/tenor.gif
https://media.tenor.com/images/1d771bdc0c22e534e5f675eba5ec3618/tenor.gif
https://media.tenor.com/images/cb2d293a76ebfb4baebc717807177c98/tenor.gif
https://media.tenor.com/images/324a1822660c452851a134f4e9b25802/tenor.gif
https://media.tenor.com/images/8df294439f420646327ead09a1789912/tenor.gif
https://media.tenor.com/images/e486734428257a7b10dbe0f5a8fa7903/tenor.gif
https://media.tenor.com/images/e07473d3cf372dbc24c760527740b85e/tenor.gif
https://media.tenor.com/images/0569ee4ea490a197b0b38e92c3eede9e/tenor.gif
https://media.tenor.com/images/7ffa5f3b6fe7a3ddd009abb0f2c62ff9/tenor.gif
https://media.tenor.com/images/3e647c2df4183920ba3b036f6d057dae/tenor.gif
https://media.tenor.com/images/2e3c72a7aaf0fe3405d4c5ba9cee9bbd/tenor.gif
https://media.tenor.com/images/a3627bd9e42008a899ca315ab51c0553/tenor.gif
https://media.tenor.com/images/545e4ea926ca2a5fd215525963bd7360/tenor.gif
https://media.tenor.com/images/4781b66b378c1566d812c699f2a09661/tenor.gif
https://media.tenor.com/images/7e7ddfd89c6aac968f33334fda3730b8/tenor.gif
https://media.tenor.com/images/7d9209867acfafa378cef022ffd3abfa/tenor.gif
https://media.tenor.com/images/f65bd4bb3441b9d647d86b04eee6542e/tenor.gif
https://media.tenor.com/images/3c295a01cf438f45d07ec5f8b1f9dd4e/tenor.gif
https://media.tenor.com/images/29b6b20fc3c729e56184b0c92a86d842/tenor.gif
https://media.tenor.com/images/218aed7cb7c6b37183cff6baed7166b8/tenor.gif
https://media.tenor.com/images/012cc6d6cb65c3c98bd5505ab2e1c42a/tenor.gif
https://media.tenor.com/images/9787de95d7d307e63f6e1a8ddb5d2ba2/tenor.gif
https://media.tenor.com/images/cf2607b5940170eac383b41355d956fa/tenor.gif
https://media.tenor.com/images/6d73b0a9cadef5310be4b6160d2f959a/tenor.gif
https://media.tenor.com/images/2db3419bd925a54ff17e44fa5d708ca0/tenor.gif
https://media.tenor.com/images/91af20b99c5f0d209cec13b3f7b4ca3a/tenor.gif
https://media.tenor.com/images/e000639b39298e8e5022f80adc8768b4/tenor.gif
https://media.tenor.com/images/4781b66b378c1566d812c699f2a09661/tenor.gif
https://media.tenor.com/images/85f48bfe2846e8cad93009ca8dba0e9a/tenor.gif
https://media.tenor.com/images/85c92e585c30685d62768d735349af43/tenor.gif
https://media.tenor.com/images/68cc73bdd66f0467ceb3e49ce5967dbc/tenor.gif
https://media.tenor.com/images/34603799afdf0b57800704f2de31ecd0/tenor.gif
https://media.tenor.com/images/3b205574d0352d4d61687f835276566d/tenor.gif
https://media.tenor.com/images/3ddd35e50f7d0500efddae755ecea3d1/tenor.gif
https://media.tenor.com/images/6ae2e8f2d7cb4762ddd489f26f6b0b47/tenor.gif
https://media.tenor.com/images/b2c300f095efb7d38108c6e7309f13de/tenor.gif
https://media.tenor.com/images/20ecc3af6a5523872854a7bc2c083b7e/tenor.gif
https://media.tenor.com/images/35641bbc12957f3a8a0b2196c965b483/tenor.gif
http://i.imgur.com/zG60zPk.gif
http://i.imgur.com/xWTyaKY.gif
http://i.imgur.com/ct76LIg.gif
http://i.imgur.com/xWTyaKY.gif
http://i.imgur.com/Asnv32U.gif

@ -1,13 +0,0 @@
https://cdn.discordapp.com/attachments/625393597231005786/726208352727597139/image3.jpg
https://cdn.discordapp.com/attachments/625393597231005786/726208352014827570/image2.jpg
https://cdn.discordapp.com/attachments/625393597231005786/726208351393808414/image1.jpg
https://cdn.discordapp.com/attachments/625393597231005786/726208350773182594/image0.jpg
https://cdn.discordapp.com/attachments/625393597231005786/726208962134933575/image0.jpg
https://cdn.discordapp.com/attachments/625393597231005786/726209091437068448/image0.jpg
https://cdn.discordapp.com/attachments/625393597231005786/726209145883066368/image0.jpg
https://cdn.discordapp.com/attachments/625393597231005786/726209317371641865/image0.jpg
https://cdn.discordapp.com/attachments/625393597231005786/726209476771971134/image0.jpg
https://cdn.discordapp.com/attachments/625393597231005786/726211884922961960/image0.jpg
https://cdn.discordapp.com/attachments/625393597231005786/726212026493567056/image0.jpg
https://cdn.discordapp.com/attachments/625393597231005786/726212027009335406/image1.jpg
https://cdn.discordapp.com/attachments/625393597231005786/726212027886075975/image2.jpg

@ -1,43 +0,0 @@
https://media.tenor.com/images/324fad6fdc3463105a404949d84bcfae/tenor.gif
https://media.tenor.com/images/35641bbc12957f3a8a0b2196c965b483/tenor.gif
https://media.tenor.com/images/9cdd31459981c41b6aa282f753273e60/tenor.gif
https://media.tenor.com/images/2cb9ed69aa1c49cbc1f5974cc9a76ed5/tenor.gif
https://media.tenor.com/images/579fdfefae8935a61b6e9614b16cfb3d/tenor.gif
https://media.tenor.com/images/81f693db5e5265c9ae21052d55ab7b3d/tenor.gif
https://media.tenor.com/images/b3a0e4968bd1555a82355bce760d8925/tenor.gif
https://media.tenor.com/images/b82d06e14a81a61a27a1c997fe57cd62/tenor.gif
https://media.tenor.com/images/57349c49dd49d24b707f506dcfaef9e0/tenor.gif
https://media.tenor.com/images/7af8d954d9b046758233c0f554b23729/tenor.gif
https://media.tenor.com/images/ec7d95ad0f77741e0b7980704351b835/tenor.gif
https://media.tenor.com/images/e7c53cbaaaf7124541f85ac421ec3d60/tenor.gif
https://media.tenor.com/images/b035aadbfbbfac443563f2d8d12372dc/tenor.gif
https://media.tenor.com/images/bb3ef2d15f0021c7dcfd688f4a6aadeb/tenor.gif
https://media.tenor.com/images/d3dca2dec335e5707e668b2f9813fde5/tenor.gif
https://media.tenor.com/images/a10f5769702e5fd50b59b8e965ef5f49/tenor.gif
https://media.tenor.com/images/c0d1624352bd64461e02238db242ed75/tenor.gif
https://media.tenor.com/images/ca2a1d63583bc23224ce45607f40703e/tenor.gif
https://media.tenor.com/images/0f837fac66cc7d00cef44675cccf9238/tenor.gif
https://media.tenor.com/images/85cab793748744ed4872d1c10aaeffd2/tenor.gif
https://media.tenor.com/images/44b4b9d5e6b4d806b6bcde2fd28a75ff/tenor.gif
https://media.tenor.com/images/749e89b235225a32bdda5211263edd14/tenor.gif
https://media.tenor.com/images/6d40d82e71dc167fd4a247704285fab7/tenor.gif
https://media.tenor.com/images/4be3396644e87d3c201f8965104e57b7/tenor.gif
https://media.tenor.com/images/9dd8b460be160d79123a9ca97d2502d6/tenor.gif
https://media.tenor.com/images/df8ba47cbd32dbebf41e465b1d5ed907/tenor.gif
https://media.tenor.com/images/9df38618eefe4998c7c6a2398808bef4/tenor.gif
https://media.tenor.com/images/62048cf3073b2670e176c470aa1d2714/tenor.gif
https://media.tenor.com/images/0570dca0f27a3aa7cc048ce829a7f53b/tenor.gif
https://media.tenor.com/images/29bd2a0cf5d863c3c803b3f4ab8d31f5/tenor.gif
https://media.tenor.com/images/b3f7b2e24f8605ed106fdeff6f03df62/tenor.gif
https://media.tenor.com/images/b5077daebe4c679dd12212e276c869ad/tenor.gif
https://media.tenor.com/images/6db54c4d6dad5f1f2863d878cfb2d8df/tenor.gif
https://media.tenor.com/images/194b5dc188fb0282636970391f1cd10b/tenor.gif
https://media.tenor.com/images/d23720aa94f1fd10dabe5255df778fb3/tenor.gif
https://media.tenor.com/images/fc26c413b99a2b571cef77d52e61fad1/tenor.gif
https://media.tenor.com/images/f8c810e24acbdfde36d1908e10e39c28/tenor.gif
https://media.tenor.com/images/fae49449a3bd6db7a3a6ec1bcade2622/tenor.gif
https://media.tenor.com/images/38e6c2549798384639533da90470e26d/tenor.gif
https://media.tenor.com/images/0e78b8d8e174fa48bd2808093360f1d5/tenor.gif
http://i.imgur.com/7C36d39.gif
http://i.imgur.com/xWTyaKY.gif
http://i.imgur.com/MrEMpE6.gif

@ -1,22 +0,0 @@
https://cdn.discordapp.com/attachments/713822779622948884/723601025377894403/kill10.gif
https://cdn.discordapp.com/attachments/713822779622948884/723601049444810834/kill14.gif
https://cdn.discordapp.com/attachments/713822779622948884/723601071481552956/kill19.gif
https://cdn.discordapp.com/attachments/713822779622948884/723601071317975070/kill7.gif
https://cdn.discordapp.com/attachments/713822779622948884/723601076619706378/kill8.gif
https://cdn.discordapp.com/attachments/713822779622948884/723601076997324830/kill17.gif
https://cdn.discordapp.com/attachments/713822779622948884/723601078733635685/kill15.gif
https://cdn.discordapp.com/attachments/713822779622948884/723601107888242728/kill13.gif
https://cdn.discordapp.com/attachments/713822779622948884/723601083590639616/kill21.gif
https://cdn.discordapp.com/attachments/713822779622948884/723601116163604541/kill2.gif
https://cdn.discordapp.com/attachments/713822779622948884/723601127320584192/kill11.gif
https://cdn.discordapp.com/attachments/713822779622948884/723601130814439534/kill18.gif
https://cdn.discordapp.com/attachments/713822779622948884/723601135973171220/kill6.gif
https://cdn.discordapp.com/attachments/713822779622948884/723601138229837864/kill5.gif
https://cdn.discordapp.com/attachments/713822779622948884/723601147213906100/kill12.gif
https://cdn.discordapp.com/attachments/713822779622948884/723601146962509844/kill3.gif
https://cdn.discordapp.com/attachments/713822779622948884/723601149164519574/kill20.gif
https://cdn.discordapp.com/attachments/713822779622948884/723601149772693584/kill9.gif
https://cdn.discordapp.com/attachments/713822779622948884/723601149659447396/kill16.gif
https://cdn.discordapp.com/attachments/713822779622948884/723601197889617920/kill22.gif
https://cdn.discordapp.com/attachments/713822779622948884/723601204319486053/kill4.gif
https://cdn.discordapp.com/attachments/713822779622948884/723601204193525791/kill1.gif

@ -1,47 +0,0 @@
https://media.giphy.com/media/G3va31oEEnIkM/giphy.gif
https://media.giphy.com/media/vHISzfc8dcVG0/giphy.gif
https://media.giphy.com/media/jLBwuKcJQcCpq/giphy.gif
https://media.giphy.com/media/Gj8bn4pgTocog/giphy.gif
https://media.giphy.com/media/TkDX9bkIROf8k/giphy.gif
https://media.giphy.com/media/bm2O3nXTcKJeU/giphy.gif
https://media.giphy.com/media/QGc8RgRvMonFm/giphy.gif
https://media.giphy.com/media/pcui5ohH3X96M/giphy.gif
https://media.giphy.com/media/nyGFcsP0kAobm/giphy.gif
https://media.giphy.com/media/dP8ONh1mN8YWQ/giphy.gif
https://media.giphy.com/media/wOtkVwroA6yzK/giphy.gif
https://media.giphy.com/media/zkppEMFvRX5FC/giphy.gif
https://media.giphy.com/media/ZL0G3c9BDX9ja/giphy.gif
https://media.giphy.com/media/12VXIxKaIEarL2/giphy.gif
https://media.giphy.com/media/hnNyVPIXgLdle/giphy.gif
https://media.giphy.com/media/iseq9MQgxo4aQ/giphy.gif
https://media.giphy.com/media/xdXIoBwYnBC0w/giphy.gif
https://cdn.discordapp.com/attachments/716823227409760337/718539083881644062/anime-kissin-7.gif
https://media.giphy.com/media/11rWoZNpAKw8w/giphy.gif
https://media.giphy.com/media/gy84qPkNTkgU/giphy.gif
https://media.giphy.com/media/fHtb1JPbfph72/giphy.gif
https://media.giphy.com/media/w2OMjUGV7mCSQ/giphy.gif
https://media.giphy.com/media/7pzeSkzilmVZS/giphy.gif
https://media.discordapp.net/attachments/716823227409760337/718597550671921213/tumblr_n5r2spwEYQ1rygh4xo1_500.gif
https://media.discordapp.net/attachments/716823227409760337/718598000406167652/tumblr_mwo343m7tK1sv72vno1_500.gif
https://cdn.discordapp.com/attachments/716823227409760337/718598371056812102/tumblr_n5h2m2u6SW1sn5ot7o2_500.gif
https://images-ext-2.discordapp.net/external/160FCOVxC5bjA5hGNUVp8RGfH-Lb9GdoTAJ4S8K7tVM/https/i.gifer.com/2q5t.gif
https://images-ext-2.discordapp.net/external/5_zU60yCUbwaBaYne1BYTa7JwfOscBLOOcBa2vnckrg/https/i.gifer.com/XrqL.gif
https://cdn.discordapp.com/attachments/652999909989023784/731574134668263454/kiss.gif
https://cdn.discordapp.com/attachments/652999909989023784/731575159823007764/BJMX2TuPb.gif
https://media.tenor.com/images/230e9fd40cd15e3f27fc891bac04248e/tenor.gif
https://media.tenor.com/images/b13ddaa73cf589346d6b6ca172873a9e/tenor.gif
https://media.tenor.com/images/5d2b53028b305f56ae753d6a7a988f6b/tenor.gif
https://media.tenor.com/images/c50aec5a3e97cce71466b06d05fe9058/tenor.gif
https://media.tenor.com/images/3cdfc0cce845667ec5184c3d2d4f9eff/tenor.gif
https://media.tenor.com/images/68d31e1a49f53b1e48a1331bf07e4655/tenor.gif
https://media.tenor.com/images/1cea57aa12dbe8e9e08e498ff57dda47/tenor.gif
https://media.tenor.com/images/5f6d2bbbbd6f5587c86865459e0a53ce/tenor.gif
https://media.tenor.com/images/6eba381d2764ea05a0fb4da0675a95db/tenor.gif
https://media.tenor.com/images/cd0d06e057bf6a01cd5f9bb5d7971b08/tenor.gif
https://media.tenor.com/images/ef9f2f0a8d16ce62ef8c2322ad6ddef9/tenor.gif
https://media.tenor.com/images/8a1dc34ab019887c21f814bb9e2fb3f2/tenor.gif
https://media.tenor.com/images/518ae408ab8a48ddf4d9504cfde88a72/tenor.gif
https://media.tenor.com/images/db79d17d7a5e08bf64e55a63eea5976f/tenor.gif
https://media.tenor.com/images/05bcc042f90c2ec3387bb27b35e0355f/tenor.gif
https://media.tenor.com/images/77b755b246126dca1a1c6880cb0a0174/tenor.gif
https://media.tenor.com/images/778d51aca07848160ad9b52e6df37b30/tenor.gif

@ -1,40 +0,0 @@
https://media.tenor.com/images/2b2f9c5d046ea2cdaca41dfdc4356eea/tenor.gif
https://media.tenor.com/images/70a1188a3db8fb41b9d539d05d685293/tenor.gif
https://media.tenor.com/images/ae1f3447e350116988d58da47d3ef778/tenor.gif
https://media.tenor.com/images/f79a9ec48bde0e592e55447b17ecfbad/tenor.gif
https://media.tenor.com/images/c22558035a2b09f1538935162dead992/tenor.gif
https://media.tenor.com/images/8331ba63516b37eb6987dcd45c4c0f66/tenor.gif
https://media.tenor.com/images/dae02df4433b91feff5c2cc74b412bec/tenor.gif
https://media.tenor.com/images/a9d0fd4bb985bb38a82a9af843fa2480/tenor.gif
https://media.tenor.com/images/196c72ab7342ac159b50c98559d81269/tenor.gif
https://media.tenor.com/images/f250100c8dbc02ca7f5a50056c8c6d33/tenor.gif
https://media.tenor.com/images/5466adf348239fba04c838639525c28a/tenor.gif
https://media.tenor.com/images/cf9a587a3fc4ef2e8f9f92bae63cb0d0/tenor.gif
https://media.tenor.com/images/3e0f3a22d4b2ca4fe91bf5839feb646b/tenor.gif
https://media.tenor.com/images/0ea33070f2294ad89032c69d77230a27/tenor.gif
https://media.tenor.com/images/d3c117054fb924d66c75169ff158c811/tenor.gif
https://media.tenor.com/images/3e94b77bfc7d4e240bb530b347a84008/tenor.gif
https://media.tenor.com/images/c61cc63503c21c8e69452639f068ad7f/tenor.gif
https://media.tenor.com/images/0339f275fff983e083997b3cfff81ae8/tenor.gif
https://media.tenor.com/images/d9e575861bb2f6389cec93da6cbdfa1f/tenor.gif
https://media.tenor.com/images/b8b0fe3f429e6d921274de333a9e2dfc/tenor.gif
https://media.tenor.com/images/c4bf2e3cba3d3e18514f635ae8b86f17/tenor.gif
https://media.tenor.com/images/57e98242606d651cc992b9525d3de2d8/tenor.gif
https://media.tenor.com/images/93ef9b0e6d1f89a92faf17e2c9db9622/tenor.gif
https://media.tenor.com/images/c9b5197227668f7fe5bca4627c539b20/tenor.gif
https://media.tenor.com/images/418caaf5331da97c2f70b4fc4bc9655d/tenor.gif
https://media.tenor.com/images/f0e0a23c4ec0aa610c69ca273f1bf541/tenor.gif
https://media.tenor.com/images/affa531884b2e7936a6bc563168f8c34/tenor.gif
https://media.tenor.com/images/4ebafc0f89234c97131e88e67adbdd58/tenor.gif
https://media.tenor.com/images/fee6bd69efbf19041c84c01381ce021e/tenor.gif
https://media.tenor.com/images/76d3b063b91ac22600d5604003047c01/tenor.gif
https://media.tenor.com/images/f4574b9bef2b67bf6f31eb8fe6617887/tenor.gif
https://media.tenor.com/images/d3c117054fb924d66c75169ff158c811/tenor.gif
https://media.tenor.com/images/220babfd5f8b629cc16399497ed9dd96/tenor.gif
https://media.tenor.com/images/e626aaa7a704c20a17e418272add6af6/tenor.gif
https://media.tenor.com/images/68d981347bf6ee8c7d6b78f8a7fe3ccb/tenor.gif
https://media.tenor.com/images/7a21fbeac0b890e3f38db405e303cb94/tenor.gif
https://media.tenor.com/images/f1d89b997e0f4165af9abe72af7f67ca/tenor.gif
https://media.tenor.com/images/6b0036b38dbc8e5b40c256ffed664829/tenor.gif
https://media.tenor.com/images/662051affffdd394e8540807b07d0fd9/tenor.gif
https://media.tenor.com/images/f5faaccf8cc78a9c6138b3a8f8d875b6/tenor.gif

@ -1,40 +0,0 @@
https://media.tenor.com/images/d0d4bc024b256d86c740a5804e4bcfc8/tenor.gif
https://media.tenor.com/images/49cc731f0d76650a83351618b495805b/tenor.gif
https://media.tenor.com/images/1387554d4c1cf903c06cd276b9f32361/tenor.gif
https://media.tenor.com/images/5eeb1a7d7d0e9b2d6ed7d7125e31667e/tenor.gif
https://media.tenor.com/images/6df057c42393deb1c483d14b9b312495/tenor.gif
https://media.tenor.com/images/c03828c5fac2d1342fa5a5462c3a5ac7/tenor.gif
https://media.tenor.com/images/106a19ebd266594f3ec70321811c6c60/tenor.gif
https://media.tenor.com/images/9d8f45c7a54b44dccf5302768182bf57/tenor.gif
https://media.tenor.com/images/b150f67bd4ba4aff38fe6f38830b4eb8/tenor.gif
https://media.tenor.com/images/33a5b1d6a3e316f12720ab3060f683cf/tenor.gif
https://media.tenor.com/images/de289d8f02fc810eb1389aca8df6a417/tenor.gif
https://media.tenor.com/images/30c9c3185e1a105571eda9b417986509/tenor.gif
https://media.tenor.com/images/d94acb5413ad36fd304b87fbd6c65c42/tenor.gif
https://media.tenor.com/images/f1ffc9863f7b43d1e171482b9d1503d2/tenor.gif
https://media.tenor.com/images/b6f922d588883687b473dcaa9e1ff312/tenor.gif
https://media.tenor.com/images/d94be9c942b034e4a00a66d1e277ba84/tenor.gif
https://media.tenor.com/images/40a27297ca170bd4456c121add83faf0/tenor.gif
https://media.tenor.com/images/71d49049a41f85331e38ad56467db093/tenor.gif
https://media.tenor.com/images/d0eafbb07e1ae4c5d8681f0239bae82b/tenor.gif
https://media.tenor.com/images/ff7adfeff2359efac2fb324e289babf7/tenor.gif
https://media.tenor.com/images/ce295550c5436b7b61edac89b3a49b07/tenor.gif
https://media.tenor.com/images/cf881d79afde980ea7cf58219167bfb7/tenor.gif
https://media.tenor.com/images/4db51ef6f447abb161a649221a88b40d/tenor.gif
https://media.tenor.com/images/d46abc46d314b30177d3e2fcc1121af6/tenor.gif
https://media.tenor.com/images/b6d8a83eb652a30b95e87cf96a21e007/tenor.gif
https://media.tenor.com/images/9d907ed56fa1c8c011791e494b1d6ce0/tenor.gif
https://media.tenor.com/images/50f65d05d91bbac8cbe15b46215fe7a0/tenor.gif
https://media.tenor.com/images/f8d4f44c6206933341da60ebaf3b147c/tenor.gif
https://media.tenor.com/images/6f5c1f380b4cb313f412f57f4508c7e9/tenor.gif
https://media.tenor.com/images/23533677bc97359b318a9cbf156905d0/tenor.gif
https://media.tenor.com/images/e8f880b13c17d61810ac381b2f6a93c3/tenor.gif
https://media.tenor.com/images/984c0dc6e682d4978504874cc75485c8/tenor.gif
https://media.tenor.com/images/26b39008b7080fbcc8d644f7237d515e/tenor.gif
https://media.tenor.com/images/13844a6bc3d247b571e2cee25651d1a1/tenor.gif
https://media.tenor.com/images/e1c6feaeb3a64be5777b746c00f2eb18/tenor.gif
https://media.tenor.com/images/f0aa023d6422ad071f91e7a825a072fa/tenor.gif
https://media.tenor.com/images/f4044eb30d1f480d705288de2f13a2de/tenor.gif
https://media.tenor.com/images/2971bb1dde784a82020f9f2fed107612/tenor.gif
https://media.tenor.com/images/4ceedd1da542cccbc32a7a49e52ce6e6/tenor.gif
https://media.tenor.com/images/b632ce9619d275dbf091c16cdb85c9bd/tenor.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Binary file not shown.

@ -1,37 +0,0 @@
# Ensō~Chan - A Multi Purpose Discord Bot That Has Everything Your Server Needs!
# Copyright (C) 2020 Hamothy
# 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/>.
from bot import Bot
# Initiating Bot Object As Client
client = Bot()
@client.event
async def on_message(message):
"""Make sure bot messages are not tracked"""
# Ignoring messages that start with 2 ..
if message.content.startswith("..") or message.author.bot:
return
# Processing the message
await client.process_commands(message)
# Run the bot
client.execute()

@ -1,85 +0,0 @@
aiofiles==0.5.0
aiohttp==3.7.4
aiomysql==0.0.20
Animapy==1.5.3.1
appdirs==1.4.4
APScheduler==3.6.3
asgiref==3.2.7
async-timeout==3.0.1
asyncpg==0.21.0
attrs==19.3.0
beautifulsoup4==4.9.1
bitarray==1.5.2
buttons==0.1.9
certifi==2020.4.5.1
cffi==1.14.0
changelogs==0.14.0
chardet==3.0.4
Click==7.0
config==0.5.0.post0
decorator==4.4.2
dill==0.3.1.1
discord==1.0.1
discord-ext-menus==1.0.0a22+gcc108be
discord.py==1.4.0
distlib==0.3.1
Django==3.1.12
dnspython==1.16.0
docopt==0.6.2
dsdev-utils==1.0.4
filelock==3.0.12
futures==3.1.1
gitdb==4.0.5
GitPython==3.1.7
greenlet==0.4.15
idna==2.8
idna-ssl==1.1.0
importlib-metadata==1.7.0
importlib-resources==3.0.0
Jinja2==2.11.3
lxml==4.6.3
MarkupSafe==1.1.1
multidict==4.7.6
mysqlclient==2.0.1
numpy==1.19.1
opencv-python==4.3.0.38
owotext==1.0.2
packaging==20.4
Paginator==0.5.1
pbr==5.4.5
Pillow==8.2.0
protobuf==3.12.2
psutil==5.7.2
pycodestyle==2.6.0
pycparser==2.20
PyMySQL==0.9.2
PyNaCl==1.3.0
pyparsing==2.4.7
pytesseract==0.3.5
python-aiml==0.9.3
python-changelog==0.2.0
python-dateutil==2.8.1
python-decouple==3.3
python-dotenv==0.13.0
pytz==2020.1
requests==2.22.0
six==1.15.0
smmap==3.0.4
soupsieve==2.0.1
South==1.0.2
sqlparse==0.3.1
statcord.py==2.2.0
stevedore==3.2.0
tenorpy==1.0.3
typing-extensions==3.7.4.2
tzlocal==2.1
urllib3==1.26.5
validators==0.17.1
virtualenv==20.0.27
virtualenv-clone==0.5.4
virtualenvwrapper==4.8.4
Voice==0.1.0
websockets==9.1
yarl==1.4.2
youtube-dl==2020.6.6
zipp==3.1.0
Loading…
Cancel
Save