mirror of https://github.com/sgoudham/Enso-Bot.git
Remove old python code
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,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,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,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.
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…
Reference in New Issue